Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion config/core/configmaps/features.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ metadata:
app.kubernetes.io/component: controller
app.kubernetes.io/version: devel
annotations:
knative.dev/example-checksum: "424df6ce"
knative.dev/example-checksum: "e32b69e5"
data:
_example: |-
################################
Expand Down Expand Up @@ -189,6 +189,13 @@ data:
# 2. Disabled: http2 connection will only be attempted when port name is set to "h2c".
autodetect-http2: "disabled"

# Controls whether a DomainMapping targeting a Knative Service or Route reuses the traffic configuration of the Route or not.
# 1. Enabled: the DomainMapping's Ingress reuses the HTTP paths of the Route's Ingress
# 2. Disabled: the DomainMapping's Ingress sends requests to the target's Kubernetes Service
#
# WARNING: When enabled, the Host is no longer rewritten to the cluster-local host.
domainmapping-direct-route-traffic: "disabled"

# Controls whether volume support for EmptyDir is enabled or not.
# 1. Enabled: enabling EmptyDir volume support
# 2. Disabled: disabling EmptyDir volume support
Expand Down
1 change: 1 addition & 0 deletions docs/controllers/CONTROLLERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ The DomainMapping controller enables mapping custom domains to Knative Services
- **Domain Claim:** Creates/validates ClusterDomainClaim to prevent hostname collisions across namespaces
- **Reference Resolution:** Resolves the target reference (typically a Knative Service) to get the backend service name
- **Ingress Creation:** Creates a Knative Ingress to route the custom domain to the target
- **Route Traffic:** Reuses the HTTP paths of the target Route's Ingress when `domainmapping-direct-route-traffic` is enabled
- **TLS Management:**
- Supports externally provided TLS secrets (`spec.tls`)
- Auto-provisions certificates when external-domain-tls is enabled
Expand Down
3 changes: 3 additions & 0 deletions pkg/apis/config/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ func defaultFeaturesConfig() *Features {
SecurePodDefaults: Disabled,
TagHeaderBasedRouting: Disabled,
AutoDetectHTTP2: Disabled,
DomainMappingDirectRouteTraffic: Disabled,
}
}

Expand All @@ -124,6 +125,7 @@ func NewFeaturesConfigFromMap(data map[string]string) (*Features, error) {

if err := cm.Parse(data,
asFlag("autodetect-http2", &nc.AutoDetectHTTP2),
asFlag("domainmapping-direct-route-traffic", &nc.DomainMappingDirectRouteTraffic),
asFlag("kubernetes.podspec-persistent-volume-write", &nc.PodSpecPersistentVolumeWrite),
asFlag("multi-container", &nc.MultiContainer),
asFlag("multi-container-probing", &nc.MultiContainerProbing),
Expand Down Expand Up @@ -202,6 +204,7 @@ type Features struct {
SecurePodDefaults Flag
TagHeaderBasedRouting Flag
AutoDetectHTTP2 Flag
DomainMappingDirectRouteTraffic Flag
}

// asFlag parses the value at key as a Flag into the target, if it exists.
Expand Down
20 changes: 20 additions & 0 deletions pkg/apis/config/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func TestFeaturesConfiguration(t *testing.T) {
SecurePodDefaults: Enabled,
QueueProxyResourceDefaults: Enabled,
TagHeaderBasedRouting: Enabled,
DomainMappingDirectRouteTraffic: Enabled,
}),
data: map[string]string{
"multi-container": "Enabled",
Expand All @@ -101,6 +102,7 @@ func TestFeaturesConfiguration(t *testing.T) {
"secure-pod-defaults": "Enabled",
"queueproxy.resource-defaults": "Enabled",
"tag-header-based-routing": "Enabled",
"domainmapping-direct-route-traffic": "Enabled",
},
}, {
name: "multi-container Allowed",
Expand Down Expand Up @@ -408,6 +410,15 @@ func TestFeaturesConfiguration(t *testing.T) {
data: map[string]string{
"tag-header-based-routing": "Enabled",
},
}, {
name: "domainmapping-direct-route-traffic Enabled",
wantErr: false,
wantFeatures: defaultWith(&Features{
DomainMappingDirectRouteTraffic: Enabled,
}),
data: map[string]string{
"domainmapping-direct-route-traffic": "Enabled",
},
}, {
name: "kubernetes.podspec-volumes-emptyDir Disabled",
wantErr: false,
Expand Down Expand Up @@ -777,6 +788,15 @@ func TestFeaturesConfiguration(t *testing.T) {
data: map[string]string{
"tag-header-based-routing": "Restricted",
},
}, {
name: "domainmapping-direct-route-traffic cannot be set to Restricted",
wantErr: false,
wantFeatures: defaultWith(&Features{
DomainMappingDirectRouteTraffic: Disabled, // Should remain default since Restricted is not valid
}),
data: map[string]string{
"domainmapping-direct-route-traffic": "Restricted",
},
}}

for _, tt := range configTests {
Expand Down
12 changes: 12 additions & 0 deletions pkg/apis/serving/v1beta1/domainmapping_lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,18 @@ func (dms *DomainMappingStatus) MarkIngressNotConfigured() {
"IngressNotConfigured", "%s", "Ingress has not yet been reconciled.")
}

// MarkTargetIngressNotConfigured marks DomainMappingConditionIngressReady unknown.
func (dms *DomainMappingStatus) MarkTargetIngressNotConfigured(message string) {
domainMappingCondSet.Manage(dms).MarkUnknown(DomainMappingConditionIngressReady,
"IngressNotConfigured", "%s", message)
}

// MarkTargetNotOwned marks DomainMappingConditionIngressReady false.
func (dms *DomainMappingStatus) MarkTargetNotOwned(message string) {
domainMappingCondSet.Manage(dms).MarkFalse(DomainMappingConditionIngressReady,
"NotOwned", "%s", message)
}

// MarkDomainClaimed updates the DomainMappingConditionDomainClaimed condition
// to indicate that the domain was successfully claimed.
func (dms *DomainMappingStatus) MarkDomainClaimed() {
Expand Down
51 changes: 51 additions & 0 deletions pkg/apis/serving/v1beta1/domainmapping_lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,57 @@ func TestReferenceResolvedCondition(t *testing.T) {
apistest.CheckConditionFailed(dms, DomainMappingConditionReady, t)
}

func TestTargetIngressConditions(t *testing.T) {
tests := []struct {
name string
mark func(*DomainMappingStatus)
wantStatus corev1.ConditionStatus
wantReason string
wantMsg string
}{
{
name: "target ingress not configured",
mark: func(dms *DomainMappingStatus) {
dms.MarkTargetIngressNotConfigured("Waiting for target Ingress.")
},
wantStatus: corev1.ConditionUnknown,
wantReason: "IngressNotConfigured",
wantMsg: "Waiting for target Ingress.",
},
{
name: "target resource not owned",
mark: func(dms *DomainMappingStatus) {
dms.MarkTargetNotOwned("Route does not own Ingress.")
},
wantStatus: corev1.ConditionFalse,
wantReason: "NotOwned",
wantMsg: "Route does not own Ingress.",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
dms := &DomainMappingStatus{}
dms.InitializeConditions()
test.mark(dms)

got := dms.GetCondition(DomainMappingConditionIngressReady)
if got == nil {
t.Fatal("IngressReady condition is nil")
}
if got.Status != test.wantStatus {
t.Errorf("Status = %q, want %q", got.Status, test.wantStatus)
}
if got.Reason != test.wantReason {
t.Errorf("Reason = %q, want %q", got.Reason, test.wantReason)
}
if got.Message != test.wantMsg {
t.Errorf("Message = %q, want %q", got.Message, test.wantMsg)
}
})
}
}

func TestCertificateNotReady(t *testing.T) {
dms := &DomainMappingStatus{}

Expand Down
28 changes: 25 additions & 3 deletions pkg/reconciler/domainmapping/config/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,37 @@ import (
netcfg "knative.dev/networking/pkg/config"
"knative.dev/pkg/configmap"
"knative.dev/pkg/logging"
apisconfig "knative.dev/serving/pkg/apis/config"
)

type cfgKey struct{}

// Config holds the collection of configurations that we attach to contexts.
type Config struct {
Network *netcfg.Config
Network *netcfg.Config
Features *apisconfig.Features
}

// FromContext extracts a Config from the provided context.
func FromContext(ctx context.Context) *Config {
return ctx.Value(cfgKey{}).(*Config)
}

// FromContextOrDefaults is like FromContext, but when no Config is attached it
// returns a Config populated with the defaults for each of the Config fields.
func FromContextOrDefaults(ctx context.Context) *Config {
cfg, _ := ctx.Value(cfgKey{}).(*Config)
if cfg == nil {
cfg = &Config{}
}

if cfg.Features == nil {
cfg.Features, _ = apisconfig.NewFeaturesConfigFromMap(map[string]string{})
}

return cfg
}

// ToContext attaches the provided Config to the provided context, returning the
// new context with the Config attached.
func ToContext(ctx context.Context, c *Config) context.Context {
Expand All @@ -54,9 +71,13 @@ func (s *Store) ToContext(ctx context.Context) context.Context {

// Load creates a Config from the current config state of the Store.
func (s *Store) Load() *Config {
return &Config{
cfg := &Config{
Network: s.UntypedLoad(netcfg.ConfigMapName).(*netcfg.Config).DeepCopy(),
}
if feat, ok := s.UntypedLoad(apisconfig.FeaturesConfigName).(*apisconfig.Features); ok {
cfg.Features = feat.DeepCopy()
}
return cfg
}

// NewStore creates a new store of Configs and optionally calls functions when ConfigMaps are updated.
Expand All @@ -66,7 +87,8 @@ func NewStore(ctx context.Context, onAfterStore ...func(name string, value inter
"domainmapping",
logging.FromContext(ctx),
configmap.Constructors{
netcfg.ConfigMapName: netcfg.NewConfigFromConfigMap,
netcfg.ConfigMapName: netcfg.NewConfigFromConfigMap,
apisconfig.FeaturesConfigName: apisconfig.NewFeaturesConfigFromConfigMap,
},
onAfterStore...,
),
Expand Down
33 changes: 33 additions & 0 deletions pkg/reconciler/domainmapping/config/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

netcfg "knative.dev/networking/pkg/config"
logtesting "knative.dev/pkg/logging/testing"
apisconfig "knative.dev/serving/pkg/apis/config"

. "knative.dev/pkg/configmap/testing"
)
Expand All @@ -33,7 +34,9 @@ func TestStoreLoadWithContext(t *testing.T) {
store := NewStore(ctx)

networkConfig := ConfigMapFromTestFile(t, netcfg.ConfigMapName)
featuresConfig := ConfigMapFromTestFile(t, apisconfig.FeaturesConfigName)
store.OnConfigChanged(networkConfig)
store.OnConfigChanged(featuresConfig)

config := FromContext(store.ToContext(context.Background()))

Expand All @@ -43,4 +46,34 @@ func TestStoreLoadWithContext(t *testing.T) {
t.Errorf("Unexpected network config (-want, +got):\n%v", diff)
}
})

t.Run("features", func(t *testing.T) {
expected, _ := apisconfig.NewFeaturesConfigFromConfigMap(featuresConfig)
if diff := cmp.Diff(expected, config.Features); diff != "" {
t.Errorf("Unexpected features config (-want, +got):\n%v", diff)
}
})
}

func TestStoreLoadWithContextOrDefaults(t *testing.T) {
store := NewStore(logtesting.TestContextWithLogger(t))
store.OnConfigChanged(ConfigMapFromTestFile(t, netcfg.ConfigMapName))

config := FromContextOrDefaults(store.ToContext(context.Background()))

t.Run("features", func(t *testing.T) {
expected, _ := apisconfig.NewFeaturesConfigFromMap(map[string]string{})
if diff := cmp.Diff(expected, config.Features); diff != "" {
t.Errorf("Unexpected features config (-want, +got):\n%v", diff)
}
})
}

func TestFromContextOrDefaultsWithoutConfig(t *testing.T) {
config := FromContextOrDefaults(context.Background())

expected, _ := apisconfig.NewFeaturesConfigFromMap(map[string]string{})
if diff := cmp.Diff(expected, config.Features); diff != "" {
t.Errorf("Unexpected features config (-want, +got):\n%v", diff)
}
}
29 changes: 28 additions & 1 deletion pkg/reconciler/domainmapping/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"

"k8s.io/client-go/tools/cache"
netv1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1"
netclient "knative.dev/networking/pkg/client/injection/client"
certificateinformer "knative.dev/networking/pkg/client/injection/informers/networking/v1alpha1/certificate"
domainclaiminformer "knative.dev/networking/pkg/client/injection/informers/networking/v1alpha1/clusterdomainclaim"
Expand All @@ -29,7 +30,11 @@ import (
"knative.dev/pkg/controller"
"knative.dev/pkg/logging"
"knative.dev/pkg/resolver"
apisconfig "knative.dev/serving/pkg/apis/config"
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
"knative.dev/serving/pkg/apis/serving/v1beta1"
routeinformer "knative.dev/serving/pkg/client/injection/informers/serving/v1/route"
serviceinformer "knative.dev/serving/pkg/client/injection/informers/serving/v1/service"
"knative.dev/serving/pkg/client/injection/informers/serving/v1beta1/domainmapping"
kindreconciler "knative.dev/serving/pkg/client/injection/reconciler/serving/v1beta1/domainmapping"
"knative.dev/serving/pkg/reconciler/domainmapping/config"
Expand All @@ -42,17 +47,22 @@ func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl
domainmappingInformer := domainmapping.Get(ctx)
ingressInformer := ingressinformer.Get(ctx)
domainClaimInformer := domainclaiminformer.Get(ctx)
routeInformer := routeinformer.Get(ctx)
serviceInformer := serviceinformer.Get(ctx)

r := &Reconciler{
certificateLister: certificateInformer.Lister(),
ingressLister: ingressInformer.Lister(),
domainClaimLister: domainClaimInformer.Lister(),
routeLister: routeInformer.Lister(),
serviceLister: serviceInformer.Lister(),
netclient: netclient.Get(ctx),
}

impl := kindreconciler.NewImpl(ctx, r, func(impl *controller.Impl) controller.Options {
configsToResync := []interface{}{
&netcfg.Config{},
&apisconfig.Features{},
}
resync := configmap.TypeFilter(configsToResync...)(func(string, interface{}) {
impl.GlobalResync(domainmappingInformer.Informer())
Expand All @@ -71,7 +81,24 @@ func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl
certificateInformer.Informer().AddEventHandler(handleControllerOf)
ingressInformer.Informer().AddEventHandler(handleControllerOf)

r.resolver = resolver.NewURIResolverFromTracker(ctx, impl.Tracker)
r.tracker = impl.Tracker
r.resolver = resolver.NewURIResolverFromTracker(ctx, r.tracker)

// Track Route and Ingress changes because their HTTP paths are copied into
// DomainMapping Ingresses. The resolver already tracks referenced Services.
// Informer events may omit TypeMeta, so populate it before notifying the tracker.
routeInformer.Informer().AddEventHandler(controller.HandleAll(
controller.EnsureTypeMeta(r.tracker.OnChanged, servingv1.SchemeGroupVersion.WithKind("Route")),
))
ingressInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
FilterFunc: controller.FilterController(&servingv1.Route{}),
Handler: controller.HandleAll(
controller.EnsureTypeMeta(r.tracker.OnChanged, netv1alpha1.SchemeGroupVersion.WithKind("Ingress")),
),
})
domainmappingInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
DeleteFunc: r.tracker.OnDeletedObserver,
})

return impl
}
Loading
Loading