diff --git a/apis/v1alpha2/nginxproxy_types.go b/apis/v1alpha2/nginxproxy_types.go index bfa21aca59..3a0a3ccc73 100644 --- a/apis/v1alpha2/nginxproxy_types.go +++ b/apis/v1alpha2/nginxproxy_types.go @@ -550,7 +550,7 @@ const ( // ExternalTrafficPolicy describes how nodes distribute service traffic they // receive on one of the Service's "externally-facing" addresses (NodePorts, ExternalIPs, -// and LoadBalancer IPs. +// and LoadBalancer IPs. Ignored for ClusterIP services. // +kubebuilder:validation:Enum=Cluster;Local type ExternalTrafficPolicy corev1.ServiceExternalTrafficPolicy diff --git a/cmd/gateway/commands.go b/cmd/gateway/commands.go index 853ddd0f72..be076a76da 100644 --- a/cmd/gateway/commands.go +++ b/cmd/gateway/commands.go @@ -12,7 +12,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" "go.uber.org/zap" - "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/klog/v2" ctlr "sigs.k8s.io/controller-runtime" @@ -59,11 +58,9 @@ func createRootCommand() *cobra.Command { func createControllerCommand() *cobra.Command { // flag names const ( - gatewayFlag = "gateway" configFlag = "config" serviceFlag = "service" agentTLSSecretFlag = "agent-tls-secret" - updateGCStatusFlag = "update-gatewayclass-status" metricsDisableFlag = "metrics-disable" metricsSecureFlag = "metrics-secure-serving" metricsPortFlag = "metrics-port" @@ -94,9 +91,7 @@ func createControllerCommand() *cobra.Command { validator: validateResourceName, } - updateGCStatus bool - gateway = namespacedNameValue{} - configName = stringValidatingValue{ + configName = stringValidatingValue{ validator: validateResourceName, } serviceName = stringValidatingValue{ @@ -200,11 +195,6 @@ func createControllerCommand() *cobra.Command { return fmt.Errorf("error parsing telemetry endpoint insecure: %w", err) } - var gwNsName *types.NamespacedName - if cmd.Flags().Changed(gatewayFlag) { - gwNsName = &gateway.value - } - var usageReportConfig config.UsageReportConfig if plus && usageReportSecretName.value == "" { return errors.New("usage-report-secret is required when using NGINX Plus") @@ -229,14 +219,12 @@ func createControllerCommand() *cobra.Command { } conf := config.Config{ - GatewayCtlrName: gatewayCtlrName.value, - ConfigName: configName.String(), - Logger: logger, - AtomicLevel: atom, - GatewayClassName: gatewayClassName.value, - GatewayNsName: gwNsName, - UpdateGatewayClassStatus: updateGCStatus, - GatewayPodConfig: podConfig, + GatewayCtlrName: gatewayCtlrName.value, + ConfigName: configName.String(), + Logger: logger, + AtomicLevel: atom, + GatewayClassName: gatewayClassName.value, + GatewayPodConfig: podConfig, HealthConfig: config.HealthConfig{ Enabled: !disableHealth, Port: healthListenPort.value, @@ -293,16 +281,6 @@ func createControllerCommand() *cobra.Command { ) utilruntime.Must(cmd.MarkFlagRequired(gatewayClassFlag)) - cmd.Flags().Var( - &gateway, - gatewayFlag, - "The namespaced name of the Gateway resource to use. "+ - "Must be of the form: NAMESPACE/NAME. "+ - "If not specified, the control plane will process all Gateways for the configured GatewayClass. "+ - "However, among them, it will choose the oldest resource by creation timestamp. If the timestamps are "+ - "equal, it will choose the resource that appears first in alphabetical order by {namespace}/{name}.", - ) - cmd.Flags().VarP( &configName, configFlag, @@ -326,13 +304,6 @@ func createControllerCommand() *cobra.Command { `NGINX Gateway Fabric control plane is running in (default namespace: nginx-gateway).`, ) - cmd.Flags().BoolVar( - &updateGCStatus, - updateGCStatusFlag, - true, - "Update the status of the GatewayClass resource.", - ) - cmd.Flags().BoolVar( &disableMetrics, metricsDisableFlag, diff --git a/cmd/gateway/commands_test.go b/cmd/gateway/commands_test.go index 0cc031a1c5..8db899f1cb 100644 --- a/cmd/gateway/commands_test.go +++ b/cmd/gateway/commands_test.go @@ -9,7 +9,6 @@ import ( . "github.com/onsi/gomega" "github.com/spf13/cobra" "github.com/spf13/pflag" - "k8s.io/apimachinery/pkg/types" "github.com/nginx/nginx-gateway-fabric/internal/mode/static/config" ) @@ -137,11 +136,9 @@ func TestControllerCmdFlagValidation(t *testing.T) { args: []string{ "--gateway-ctlr-name=gateway.nginx.org/nginx-gateway", // common and required flag "--gatewayclass=nginx", // common and required flag - "--gateway=nginx-gateway/nginx", "--config=nginx-gateway-config", "--service=nginx-gateway", "--agent-tls-secret=agent-tls", - "--update-gatewayclass-status=true", "--metrics-port=9114", "--metrics-disable", "--metrics-secure-serving", @@ -170,23 +167,6 @@ func TestControllerCmdFlagValidation(t *testing.T) { }, wantErr: false, }, - { - name: "gateway is set to empty string", - args: []string{ - "--gateway=", - }, - wantErr: true, - expectedErrPrefix: `invalid argument "" for "--gateway" flag: must be set`, - }, - { - name: "gateway is invalid", - args: []string{ - "--gateway=nginx-gateway", // no namespace - }, - wantErr: true, - expectedErrPrefix: `invalid argument "nginx-gateway" for "--gateway" flag: invalid format; ` + - "must be NAMESPACE/NAME", - }, { name: "config is set to empty string", args: []string{ @@ -235,22 +215,6 @@ func TestControllerCmdFlagValidation(t *testing.T) { wantErr: true, expectedErrPrefix: `invalid argument "!@#$" for "--agent-tls-secret" flag: invalid format`, }, - { - name: "update-gatewayclass-status is set to empty string", - args: []string{ - "--update-gatewayclass-status=", - }, - wantErr: true, - expectedErrPrefix: `invalid argument "" for "--update-gatewayclass-status" flag: strconv.ParseBool`, - }, - { - name: "update-gatewayclass-status is invalid", - args: []string{ - "--update-gatewayclass-status=invalid", // not a boolean - }, - wantErr: true, - expectedErrPrefix: `invalid argument "invalid" for "--update-gatewayclass-status" flag: strconv.ParseBool`, - }, { name: "metrics-port is invalid type", args: []string{ @@ -727,30 +691,6 @@ func TestParseFlags(t *testing.T) { err = flagSet.Set("customStringFlagUserDefined", "changed-test-flag-value") g.Expect(err).To(Not(HaveOccurred())) - customStringFlagNoDefaultValueUnset := namespacedNameValue{ - value: types.NamespacedName{}, - } - flagSet.Var( - &customStringFlagNoDefaultValueUnset, - "customStringFlagNoDefaultValueUnset", - "no default value custom string test flag", - ) - - customStringFlagNoDefaultValueUserDefined := namespacedNameValue{ - value: types.NamespacedName{}, - } - flagSet.Var( - &customStringFlagNoDefaultValueUserDefined, - "customStringFlagNoDefaultValueUserDefined", - "no default value but with user defined namespacedName test flag", - ) - userDefinedNamespacedName := types.NamespacedName{ - Namespace: "changed-namespace", - Name: "changed-name", - } - err = flagSet.Set("customStringFlagNoDefaultValueUserDefined", userDefinedNamespacedName.String()) - g.Expect(err).To(Not(HaveOccurred())) - expectedKeys := []string{ "boolFlagTrue", "boolFlagFalse", @@ -760,9 +700,6 @@ func TestParseFlags(t *testing.T) { "customStringFlagDefault", "customStringFlagUserDefined", - - "customStringFlagNoDefaultValueUnset", - "customStringFlagNoDefaultValueUserDefined", } expectedValues := []string{ "true", @@ -773,9 +710,6 @@ func TestParseFlags(t *testing.T) { "default", "user-defined", - - "default", - "user-defined", } flagKeys, flagValues := parseFlags(flagSet) diff --git a/cmd/gateway/validating_types.go b/cmd/gateway/validating_types.go index 1db3eab8dc..c0fd93da81 100644 --- a/cmd/gateway/validating_types.go +++ b/cmd/gateway/validating_types.go @@ -6,8 +6,6 @@ import ( "fmt" "strconv" "strings" - - "k8s.io/apimachinery/pkg/types" ) // stringValidatingValue is a string flag value with custom validation logic. @@ -106,31 +104,3 @@ func (v *intValidatingValue) Set(param string) error { func (v *intValidatingValue) Type() string { return "int" } - -// namespacedNameValue is a string flag value that represents a namespaced name. -// it implements the pflag.Value interface. -type namespacedNameValue struct { - value types.NamespacedName -} - -func (v *namespacedNameValue) String() string { - if (v.value == types.NamespacedName{}) { - // if we don't do that, the default value in the help message will be printed as "/" - return "" - } - return v.value.String() -} - -func (v *namespacedNameValue) Set(param string) error { - nsname, err := parseNamespacedResourceName(param) - if err != nil { - return err - } - - v.value = nsname - return nil -} - -func (v *namespacedNameValue) Type() string { - return "string" -} diff --git a/cmd/gateway/validation.go b/cmd/gateway/validation.go index b6e0cc84f3..a59a8469f4 100644 --- a/cmd/gateway/validation.go +++ b/cmd/gateway/validation.go @@ -8,7 +8,6 @@ import ( "strconv" "strings" - "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation" ) @@ -55,40 +54,6 @@ func validateResourceName(value string) error { return nil } -func validateNamespaceName(value string) error { - // used by Kubernetes to validate resource namespace names - messages := validation.IsDNS1123Label(value) - if len(messages) > 0 { - msg := strings.Join(messages, "; ") - return fmt.Errorf("invalid format: %s", msg) - } - - return nil -} - -func parseNamespacedResourceName(value string) (types.NamespacedName, error) { - if value == "" { - return types.NamespacedName{}, errors.New("must be set") - } - - parts := strings.Split(value, "/") - if len(parts) != 2 { - return types.NamespacedName{}, errors.New("invalid format; must be NAMESPACE/NAME") - } - - if err := validateNamespaceName(parts[0]); err != nil { - return types.NamespacedName{}, fmt.Errorf("invalid namespace name: %w", err) - } - if err := validateResourceName(parts[1]); err != nil { - return types.NamespacedName{}, fmt.Errorf("invalid resource name: %w", err) - } - - return types.NamespacedName{ - Namespace: parts[0], - Name: parts[1], - }, nil -} - func validateQualifiedName(name string) error { if len(name) == 0 { return errors.New("must be set") diff --git a/cmd/gateway/validation_test.go b/cmd/gateway/validation_test.go index 59db6fc57c..665bd91582 100644 --- a/cmd/gateway/validation_test.go +++ b/cmd/gateway/validation_test.go @@ -4,7 +4,6 @@ import ( "testing" . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/types" ) func TestValidateGatewayControllerName(t *testing.T) { @@ -132,137 +131,6 @@ func TestValidateResourceName(t *testing.T) { } } -func TestValidateNamespaceName(t *testing.T) { - t.Parallel() - tests := []struct { - name string - value string - expErr bool - }{ - { - name: "valid", - value: "mynamespace", - expErr: false, - }, - { - name: "valid - with dash", - value: "my-namespace", - expErr: false, - }, - { - name: "valid - with numbers", - value: "mynamespace123", - expErr: false, - }, - { - name: "invalid - empty", - value: "", - expErr: true, - }, - { - name: "invalid - invalid character '.'", - value: "my.namespace", - expErr: true, - }, - { - name: "invalid - invalid character '/'", - value: "my/namespace", - expErr: true, - }, - { - name: "invalid - invalid character '_'", - value: "my_namespace", - expErr: true, - }, - { - name: "invalid - invalid character '@'", - value: "my@namespace", - expErr: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - err := validateNamespaceName(test.value) - - if test.expErr { - g.Expect(err).To(HaveOccurred()) - } else { - g.Expect(err).ToNot(HaveOccurred()) - } - }) - } -} - -func TestParseNamespacedResourceName(t *testing.T) { - t.Parallel() - tests := []struct { - name string - value string - expectedErrPrefix string - expectedNsName types.NamespacedName - expectErr bool - }{ - { - name: "valid", - value: "test/my-gateway", - expectedNsName: types.NamespacedName{ - Namespace: "test", - Name: "my-gateway", - }, - expectErr: false, - }, - { - name: "empty", - value: "", - expectedNsName: types.NamespacedName{}, - expectErr: true, - expectedErrPrefix: "must be set", - }, - { - name: "wrong number of parts", - value: "test", - expectedNsName: types.NamespacedName{}, - expectErr: true, - expectedErrPrefix: "invalid format; must be NAMESPACE/NAME", - }, - { - name: "invalid namespace", - value: "t@st/my-gateway", - expectedNsName: types.NamespacedName{}, - expectErr: true, - expectedErrPrefix: "invalid namespace name", - }, - { - name: "invalid name", - value: "test/my-g@teway", - expectedNsName: types.NamespacedName{}, - expectErr: true, - expectedErrPrefix: "invalid resource name", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - nsName, err := parseNamespacedResourceName(test.value) - - if test.expectErr { - g.Expect(err).To(HaveOccurred()) - g.Expect(err.Error()).To(HavePrefix(test.expectedErrPrefix)) - } else { - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(nsName).To(Equal(test.expectedNsName)) - } - }) - } -} - func TestValidateQualifiedName(t *testing.T) { t.Parallel() tests := []struct { diff --git a/internal/mode/static/config/config.go b/internal/mode/static/config/config.go index 9248070e03..25630eb8f0 100644 --- a/internal/mode/static/config/config.go +++ b/internal/mode/static/config/config.go @@ -5,7 +5,6 @@ import ( "github.com/go-logr/logr" "go.uber.org/zap" - "k8s.io/apimachinery/pkg/types" ) const DefaultNginxMetricsPort = int32(9113) @@ -19,9 +18,6 @@ type Config struct { ImageSource string // Flags contains the NGF command-line flag names and values. Flags Flags - // GatewayNsName is the namespaced name of a Gateway resource that the Gateway will use. - // The Gateway will ignore all other Gateway resources. - GatewayNsName *types.NamespacedName // GatewayPodConfig contains information about this Pod. GatewayPodConfig GatewayPodConfig // Logger is the Zap Logger used by all components. @@ -46,8 +42,6 @@ type Config struct { MetricsConfig MetricsConfig // HealthConfig specifies the health probe config. HealthConfig HealthConfig - // UpdateGatewayClassStatus enables updating the status of the GatewayClass resource. - UpdateGatewayClassStatus bool // Plus indicates whether NGINX Plus is being used. Plus bool // ExperimentalFeatures indicates if experimental features are enabled. diff --git a/internal/mode/static/handler.go b/internal/mode/static/handler.go index c22b182e8e..af510dadfc 100644 --- a/internal/mode/static/handler.go +++ b/internal/mode/static/handler.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "sync" "time" @@ -79,8 +80,6 @@ type eventHandlerConfig struct { gatewayCtlrName string // gatewayClassName is the name of the GatewayClass. gatewayClassName string - // updateGatewayClassStatus enables updating the status of the GatewayClass resource. - updateGatewayClassStatus bool // plus is whether or not we are running NGINX Plus. plus bool } @@ -185,7 +184,7 @@ func (h *eventHandlerImpl) sendNginxConfig( return } - if gr.Gateway == nil { + if len(gr.Gateways) == 0 { // still need to update GatewayClass status obj := &status.QueueObject{ UpdateType: status.UpdateAll, @@ -194,40 +193,42 @@ func (h *eventHandlerImpl) sendNginxConfig( return } - go func() { - if err := h.cfg.nginxProvisioner.RegisterGateway(ctx, gr.Gateway, gr.DeploymentName.Name); err != nil { - logger.Error(err, "error from provisioner") - } - }() + for _, gw := range gr.Gateways { + go func() { + if err := h.cfg.nginxProvisioner.RegisterGateway(ctx, gw, gw.DeploymentName.Name); err != nil { + logger.Error(err, "error from provisioner") + } + }() - if !gr.Gateway.Valid { - obj := &status.QueueObject{ - Deployment: gr.DeploymentName, - UpdateType: status.UpdateAll, + if !gw.Valid { + obj := &status.QueueObject{ + Deployment: gw.DeploymentName, + UpdateType: status.UpdateAll, + } + h.cfg.statusQueue.Enqueue(obj) + return } - h.cfg.statusQueue.Enqueue(obj) - return - } - stopCh := make(chan struct{}) - deployment := h.cfg.nginxDeployments.GetOrStore(ctx, gr.DeploymentName, stopCh) - if deployment == nil { - panic("expected deployment, got nil") - } + stopCh := make(chan struct{}) + deployment := h.cfg.nginxDeployments.GetOrStore(ctx, gw.DeploymentName, stopCh) + if deployment == nil { + panic("expected deployment, got nil") + } - configApplied := h.processStateAndBuildConfig(ctx, logger, gr, changeType, deployment) + configApplied := h.processStateAndBuildConfig(ctx, logger, gr, gw, changeType, deployment) - configErr := deployment.GetLatestConfigError() - upstreamErr := deployment.GetLatestUpstreamError() - err := errors.Join(configErr, upstreamErr) + configErr := deployment.GetLatestConfigError() + upstreamErr := deployment.GetLatestUpstreamError() + err := errors.Join(configErr, upstreamErr) - if configApplied || err != nil { - obj := &status.QueueObject{ - UpdateType: status.UpdateAll, - Error: err, - Deployment: gr.DeploymentName, + if configApplied || err != nil { + obj := &status.QueueObject{ + UpdateType: status.UpdateAll, + Error: err, + Deployment: gw.DeploymentName, + } + h.cfg.statusQueue.Enqueue(obj) } - h.cfg.statusQueue.Enqueue(obj) } } @@ -235,6 +236,7 @@ func (h *eventHandlerImpl) processStateAndBuildConfig( ctx context.Context, logger logr.Logger, gr *graph.Graph, + currentGateway *graph.Gateway, changeType state.ChangeType, deployment *agent.Deployment, ) bool { @@ -242,7 +244,7 @@ func (h *eventHandlerImpl) processStateAndBuildConfig( switch changeType { case state.EndpointsOnlyChange: h.version++ - cfg := dataplane.BuildConfiguration(ctx, gr, h.cfg.serviceResolver, h.version, h.cfg.plus) + cfg := dataplane.BuildConfiguration(ctx, gr, currentGateway, h.cfg.serviceResolver, h.version, h.cfg.plus) depCtx, getErr := h.getDeploymentContext(ctx) if getErr != nil { logger.Error(getErr, "error getting deployment context for usage reporting") @@ -260,7 +262,7 @@ func (h *eventHandlerImpl) processStateAndBuildConfig( deployment.FileLock.Unlock() case state.ClusterStateChange: h.version++ - cfg := dataplane.BuildConfiguration(ctx, gr, h.cfg.serviceResolver, h.version, h.cfg.plus) + cfg := dataplane.BuildConfiguration(ctx, gr, currentGateway, h.cfg.serviceResolver, h.version, h.cfg.plus) depCtx, getErr := h.getDeploymentContext(ctx) if getErr != nil { logger.Error(getErr, "error getting deployment context for usage reporting") @@ -284,32 +286,46 @@ func (h *eventHandlerImpl) waitForStatusUpdates(ctx context.Context) { return } - // TODO(sberman): once we support multiple Gateways, we'll have to get - // the correct Graph for the Deployment contained in the update message gr := h.cfg.processor.GetLatestGraph() if gr == nil { continue } var nginxReloadRes graph.NginxReloadResult + var gw *graph.Gateway + if item.Deployment.Name != "" { + gwNSName := types.NamespacedName{ + Namespace: item.Deployment.Namespace, + Name: strings.TrimSuffix(item.Deployment.Name, fmt.Sprintf("-%s", h.cfg.gatewayClassName)), + } + + gw = gr.Gateways[gwNSName] + } + switch { case item.Error != nil: h.cfg.logger.Error(item.Error, "Failed to update NGINX configuration") nginxReloadRes.Error = item.Error - case gr.Gateway != nil: + case gw != nil: h.cfg.logger.Info("NGINX configuration was successfully updated") } - gr.LatestReloadResult = nginxReloadRes + if gw != nil { + gw.LatestReloadResult = nginxReloadRes + } switch item.UpdateType { case status.UpdateAll: - h.updateStatuses(ctx, gr) + h.updateStatuses(ctx, gr, gw) case status.UpdateGateway: + if gw == nil { + continue + } + gwAddresses, err := getGatewayAddresses( ctx, h.cfg.k8sClient, item.GatewayService, - gr.Gateway, + gw, h.cfg.gatewayClassName, ) if err != nil { @@ -326,12 +342,12 @@ func (h *eventHandlerImpl) waitForStatusUpdates(ctx context.Context) { } transitionTime := metav1.Now() + gatewayStatuses := status.PrepareGatewayRequests( - gr.Gateway, - gr.IgnoredGateways, + gw, transitionTime, gwAddresses, - gr.LatestReloadResult, + gw.LatestReloadResult, ) h.cfg.statusUpdater.UpdateGroup(ctx, groupGateways, gatewayStatuses...) default: @@ -340,8 +356,16 @@ func (h *eventHandlerImpl) waitForStatusUpdates(ctx context.Context) { } } -func (h *eventHandlerImpl) updateStatuses(ctx context.Context, gr *graph.Graph) { - gwAddresses, err := getGatewayAddresses(ctx, h.cfg.k8sClient, nil, gr.Gateway, h.cfg.gatewayClassName) +func (h *eventHandlerImpl) updateStatuses(ctx context.Context, gr *graph.Graph, gw *graph.Gateway) { + transitionTime := metav1.Now() + gcReqs := status.PrepareGatewayClassRequests(gr.GatewayClass, gr.IgnoredGatewayClasses, transitionTime) + + if gw == nil { + h.cfg.statusUpdater.UpdateGroup(ctx, groupAllExceptGateways, gcReqs...) + return + } + + gwAddresses, err := getGatewayAddresses(ctx, h.cfg.k8sClient, nil, gw, h.cfg.gatewayClassName) if err != nil { msg := "error getting Gateway Service IP address" h.cfg.logger.Error(err, msg) @@ -354,17 +378,11 @@ func (h *eventHandlerImpl) updateStatuses(ctx context.Context, gr *graph.Graph) ) } - transitionTime := metav1.Now() - - var gcReqs []frameworkStatus.UpdateRequest - if h.cfg.updateGatewayClassStatus { - gcReqs = status.PrepareGatewayClassRequests(gr.GatewayClass, gr.IgnoredGatewayClasses, transitionTime) - } routeReqs := status.PrepareRouteRequests( gr.L4Routes, gr.Routes, transitionTime, - gr.LatestReloadResult, + gw.LatestReloadResult, h.cfg.gatewayCtlrName, ) @@ -392,11 +410,10 @@ func (h *eventHandlerImpl) updateStatuses(ctx context.Context, gr *graph.Graph) // We put Gateway status updates separately from the rest of the statuses because we want to be able // to update them separately from the rest of the graph whenever the public IP of NGF changes. gwReqs := status.PrepareGatewayRequests( - gr.Gateway, - gr.IgnoredGateways, + gw, transitionTime, gwAddresses, - gr.LatestReloadResult, + gw.LatestReloadResult, ) h.cfg.statusUpdater.UpdateGroup(ctx, groupGateways, gwReqs...) } diff --git a/internal/mode/static/handler_test.go b/internal/mode/static/handler_test.go index 5175195a7f..6d62be42b3 100644 --- a/internal/mode/static/handler_test.go +++ b/internal/mode/static/handler_test.go @@ -19,6 +19,7 @@ import ( gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" ngfAPI "github.com/nginx/nginx-gateway-fabric/apis/v1alpha1" + "github.com/nginx/nginx-gateway-fabric/internal/framework/controller" "github.com/nginx/nginx-gateway-fabric/internal/framework/events" "github.com/nginx/nginx-gateway-fabric/internal/framework/helpers" "github.com/nginx/nginx-gateway-fabric/internal/framework/status/statusfakes" @@ -56,17 +57,6 @@ var _ = Describe("eventHandler", func() { cancel context.CancelFunc ) - const nginxGatewayServiceName = "nginx-gateway" - - createService := func(name string) *v1.Service { - return &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: "nginx-gateway", - }, - } - } - expectReconfig := func(expectedConf dataplane.Configuration, expectedFiles []agent.File) { Expect(fakeProcessor.ProcessCallCount()).Should(Equal(1)) @@ -96,9 +86,15 @@ var _ = Describe("eventHandler", func() { ctx, cancel = context.WithCancel(context.Background()) //nolint:fatcontext // ignore for test baseGraph = &graph.Graph{ - Gateway: &graph.Gateway{ - Valid: true, - Source: &gatewayv1.Gateway{}, + Gateways: map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway"}: { + Valid: true, + Source: &gatewayv1.Gateway{}, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway", "nginx"), + }, + }, }, } @@ -116,9 +112,6 @@ var _ = Describe("eventHandler", func() { fakeK8sClient = fake.NewFakeClient() queue = status.NewQueue() - // Needed because handler checks the service from the API on every HandleEventBatch - Expect(fakeK8sClient.Create(context.Background(), createService(nginxGatewayServiceName))).To(Succeed()) - handler = newEventHandlerImpl(eventHandlerConfig{ ctx: ctx, k8sClient: fakeK8sClient, @@ -138,8 +131,8 @@ var _ = Describe("eventHandler", func() { ServiceName: "nginx-gateway", Namespace: "nginx-gateway", }, - metricsCollector: collectors.NewControllerNoopCollector(), - updateGatewayClassStatus: true, + gatewayClassName: "nginx", + metricsCollector: collectors.NewControllerNoopCollector(), }) Expect(handler.cfg.graphBuiltHealthChecker.ready).To(BeFalse()) }) @@ -185,13 +178,12 @@ var _ = Describe("eventHandler", func() { handler.HandleEventBatch(context.Background(), logr.Discard(), batch) - dcfg := dataplane.GetDefaultConfiguration(&graph.Graph{}, 1) + dcfg := dataplane.GetDefaultConfiguration(&graph.Graph{}, 1, &graph.Gateway{}) checkUpsertEventExpectations(e) expectReconfig(dcfg, fakeCfgFiles) Expect(helpers.Diff(handler.GetLatestConfiguration(), &dcfg)).To(BeEmpty()) }) - It("should process Delete", func() { e := &events.DeleteEvent{ Type: &gatewayv1.HTTPRoute{}, @@ -201,7 +193,7 @@ var _ = Describe("eventHandler", func() { handler.HandleEventBatch(context.Background(), logr.Discard(), batch) - dcfg := dataplane.GetDefaultConfiguration(&graph.Graph{}, 1) + dcfg := dataplane.GetDefaultConfiguration(&graph.Graph{}, 1, &graph.Gateway{}) checkDeleteEventExpectations(e) expectReconfig(dcfg, fakeCfgFiles) @@ -219,6 +211,51 @@ var _ = Describe("eventHandler", func() { checkUpsertEventExpectations(e) Expect(fakeProvisioner.RegisterGatewayCallCount()).Should(Equal(0)) Expect(fakeGenerator.GenerateCallCount()).Should(Equal(0)) + // status update for GatewayClass should still occur + Eventually( + func() int { + return fakeStatusUpdater.UpdateGroupCallCount() + }).Should(Equal(1)) + }) + It("should not build anything if graph is nil", func() { + fakeProcessor.ProcessReturns(state.ClusterStateChange, nil) + + e := &events.UpsertEvent{Resource: &gatewayv1.HTTPRoute{}} + batch := []interface{}{e} + + handler.HandleEventBatch(context.Background(), logr.Discard(), batch) + + checkUpsertEventExpectations(e) + Expect(fakeProvisioner.RegisterGatewayCallCount()).Should(Equal(0)) + Expect(fakeGenerator.GenerateCallCount()).Should(Equal(0)) + // status update for GatewayClass should not occur + Eventually( + func() int { + return fakeStatusUpdater.UpdateGroupCallCount() + }).Should(Equal(0)) + }) + It("should update gateway class even if gateway is invalid", func() { + fakeProcessor.ProcessReturns(state.ClusterStateChange, &graph.Graph{ + Gateways: map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway"}: { + Valid: false, + }, + }, + }) + + e := &events.UpsertEvent{Resource: &gatewayv1.HTTPRoute{}} + batch := []interface{}{e} + + handler.HandleEventBatch(context.Background(), logr.Discard(), batch) + + checkUpsertEventExpectations(e) + Expect(fakeProvisioner.RegisterGatewayCallCount()).Should(Equal(0)) + Expect(fakeGenerator.GenerateCallCount()).Should(Equal(0)) + // status update should still occur for GatewayClasses + Eventually( + func() int { + return fakeStatusUpdater.UpdateGroupCallCount() + }).Should(Equal(1)) }) }) @@ -238,74 +275,12 @@ var _ = Describe("eventHandler", func() { handler.HandleEventBatch(context.Background(), logr.Discard(), batch) - dcfg := dataplane.GetDefaultConfiguration(&graph.Graph{}, 2) + dcfg := dataplane.GetDefaultConfiguration(&graph.Graph{}, 2, &graph.Gateway{}) Expect(helpers.Diff(handler.GetLatestConfiguration(), &dcfg)).To(BeEmpty()) }) }) }) - DescribeTable( - "updating statuses of GatewayClass conditionally based on handler configuration", - func(updateGatewayClassStatus bool) { - handler.cfg.updateGatewayClassStatus = updateGatewayClassStatus - - gc := &gatewayv1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - }, - } - ignoredGC := &gatewayv1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ignored", - }, - } - - gr := &graph.Graph{ - GatewayClass: &graph.GatewayClass{ - Source: gc, - Valid: true, - }, - IgnoredGatewayClasses: map[types.NamespacedName]*gatewayv1.GatewayClass{ - client.ObjectKeyFromObject(ignoredGC): ignoredGC, - }, - } - - fakeProcessor.ProcessReturns(state.ClusterStateChange, gr) - fakeProcessor.GetLatestGraphReturns(gr) - - e := &events.UpsertEvent{ - Resource: &gatewayv1.HTTPRoute{}, // any supported is OK - } - - batch := []interface{}{e} - - var expectedReqsCount int - if updateGatewayClassStatus { - expectedReqsCount = 2 - } - - handler.HandleEventBatch(context.Background(), logr.Discard(), batch) - - Eventually( - func() int { - return fakeStatusUpdater.UpdateGroupCallCount() - }).Should(Equal(2)) - - _, name, reqs := fakeStatusUpdater.UpdateGroupArgsForCall(0) - Expect(name).To(Equal(groupAllExceptGateways)) - Expect(reqs).To(HaveLen(expectedReqsCount)) - for _, req := range reqs { - Expect(req.NsName).To(BeElementOf( - client.ObjectKeyFromObject(gc), - client.ObjectKeyFromObject(ignoredGC), - )) - Expect(req.ResourceType).To(Equal(&gatewayv1.GatewayClass{})) - } - }, - Entry("should update statuses of GatewayClass", true), - Entry("should not update statuses of GatewayClass", false), - ) - When("receiving control plane configuration updates", func() { cfg := func(level ngfAPI.ControllerLogLevel) *ngfAPI.NginxGateway { return &ngfAPI.NginxGateway{ @@ -327,7 +302,11 @@ var _ = Describe("eventHandler", func() { Expect(handler.GetLatestConfiguration()).To(BeNil()) - Expect(fakeStatusUpdater.UpdateGroupCallCount()).To(Equal(1)) + Eventually( + func() int { + return fakeStatusUpdater.UpdateGroupCallCount() + }).Should(Equal(1)) + _, name, reqs := fakeStatusUpdater.UpdateGroupArgsForCall(0) Expect(name).To(Equal(groupControlPlane)) Expect(reqs).To(HaveLen(1)) @@ -342,7 +321,11 @@ var _ = Describe("eventHandler", func() { Expect(handler.GetLatestConfiguration()).To(BeNil()) - Expect(fakeStatusUpdater.UpdateGroupCallCount()).To(Equal(1)) + Eventually( + func() int { + return fakeStatusUpdater.UpdateGroupCallCount() + }).Should(Equal(1)) + _, name, reqs := fakeStatusUpdater.UpdateGroupArgsForCall(0) Expect(name).To(Equal(groupControlPlane)) Expect(reqs).To(HaveLen(1)) @@ -370,7 +353,11 @@ var _ = Describe("eventHandler", func() { Expect(handler.GetLatestConfiguration()).To(BeNil()) - Expect(fakeStatusUpdater.UpdateGroupCallCount()).To(Equal(1)) + Eventually( + func() int { + return fakeStatusUpdater.UpdateGroupCallCount() + }).Should(Equal(1)) + _, name, reqs := fakeStatusUpdater.UpdateGroupArgsForCall(0) Expect(name).To(Equal(groupControlPlane)) Expect(reqs).To(BeEmpty()) @@ -392,7 +379,11 @@ var _ = Describe("eventHandler", func() { batch := []interface{}{e} BeforeEach(func() { - fakeProcessor.ProcessReturns(state.EndpointsOnlyChange, &graph.Graph{Gateway: &graph.Gateway{Valid: true}}) + fakeProcessor.ProcessReturns(state.EndpointsOnlyChange, &graph.Graph{ + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: {Valid: true}, + }, + }) }) When("running NGINX Plus", func() { @@ -401,7 +392,7 @@ var _ = Describe("eventHandler", func() { handler.HandleEventBatch(context.Background(), logr.Discard(), batch) - dcfg := dataplane.GetDefaultConfiguration(&graph.Graph{}, 1) + dcfg := dataplane.GetDefaultConfiguration(&graph.Graph{}, 1, &graph.Gateway{}) dcfg.NginxPlus = dataplane.NginxPlus{AllowedAddresses: []string{"127.0.0.1"}} Expect(helpers.Diff(handler.GetLatestConfiguration(), &dcfg)).To(BeEmpty()) @@ -414,7 +405,7 @@ var _ = Describe("eventHandler", func() { It("should not call the NGINX Plus API", func() { handler.HandleEventBatch(context.Background(), logr.Discard(), batch) - dcfg := dataplane.GetDefaultConfiguration(&graph.Graph{}, 1) + dcfg := dataplane.GetDefaultConfiguration(&graph.Graph{}, 1, &graph.Gateway{}) Expect(helpers.Diff(handler.GetLatestConfiguration(), &dcfg)).To(BeEmpty()) Expect(fakeGenerator.GenerateCallCount()).To(Equal(1)) @@ -427,8 +418,11 @@ var _ = Describe("eventHandler", func() { It("should update status when receiving a queue event", func() { obj := &status.QueueObject{ UpdateType: status.UpdateAll, - Deployment: types.NamespacedName{}, - Error: errors.New("status error"), + Deployment: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway", "nginx"), + }, + Error: errors.New("status error"), } queue.Enqueue(obj) @@ -438,13 +432,17 @@ var _ = Describe("eventHandler", func() { }).Should(Equal(2)) gr := handler.cfg.processor.GetLatestGraph() - Expect(gr.LatestReloadResult.Error.Error()).To(Equal("status error")) + gw := gr.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway"}] + Expect(gw.LatestReloadResult.Error.Error()).To(Equal("status error")) }) It("should update Gateway status when receiving a queue event", func() { obj := &status.QueueObject{ - UpdateType: status.UpdateGateway, - Deployment: types.NamespacedName{}, + UpdateType: status.UpdateGateway, + Deployment: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway", "nginx"), + }, GatewayService: &v1.Service{}, } queue.Enqueue(obj) @@ -460,12 +458,16 @@ var _ = Describe("eventHandler", func() { batch := []interface{}{e} readyChannel := handler.cfg.graphBuiltHealthChecker.getReadyCh() - fakeProcessor.ProcessReturns(state.ClusterStateChange, &graph.Graph{Gateway: &graph.Gateway{Valid: true}}) + fakeProcessor.ProcessReturns(state.ClusterStateChange, &graph.Graph{ + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: {Valid: true}, + }, + }) Expect(handler.cfg.graphBuiltHealthChecker.readyCheck(nil)).ToNot(Succeed()) handler.HandleEventBatch(context.Background(), logr.Discard(), batch) - dcfg := dataplane.GetDefaultConfiguration(&graph.Graph{}, 1) + dcfg := dataplane.GetDefaultConfiguration(&graph.Graph{}, 1, &graph.Gateway{}) Expect(helpers.Diff(handler.GetLatestConfiguration(), &dcfg)).To(BeEmpty()) Expect(readyChannel).To(BeClosed()) diff --git a/internal/mode/static/manager.go b/internal/mode/static/manager.go index 7b3d28f140..0c78a8f10d 100644 --- a/internal/mode/static/manager.go +++ b/internal/mode/static/manager.go @@ -245,21 +245,20 @@ func StartManager(cfg config.Config) error { &cfg.UsageReportConfig, cfg.Logger.WithName("generator"), ), - k8sClient: mgr.GetClient(), - k8sReader: mgr.GetAPIReader(), - logger: cfg.Logger.WithName("eventHandler"), - logLevelSetter: logLevelSetter, - eventRecorder: recorder, - deployCtxCollector: deployCtxCollector, - graphBuiltHealthChecker: healthChecker, - gatewayPodConfig: cfg.GatewayPodConfig, - controlConfigNSName: controlConfigNSName, - gatewayCtlrName: cfg.GatewayCtlrName, - gatewayClassName: cfg.GatewayClassName, - updateGatewayClassStatus: cfg.UpdateGatewayClassStatus, - plus: cfg.Plus, - statusQueue: statusQueue, - nginxDeployments: nginxUpdater.NginxDeployments, + k8sClient: mgr.GetClient(), + k8sReader: mgr.GetAPIReader(), + logger: cfg.Logger.WithName("eventHandler"), + logLevelSetter: logLevelSetter, + eventRecorder: recorder, + deployCtxCollector: deployCtxCollector, + graphBuiltHealthChecker: healthChecker, + gatewayPodConfig: cfg.GatewayPodConfig, + controlConfigNSName: controlConfigNSName, + gatewayCtlrName: cfg.GatewayCtlrName, + gatewayClassName: cfg.GatewayClassName, + plus: cfg.Plus, + statusQueue: statusQueue, + nginxDeployments: nginxUpdater.NginxDeployments, }) objects, objectLists := prepareFirstEventBatchPreparerArgs(cfg) @@ -435,12 +434,6 @@ func registerControllers( options := []controller.Option{ controller.WithK8sPredicate(k8spredicate.GenerationChangedPredicate{}), } - if cfg.GatewayNsName != nil { - options = append( - options, - controller.WithNamespacedNameFilter(filter.CreateSingleResourceFilter(*cfg.GatewayNsName)), - ) - } return options }(), }, @@ -778,16 +771,7 @@ func prepareFirstEventBatchPreparerArgs(cfg config.Config) ([]client.Object, []c ) } - gwNsName := cfg.GatewayNsName - - if gwNsName == nil { - objectLists = append(objectLists, &gatewayv1.GatewayList{}) - } else { - objects = append( - objects, - &gatewayv1.Gateway{ObjectMeta: metav1.ObjectMeta{Name: gwNsName.Name, Namespace: gwNsName.Namespace}}, - ) - } + objectLists = append(objectLists, &gatewayv1.GatewayList{}) return objects, objectLists } diff --git a/internal/mode/static/manager_test.go b/internal/mode/static/manager_test.go index 98a6146905..9a9f0768b7 100644 --- a/internal/mode/static/manager_test.go +++ b/internal/mode/static/manager_test.go @@ -45,10 +45,9 @@ func TestPrepareFirstEventBatchPreparerArgs(t *testing.T) { cfg config.Config }{ { - name: "gwNsName is nil", + name: "base case", cfg: config.Config{ GatewayClassName: gcName, - GatewayNsName: nil, ExperimentalFeatures: false, SnippetsFilters: false, }, @@ -72,49 +71,14 @@ func TestPrepareFirstEventBatchPreparerArgs(t *testing.T) { }, }, { - name: "gwNsName is not nil", + name: "experimental enabled", cfg: config.Config{ - GatewayClassName: gcName, - GatewayNsName: &types.NamespacedName{ - Namespace: "test", - Name: "my-gateway", - }, - ExperimentalFeatures: false, - SnippetsFilters: false, - }, - expectedObjects: []client.Object{ - &gatewayv1.GatewayClass{ObjectMeta: metav1.ObjectMeta{Name: "nginx"}}, - &gatewayv1.Gateway{ObjectMeta: metav1.ObjectMeta{Name: "my-gateway", Namespace: "test"}}, - }, - expectedObjectLists: []client.ObjectList{ - &apiv1.ServiceList{}, - &apiv1.SecretList{}, - &apiv1.NamespaceList{}, - &discoveryV1.EndpointSliceList{}, - &gatewayv1.HTTPRouteList{}, - &gatewayv1beta1.ReferenceGrantList{}, - &ngfAPIv1alpha2.NginxProxyList{}, - &gatewayv1.GRPCRouteList{}, - partialObjectMetadataList, - &ngfAPIv1alpha1.ClientSettingsPolicyList{}, - &ngfAPIv1alpha2.ObservabilityPolicyList{}, - &ngfAPIv1alpha1.UpstreamSettingsPolicyList{}, - }, - }, - { - name: "gwNsName is not nil and experimental enabled", - cfg: config.Config{ - GatewayClassName: gcName, - GatewayNsName: &types.NamespacedName{ - Namespace: "test", - Name: "my-gateway", - }, + GatewayClassName: gcName, ExperimentalFeatures: true, SnippetsFilters: false, }, expectedObjects: []client.Object{ &gatewayv1.GatewayClass{ObjectMeta: metav1.ObjectMeta{Name: "nginx"}}, - &gatewayv1.Gateway{ObjectMeta: metav1.ObjectMeta{Name: "my-gateway", Namespace: "test"}}, }, expectedObjectLists: []client.ObjectList{ &apiv1.ServiceList{}, @@ -123,6 +87,7 @@ func TestPrepareFirstEventBatchPreparerArgs(t *testing.T) { &apiv1.ConfigMapList{}, &discoveryV1.EndpointSliceList{}, &gatewayv1.HTTPRouteList{}, + &gatewayv1.GatewayList{}, &gatewayv1beta1.ReferenceGrantList{}, &ngfAPIv1alpha2.NginxProxyList{}, partialObjectMetadataList, @@ -135,19 +100,14 @@ func TestPrepareFirstEventBatchPreparerArgs(t *testing.T) { }, }, { - name: "gwNsName is not nil and snippets filters enabled", + name: "snippets filters enabled", cfg: config.Config{ - GatewayClassName: gcName, - GatewayNsName: &types.NamespacedName{ - Namespace: "test", - Name: "my-gateway", - }, + GatewayClassName: gcName, ExperimentalFeatures: false, SnippetsFilters: true, }, expectedObjects: []client.Object{ &gatewayv1.GatewayClass{ObjectMeta: metav1.ObjectMeta{Name: "nginx"}}, - &gatewayv1.Gateway{ObjectMeta: metav1.ObjectMeta{Name: "my-gateway", Namespace: "test"}}, }, expectedObjectLists: []client.ObjectList{ &apiv1.ServiceList{}, @@ -155,6 +115,7 @@ func TestPrepareFirstEventBatchPreparerArgs(t *testing.T) { &apiv1.NamespaceList{}, &discoveryV1.EndpointSliceList{}, &gatewayv1.HTTPRouteList{}, + &gatewayv1.GatewayList{}, &gatewayv1beta1.ReferenceGrantList{}, &ngfAPIv1alpha2.NginxProxyList{}, partialObjectMetadataList, @@ -166,19 +127,14 @@ func TestPrepareFirstEventBatchPreparerArgs(t *testing.T) { }, }, { - name: "gwNsName is not nil, experimental and snippets filters enabled", + name: "experimental and snippets filters enabled", cfg: config.Config{ - GatewayClassName: gcName, - GatewayNsName: &types.NamespacedName{ - Namespace: "test", - Name: "my-gateway", - }, + GatewayClassName: gcName, ExperimentalFeatures: true, SnippetsFilters: true, }, expectedObjects: []client.Object{ &gatewayv1.GatewayClass{ObjectMeta: metav1.ObjectMeta{Name: "nginx"}}, - &gatewayv1.Gateway{ObjectMeta: metav1.ObjectMeta{Name: "my-gateway", Namespace: "test"}}, }, expectedObjectLists: []client.ObjectList{ &apiv1.ServiceList{}, @@ -187,6 +143,7 @@ func TestPrepareFirstEventBatchPreparerArgs(t *testing.T) { &apiv1.ConfigMapList{}, &discoveryV1.EndpointSliceList{}, &gatewayv1.HTTPRouteList{}, + &gatewayv1.GatewayList{}, &gatewayv1beta1.ReferenceGrantList{}, &ngfAPIv1alpha2.NginxProxyList{}, partialObjectMetadataList, diff --git a/internal/mode/static/nginx/config/policies/clientsettings/validator.go b/internal/mode/static/nginx/config/policies/clientsettings/validator.go index 98198eb264..7c450b2379 100644 --- a/internal/mode/static/nginx/config/policies/clientsettings/validator.go +++ b/internal/mode/static/nginx/config/policies/clientsettings/validator.go @@ -25,7 +25,7 @@ func NewValidator(genericValidator validation.GenericValidator) *Validator { } // Validate validates the spec of a ClientSettingsPolicy. -func (v *Validator) Validate(policy policies.Policy, _ *policies.GlobalSettings) []conditions.Condition { +func (v *Validator) Validate(policy policies.Policy) []conditions.Condition { csp := helpers.MustCastObject[*ngfAPI.ClientSettingsPolicy](policy) targetRefPath := field.NewPath("spec").Child("targetRef") @@ -43,6 +43,14 @@ func (v *Validator) Validate(policy policies.Policy, _ *policies.GlobalSettings) return nil } +// ValidateGlobalSettings validates a ClientSettingsPolicy with respect to the NginxProxy global settings. +func (v *Validator) ValidateGlobalSettings( + _ policies.Policy, + _ *policies.GlobalSettings, +) []conditions.Condition { + return nil +} + // Conflicts returns true if the two ClientSettingsPolicies conflict. func (v *Validator) Conflicts(polA, polB policies.Policy) bool { cspA := helpers.MustCastObject[*ngfAPI.ClientSettingsPolicy](polA) diff --git a/internal/mode/static/nginx/config/policies/clientsettings/validator_test.go b/internal/mode/static/nginx/config/policies/clientsettings/validator_test.go index bce96d81c8..88b99ba292 100644 --- a/internal/mode/static/nginx/config/policies/clientsettings/validator_test.go +++ b/internal/mode/static/nginx/config/policies/clientsettings/validator_test.go @@ -143,7 +143,7 @@ func TestValidator_Validate(t *testing.T) { t.Parallel() g := NewWithT(t) - conds := v.Validate(test.policy, nil) + conds := v.Validate(test.policy) g.Expect(conds).To(Equal(test.expConditions)) }) } @@ -154,7 +154,7 @@ func TestValidator_ValidatePanics(t *testing.T) { v := clientsettings.NewValidator(nil) validate := func() { - _ = v.Validate(&policiesfakes.FakePolicy{}, nil) + _ = v.Validate(&policiesfakes.FakePolicy{}) } g := NewWithT(t) @@ -162,6 +162,15 @@ func TestValidator_ValidatePanics(t *testing.T) { g.Expect(validate).To(Panic()) } +func TestValidator_ValidateGlobalSettings(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + v := clientsettings.NewValidator(validation.GenericValidator{}) + + g.Expect(v.ValidateGlobalSettings(nil, nil)).To(BeNil()) +} + func TestValidator_Conflicts(t *testing.T) { t.Parallel() tests := []struct { diff --git a/internal/mode/static/nginx/config/policies/observability/validator.go b/internal/mode/static/nginx/config/policies/observability/validator.go index 4d7182e128..798b3099c1 100644 --- a/internal/mode/static/nginx/config/policies/observability/validator.go +++ b/internal/mode/static/nginx/config/policies/observability/validator.go @@ -25,24 +25,9 @@ func NewValidator(genericValidator validation.GenericValidator) *Validator { } // Validate validates the spec of an ObservabilityPolicy. -func (v *Validator) Validate( - policy policies.Policy, - globalSettings *policies.GlobalSettings, -) []conditions.Condition { +func (v *Validator) Validate(policy policies.Policy) []conditions.Condition { obs := helpers.MustCastObject[*ngfAPIv1alpha2.ObservabilityPolicy](policy) - if globalSettings == nil || !globalSettings.NginxProxyValid { - return []conditions.Condition{ - staticConds.NewPolicyNotAcceptedNginxProxyNotSet(staticConds.PolicyMessageNginxProxyInvalid), - } - } - - if !globalSettings.TelemetryEnabled { - return []conditions.Condition{ - staticConds.NewPolicyNotAcceptedNginxProxyNotSet(staticConds.PolicyMessageTelemetryNotEnabled), - } - } - targetRefPath := field.NewPath("spec").Child("targetRefs") supportedKinds := []gatewayv1.Kind{kinds.HTTPRoute, kinds.GRPCRoute} supportedGroups := []gatewayv1.Group{gatewayv1.GroupName} @@ -60,6 +45,26 @@ func (v *Validator) Validate( return nil } +// ValidateGlobalSettings validates an ObservabilityPolicy with respect to the NginxProxy global settings. +func (v *Validator) ValidateGlobalSettings( + _ policies.Policy, + globalSettings *policies.GlobalSettings, +) []conditions.Condition { + if globalSettings == nil { + return []conditions.Condition{ + staticConds.NewPolicyNotAcceptedNginxProxyNotSet(staticConds.PolicyMessageNginxProxyInvalid), + } + } + + if !globalSettings.TelemetryEnabled { + return []conditions.Condition{ + staticConds.NewPolicyNotAcceptedNginxProxyNotSet(staticConds.PolicyMessageTelemetryNotEnabled), + } + } + + return nil +} + // Conflicts returns true if the two ObservabilityPolicies conflict. func (v *Validator) Conflicts(polA, polB policies.Policy) bool { a := helpers.MustCastObject[*ngfAPIv1alpha2.ObservabilityPolicy](polA) diff --git a/internal/mode/static/nginx/config/policies/observability/validator_test.go b/internal/mode/static/nginx/config/policies/observability/validator_test.go index 5b0894110d..9736320545 100644 --- a/internal/mode/static/nginx/config/policies/observability/validator_test.go +++ b/internal/mode/static/nginx/config/policies/observability/validator_test.go @@ -54,47 +54,18 @@ func createModifiedPolicy(mod policyModFunc) *ngfAPIv1alpha2.ObservabilityPolicy func TestValidator_Validate(t *testing.T) { t.Parallel() - globalSettings := &policies.GlobalSettings{ - NginxProxyValid: true, - TelemetryEnabled: true, - } tests := []struct { - name string - policy *ngfAPIv1alpha2.ObservabilityPolicy - globalSettings *policies.GlobalSettings - expConditions []conditions.Condition + name string + policy *ngfAPIv1alpha2.ObservabilityPolicy + expConditions []conditions.Condition }{ - { - name: "validation context is nil", - policy: createValidPolicy(), - expConditions: []conditions.Condition{ - staticConds.NewPolicyNotAcceptedNginxProxyNotSet(staticConds.PolicyMessageNginxProxyInvalid), - }, - }, - { - name: "validation context is invalid", - policy: createValidPolicy(), - globalSettings: &policies.GlobalSettings{NginxProxyValid: false}, - expConditions: []conditions.Condition{ - staticConds.NewPolicyNotAcceptedNginxProxyNotSet(staticConds.PolicyMessageNginxProxyInvalid), - }, - }, - { - name: "telemetry is not enabled", - policy: createValidPolicy(), - globalSettings: &policies.GlobalSettings{NginxProxyValid: true, TelemetryEnabled: false}, - expConditions: []conditions.Condition{ - staticConds.NewPolicyNotAcceptedNginxProxyNotSet(staticConds.PolicyMessageTelemetryNotEnabled), - }, - }, { name: "invalid target ref; unsupported group", policy: createModifiedPolicy(func(p *ngfAPIv1alpha2.ObservabilityPolicy) *ngfAPIv1alpha2.ObservabilityPolicy { p.Spec.TargetRefs[0].Group = "Unsupported" return p }), - globalSettings: globalSettings, expConditions: []conditions.Condition{ staticConds.NewPolicyInvalid("spec.targetRefs.group: Unsupported value: \"Unsupported\": " + "supported values: \"gateway.networking.k8s.io\""), @@ -106,7 +77,6 @@ func TestValidator_Validate(t *testing.T) { p.Spec.TargetRefs[0].Kind = "Unsupported" return p }), - globalSettings: globalSettings, expConditions: []conditions.Condition{ staticConds.NewPolicyInvalid("spec.targetRefs.kind: Unsupported value: \"Unsupported\": " + "supported values: \"HTTPRoute\", \"GRPCRoute\""), @@ -118,7 +88,6 @@ func TestValidator_Validate(t *testing.T) { p.Spec.Tracing.Strategy = "invalid" return p }), - globalSettings: globalSettings, expConditions: []conditions.Condition{ staticConds.NewPolicyInvalid("spec.tracing.strategy: Unsupported value: \"invalid\": " + "supported values: \"ratio\", \"parent\""), @@ -130,7 +99,6 @@ func TestValidator_Validate(t *testing.T) { p.Spec.Tracing.Context = helpers.GetPointer[ngfAPIv1alpha2.TraceContext]("invalid") return p }), - globalSettings: globalSettings, expConditions: []conditions.Condition{ staticConds.NewPolicyInvalid("spec.tracing.context: Unsupported value: \"invalid\": " + "supported values: \"extract\", \"inject\", \"propagate\", \"ignore\""), @@ -142,7 +110,6 @@ func TestValidator_Validate(t *testing.T) { p.Spec.Tracing.SpanName = helpers.GetPointer("invalid$$$") return p }), - globalSettings: globalSettings, expConditions: []conditions.Condition{ staticConds.NewPolicyInvalid("spec.tracing.spanName: Invalid value: \"invalid$$$\": " + "a valid value must have all '\"' escaped and must not contain any '$' or end with an " + @@ -155,7 +122,6 @@ func TestValidator_Validate(t *testing.T) { p.Spec.Tracing.SpanAttributes[0].Key = "invalid$$$" return p }), - globalSettings: globalSettings, expConditions: []conditions.Condition{ staticConds.NewPolicyInvalid("spec.tracing.spanAttributes.key: Invalid value: \"invalid$$$\": " + "a valid value must have all '\"' escaped and must not contain any '$' or end with an " + @@ -168,7 +134,6 @@ func TestValidator_Validate(t *testing.T) { p.Spec.Tracing.SpanAttributes[0].Value = "invalid$$$" return p }), - globalSettings: globalSettings, expConditions: []conditions.Condition{ staticConds.NewPolicyInvalid("spec.tracing.spanAttributes.value: Invalid value: \"invalid$$$\": " + "a valid value must have all '\"' escaped and must not contain any '$' or end with an " + @@ -176,10 +141,9 @@ func TestValidator_Validate(t *testing.T) { }, }, { - name: "valid", - policy: createValidPolicy(), - globalSettings: globalSettings, - expConditions: nil, + name: "valid", + policy: createValidPolicy(), + expConditions: nil, }, } @@ -190,7 +154,7 @@ func TestValidator_Validate(t *testing.T) { t.Parallel() g := NewWithT(t) - conds := v.Validate(test.policy, test.globalSettings) + conds := v.Validate(test.policy) g.Expect(conds).To(Equal(test.expConditions)) }) } @@ -201,7 +165,7 @@ func TestValidator_ValidatePanics(t *testing.T) { v := observability.NewValidator(nil) validate := func() { - _ = v.Validate(&policiesfakes.FakePolicy{}, nil) + _ = v.Validate(&policiesfakes.FakePolicy{}) } g := NewWithT(t) @@ -209,6 +173,49 @@ func TestValidator_ValidatePanics(t *testing.T) { g.Expect(validate).To(Panic()) } +func TestValidator_ValidateGlobalSettings(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + globalSettings *policies.GlobalSettings + expConditions []conditions.Condition + }{ + { + name: "global settings are nil", + expConditions: []conditions.Condition{ + staticConds.NewPolicyNotAcceptedNginxProxyNotSet(staticConds.PolicyMessageNginxProxyInvalid), + }, + }, + { + name: "telemetry is not enabled", + globalSettings: &policies.GlobalSettings{TelemetryEnabled: false}, + expConditions: []conditions.Condition{ + staticConds.NewPolicyNotAcceptedNginxProxyNotSet(staticConds.PolicyMessageTelemetryNotEnabled), + }, + }, + { + name: "valid", + globalSettings: &policies.GlobalSettings{ + TelemetryEnabled: true, + }, + expConditions: nil, + }, + } + + v := observability.NewValidator(validation.GenericValidator{}) + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + conds := v.ValidateGlobalSettings(nil, test.globalSettings) + g.Expect(conds).To(Equal(test.expConditions)) + }) + } +} + func TestValidator_Conflicts(t *testing.T) { t.Parallel() tests := []struct { diff --git a/internal/mode/static/nginx/config/policies/policiesfakes/fake_validator.go b/internal/mode/static/nginx/config/policies/policiesfakes/fake_validator.go index 43cfb7e87a..598c982837 100644 --- a/internal/mode/static/nginx/config/policies/policiesfakes/fake_validator.go +++ b/internal/mode/static/nginx/config/policies/policiesfakes/fake_validator.go @@ -21,11 +21,10 @@ type FakeValidator struct { conflictsReturnsOnCall map[int]struct { result1 bool } - ValidateStub func(policies.Policy, *policies.GlobalSettings) []conditions.Condition + ValidateStub func(policies.Policy) []conditions.Condition validateMutex sync.RWMutex validateArgsForCall []struct { arg1 policies.Policy - arg2 *policies.GlobalSettings } validateReturns struct { result1 []conditions.Condition @@ -33,6 +32,18 @@ type FakeValidator struct { validateReturnsOnCall map[int]struct { result1 []conditions.Condition } + ValidateGlobalSettingsStub func(policies.Policy, *policies.GlobalSettings) []conditions.Condition + validateGlobalSettingsMutex sync.RWMutex + validateGlobalSettingsArgsForCall []struct { + arg1 policies.Policy + arg2 *policies.GlobalSettings + } + validateGlobalSettingsReturns struct { + result1 []conditions.Condition + } + validateGlobalSettingsReturnsOnCall map[int]struct { + result1 []conditions.Condition + } invocations map[string][][]interface{} invocationsMutex sync.RWMutex } @@ -99,19 +110,18 @@ func (fake *FakeValidator) ConflictsReturnsOnCall(i int, result1 bool) { }{result1} } -func (fake *FakeValidator) Validate(arg1 policies.Policy, arg2 *policies.GlobalSettings) []conditions.Condition { +func (fake *FakeValidator) Validate(arg1 policies.Policy) []conditions.Condition { fake.validateMutex.Lock() ret, specificReturn := fake.validateReturnsOnCall[len(fake.validateArgsForCall)] fake.validateArgsForCall = append(fake.validateArgsForCall, struct { arg1 policies.Policy - arg2 *policies.GlobalSettings - }{arg1, arg2}) + }{arg1}) stub := fake.ValidateStub fakeReturns := fake.validateReturns - fake.recordInvocation("Validate", []interface{}{arg1, arg2}) + fake.recordInvocation("Validate", []interface{}{arg1}) fake.validateMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1) } if specificReturn { return ret.result1 @@ -125,17 +135,17 @@ func (fake *FakeValidator) ValidateCallCount() int { return len(fake.validateArgsForCall) } -func (fake *FakeValidator) ValidateCalls(stub func(policies.Policy, *policies.GlobalSettings) []conditions.Condition) { +func (fake *FakeValidator) ValidateCalls(stub func(policies.Policy) []conditions.Condition) { fake.validateMutex.Lock() defer fake.validateMutex.Unlock() fake.ValidateStub = stub } -func (fake *FakeValidator) ValidateArgsForCall(i int) (policies.Policy, *policies.GlobalSettings) { +func (fake *FakeValidator) ValidateArgsForCall(i int) policies.Policy { fake.validateMutex.RLock() defer fake.validateMutex.RUnlock() argsForCall := fake.validateArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1 } func (fake *FakeValidator) ValidateReturns(result1 []conditions.Condition) { @@ -161,6 +171,68 @@ func (fake *FakeValidator) ValidateReturnsOnCall(i int, result1 []conditions.Con }{result1} } +func (fake *FakeValidator) ValidateGlobalSettings(arg1 policies.Policy, arg2 *policies.GlobalSettings) []conditions.Condition { + fake.validateGlobalSettingsMutex.Lock() + ret, specificReturn := fake.validateGlobalSettingsReturnsOnCall[len(fake.validateGlobalSettingsArgsForCall)] + fake.validateGlobalSettingsArgsForCall = append(fake.validateGlobalSettingsArgsForCall, struct { + arg1 policies.Policy + arg2 *policies.GlobalSettings + }{arg1, arg2}) + stub := fake.ValidateGlobalSettingsStub + fakeReturns := fake.validateGlobalSettingsReturns + fake.recordInvocation("ValidateGlobalSettings", []interface{}{arg1, arg2}) + fake.validateGlobalSettingsMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeValidator) ValidateGlobalSettingsCallCount() int { + fake.validateGlobalSettingsMutex.RLock() + defer fake.validateGlobalSettingsMutex.RUnlock() + return len(fake.validateGlobalSettingsArgsForCall) +} + +func (fake *FakeValidator) ValidateGlobalSettingsCalls(stub func(policies.Policy, *policies.GlobalSettings) []conditions.Condition) { + fake.validateGlobalSettingsMutex.Lock() + defer fake.validateGlobalSettingsMutex.Unlock() + fake.ValidateGlobalSettingsStub = stub +} + +func (fake *FakeValidator) ValidateGlobalSettingsArgsForCall(i int) (policies.Policy, *policies.GlobalSettings) { + fake.validateGlobalSettingsMutex.RLock() + defer fake.validateGlobalSettingsMutex.RUnlock() + argsForCall := fake.validateGlobalSettingsArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeValidator) ValidateGlobalSettingsReturns(result1 []conditions.Condition) { + fake.validateGlobalSettingsMutex.Lock() + defer fake.validateGlobalSettingsMutex.Unlock() + fake.ValidateGlobalSettingsStub = nil + fake.validateGlobalSettingsReturns = struct { + result1 []conditions.Condition + }{result1} +} + +func (fake *FakeValidator) ValidateGlobalSettingsReturnsOnCall(i int, result1 []conditions.Condition) { + fake.validateGlobalSettingsMutex.Lock() + defer fake.validateGlobalSettingsMutex.Unlock() + fake.ValidateGlobalSettingsStub = nil + if fake.validateGlobalSettingsReturnsOnCall == nil { + fake.validateGlobalSettingsReturnsOnCall = make(map[int]struct { + result1 []conditions.Condition + }) + } + fake.validateGlobalSettingsReturnsOnCall[i] = struct { + result1 []conditions.Condition + }{result1} +} + func (fake *FakeValidator) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() @@ -168,6 +240,8 @@ func (fake *FakeValidator) Invocations() map[string][][]interface{} { defer fake.conflictsMutex.RUnlock() fake.validateMutex.RLock() defer fake.validateMutex.RUnlock() + fake.validateGlobalSettingsMutex.RLock() + defer fake.validateGlobalSettingsMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/internal/mode/static/nginx/config/policies/policy.go b/internal/mode/static/nginx/config/policies/policy.go index d65a375db8..93d6054155 100644 --- a/internal/mode/static/nginx/config/policies/policy.go +++ b/internal/mode/static/nginx/config/policies/policy.go @@ -24,8 +24,6 @@ type Policy interface { // GlobalSettings contains global settings from the current state of the graph that may be // needed for policy validation or generation if certain policies rely on those global settings. type GlobalSettings struct { - // NginxProxyValid is whether the NginxProxy resource is valid. - NginxProxyValid bool // TelemetryEnabled is whether telemetry is enabled in the NginxProxy resource. TelemetryEnabled bool } diff --git a/internal/mode/static/nginx/config/policies/upstreamsettings/validator.go b/internal/mode/static/nginx/config/policies/upstreamsettings/validator.go index c3c0a1af5b..aaabcbebc9 100644 --- a/internal/mode/static/nginx/config/policies/upstreamsettings/validator.go +++ b/internal/mode/static/nginx/config/policies/upstreamsettings/validator.go @@ -25,7 +25,7 @@ func NewValidator(genericValidator validation.GenericValidator) Validator { } // Validate validates the spec of an UpstreamsSettingsPolicy. -func (v Validator) Validate(policy policies.Policy, _ *policies.GlobalSettings) []conditions.Condition { +func (v Validator) Validate(policy policies.Policy) []conditions.Condition { usp := helpers.MustCastObject[*ngfAPI.UpstreamSettingsPolicy](policy) targetRefsPath := field.NewPath("spec").Child("targetRefs") @@ -46,6 +46,14 @@ func (v Validator) Validate(policy policies.Policy, _ *policies.GlobalSettings) return nil } +// ValidateGlobalSettings validates an UpstreamSettingsPolicy with respect to the NginxProxy global settings. +func (v Validator) ValidateGlobalSettings( + _ policies.Policy, + _ *policies.GlobalSettings, +) []conditions.Condition { + return nil +} + // Conflicts returns true if the two UpstreamsSettingsPolicies conflict. func (v Validator) Conflicts(polA, polB policies.Policy) bool { cspA := helpers.MustCastObject[*ngfAPI.UpstreamSettingsPolicy](polA) diff --git a/internal/mode/static/nginx/config/policies/upstreamsettings/validator_test.go b/internal/mode/static/nginx/config/policies/upstreamsettings/validator_test.go index e34f4738e0..85699ea297 100644 --- a/internal/mode/static/nginx/config/policies/upstreamsettings/validator_test.go +++ b/internal/mode/static/nginx/config/policies/upstreamsettings/validator_test.go @@ -132,7 +132,7 @@ func TestValidator_Validate(t *testing.T) { t.Parallel() g := NewWithT(t) - conds := v.Validate(test.policy, nil) + conds := v.Validate(test.policy) g.Expect(conds).To(Equal(test.expConditions)) }) } @@ -143,7 +143,7 @@ func TestValidator_ValidatePanics(t *testing.T) { v := upstreamsettings.NewValidator(nil) validate := func() { - _ = v.Validate(&policiesfakes.FakePolicy{}, nil) + _ = v.Validate(&policiesfakes.FakePolicy{}) } g := NewWithT(t) @@ -151,6 +151,15 @@ func TestValidator_ValidatePanics(t *testing.T) { g.Expect(validate).To(Panic()) } +func TestValidator_ValidateGlobalSettings(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + v := upstreamsettings.NewValidator(validation.GenericValidator{}) + + g.Expect(v.ValidateGlobalSettings(nil, nil)).To(BeNil()) +} + func TestValidator_Conflicts(t *testing.T) { t.Parallel() tests := []struct { diff --git a/internal/mode/static/nginx/config/policies/validator.go b/internal/mode/static/nginx/config/policies/validator.go index 0bb7b58924..e618eeea17 100644 --- a/internal/mode/static/nginx/config/policies/validator.go +++ b/internal/mode/static/nginx/config/policies/validator.go @@ -16,7 +16,9 @@ import ( //counterfeiter:generate . Validator type Validator interface { // Validate validates an NGF Policy. - Validate(policy Policy, globalSettings *GlobalSettings) []conditions.Condition + Validate(policy Policy) []conditions.Condition + // ValidateGlobalSettings validates an NGF Policy with the NginxProxy settings. + ValidateGlobalSettings(policy Policy, globalSettings *GlobalSettings) []conditions.Condition // Conflicts returns true if the two Policies conflict. Conflicts(a, b Policy) bool } @@ -54,7 +56,7 @@ func NewManager( } // Validate validates the policy. -func (m *CompositeValidator) Validate(policy Policy, globalSettings *GlobalSettings) []conditions.Condition { +func (m *CompositeValidator) Validate(policy Policy) []conditions.Condition { gvk := m.mustExtractGVK(policy) validator, ok := m.validators[gvk] @@ -62,7 +64,22 @@ func (m *CompositeValidator) Validate(policy Policy, globalSettings *GlobalSetti panic(fmt.Sprintf("no validator registered for policy %T", policy)) } - return validator.Validate(policy, globalSettings) + return validator.Validate(policy) +} + +// ValidateGlobalSettings validates an NGF Policy with the NginxProxy settings. +func (m *CompositeValidator) ValidateGlobalSettings( + policy Policy, + globalSettings *GlobalSettings, +) []conditions.Condition { + gvk := m.mustExtractGVK(policy) + + validator, ok := m.validators[gvk] + if !ok { + panic(fmt.Sprintf("no validator registered for policy %T", policy)) + } + + return validator.ValidateGlobalSettings(policy, globalSettings) } // Conflicts returns true if the policies conflict. diff --git a/internal/mode/static/nginx/config/policies/validator_test.go b/internal/mode/static/nginx/config/policies/validator_test.go index 81b1ee87c8..6787d5360b 100644 --- a/internal/mode/static/nginx/config/policies/validator_test.go +++ b/internal/mode/static/nginx/config/policies/validator_test.go @@ -27,6 +27,13 @@ var _ = Describe("Policy CompositeValidator", func() { }, } + bananaGVK := schema.GroupVersionKind{Group: "fruit", Version: "1", Kind: "banana"} + bananaPolicy := &policiesfakes.FakePolicy{ + GetNameStub: func() string { + return "banana" + }, + } + mustExtractGVK := func(object client.Object) schema.GroupVersionKind { switch object.GetName() { case "apple": @@ -42,34 +49,54 @@ var _ = Describe("Policy CompositeValidator", func() { mustExtractGVK, policies.ManagerConfig{ Validator: &policiesfakes.FakeValidator{ - ValidateStub: func(_ policies.Policy, _ *policies.GlobalSettings) []conditions.Condition { + ValidateStub: func(_ policies.Policy) []conditions.Condition { return []conditions.Condition{staticConds.NewPolicyInvalid("apple error")} }, + ValidateGlobalSettingsStub: func(_ policies.Policy, _ *policies.GlobalSettings) []conditions.Condition { + return []conditions.Condition{staticConds.NewPolicyInvalid("apple global settings error")} + }, ConflictsStub: func(_ policies.Policy, _ policies.Policy) bool { return true }, }, GVK: appleGVK, }, policies.ManagerConfig{ Validator: &policiesfakes.FakeValidator{ - ValidateStub: func(_ policies.Policy, _ *policies.GlobalSettings) []conditions.Condition { + ValidateStub: func(_ policies.Policy) []conditions.Condition { return []conditions.Condition{staticConds.NewPolicyInvalid("orange error")} }, + ValidateGlobalSettingsStub: func(_ policies.Policy, _ *policies.GlobalSettings) []conditions.Condition { + return []conditions.Condition{staticConds.NewPolicyInvalid("orange global settings error")} + }, ConflictsStub: func(_ policies.Policy, _ policies.Policy) bool { return false }, }, GVK: orangeGVK, }, + policies.ManagerConfig{ + Validator: &policiesfakes.FakeValidator{}, + GVK: bananaGVK, + }, ) Context("Validation", func() { When("Policy is registered with manager", func() { It("Validates the policy", func() { - conds := mgr.Validate(applePolicy, nil) + globalSettings := &policies.GlobalSettings{} + + conds := mgr.Validate(applePolicy) Expect(conds).To(HaveLen(1)) Expect(conds[0].Message).To(Equal("apple error")) - conds = mgr.Validate(orangePolicy, nil) + conds = mgr.ValidateGlobalSettings(applePolicy, globalSettings) + Expect(conds).To(HaveLen(1)) + Expect(conds[0].Message).To(Equal("apple global settings error")) + + conds = mgr.Validate(orangePolicy) Expect(conds).To(HaveLen(1)) Expect(conds[0].Message).To(Equal("orange error")) + + conds = mgr.ValidateGlobalSettings(orangePolicy, globalSettings) + Expect(conds).To(HaveLen(1)) + Expect(conds[0].Message).To(Equal("orange global settings error")) }) It("Returns whether the policies conflict", func() { Expect(mgr.Conflicts(applePolicy, applePolicy)).To(BeTrue()) @@ -79,7 +106,7 @@ var _ = Describe("Policy CompositeValidator", func() { When("Policy is not registered with manager", func() { It("Panics on call to validate", func() { validate := func() { - _ = mgr.Validate(&policiesfakes.FakePolicy{}, nil) + _ = mgr.Validate(&policiesfakes.FakePolicy{}) } Expect(validate).To(Panic()) @@ -89,6 +116,13 @@ var _ = Describe("Policy CompositeValidator", func() { _ = mgr.Conflicts(&policiesfakes.FakePolicy{}, &policiesfakes.FakePolicy{}) } + Expect(conflict).To(Panic()) + }) + It("panics on call to conflicts when no validator is registered for policy", func() { + conflict := func() { + _ = mgr.Conflicts(bananaPolicy, bananaPolicy) + } + Expect(conflict).To(Panic()) }) }) diff --git a/internal/mode/static/provisioner/objects.go b/internal/mode/static/provisioner/objects.go index 7c058bf784..f925b9c133 100644 --- a/internal/mode/static/provisioner/objects.go +++ b/internal/mode/static/provisioner/objects.go @@ -423,9 +423,12 @@ func buildNginxService( serviceType = corev1.ServiceType(*serviceCfg.ServiceType) } - servicePolicy := defaultServicePolicy - if serviceCfg.ExternalTrafficPolicy != nil { - servicePolicy = corev1.ServiceExternalTrafficPolicy(*serviceCfg.ExternalTrafficPolicy) + var servicePolicy corev1.ServiceExternalTrafficPolicyType + if serviceType != corev1.ServiceTypeClusterIP { + servicePolicy = defaultServicePolicy + if serviceCfg.ExternalTrafficPolicy != nil { + servicePolicy = corev1.ServiceExternalTrafficPolicy(*serviceCfg.ExternalTrafficPolicy) + } } servicePorts := make([]corev1.ServicePort, 0, len(ports)) @@ -593,6 +596,7 @@ func (p *NginxProvisioner) buildNginxPodTemplateSpec( {MountPath: "/var/run/secrets/ngf", Name: "nginx-agent-tls"}, {MountPath: "/var/run/secrets/ngf/serviceaccount", Name: "token"}, {MountPath: "/var/log/nginx-agent", Name: "nginx-agent-log"}, + {MountPath: "/var/lib/nginx-agent", Name: "nginx-agent-lib"}, {MountPath: "/etc/nginx/conf.d", Name: "nginx-conf"}, {MountPath: "/etc/nginx/stream-conf.d", Name: "nginx-stream-conf"}, {MountPath: "/etc/nginx/main-includes", Name: "nginx-main-includes"}, @@ -687,6 +691,7 @@ func (p *NginxProvisioner) buildNginxPodTemplateSpec( }, }, {Name: "nginx-agent-log", VolumeSource: emptyDirVolumeSource}, + {Name: "nginx-agent-lib", VolumeSource: emptyDirVolumeSource}, {Name: "nginx-conf", VolumeSource: emptyDirVolumeSource}, {Name: "nginx-stream-conf", VolumeSource: emptyDirVolumeSource}, {Name: "nginx-main-includes", VolumeSource: emptyDirVolumeSource}, diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 626f87a0aa..eb18eb97dd 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -58,18 +58,14 @@ func createHTTPRoute( CommonRouteSpec: v1.CommonRouteSpec{ ParentRefs: []v1.ParentReference{ { - Namespace: (*v1.Namespace)(helpers.GetPointer("test")), - Name: v1.ObjectName(gateway), - SectionName: (*v1.SectionName)( - helpers.GetPointer(httpListenerName), - ), + Namespace: (*v1.Namespace)(helpers.GetPointer("test")), + Name: v1.ObjectName(gateway), + SectionName: (*v1.SectionName)(helpers.GetPointer(httpListenerName)), }, { - Namespace: (*v1.Namespace)(helpers.GetPointer("test")), - Name: v1.ObjectName(gateway), - SectionName: (*v1.SectionName)( - helpers.GetPointer(httpsListenerName), - ), + Namespace: (*v1.Namespace)(helpers.GetPointer("test")), + Name: v1.ObjectName(gateway), + SectionName: (*v1.SectionName)(helpers.GetPointer(httpsListenerName)), }, }, }, @@ -109,18 +105,14 @@ func createGRPCRoute( CommonRouteSpec: v1.CommonRouteSpec{ ParentRefs: []v1.ParentReference{ { - Namespace: (*v1.Namespace)(helpers.GetPointer("test")), - Name: v1.ObjectName(gateway), - SectionName: (*v1.SectionName)( - helpers.GetPointer(httpListenerName), - ), + Namespace: (*v1.Namespace)(helpers.GetPointer("test")), + Name: v1.ObjectName(gateway), + SectionName: (*v1.SectionName)(helpers.GetPointer(httpListenerName)), }, { - Namespace: (*v1.Namespace)(helpers.GetPointer("test")), - Name: v1.ObjectName(gateway), - SectionName: (*v1.SectionName)( - helpers.GetPointer(httpsListenerName), - ), + Namespace: (*v1.Namespace)(helpers.GetPointer("test")), + Name: v1.ObjectName(gateway), + SectionName: (*v1.SectionName)(helpers.GetPointer(httpsListenerName)), }, }, }, @@ -156,11 +148,9 @@ func createTLSRoute(name, gateway, hostname string, backendRefs ...v1.BackendRef CommonRouteSpec: v1.CommonRouteSpec{ ParentRefs: []v1.ParentReference{ { - Namespace: (*v1.Namespace)(helpers.GetPointer("test")), - Name: v1.ObjectName(gateway), - SectionName: (*v1.SectionName)( - helpers.GetPointer(tlsListenerName), - ), + Namespace: (*v1.Namespace)(helpers.GetPointer("test")), + Name: v1.ObjectName(gateway), + SectionName: (*v1.SectionName)(helpers.GetPointer(tlsListenerName)), }, }, }, @@ -178,7 +168,7 @@ func createTLSRoute(name, gateway, hostname string, backendRefs ...v1.BackendRef func createHTTPListener() v1.Listener { return v1.Listener{ - Name: httpListenerName, + Name: v1.SectionName(httpListenerName), Hostname: nil, Port: 80, Protocol: v1.HTTPProtocolType, @@ -428,15 +418,15 @@ var _ = Describe("ChangeProcessor", func() { hr1, hr1Updated, hr2 *v1.HTTPRoute gr1, gr1Updated, gr2 *v1.GRPCRoute tr1, tr1Updated, tr2 *v1alpha2.TLSRoute - gw1, gw1Updated, gw2 *v1.Gateway + gw1, gw1Updated, gw2, gw2Updated *v1.Gateway secretRefGrant, hrServiceRefGrant *v1beta1.ReferenceGrant grServiceRefGrant, trServiceRefGrant *v1beta1.ReferenceGrant - expGraph *graph.Graph + expGraph, expGraph2 *graph.Graph expRouteHR1, expRouteHR2 *graph.L7Route expRouteGR1, expRouteGR2 *graph.L7Route expRouteTR1, expRouteTR2 *graph.L4Route gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata - httpRouteKey1, httpRouteKey2, grpcRouteKey1, grpcRouteKey2 graph.RouteKey + httpRouteKey1, httpRouteKey2, grpcRouteKey1, grpcRouteKey2 graph.RouteKey // gitleaks:allow not a secret trKey1, trKey2 graph.L4RouteKey refSvc, refGRPCSvc, refTLSSvc types.NamespacedName ) @@ -482,14 +472,16 @@ var _ = Describe("ChangeProcessor", func() { httpRouteKey1 = graph.CreateRouteKey(hr1) hr1Updated = hr1.DeepCopy() hr1Updated.Generation++ - hr2 = createHTTPRoute("hr-2", "gateway-2", "bar.example.com") + + hr2 = createHTTPRoute("hr-2", "gateway-2", "bar.example.com", crossNsHTTPBackendRef) httpRouteKey2 = graph.CreateRouteKey(hr2) gr1 = createGRPCRoute("gr-1", "gateway-1", "foo.example.com", grpcBackendRef) grpcRouteKey1 = graph.CreateRouteKey(gr1) gr1Updated = gr1.DeepCopy() gr1Updated.Generation++ - gr2 = createGRPCRoute("gr-2", "gateway-2", "bar.example.com") + + gr2 = createGRPCRoute("gr-2", "gateway-2", "bar.example.com", grpcBackendRef) grpcRouteKey2 = graph.CreateRouteKey(gr2) tlsBackendRef := createTLSBackendRef(refTLSSvc.Name, refTLSSvc.Namespace) @@ -497,6 +489,7 @@ var _ = Describe("ChangeProcessor", func() { trKey1 = graph.CreateRouteKeyL4(tr1) tr1Updated = tr1.DeepCopy() tr1Updated.Generation++ + tr2 = createTLSRoute("tr-2", "gateway-2", "bar.tls.com", tlsBackendRef) trKey2 = graph.CreateRouteKeyL4(tr2) @@ -645,6 +638,9 @@ var _ = Describe("ChangeProcessor", func() { createTLSListener(tlsListenerName), ) + gw2Updated = gw2.DeepCopy() + gw2Updated.Generation++ + gatewayAPICRD = &metav1.PartialObjectMetadata{ TypeMeta: metav1.TypeMeta{ Kind: "CustomResourceDefinition", @@ -668,20 +664,34 @@ var _ = Describe("ChangeProcessor", func() { ParentRefs: []graph.ParentRef{ { Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpListenerName: {"foo.example.com"}}, - Attached: true, - ListenerPort: 80, + AcceptedHostnames: map[string][]string{ + graph.CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw1), + httpListenerName, + ): {"foo.example.com"}, + }, + Attached: true, + ListenerPort: 80, + }, + Gateway: &graph.ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw1), }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, SectionName: hr1.Spec.ParentRefs[0].SectionName, }, { Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpsListenerName: {"foo.example.com"}}, - Attached: true, - ListenerPort: 443, + AcceptedHostnames: map[string][]string{ + graph.CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw1), + httpsListenerName, + ): {"foo.example.com"}, + }, + Attached: true, + ListenerPort: 443, + }, + Gateway: &graph.ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw1), }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, Idx: 1, SectionName: hr1.Spec.ParentRefs[1].SectionName, }, @@ -692,8 +702,9 @@ var _ = Describe("ChangeProcessor", func() { { BackendRefs: []graph.BackendRef{ { - SvcNsName: refSvc, - Weight: 1, + SvcNsName: refSvc, + Weight: 1, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, ValidMatches: true, @@ -721,20 +732,34 @@ var _ = Describe("ChangeProcessor", func() { ParentRefs: []graph.ParentRef{ { Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, - Attached: true, - ListenerPort: 80, + AcceptedHostnames: map[string][]string{ + graph.CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw2), + httpListenerName, + ): {"bar.example.com"}, + }, + Attached: true, + ListenerPort: 80, + }, + Gateway: &graph.ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw2), }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, SectionName: hr2.Spec.ParentRefs[0].SectionName, }, { Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpsListenerName: {"bar.example.com"}}, - Attached: true, - ListenerPort: 443, + AcceptedHostnames: map[string][]string{ + graph.CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw2), + httpsListenerName, + ): {"bar.example.com"}, + }, + Attached: true, + ListenerPort: 443, + }, + Gateway: &graph.ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw2), }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, Idx: 1, SectionName: hr2.Spec.ParentRefs[1].SectionName, }, @@ -743,18 +768,30 @@ var _ = Describe("ChangeProcessor", func() { Hostnames: hr2.Spec.Hostnames, Rules: []graph.RouteRule{ { + BackendRefs: []graph.BackendRef{ + { + SvcNsName: refSvc, + Weight: 1, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, + }, + }, ValidMatches: true, Filters: graph.RouteRuleFilters{ Valid: true, Filters: []graph.Filter{}, }, Matches: hr2.Spec.Rules[0].Matches, - RouteBackendRefs: []graph.RouteBackendRef{}, + RouteBackendRefs: createRouteBackendRefs(hr2.Spec.Rules[0].BackendRefs), }, }, }, Valid: true, Attachable: true, + Conditions: []conditions.Condition{ + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"service\"", + ), + }, } expRouteGR1 = &graph.L7Route{ @@ -763,20 +800,34 @@ var _ = Describe("ChangeProcessor", func() { ParentRefs: []graph.ParentRef{ { Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpListenerName: {"foo.example.com"}}, - Attached: true, - ListenerPort: 80, + AcceptedHostnames: map[string][]string{ + graph.CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw1), + httpListenerName, + ): {"foo.example.com"}, + }, + Attached: true, + ListenerPort: 80, + }, + Gateway: &graph.ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw1), }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, SectionName: gr1.Spec.ParentRefs[0].SectionName, }, { Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpsListenerName: {"foo.example.com"}}, - Attached: true, - ListenerPort: 443, + AcceptedHostnames: map[string][]string{ + graph.CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw1), + httpsListenerName, + ): {"foo.example.com"}, + }, + Attached: true, + ListenerPort: 443, + }, + Gateway: &graph.ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw1), }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, Idx: 1, SectionName: gr1.Spec.ParentRefs[1].SectionName, }, @@ -787,8 +838,9 @@ var _ = Describe("ChangeProcessor", func() { { BackendRefs: []graph.BackendRef{ { - SvcNsName: refGRPCSvc, - Weight: 1, + SvcNsName: refGRPCSvc, + Weight: 1, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, ValidMatches: true, @@ -816,20 +868,34 @@ var _ = Describe("ChangeProcessor", func() { ParentRefs: []graph.ParentRef{ { Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, - Attached: true, - ListenerPort: 80, + AcceptedHostnames: map[string][]string{ + graph.CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw2), + httpListenerName, + ): {"bar.example.com"}, + }, + Attached: true, + ListenerPort: 80, + }, + Gateway: &graph.ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw2), }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, SectionName: gr2.Spec.ParentRefs[0].SectionName, }, { Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpsListenerName: {"bar.example.com"}}, - Attached: true, - ListenerPort: 443, + AcceptedHostnames: map[string][]string{ + graph.CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw2), + httpsListenerName, + ): {"bar.example.com"}, + }, + Attached: true, + ListenerPort: 443, + }, + Gateway: &graph.ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw2), }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, Idx: 1, SectionName: gr2.Spec.ParentRefs[1].SectionName, }, @@ -838,18 +904,30 @@ var _ = Describe("ChangeProcessor", func() { Hostnames: gr2.Spec.Hostnames, Rules: []graph.RouteRule{ { + BackendRefs: []graph.BackendRef{ + { + SvcNsName: refGRPCSvc, + Weight: 1, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, + }, + }, ValidMatches: true, Filters: graph.RouteRuleFilters{ Valid: true, Filters: []graph.Filter{}, }, Matches: graph.ConvertGRPCMatches(gr2.Spec.Rules[0].Matches), - RouteBackendRefs: []graph.RouteBackendRef{}, + RouteBackendRefs: createGRPCRouteBackendRefs(gr2.Spec.Rules[0].BackendRefs), }, }, }, Valid: true, Attachable: true, + Conditions: []conditions.Condition{ + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", + ), + }, } expRouteTR1 = &graph.L4Route{ @@ -857,18 +935,26 @@ var _ = Describe("ChangeProcessor", func() { ParentRefs: []graph.ParentRef{ { Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{tlsListenerName: {"foo.tls.com"}}, - Attached: true, + AcceptedHostnames: map[string][]string{ + graph.CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw1), + tlsListenerName, + ): {"foo.tls.com"}, + }, + Attached: true, + }, + Gateway: &graph.ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw1), }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, SectionName: tr1.Spec.ParentRefs[0].SectionName, }, }, Spec: graph.L4RouteSpec{ Hostnames: tr1.Spec.Hostnames, BackendRef: graph.BackendRef{ - SvcNsName: refTLSSvc, - Valid: false, + SvcNsName: refTLSSvc, + Valid: false, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, Valid: true, @@ -885,18 +971,26 @@ var _ = Describe("ChangeProcessor", func() { ParentRefs: []graph.ParentRef{ { Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{tlsListenerName: {"bar.tls.com"}}, - Attached: true, + AcceptedHostnames: map[string][]string{ + graph.CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw2), + tlsListenerName, + ): {"bar.tls.com"}, + }, + Attached: true, + }, + Gateway: &graph.ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw2), }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, SectionName: tr2.Spec.ParentRefs[0].SectionName, }, }, Spec: graph.L4RouteSpec{ Hostnames: tr2.Spec.Hostnames, BackendRef: graph.BackendRef{ - SvcNsName: refTLSSvc, - Valid: false, + SvcNsName: refTLSSvc, + Valid: false, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, Valid: true, @@ -915,60 +1009,200 @@ var _ = Describe("ChangeProcessor", func() { Source: gc, Valid: true, }, - Gateway: &graph.Gateway{ - Source: gw1, - Listeners: []*graph.Listener{ - { - Name: httpListenerName, - Source: gw1.Spec.Listeners[0], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, - SupportedKinds: []v1.RouteGroupKind{ - {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, - {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + Gateways: map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway-1"}: { + Source: gw1, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + GatewayName: client.ObjectKeyFromObject(gw1), + Source: gw1.Spec.Listeners[0], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, }, - }, - { - Name: httpsListenerName, - Source: gw1.Spec.Listeners[1], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(diffNsTLSSecret)), - SupportedKinds: []v1.RouteGroupKind{ - {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, - {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + { + Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw1), + Source: gw1.Spec.Listeners[1], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(diffNsTLSSecret)), + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, }, - }, - { - Name: tlsListenerName, - Source: gw1.Spec.Listeners[2], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, - SupportedKinds: []v1.RouteGroupKind{ - {Kind: v1.Kind(kinds.TLSRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + { + Name: tlsListenerName, + GatewayName: client.ObjectKeyFromObject(gw1), + Source: gw1.Spec.Listeners[2], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.TLSRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, }, }, + Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway-1-test-class", + }, }, - Valid: true, }, - IgnoredGateways: map[types.NamespacedName]*v1.Gateway{}, L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, ReferencedSecrets: map[types.NamespacedName]*graph.Secret{}, ReferencedServices: map[types.NamespacedName]*graph.ReferencedService{ - refSvc: {}, - refTLSSvc: {}, - refGRPCSvc: {}, + refSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, + }, + refTLSSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, + }, + refGRPCSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, + }, }, - DeploymentName: types.NamespacedName{ - Namespace: "test", - Name: "gateway-1-test-class", + } + + expGraph2 = &graph.Graph{ + GatewayClass: &graph.GatewayClass{ + Source: gc, + Valid: true, + }, + Gateways: map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway-1"}: { + Source: gw1, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + GatewayName: client.ObjectKeyFromObject(gw1), + Source: gw1.Spec.Listeners[0], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw1), + Source: gw1.Spec.Listeners[1], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(diffNsTLSSecret)), + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: tlsListenerName, + GatewayName: client.ObjectKeyFromObject(gw1), + Source: gw1.Spec.Listeners[2], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.TLSRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + }, + Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway-1-test-class", + }, + }, + {Namespace: "test", Name: "gateway-2"}: { + Source: gw2, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[0], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[1], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)), + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: tlsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[2], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.TLSRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + }, + Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway-2-test-class", + }, + }, + }, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, + Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, + ReferencedSecrets: map[types.NamespacedName]*graph.Secret{ + client.ObjectKeyFromObject(sameNsTLSSecret): { + Source: sameNsTLSSecret, + CertBundle: sameNsTLSCert, + }, + client.ObjectKeyFromObject(diffNsTLSSecret): { + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, + }, + }, + ReferencedServices: map[types.NamespacedName]*graph.ReferencedService{ + refSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, + }, + refTLSSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, + }, + refGRPCSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, + }, }, } }) @@ -1026,9 +1260,10 @@ var _ = Describe("ChangeProcessor", func() { expGraph.GatewayClass = nil - expGraph.Gateway.Conditions = staticConds.NewGatewayInvalid("GatewayClass doesn't exist") - expGraph.Gateway.Valid = false - expGraph.Gateway.Listeners = nil + gw := expGraph.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-1"}] + gw.Conditions = staticConds.NewGatewayInvalid("GatewayClass doesn't exist") + gw.Valid = false + gw.Listeners = nil // no ref grant exists yet for the routes expGraph.Routes[httpRouteKey1].Conditions = []conditions.Condition{ @@ -1052,23 +1287,23 @@ var _ = Describe("ChangeProcessor", func() { // gateway class does not exist so routes cannot attach expGraph.Routes[httpRouteKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingParent()}, } expGraph.Routes[httpRouteKey1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingParent()}, } expGraph.Routes[grpcRouteKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingParent()}, } expGraph.Routes[grpcRouteKey1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingParent()}, } expGraph.L4Routes[trKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingParent()}, } expGraph.ReferencedSecrets = nil @@ -1089,7 +1324,8 @@ var _ = Describe("ChangeProcessor", func() { // No ref grant exists yet for gw1 // so the listener is not valid, but still attachable - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + gw := expGraph.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-1"}] + listener443 := getListenerByName(gw, httpsListenerName) listener443.Valid = false listener443.ResolvedSecret = nil listener443.Conditions = staticConds.NewListenerRefNotPermitted( @@ -1098,7 +1334,10 @@ var _ = Describe("ChangeProcessor", func() { expAttachment80 := &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - httpListenerName: {"foo.example.com"}, + graph.CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw1), + httpListenerName, + ): {"foo.example.com"}, }, Attached: true, ListenerPort: 80, @@ -1106,13 +1345,16 @@ var _ = Describe("ChangeProcessor", func() { expAttachment443 := &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - httpsListenerName: {"foo.example.com"}, + graph.CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw1), + httpsListenerName, + ): {"foo.example.com"}, }, Attached: true, ListenerPort: 443, } - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener80 := getListenerByName(gw, httpListenerName) listener80.Routes[httpRouteKey1].ParentRefs[0].Attachment = expAttachment80 listener443.Routes[httpRouteKey1].ParentRefs[1].Attachment = expAttachment443 listener80.Routes[grpcRouteKey1].ParentRefs[0].Attachment = expAttachment80 @@ -1120,20 +1362,20 @@ var _ = Describe("ChangeProcessor", func() { // no ref grant exists yet for hr1 expGraph.Routes[httpRouteKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteInvalidListener(), staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", ), + staticConds.NewRouteInvalidListener(), } expGraph.Routes[httpRouteKey1].ParentRefs[0].Attachment = expAttachment80 expGraph.Routes[httpRouteKey1].ParentRefs[1].Attachment = expAttachment443 // no ref grant exists yet for gr1 expGraph.Routes[grpcRouteKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteInvalidListener(), staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service grpc-service-ns/grpc-service not permitted by any ReferenceGrant", ), + staticConds.NewRouteInvalidListener(), } expGraph.Routes[grpcRouteKey1].ParentRefs[0].Attachment = expAttachment80 expGraph.Routes[grpcRouteKey1].ParentRefs[1].Attachment = expAttachment443 @@ -1331,10 +1573,11 @@ var _ = Describe("ChangeProcessor", func() { It("returns populated graph", func() { processor.CaptureUpsertChange(hr1Updated) - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + gw := expGraph.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-1"}] + listener443 := getListenerByName(gw, httpsListenerName) listener443.Routes[httpRouteKey1].Source.SetGeneration(hr1Updated.Generation) - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener80 := getListenerByName(gw, httpListenerName) listener80.Routes[httpRouteKey1].Source.SetGeneration(hr1Updated.Generation) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ Source: diffNsTLSSecret, @@ -1349,10 +1592,11 @@ var _ = Describe("ChangeProcessor", func() { It("returns populated graph", func() { processor.CaptureUpsertChange(gr1Updated) - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + gw := expGraph.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-1"}] + listener443 := getListenerByName(gw, httpsListenerName) listener443.Routes[grpcRouteKey1].Source.SetGeneration(gr1Updated.Generation) - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener80 := getListenerByName(gw, httpListenerName) listener80.Routes[grpcRouteKey1].Source.SetGeneration(gr1Updated.Generation) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ Source: diffNsTLSSecret, @@ -1366,7 +1610,8 @@ var _ = Describe("ChangeProcessor", func() { It("returns populated graph", func() { processor.CaptureUpsertChange(tr1Updated) - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) + gw := expGraph.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-1"}] + tlsListener := getListenerByName(gw, tlsListenerName) tlsListener.L4Routes[trKey1].Source.SetGeneration(tr1Updated.Generation) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ @@ -1381,7 +1626,8 @@ var _ = Describe("ChangeProcessor", func() { It("returns populated graph", func() { processor.CaptureUpsertChange(gw1Updated) - expGraph.Gateway.Source.Generation = gw1Updated.Generation + gw := expGraph.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-1"}] + gw.Source.Generation = gw1Updated.Generation expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ Source: diffNsTLSSecret, CertBundle: diffNsTLSCert, @@ -1444,118 +1690,118 @@ var _ = Describe("ChangeProcessor", func() { }) }) When("the second Gateway is upserted", func() { - It("returns populated graph using first gateway", func() { + It("returns populated graph with second gateway", func() { processor.CaptureUpsertChange(gw2) - expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-2"}: gw2, - } - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - CertBundle: diffNsTLSCert, - } - - processAndValidateGraph(expGraph) + processAndValidateGraph(expGraph2) }) }) When("the second HTTPRoute is upserted", func() { It("returns populated graph", func() { processor.CaptureUpsertChange(hr2) - expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-2"}: gw2, + expGraph2.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + CertBundle: diffNsTLSCert, } - expGraph.Routes[httpRouteKey2] = expRouteHR2 - expGraph.Routes[httpRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + + gw2NSName := client.ObjectKeyFromObject(gw2) + gw := expGraph2.Gateways[gw2NSName] + + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRouteKey2: expRouteHR2, } - expGraph.Routes[httpRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRouteKey2: expRouteHR2, } - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - CertBundle: diffNsTLSCert, + + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRouteKey2: expRouteHR2, + httpRouteKey1: expRouteHR1, + grpcRouteKey1: expRouteGR1, } - processAndValidateGraph(expGraph) + expGraph2.ReferencedServices[refSvc].GatewayNsNames[gw2NSName] = struct{}{} + + processAndValidateGraph(expGraph2) }) }) When("the second GRPCRoute is upserted", func() { It("returns populated graph", func() { processor.CaptureUpsertChange(gr2) - expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-2"}: gw2, - } - expGraph.Routes[httpRouteKey2] = expRouteHR2 - expGraph.Routes[httpRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - expGraph.Routes[httpRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } + gw2NSName := client.ObjectKeyFromObject(gw2) + gw := expGraph2.Gateways[gw2NSName] - expGraph.Routes[grpcRouteKey2] = expRouteGR2 - expGraph.Routes[grpcRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, } - expGraph.Routes[grpcRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, } - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - CertBundle: diffNsTLSCert, + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRouteKey2: expRouteHR2, + httpRouteKey1: expRouteHR1, + grpcRouteKey1: expRouteGR1, + grpcRouteKey2: expRouteGR2, } - processAndValidateGraph(expGraph) + expGraph2.ReferencedServices[refSvc].GatewayNsNames[gw2NSName] = struct{}{} + expGraph2.ReferencedServices[refGRPCSvc].GatewayNsNames[gw2NSName] = struct{}{} + + processAndValidateGraph(expGraph2) }) }) When("the second TLSRoute is upserted", func() { It("returns populated graph", func() { processor.CaptureUpsertChange(tr2) - expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-2"}: gw2, - } - expGraph.Routes[httpRouteKey2] = expRouteHR2 - expGraph.Routes[httpRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - expGraph.Routes[httpRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + gw2NSName := client.ObjectKeyFromObject(gw2) + gw := expGraph2.Gateways[gw2NSName] + + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, } - expGraph.Routes[grpcRouteKey2] = expRouteGR2 - expGraph.Routes[grpcRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, } - expGraph.Routes[grpcRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + + tlsListener := getListenerByName(gw, tlsListenerName) + tlsListener.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey2: expRouteTR2, } - expGraph.L4Routes[trKey2] = expRouteTR2 - expGraph.L4Routes[trKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRouteKey2: expRouteHR2, + httpRouteKey1: expRouteHR1, + grpcRouteKey1: expRouteGR1, + grpcRouteKey2: expRouteGR2, } - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - CertBundle: diffNsTLSCert, + expGraph2.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey1: expRouteTR1, + trKey2: expRouteTR2, } - processAndValidateGraph(expGraph) + expGraph2.ReferencedServices[refSvc].GatewayNsNames[gw2NSName] = struct{}{} + expGraph2.ReferencedServices[refGRPCSvc].GatewayNsNames[gw2NSName] = struct{}{} + expGraph2.ReferencedServices[refTLSSvc].GatewayNsNames[gw2NSName] = struct{}{} + + processAndValidateGraph(expGraph2) }) }) When("the first Gateway is deleted", func() { @@ -1565,51 +1811,112 @@ var _ = Describe("ChangeProcessor", func() { types.NamespacedName{Namespace: "test", Name: "gateway-1"}, ) - // gateway 2 takes over; - expGraph.DeploymentName.Name = "gateway-2-test-class" - // route 1 has been replaced by route 2 - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) - - expGraph.Gateway.Source = gw2 - listener80.Source = gw2.Spec.Listeners[0] - listener443.Source = gw2.Spec.Listeners[1] - tlsListener.Source = gw2.Spec.Listeners[2] - - delete(listener80.Routes, httpRouteKey1) - delete(listener443.Routes, httpRouteKey1) - delete(listener80.Routes, grpcRouteKey1) - delete(listener443.Routes, grpcRouteKey1) - delete(tlsListener.L4Routes, trKey1) - - listener80.Routes[httpRouteKey2] = expRouteHR2 - listener443.Routes[httpRouteKey2] = expRouteHR2 - listener80.Routes[grpcRouteKey2] = expRouteGR2 - listener443.Routes[grpcRouteKey2] = expRouteGR2 - tlsListener.L4Routes[trKey2] = expRouteTR2 - - delete(expGraph.Routes, httpRouteKey1) - delete(expGraph.Routes, grpcRouteKey1) - delete(expGraph.L4Routes, trKey1) - - expGraph.Routes[httpRouteKey2] = expRouteHR2 - expGraph.Routes[grpcRouteKey2] = expRouteGR2 - expGraph.L4Routes[trKey2] = expRouteTR2 - - sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) - listener443.ResolvedSecret = sameNsTLSSecretRef - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, - CertBundle: sameNsTLSCert, - } - - delete(expGraph.ReferencedServices, expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName) - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - delete(expGraph.ReferencedServices, expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName) - expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + // gateway 2 only remains; + expGraph2.Gateways = map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway-2"}: { + Source: gw2, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[0], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[1], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)), + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: tlsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[2], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.TLSRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + }, + Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway-2-test-class", + }, + }, + } - processAndValidateGraph(expGraph) + gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] + + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, + } + + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, + } + + tlsListener := getListenerByName(gw, tlsListenerName) + tlsListener.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey2: expRouteTR2, + } + + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, + } + + expGraph2.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey2: expRouteTR2, + } + + expGraph2.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ + refSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-2"}: {}, + }, + }, + refTLSSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-2"}: {}, + }, + }, + refGRPCSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-2"}: {}, + }, + }, + } + expGraph2.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + client.ObjectKeyFromObject(sameNsTLSSecret): { + Source: sameNsTLSSecret, + CertBundle: sameNsTLSCert, + }, + } + + processAndValidateGraph(expGraph2) }) }) When("the second HTTPRoute is deleted", func() { @@ -1619,50 +1926,103 @@ var _ = Describe("ChangeProcessor", func() { types.NamespacedName{Namespace: "test", Name: "hr-2"}, ) - // gateway 2 still in charge; - expGraph.DeploymentName.Name = "gateway-2-test-class" - // no HTTP routes remain - // GRPCRoute 2 still exists - // TLSRoute 2 still exists - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) - - expGraph.Gateway.Source = gw2 - listener80.Source = gw2.Spec.Listeners[0] - listener443.Source = gw2.Spec.Listeners[1] - tlsListener.Source = gw2.Spec.Listeners[2] - - delete(listener80.Routes, httpRouteKey1) - delete(listener443.Routes, httpRouteKey1) - delete(listener80.Routes, grpcRouteKey1) - delete(listener443.Routes, grpcRouteKey1) - delete(tlsListener.L4Routes, trKey1) - - listener80.Routes[grpcRouteKey2] = expRouteGR2 - listener443.Routes[grpcRouteKey2] = expRouteGR2 - tlsListener.L4Routes[trKey2] = expRouteTR2 - - delete(expGraph.Routes, httpRouteKey1) - delete(expGraph.Routes, grpcRouteKey1) - expGraph.Routes[grpcRouteKey2] = expRouteGR2 - - delete(expGraph.L4Routes, trKey1) - expGraph.L4Routes[trKey2] = expRouteTR2 - - sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) - listener443.ResolvedSecret = sameNsTLSSecretRef - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, - CertBundle: sameNsTLSCert, - } - - delete(expGraph.ReferencedServices, expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName) - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - delete(expGraph.ReferencedServices, expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName) - expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + // gateway 2 only remains; + expGraph2.Gateways = map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway-2"}: { + Source: gw2, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[0], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[1], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)), + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: tlsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[2], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.TLSRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + }, + Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway-2-test-class", + }, + }, + } - processAndValidateGraph(expGraph) + gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] + + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{ + grpcRouteKey2: expRouteGR2, + } + + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{ + grpcRouteKey2: expRouteGR2, + } + + tlsListener := getListenerByName(gw, tlsListenerName) + tlsListener.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey2: expRouteTR2, + } + + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ + grpcRouteKey2: expRouteGR2, + } + + expGraph2.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey2: expRouteTR2, + } + + expGraph2.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ + refTLSSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-2"}: {}, + }, + }, + refGRPCSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-2"}: {}, + }, + }, + } + expGraph2.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + client.ObjectKeyFromObject(sameNsTLSSecret): { + Source: sameNsTLSSecret, + CertBundle: sameNsTLSCert, + }, + } + processAndValidateGraph(expGraph2) }) }) When("the second GRPCRoute is deleted", func() { @@ -1672,43 +2032,92 @@ var _ = Describe("ChangeProcessor", func() { types.NamespacedName{Namespace: "test", Name: "gr-2"}, ) - // gateway 2 still in charge; - expGraph.DeploymentName.Name = "gateway-2-test-class" - // no routes remain - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) - - expGraph.Gateway.Source = gw2 - listener80.Source = gw2.Spec.Listeners[0] - listener443.Source = gw2.Spec.Listeners[1] - tlsListener.Source = gw2.Spec.Listeners[2] + // gateway 2 only remains; + expGraph2.Gateways = map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway-2"}: { + Source: gw2, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[0], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[1], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)), + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: tlsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[2], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.TLSRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + }, + Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway-2-test-class", + }, + }, + } - delete(listener80.Routes, httpRouteKey1) - delete(listener443.Routes, httpRouteKey1) - delete(listener80.Routes, grpcRouteKey1) - delete(listener443.Routes, grpcRouteKey1) - delete(tlsListener.L4Routes, trKey1) + gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] - tlsListener.L4Routes[trKey2] = expRouteTR2 - expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{} - delete(expGraph.L4Routes, trKey1) - expGraph.L4Routes[trKey2] = expRouteTR2 + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{} - sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) - listener443.ResolvedSecret = sameNsTLSSecretRef - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, - CertBundle: sameNsTLSCert, + tlsListener := getListenerByName(gw, tlsListenerName) + tlsListener.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey2: expRouteTR2, } - delete(expGraph.ReferencedServices, expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName) - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - delete(expGraph.ReferencedServices, expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName) - expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{} - processAndValidateGraph(expGraph) + expGraph2.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey2: expRouteTR2, + } + + expGraph2.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ + refTLSSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-2"}: {}, + }, + }, + } + expGraph2.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + client.ObjectKeyFromObject(sameNsTLSSecret): { + Source: sameNsTLSSecret, + CertBundle: sameNsTLSCert, + }, + } + processAndValidateGraph(expGraph2) }) }) When("the second TLSRoute is deleted", func() { @@ -1718,39 +2127,81 @@ var _ = Describe("ChangeProcessor", func() { types.NamespacedName{Namespace: "test", Name: "tr-2"}, ) - // gateway 2 still in charge; - expGraph.DeploymentName.Name = "gateway-2-test-class" - // no HTTP or TLS routes remain - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) + // gateway 2 only remains; + expGraph2.Gateways = map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway-2"}: { + Source: gw2, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[0], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[1], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)), + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: tlsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), + Source: gw2.Spec.Listeners[2], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: v1.Kind(kinds.TLSRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + }, + Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway-2-test-class", + }, + }, + } - expGraph.Gateway.Source = gw2 - listener80.Source = gw2.Spec.Listeners[0] - listener443.Source = gw2.Spec.Listeners[1] - tlsListener.Source = gw2.Spec.Listeners[2] + gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] - delete(listener80.Routes, httpRouteKey1) - delete(listener443.Routes, httpRouteKey1) - delete(listener80.Routes, grpcRouteKey1) - delete(listener443.Routes, grpcRouteKey1) - delete(tlsListener.L4Routes, trKey1) + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{} - expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{} - sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) - listener443.ResolvedSecret = sameNsTLSSecretRef - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, - CertBundle: sameNsTLSCert, - } + tlsListener := getListenerByName(gw, tlsListenerName) + tlsListener.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{} + expGraph2.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} - processAndValidateGraph(expGraph) + expGraph2.ReferencedServices = nil + expGraph2.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + client.ObjectKeyFromObject(sameNsTLSSecret): { + Source: sameNsTLSSecret, + CertBundle: sameNsTLSCert, + }, + } + processAndValidateGraph(expGraph2) }) }) When("the GatewayClass is deleted", func() { @@ -1760,21 +2211,40 @@ var _ = Describe("ChangeProcessor", func() { types.NamespacedName{Name: gcName}, ) - expGraph.GatewayClass = nil - expGraph.Gateway = &graph.Gateway{ - Source: gw2, - Conditions: staticConds.NewGatewayInvalid("GatewayClass doesn't exist"), + expGraph2.GatewayClass = nil + expGraph2.Gateways = map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway-2"}: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway-2", + Generation: 1, + }, + Spec: v1.GatewaySpec{ + GatewayClassName: "test-class", + Listeners: []v1.Listener{ + createHTTPListener(), + createHTTPSListener(httpsListenerName, sameNsTLSSecret), + createTLSListener(tlsListenerName), + }, + }, + }, + Conditions: staticConds.NewGatewayInvalid("GatewayClass doesn't exist"), + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway-2-test-class", + }, + }, } - expGraph.DeploymentName.Name = "gateway-2-test-class" - expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} - expGraph.ReferencedSecrets = nil + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{} + expGraph2.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} + expGraph2.ReferencedSecrets = nil expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil + expGraph2.ReferencedServices = nil - processAndValidateGraph(expGraph) + processAndValidateGraph(expGraph2) }) }) When("the second Gateway is deleted", func() { @@ -2476,8 +2946,9 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(paramGW) changed, graph := processor.Process() + gw := graph.Gateways[types.NamespacedName{Namespace: "test", Name: "param-gw"}] Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.Gateway.NginxProxy.Source).To(Equal(np)) + Expect(gw.NginxProxy.Source).To(Equal(np)) }) It("captures changes for an NginxProxy", func() { processor.CaptureUpsertChange(npUpdated) @@ -2485,14 +2956,16 @@ var _ = Describe("ChangeProcessor", func() { changed, graph := processor.Process() Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.Gateway.NginxProxy.Source).To(Equal(npUpdated)) + gw := graph.Gateways[types.NamespacedName{Namespace: "test", Name: "param-gw"}] + Expect(gw.NginxProxy.Source).To(Equal(npUpdated)) }) It("handles deletes for an NginxProxy", func() { processor.CaptureDeleteChange(np, client.ObjectKeyFromObject(np)) changed, graph := processor.Process() Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.Gateway.NginxProxy).To(BeNil()) + gw := graph.Gateways[types.NamespacedName{Namespace: "test", Name: "param-gw"}] + Expect(gw.NginxProxy).To(BeNil()) }) }) }) diff --git a/internal/mode/static/state/conditions/conditions.go b/internal/mode/static/state/conditions/conditions.go index b82ccead99..9452ceb533 100644 --- a/internal/mode/static/state/conditions/conditions.go +++ b/internal/mode/static/state/conditions/conditions.go @@ -107,10 +107,6 @@ const ( // has an overlapping hostname:port/path combination with another Route. PolicyReasonTargetConflict v1alpha2.PolicyConditionReason = "TargetConflict" - // GatewayIgnoredReason is used with v1.RouteConditionAccepted when the route references a Gateway that is ignored - // by NGF. - GatewayIgnoredReason v1.RouteConditionReason = "GatewayIgnored" - // GatewayResolvedRefs condition indicates whether the controller was able to resolve the // parametersRef on the Gateway. GatewayResolvedRefs v1.GatewayConditionType = "ResolvedRefs" @@ -127,17 +123,6 @@ const ( GatewayReasonParamsRefInvalid v1.GatewayConditionReason = "ParametersRefInvalid" ) -// NewRouteNotAcceptedGatewayIgnored returns a Condition that indicates that the Route is not accepted by the Gateway -// because the Gateway is ignored by NGF. -func NewRouteNotAcceptedGatewayIgnored() conditions.Condition { - return conditions.Condition{ - Type: string(v1.RouteConditionAccepted), - Status: metav1.ConditionFalse, - Reason: string(GatewayIgnoredReason), - Message: "The Gateway is ignored by the controller", - } -} - // NewDefaultRouteConditions returns the default conditions that must be present in the status of a Route. func NewDefaultRouteConditions() []conditions.Condition { return []conditions.Condition{ diff --git a/internal/mode/static/state/dataplane/configuration.go b/internal/mode/static/state/dataplane/configuration.go index ff53ae3523..0119476856 100644 --- a/internal/mode/static/state/dataplane/configuration.go +++ b/internal/mode/static/state/dataplane/configuration.go @@ -31,26 +31,27 @@ const ( func BuildConfiguration( ctx context.Context, g *graph.Graph, + gateway *graph.Gateway, serviceResolver resolver.ServiceResolver, configVersion int, plus bool, ) Configuration { - if g.GatewayClass == nil || !g.GatewayClass.Valid || g.Gateway == nil { - config := GetDefaultConfiguration(g, configVersion) + if g.GatewayClass == nil || !g.GatewayClass.Valid || gateway == nil { + config := GetDefaultConfiguration(g, configVersion, gateway) if plus { - config.NginxPlus = buildNginxPlus(g) + config.NginxPlus = buildNginxPlus(gateway) } return config } - baseHTTPConfig := buildBaseHTTPConfig(g) + baseHTTPConfig := buildBaseHTTPConfig(g, gateway) - httpServers, sslServers := buildServers(g) + httpServers, sslServers := buildServers(gateway) backendGroups := buildBackendGroups(append(httpServers, sslServers...)) upstreams := buildUpstreams( ctx, - g.Gateway.Listeners, + gateway, serviceResolver, g.ReferencedServices, baseHTTPConfig.IPFamily, @@ -58,25 +59,25 @@ func BuildConfiguration( var nginxPlus NginxPlus if plus { - nginxPlus = buildNginxPlus(g) + nginxPlus = buildNginxPlus(gateway) } config := Configuration{ HTTPServers: httpServers, SSLServers: sslServers, - TLSPassthroughServers: buildPassthroughServers(g), + TLSPassthroughServers: buildPassthroughServers(gateway), Upstreams: upstreams, - StreamUpstreams: buildStreamUpstreams(ctx, g.Gateway.Listeners, serviceResolver, baseHTTPConfig.IPFamily), + StreamUpstreams: buildStreamUpstreams(ctx, gateway, serviceResolver, baseHTTPConfig.IPFamily), BackendGroups: backendGroups, - SSLKeyPairs: buildSSLKeyPairs(g.ReferencedSecrets, g.Gateway.Listeners), + SSLKeyPairs: buildSSLKeyPairs(g.ReferencedSecrets, gateway.Listeners), Version: configVersion, CertBundles: buildCertBundles( buildRefCertificateBundles(g.ReferencedSecrets, g.ReferencedCaCertConfigMaps), backendGroups, ), - Telemetry: buildTelemetry(g), + Telemetry: buildTelemetry(g, gateway), BaseHTTPConfig: baseHTTPConfig, - Logging: buildLogging(g), + Logging: buildLogging(gateway), NginxPlus: nginxPlus, MainSnippets: buildSnippetsForContext(g.SnippetsFilters, ngfAPIv1alpha1.NginxContextMain), AuxiliarySecrets: buildAuxiliarySecrets(g.PlusSecrets), @@ -86,13 +87,13 @@ func BuildConfiguration( } // buildPassthroughServers builds TLSPassthroughServers from TLSRoutes attaches to listeners. -func buildPassthroughServers(g *graph.Graph) []Layer4VirtualServer { +func buildPassthroughServers(gateway *graph.Gateway) []Layer4VirtualServer { passthroughServersMap := make(map[graph.L4RouteKey][]Layer4VirtualServer) listenerPassthroughServers := make([]Layer4VirtualServer, 0) passthroughServerCount := 0 - for _, l := range g.Gateway.Listeners { + for _, l := range gateway.Listeners { if !l.Valid || l.Source.Protocol != v1.TLSProtocolType { continue } @@ -105,7 +106,8 @@ func buildPassthroughServers(g *graph.Graph) []Layer4VirtualServer { var hostnames []string for _, p := range r.ParentRefs { - if val, exist := p.Attachment.AcceptedHostnames[l.Name]; exist { + key := graph.CreateGatewayListenerKey(l.GatewayName, l.Name) + if val, exist := p.Attachment.AcceptedHostnames[key]; exist { hostnames = val break } @@ -157,7 +159,7 @@ func buildPassthroughServers(g *graph.Graph) []Layer4VirtualServer { // buildStreamUpstreams builds all stream upstreams. func buildStreamUpstreams( ctx context.Context, - listeners []*graph.Listener, + gateway *graph.Gateway, serviceResolver resolver.ServiceResolver, ipFamily IPFamilyType, ) []Upstream { @@ -165,7 +167,7 @@ func buildStreamUpstreams( // We use a map to deduplicate them. uniqueUpstreams := make(map[string]Upstream) - for _, l := range listeners { + for _, l := range gateway.Listeners { if !l.Valid || l.Source.Protocol != v1.TLSProtocolType { continue } @@ -181,6 +183,11 @@ func buildStreamUpstreams( continue } + gatewayNSName := client.ObjectKeyFromObject(gateway.Source) + if _, ok := br.InvalidForGateways[gatewayNSName]; ok { + continue + } + upstreamName := br.ServicePortReference() if _, exist := uniqueUpstreams[upstreamName]; exist { @@ -338,7 +345,12 @@ func buildBackendGroups(servers []VirtualServer) []BackendGroup { return groups } -func newBackendGroup(refs []graph.BackendRef, sourceNsName types.NamespacedName, ruleIdx int) BackendGroup { +func newBackendGroup( + refs []graph.BackendRef, + gatewayName types.NamespacedName, + sourceNsName types.NamespacedName, + ruleIdx int, +) BackendGroup { var backends []Backend if len(refs) > 0 { @@ -346,10 +358,15 @@ func newBackendGroup(refs []graph.BackendRef, sourceNsName types.NamespacedName, } for _, ref := range refs { + valid := ref.Valid + if _, ok := ref.InvalidForGateways[gatewayName]; ok { + valid = false + } + backends = append(backends, Backend{ UpstreamName: ref.ServicePortReference(), Weight: ref.Weight, - Valid: ref.Valid, + Valid: valid, VerifyTLS: convertBackendTLS(ref.BackendTLSPolicy), }) } @@ -375,13 +392,13 @@ func convertBackendTLS(btp *graph.BackendTLSPolicy) *VerifyTLS { return verify } -func buildServers(g *graph.Graph) (http, ssl []VirtualServer) { +func buildServers(gateway *graph.Gateway) (http, ssl []VirtualServer) { rulesForProtocol := map[v1.ProtocolType]portPathRules{ v1.HTTPProtocolType: make(portPathRules), v1.HTTPSProtocolType: make(portPathRules), } - for _, l := range g.Gateway.Listeners { + for _, l := range gateway.Listeners { if l.Source.Protocol == v1.TLSProtocolType { continue } @@ -392,7 +409,7 @@ func buildServers(g *graph.Graph) (http, ssl []VirtualServer) { rulesForProtocol[l.Source.Protocol][l.Source.Port] = rules } - rules.upsertListener(l) + rules.upsertListener(l, gateway) } } @@ -401,7 +418,7 @@ func buildServers(g *graph.Graph) (http, ssl []VirtualServer) { httpServers, sslServers := httpRules.buildServers(), sslRules.buildServers() - pols := buildPolicies(g.Gateway.Policies) + pols := buildPolicies(gateway, gateway.Policies) for i := range httpServers { httpServers[i].Policies = pols @@ -453,7 +470,7 @@ func newHostPathRules() *hostPathRules { } } -func (hpr *hostPathRules) upsertListener(l *graph.Listener) { +func (hpr *hostPathRules) upsertListener(l *graph.Listener, gateway *graph.Gateway) { hpr.listenersExist = true hpr.port = int32(l.Source.Port) @@ -466,13 +483,14 @@ func (hpr *hostPathRules) upsertListener(l *graph.Listener) { continue } - hpr.upsertRoute(r, l) + hpr.upsertRoute(r, l, gateway) } } func (hpr *hostPathRules) upsertRoute( route *graph.L7Route, listener *graph.Listener, + gateway *graph.Gateway, ) { var hostnames []string GRPC := route.RouteType == graph.RouteTypeGRPC @@ -486,7 +504,9 @@ func (hpr *hostPathRules) upsertRoute( } for _, p := range route.ParentRefs { - if val, exist := p.Attachment.AcceptedHostnames[string(listener.Source.Name)]; exist { + key := graph.CreateGatewayListenerKey(listener.GatewayName, listener.Name) + + if val, exist := p.Attachment.AcceptedHostnames[key]; exist { hostnames = val break } @@ -521,7 +541,7 @@ func (hpr *hostPathRules) upsertRoute( } } - pols := buildPolicies(route.Policies) + pols := buildPolicies(gateway, route.Policies) for _, h := range hostnames { for _, m := range rule.Matches { @@ -545,7 +565,7 @@ func (hpr *hostPathRules) upsertRoute( hostRule.MatchRules = append(hostRule.MatchRules, MatchRule{ Source: objectSrc, - BackendGroup: newBackendGroup(rule.BackendRefs, routeNsName, i), + BackendGroup: newBackendGroup(rule.BackendRefs, listener.GatewayName, routeNsName, i), Filters: filters, Match: convertMatch(m), }) @@ -642,7 +662,7 @@ func (hpr *hostPathRules) maxServerCount() int { func buildUpstreams( ctx context.Context, - listeners []*graph.Listener, + gateway *graph.Gateway, svcResolver resolver.ServiceResolver, referencedServices map[types.NamespacedName]*graph.ReferencedService, ipFamily IPFamilyType, @@ -654,7 +674,7 @@ func buildUpstreams( // We need to build endpoints based on the IPFamily of NGINX. allowedAddressType := getAllowedAddressType(ipFamily) - for _, l := range listeners { + for _, l := range gateway.Listeners { if !l.Valid { continue } @@ -669,33 +689,18 @@ func buildUpstreams( // don't generate upstreams for rules that have invalid matches or filters continue } + for _, br := range rule.BackendRefs { - if br.Valid { - upstreamName := br.ServicePortReference() - _, exist := uniqueUpstreams[upstreamName] - - if exist { - continue - } - - var errMsg string - - eps, err := svcResolver.Resolve(ctx, br.SvcNsName, br.ServicePort, allowedAddressType) - if err != nil { - errMsg = err.Error() - } - - var upstreamPolicies []policies.Policy - if graphSvc, exists := referencedServices[br.SvcNsName]; exists { - upstreamPolicies = buildPolicies(graphSvc.Policies) - } - - uniqueUpstreams[upstreamName] = Upstream{ - Name: upstreamName, - Endpoints: eps, - ErrorMsg: errMsg, - Policies: upstreamPolicies, - } + if upstream := buildUpstream( + ctx, + br, + gateway, + svcResolver, + referencedServices, + uniqueUpstreams, + allowedAddressType, + ); upstream != nil { + uniqueUpstreams[upstream.Name] = *upstream } } } @@ -714,6 +719,51 @@ func buildUpstreams( return upstreams } +func buildUpstream( + ctx context.Context, + br graph.BackendRef, + gateway *graph.Gateway, + svcResolver resolver.ServiceResolver, + referencedServices map[types.NamespacedName]*graph.ReferencedService, + uniqueUpstreams map[string]Upstream, + allowedAddressType []discoveryV1.AddressType, +) *Upstream { + if !br.Valid { + return nil + } + + gatewayNSName := client.ObjectKeyFromObject(gateway.Source) + if _, ok := br.InvalidForGateways[gatewayNSName]; ok { + return nil + } + + upstreamName := br.ServicePortReference() + _, exist := uniqueUpstreams[upstreamName] + + if exist { + return nil + } + + var errMsg string + + eps, err := svcResolver.Resolve(ctx, br.SvcNsName, br.ServicePort, allowedAddressType) + if err != nil { + errMsg = err.Error() + } + + var upstreamPolicies []policies.Policy + if graphSvc, exists := referencedServices[br.SvcNsName]; exists { + upstreamPolicies = buildPolicies(gateway, graphSvc.Policies) + } + + return &Upstream{ + Name: upstreamName, + Endpoints: eps, + ErrorMsg: errMsg, + Policies: upstreamPolicies, + } +} + func getAllowedAddressType(ipFamily IPFamilyType) []discoveryV1.AddressType { switch ipFamily { case IPv4: @@ -831,13 +881,13 @@ func telemetryEnabled(gw *graph.Gateway) bool { } // buildTelemetry generates the Otel configuration. -func buildTelemetry(g *graph.Graph) Telemetry { - if !telemetryEnabled(g.Gateway) { +func buildTelemetry(g *graph.Graph, gateway *graph.Gateway) Telemetry { + if !telemetryEnabled(gateway) { return Telemetry{} } - serviceName := fmt.Sprintf("ngf:%s:%s", g.Gateway.Source.Namespace, g.Gateway.Source.Name) - telemetry := g.Gateway.EffectiveNginxProxy.Telemetry + serviceName := fmt.Sprintf("ngf:%s:%s", gateway.Source.Namespace, gateway.Source.Name) + telemetry := gateway.EffectiveNginxProxy.Telemetry if telemetry.ServiceName != nil { serviceName = serviceName + ":" + *telemetry.ServiceName } @@ -900,7 +950,7 @@ func CreateRatioVarName(ratio int32) string { } // buildBaseHTTPConfig generates the base http context config that should be applied to all servers. -func buildBaseHTTPConfig(g *graph.Graph) BaseHTTPConfig { +func buildBaseHTTPConfig(g *graph.Graph, gateway *graph.Gateway) BaseHTTPConfig { baseConfig := BaseHTTPConfig{ // HTTP2 should be enabled by default HTTP2: true, @@ -909,7 +959,7 @@ func buildBaseHTTPConfig(g *graph.Graph) BaseHTTPConfig { } // safe to access EffectiveNginxProxy since we only call this function when the Gateway is not nil. - np := g.Gateway.EffectiveNginxProxy + np := gateway.EffectiveNginxProxy if np == nil { return baseConfig } @@ -990,8 +1040,8 @@ func buildSnippetsForContext( return snippetsForContext } -func buildPolicies(graphPolicies []*graph.Policy) []policies.Policy { - if len(graphPolicies) == 0 { +func buildPolicies(gateway *graph.Gateway, graphPolicies []*graph.Policy) []policies.Policy { + if len(graphPolicies) == 0 || gateway == nil { return nil } @@ -1001,6 +1051,9 @@ func buildPolicies(graphPolicies []*graph.Policy) []policies.Policy { if !policy.Valid { continue } + if _, exists := policy.InvalidForGateways[client.ObjectKeyFromObject(gateway.Source)]; exists { + continue + } finalPolicies = append(finalPolicies, policy.Source) } @@ -1016,14 +1069,14 @@ func convertAddresses(addresses []ngfAPIv1alpha2.RewriteClientIPAddress) []strin return trustedAddresses } -func buildLogging(g *graph.Graph) Logging { +func buildLogging(gateway *graph.Gateway) Logging { logSettings := Logging{ErrorLevel: defaultErrorLogLevel} - if g.Gateway == nil || g.Gateway.EffectiveNginxProxy == nil { + if gateway == nil || gateway.EffectiveNginxProxy == nil { return logSettings } - ngfProxy := g.Gateway.EffectiveNginxProxy + ngfProxy := gateway.EffectiveNginxProxy if ngfProxy.Logging != nil { if ngfProxy.Logging.ErrorLevel != nil { logSettings.ErrorLevel = string(*ngfProxy.Logging.ErrorLevel) @@ -1047,14 +1100,14 @@ func buildAuxiliarySecrets( return auxSecrets } -func buildNginxPlus(g *graph.Graph) NginxPlus { +func buildNginxPlus(gateway *graph.Gateway) NginxPlus { nginxPlusSettings := NginxPlus{AllowedAddresses: []string{"127.0.0.1"}} - if g.Gateway == nil || g.Gateway.EffectiveNginxProxy == nil { + if gateway == nil || gateway.EffectiveNginxProxy == nil { return nginxPlusSettings } - ngfProxy := g.Gateway.EffectiveNginxProxy + ngfProxy := gateway.EffectiveNginxProxy if ngfProxy.NginxPlus != nil { if ngfProxy.NginxPlus.AllowedAddresses != nil { addresses := make([]string, 0, len(ngfProxy.NginxPlus.AllowedAddresses)) @@ -1069,10 +1122,10 @@ func buildNginxPlus(g *graph.Graph) NginxPlus { return nginxPlusSettings } -func GetDefaultConfiguration(g *graph.Graph, configVersion int) Configuration { +func GetDefaultConfiguration(g *graph.Graph, configVersion int, gateway *graph.Gateway) Configuration { return Configuration{ Version: configVersion, - Logging: buildLogging(g), + Logging: buildLogging(gateway), NginxPlus: NginxPlus{}, AuxiliarySecrets: buildAuxiliarySecrets(g.PlusSecrets), } diff --git a/internal/mode/static/state/dataplane/configuration_test.go b/internal/mode/static/state/dataplane/configuration_test.go index 9935b1e89d..5c7b16ecca 100644 --- a/internal/mode/static/state/dataplane/configuration_test.go +++ b/internal/mode/static/state/dataplane/configuration_test.go @@ -21,6 +21,7 @@ import ( ngfAPIv1alpha1 "github.com/nginx/nginx-gateway-fabric/apis/v1alpha1" ngfAPIv1alpha2 "github.com/nginx/nginx-gateway-fabric/apis/v1alpha2" + "github.com/nginx/nginx-gateway-fabric/internal/framework/conditions" "github.com/nginx/nginx-gateway-fabric/internal/framework/helpers" "github.com/nginx/nginx-gateway-fabric/internal/framework/kinds" "github.com/nginx/nginx-gateway-fabric/internal/mode/static/nginx/config/policies" @@ -70,15 +71,27 @@ func getExpectedConfiguration() Configuration { } } +var gatewayNsName = types.NamespacedName{ + Namespace: "test", + Name: "gateway", +} + func getNormalGraph() *graph.Graph { return &graph.Graph{ GatewayClass: &graph.GatewayClass{ Source: &v1.GatewayClass{}, Valid: true, }, - Gateway: &graph.Gateway{ - Source: &v1.Gateway{}, - Listeners: []*graph.Listener{}, + Gateways: map[types.NamespacedName]*graph.Gateway{ + gatewayNsName: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway", + }, + }, + Listeners: []*graph.Listener{}, + }, }, Routes: map[graph.RouteKey]*graph.L7Route{}, ReferencedSecrets: map[types.NamespacedName]*graph.Secret{}, @@ -255,9 +268,12 @@ func TestBuildConfiguration(t *testing.T) { Valid: true, ParentRefs: []graph.ParentRef{ { + Gateway: &graph.ParentRefGateway{ + NamespacedName: gatewayNsName, + }, Attachment: &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - listenerName: hostnames, + graph.CreateGatewayListenerKey(gatewayNsName, listenerName): hostnames, }, }, }, @@ -473,7 +489,8 @@ func TestBuildConfiguration(t *testing.T) { pathAndType{path: "/", pathType: prefix}, ) // add extra attachment for this route for duplicate listener test - httpsRouteHR5.ParentRefs[0].Attachment.AcceptedHostnames["listener-443-1"] = []string{"example.com"} + key := graph.CreateGatewayListenerKey(gatewayNsName, "listener-443-1") + httpsRouteHR5.ParentRefs[0].Attachment.AcceptedHostnames[key] = []string{"example.com"} httpsHR6, expHTTPSHR6Groups, httpsRouteHR6 := createTestResources( "https-hr-6", @@ -506,14 +523,14 @@ func TestBuildConfiguration(t *testing.T) { { Attachment: &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "listener-443-2": {"app.example.com"}, + graph.CreateGatewayListenerKey(gatewayNsName, "listener-443-2"): {"app.example.com"}, }, }, }, { Attachment: &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "listener-444-3": {"app.example.com"}, + graph.CreateGatewayListenerKey(gatewayNsName, "listener-444-3"): {"app.example.com"}, }, }, }, @@ -899,10 +916,12 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, }) return g }), @@ -915,19 +934,22 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, []*graph.Listener{ { - Name: "listener-80-1", - Source: listener80, - Valid: true, + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr1Invalid): routeHR1Invalid, }, }, { - Name: "listener-443-1", - Source: listener443, // nil hostname - Valid: true, + Name: "listener-443-1", + GatewayName: gatewayNsName, + Source: listener443, // nil hostname + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR1Invalid): httpsRouteHR1Invalid, }, @@ -954,9 +976,11 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, []*graph.Listener{ { Name: "listener-443-1", + GatewayName: gatewayNsName, Source: listener443, // nil hostname Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{}, @@ -964,6 +988,7 @@ func TestBuildConfiguration(t *testing.T) { }, { Name: "listener-443-with-hostname", + GatewayName: gatewayNsName, Source: listener443WithHostname, // non-nil hostname Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{}, @@ -1000,8 +1025,10 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, &graph.Listener{ Name: "invalid-listener", + GatewayName: gatewayNsName, Source: invalidListener, Valid: false, ResolvedSecret: &secret1NsName, @@ -1023,10 +1050,12 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr1): routeHR1, graph.CreateRouteKey(hr2): routeHR2, @@ -1084,10 +1113,12 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(gr): routeGR, }, @@ -1124,11 +1155,13 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, []*graph.Listener{ { - Name: "listener-443-1", - Source: listener443, - Valid: true, + Name: "listener-443-1", + GatewayName: gatewayNsName, + Source: listener443, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR1): httpsRouteHR1, graph.CreateRouteKey(httpsHR2): httpsRouteHR2, @@ -1136,9 +1169,10 @@ func TestBuildConfiguration(t *testing.T) { ResolvedSecret: &secret1NsName, }, { - Name: "listener-443-with-hostname", - Source: listener443WithHostname, - Valid: true, + Name: "listener-443-with-hostname", + GatewayName: gatewayNsName, + Source: listener443WithHostname, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR5): httpsRouteHR5, }, @@ -1234,20 +1268,23 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, []*graph.Listener{ { - Name: "listener-80-1", - Source: listener80, - Valid: true, + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr3): routeHR3, graph.CreateRouteKey(hr4): routeHR4, }, }, { - Name: "listener-443-1", - Source: listener443, - Valid: true, + Name: "listener-443-1", + GatewayName: gatewayNsName, + Source: listener443, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR3): httpsRouteHR3, graph.CreateRouteKey(httpsHR4): httpsRouteHR4, @@ -1374,36 +1411,41 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, []*graph.Listener{ { - Name: "listener-80-1", - Source: listener80, - Valid: true, + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr3): routeHR3, }, }, { - Name: "listener-8080", - Source: listener8080, - Valid: true, + Name: "listener-8080", + GatewayName: gatewayNsName, + Source: listener8080, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr8): routeHR8, }, }, { - Name: "listener-443-1", - Source: listener443, - Valid: true, + Name: "listener-443-1", + GatewayName: gatewayNsName, + Source: listener443, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR3): httpsRouteHR3, }, ResolvedSecret: &secret1NsName, }, { - Name: "listener-8443", - Source: listener8443, - Valid: true, + Name: "listener-8443", + GatewayName: gatewayNsName, + Source: listener8443, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR7): httpsRouteHR7, }, @@ -1583,7 +1625,7 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway = nil + delete(g.Gateways, gatewayNsName) return g }), expConf: defaultConfig, @@ -1591,10 +1633,12 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr5): routeHR5, }, @@ -1650,29 +1694,33 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, []*graph.Listener{ { - Name: "listener-80-1", - Source: listener80, - Valid: true, + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr6): routeHR6, }, }, { - Name: "listener-443-1", - Source: listener443, - Valid: true, + Name: "listener-443-1", + GatewayName: gatewayNsName, + Source: listener443, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR6): httpsRouteHR6, }, ResolvedSecret: &secret1NsName, }, { - Name: "listener-443-2", - Source: listener443_2, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + Name: "listener-443-2", + GatewayName: gatewayNsName, + Source: listener443_2, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, L4Routes: map[graph.L4RouteKey]*graph.L4Route{ TR1Key: &tlsTR1, TR2Key: &invalidBackendRefTR2, @@ -1680,10 +1728,11 @@ func TestBuildConfiguration(t *testing.T) { ResolvedSecret: &secret1NsName, }, { - Name: "listener-444-3", - Source: listener444_3, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + Name: "listener-444-3", + GatewayName: gatewayNsName, + Source: listener444_3, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, L4Routes: map[graph.L4RouteKey]*graph.L4Route{ TR1Key: &tlsTR1, TR2Key: &invalidBackendRefTR2, @@ -1692,6 +1741,7 @@ func TestBuildConfiguration(t *testing.T) { }, { Name: "listener-443-4", + GatewayName: gatewayNsName, Source: listener443_4, Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{}, @@ -1794,10 +1844,12 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr7): routeHR7, }, @@ -1846,20 +1898,23 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, []*graph.Listener{ { - Name: "listener-443-with-hostname", - Source: listener443WithHostname, - Valid: true, + Name: "listener-443-with-hostname", + GatewayName: gatewayNsName, + Source: listener443WithHostname, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR5): httpsRouteHR5, }, ResolvedSecret: &secret2NsName, }, { - Name: "listener-443-1", - Source: listener443, - Valid: true, + Name: "listener-443-1", + GatewayName: gatewayNsName, + Source: listener443, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR5): httpsRouteHR5, }, @@ -1924,10 +1979,12 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-443", - Source: listener443, - Valid: true, + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-443-1", + GatewayName: gatewayNsName, + Source: listener443, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR8): httpsRouteHR8, }, @@ -1983,10 +2040,12 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-443", - Source: listener443, - Valid: true, + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-443-1", + GatewayName: gatewayNsName, + Source: listener443, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR9): httpsRouteHR9, }, @@ -2042,17 +2101,19 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + gw := g.Gateways[gatewayNsName] + gw.Source.ObjectMeta = metav1.ObjectMeta{ Name: "gw", Namespace: "ns", } - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) - g.Gateway.EffectiveNginxProxy = nginxProxy + gw.EffectiveNginxProxy = nginxProxy return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { @@ -2074,26 +2135,29 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + gw := g.Gateways[gatewayNsName] + gw.Listeners = append(gw.Listeners, []*graph.Listener{ { - Name: "listener-80-1", - Source: listener80, - Valid: true, + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, }, }, { - Name: "listener-443", - Source: listener443, - Valid: true, + Name: "listener-443-1", + GatewayName: gatewayNsName, + Source: listener443, + Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHRWithPolicy): l7HTTPSRouteWithPolicy, }, ResolvedSecret: &secret1NsName, }, }...) - g.Gateway.Policies = []*graph.Policy{gwPolicy1, gwPolicy2} + gw.Policies = []*graph.Policy{gwPolicy1, gwPolicy2} g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, graph.CreateRouteKey(httpsHRWithPolicy): l7HTTPSRouteWithPolicy, @@ -2169,17 +2233,19 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + gw := g.Gateways[gatewayNsName] + gw.Source.ObjectMeta = metav1.ObjectMeta{ Name: "gw", Namespace: "ns", } - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) - g.Gateway.EffectiveNginxProxy = nginxProxyIPv4 + gw.EffectiveNginxProxy = nginxProxyIPv4 return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { @@ -2192,17 +2258,19 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + gw := g.Gateways[gatewayNsName] + gw.Source.ObjectMeta = metav1.ObjectMeta{ Name: "gw", Namespace: "ns", } - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) - g.Gateway.EffectiveNginxProxy = nginxProxyIPv6 + gw.EffectiveNginxProxy = nginxProxyIPv6 return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { @@ -2215,17 +2283,19 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + gw := g.Gateways[gatewayNsName] + gw.Source.ObjectMeta = metav1.ObjectMeta{ Name: "gw", Namespace: "ns", } - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) - g.Gateway.EffectiveNginxProxy = &graph.EffectiveNginxProxy{ + gw.EffectiveNginxProxy = &graph.EffectiveNginxProxy{ RewriteClientIP: &ngfAPIv1alpha2.RewriteClientIP{ SetIPRecursively: helpers.GetPointer(true), TrustedAddresses: []ngfAPIv1alpha2.RewriteClientIPAddress{ @@ -2257,17 +2327,19 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + gw := g.Gateways[gatewayNsName] + gw.Source.ObjectMeta = metav1.ObjectMeta{ Name: "gw", Namespace: "ns", } - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) - g.Gateway.EffectiveNginxProxy = &graph.EffectiveNginxProxy{ + gw.EffectiveNginxProxy = &graph.EffectiveNginxProxy{ Logging: &ngfAPIv1alpha2.NginxLogging{ ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelDebug), }, @@ -2320,17 +2392,19 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + gw := g.Gateways[gatewayNsName] + gw.Source.ObjectMeta = metav1.ObjectMeta{ Name: "gw", Namespace: "ns", } - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) - g.Gateway.EffectiveNginxProxy = &graph.EffectiveNginxProxy{ + gw.EffectiveNginxProxy = &graph.EffectiveNginxProxy{ NginxPlus: &ngfAPIv1alpha2.NginxPlus{ AllowedAddresses: []ngfAPIv1alpha2.NginxPlusAllowAddress{ {Type: ngfAPIv1alpha2.NginxPlusAllowIPAddressType, Value: "127.0.0.3"}, @@ -2357,6 +2431,7 @@ func TestBuildConfiguration(t *testing.T) { result := BuildConfiguration( context.TODO(), test.graph, + test.graph.Gateways[gatewayNsName], fakeResolver, 1, false, @@ -2409,17 +2484,19 @@ func TestBuildConfiguration_Plus(t *testing.T) { }{ { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + gw := g.Gateways[gatewayNsName] + gw.Source.ObjectMeta = metav1.ObjectMeta{ Name: "gw", Namespace: "ns", } - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + gw.Listeners = append(gw.Listeners, &graph.Listener{ + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) - g.Gateway.EffectiveNginxProxy = &graph.EffectiveNginxProxy{ + gw.EffectiveNginxProxy = &graph.EffectiveNginxProxy{ NginxPlus: &ngfAPIv1alpha2.NginxPlus{ AllowedAddresses: []ngfAPIv1alpha2.NginxPlusAllowAddress{ {Type: ngfAPIv1alpha2.NginxPlusAllowIPAddressType, Value: "127.0.0.3"}, @@ -2455,7 +2532,7 @@ func TestBuildConfiguration_Plus(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway = nil + delete(g.Gateways, gatewayNsName) return g }), expConf: defaultPlusConfig, @@ -2471,6 +2548,7 @@ func TestBuildConfiguration_Plus(t *testing.T) { result := BuildConfiguration( context.TODO(), test.graph, + test.graph.Gateways[gatewayNsName], fakeResolver, 1, true, @@ -2863,6 +2941,13 @@ func TestBuildUpstreams(t *testing.T) { }, } + invalidEndpoints := []resolver.Endpoint{ + { + Address: "11.5.5.5", + Port: 80, + }, + } + bazEndpoints := []resolver.Endpoint{ { Address: "12.0.0.0", @@ -2926,6 +3011,11 @@ func TestBuildUpstreams(t *testing.T) { hr1Refs1 := createBackendRefs("baz", "", "") // empty service names should be ignored + hr1Refs2 := createBackendRefs("invalid-for-gateway") + hr1Refs2[0].InvalidForGateways = map[types.NamespacedName]conditions.Condition{ + {Namespace: "test", Name: "gateway"}: {}, + } + hr2Refs0 := createBackendRefs("foo", "baz") // shouldn't duplicate foo and baz upstream hr2Refs1 := createBackendRefs("nil-endpoints") @@ -2948,7 +3038,7 @@ func TestBuildUpstreams(t *testing.T) { {NamespacedName: types.NamespacedName{Name: "hr1", Namespace: "test"}}: { Valid: true, Spec: graph.L7RouteSpec{ - Rules: refsToValidRules(hr1Refs0, hr1Refs1), + Rules: refsToValidRules(hr1Refs0, hr1Refs1, hr1Refs2), }, }, {NamespacedName: types.NamespacedName{Name: "hr2", Namespace: "test"}}: { @@ -3010,36 +3100,44 @@ func TestBuildUpstreams(t *testing.T) { }, } - listeners := []*graph.Listener{ - { - Name: "invalid-listener", - Valid: false, - Routes: routesWithNonExistingRefs, // shouldn't be included since listener is invalid - }, - { - Name: "listener-1", - Valid: true, - Routes: routes, - }, - { - Name: "listener-2", - Valid: true, - Routes: routes2, - }, - { - Name: "listener-3", - Valid: true, - Routes: invalidRoutes, // shouldn't be included since routes are invalid - }, - { - Name: "listener-4", - Valid: true, - Routes: routes3, + gateway := &graph.Gateway{ + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway", + }, }, - { - Name: "listener-5", - Valid: true, - Routes: routesWithPolicies, + Listeners: []*graph.Listener{ + { + Name: "invalid-listener", + Valid: false, + Routes: routesWithNonExistingRefs, // shouldn't be included since listener is invalid + }, + { + Name: "listener-1", + Valid: true, + Routes: routes, + }, + { + Name: "listener-2", + Valid: true, + Routes: routes2, + }, + { + Name: "listener-3", + Valid: true, + Routes: invalidRoutes, // shouldn't be included since routes are invalid + }, + { + Name: "listener-4", + Valid: true, + Routes: routes3, + }, + { + Name: "listener-5", + Valid: true, + Routes: routesWithPolicies, + }, }, } @@ -3048,13 +3146,14 @@ func TestBuildUpstreams(t *testing.T) { invalidPolicy := &policiesfakes.FakePolicy{} referencedServices := map[types.NamespacedName]*graph.ReferencedService{ - {Name: "bar", Namespace: "test"}: {}, - {Name: "baz", Namespace: "test"}: {}, - {Name: "baz2", Namespace: "test"}: {}, - {Name: "foo", Namespace: "test"}: {}, - {Name: "empty-endpoints", Namespace: "test"}: {}, - {Name: "nil-endpoints", Namespace: "test"}: {}, - {Name: "ipv6-endpoints", Namespace: "test"}: {}, + {Name: "bar", Namespace: "test"}: {}, + {Name: "invalid-for-gateway", Namespace: "test"}: {}, + {Name: "baz", Namespace: "test"}: {}, + {Name: "baz2", Namespace: "test"}: {}, + {Name: "foo", Namespace: "test"}: {}, + {Name: "empty-endpoints", Namespace: "test"}: {}, + {Name: "nil-endpoints", Namespace: "test"}: {}, + {Name: "ipv6-endpoints", Namespace: "test"}: {}, {Name: "policies", Namespace: "test"}: { Policies: []*graph.Policy{ { @@ -3124,6 +3223,8 @@ func TestBuildUpstreams(t *testing.T) { switch svcNsName.Name { case "bar": return barEndpoints, nil + case "invalid-for-gateway": + return invalidEndpoints, nil case "baz": return bazEndpoints, nil case "baz2": @@ -3147,7 +3248,7 @@ func TestBuildUpstreams(t *testing.T) { g := NewWithT(t) - upstreams := buildUpstreams(context.TODO(), listeners, fakeResolver, referencedServices, Dual) + upstreams := buildUpstreams(context.TODO(), gateway, fakeResolver, referencedServices, Dual) g.Expect(upstreams).To(ConsistOf(expUpstreams)) } @@ -3420,8 +3521,10 @@ func TestBuildTelemetry(t *testing.T) { }, { g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: nil, + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: { + EffectiveNginxProxy: nil, + }, }, }, expTelemetry: Telemetry{}, @@ -3429,8 +3532,8 @@ func TestBuildTelemetry(t *testing.T) { }, { g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{}, + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: {EffectiveNginxProxy: &graph.EffectiveNginxProxy{}}, }, }, expTelemetry: Telemetry{}, @@ -3438,14 +3541,16 @@ func TestBuildTelemetry(t *testing.T) { }, { g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - Telemetry: &ngfAPIv1alpha2.Telemetry{ - Exporter: &ngfAPIv1alpha2.TelemetryExporter{ - Endpoint: helpers.GetPointer("my-otel.svc:4563"), - }, - DisabledFeatures: []ngfAPIv1alpha2.DisableTelemetryFeature{ - ngfAPIv1alpha2.DisableTracing, + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: { + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + Telemetry: &ngfAPIv1alpha2.Telemetry{ + Exporter: &ngfAPIv1alpha2.TelemetryExporter{ + Endpoint: helpers.GetPointer("my-otel.svc:4563"), + }, + DisabledFeatures: []ngfAPIv1alpha2.DisableTelemetryFeature{ + ngfAPIv1alpha2.DisableTracing, + }, }, }, }, @@ -3456,10 +3561,12 @@ func TestBuildTelemetry(t *testing.T) { }, { g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - Telemetry: &ngfAPIv1alpha2.Telemetry{ - Exporter: nil, + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: { + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + Telemetry: &ngfAPIv1alpha2.Telemetry{ + Exporter: nil, + }, }, }, }, @@ -3469,11 +3576,13 @@ func TestBuildTelemetry(t *testing.T) { }, { g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - Telemetry: &ngfAPIv1alpha2.Telemetry{ - Exporter: &ngfAPIv1alpha2.TelemetryExporter{ - Endpoint: nil, + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: { + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + Telemetry: &ngfAPIv1alpha2.Telemetry{ + Exporter: &ngfAPIv1alpha2.TelemetryExporter{ + Endpoint: nil, + }, }, }, }, @@ -3484,14 +3593,16 @@ func TestBuildTelemetry(t *testing.T) { }, { g: &graph.Graph{ - Gateway: &graph.Gateway{ - Source: &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + }, }, + EffectiveNginxProxy: telemetryConfigured, }, - EffectiveNginxProxy: telemetryConfigured, }, }, expTelemetry: createTelemetry(), @@ -3499,14 +3610,16 @@ func TestBuildTelemetry(t *testing.T) { }, { g: &graph.Graph{ - Gateway: &graph.Gateway{ - Source: &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + }, }, + EffectiveNginxProxy: telemetryConfigured, }, - EffectiveNginxProxy: telemetryConfigured, }, NGFPolicies: map[graph.PolicyKey]*graph.Policy{ {NsName: types.NamespacedName{Name: "obsPolicy"}}: { @@ -3534,14 +3647,16 @@ func TestBuildTelemetry(t *testing.T) { }, { g: &graph.Graph{ - Gateway: &graph.Gateway{ - Source: &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + }, }, + EffectiveNginxProxy: telemetryConfigured, }, - EffectiveNginxProxy: telemetryConfigured, }, NGFPolicies: map[graph.PolicyKey]*graph.Policy{ {NsName: types.NamespacedName{Name: "obsPolicy"}}: { @@ -3604,14 +3719,16 @@ func TestBuildTelemetry(t *testing.T) { }, { g: &graph.Graph{ - Gateway: &graph.Gateway{ - Source: &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + }, }, + EffectiveNginxProxy: telemetryConfigured, }, - EffectiveNginxProxy: telemetryConfigured, }, NGFPolicies: map[graph.PolicyKey]*graph.Policy{ {NsName: types.NamespacedName{Name: "obsPolicy"}}: { @@ -3638,7 +3755,7 @@ func TestBuildTelemetry(t *testing.T) { t.Run(tc.msg, func(t *testing.T) { t.Parallel() g := NewWithT(t) - tel := buildTelemetry(tc.g) + tel := buildTelemetry(tc.g, tc.g.Gateways[types.NamespacedName{}]) sort.Slice(tel.Ratios, func(i, j int) bool { return tel.Ratios[i].Value < tel.Ratios[j].Value }) @@ -3671,6 +3788,7 @@ func TestBuildPolicies(t *testing.T) { tests := []struct { name string + gateway *graph.Gateway policies []*graph.Policy expPolicies []string }{ @@ -3683,24 +3801,37 @@ func TestBuildPolicies(t *testing.T) { name: "mix of valid and invalid policies", policies: []*graph.Policy{ { - Source: getPolicy("Kind1", "valid1"), - Valid: true, + Source: getPolicy("Kind1", "valid1"), + Valid: true, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, { - Source: getPolicy("Kind2", "valid2"), - Valid: true, + Source: getPolicy("Kind2", "valid2"), + Valid: true, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, { - Source: getPolicy("Kind1", "invalid1"), - Valid: false, + Source: getPolicy("Kind1", "invalid1"), + Valid: false, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, { - Source: getPolicy("Kind2", "invalid2"), - Valid: false, + Source: getPolicy("Kind2", "invalid2"), + Valid: false, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, { - Source: getPolicy("Kind3", "valid3"), - Valid: true, + Source: getPolicy("Kind3", "valid3"), + Valid: true, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + }, + }, + gateway: &graph.Gateway{ + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Namespace: "test", + }, }, }, expPolicies: []string{ @@ -3709,6 +3840,27 @@ func TestBuildPolicies(t *testing.T) { "valid3", }, }, + { + name: "invalid for a Gateway", + policies: []*graph.Policy{ + { + Source: getPolicy("Kind1", "valid1"), + Valid: true, + InvalidForGateways: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway"}: {}, + }, + }, + }, + gateway: &graph.Gateway{ + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Namespace: "test", + }, + }, + }, + expPolicies: nil, + }, } for _, test := range tests { @@ -3716,7 +3868,7 @@ func TestBuildPolicies(t *testing.T) { t.Parallel() g := NewWithT(t) - pols := buildPolicies(test.policies) + pols := buildPolicies(test.gateway, test.policies) g.Expect(pols).To(HaveLen(len(test.expPolicies))) for _, pol := range pols { g.Expect(test.expPolicies).To(ContainElement(pol.GetName())) @@ -3782,97 +3934,107 @@ func TestCreatePassthroughServers(t *testing.T) { secureAppKey := getL4RouteKey("secure-app") secureApp2Key := getL4RouteKey("secure-app2") secureApp3Key := getL4RouteKey("secure-app3") - testGraph := graph.Graph{ - Gateway: &graph.Gateway{ - Listeners: []*graph.Listener{ - { - Name: "testingListener", - Valid: true, - Source: v1.Listener{ - Protocol: v1.TLSProtocolType, - Port: 443, - Hostname: helpers.GetPointer[v1.Hostname]("*.example.com"), - }, - Routes: make(map[graph.RouteKey]*graph.L7Route), - L4Routes: map[graph.L4RouteKey]*graph.L4Route{ - secureAppKey: { - Valid: true, - Spec: graph.L4RouteSpec{ - Hostnames: []v1.Hostname{"app.example.com", "cafe.example.com"}, - BackendRef: graph.BackendRef{ - Valid: true, - SvcNsName: secureAppKey.NamespacedName, - ServicePort: apiv1.ServicePort{ - Name: "https", - Protocol: "TCP", - Port: 8443, - TargetPort: intstr.IntOrString{ - Type: intstr.Int, - IntVal: 8443, - }, + gateway := &graph.Gateway{ + Listeners: []*graph.Listener{ + { + Name: "testingListener", + GatewayName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, + Valid: true, + Source: v1.Listener{ + Protocol: v1.TLSProtocolType, + Port: 443, + Hostname: helpers.GetPointer[v1.Hostname]("*.example.com"), + }, + Routes: make(map[graph.RouteKey]*graph.L7Route), + L4Routes: map[graph.L4RouteKey]*graph.L4Route{ + secureAppKey: { + Valid: true, + Spec: graph.L4RouteSpec{ + Hostnames: []v1.Hostname{"app.example.com", "cafe.example.com"}, + BackendRef: graph.BackendRef{ + Valid: true, + SvcNsName: secureAppKey.NamespacedName, + ServicePort: apiv1.ServicePort{ + Name: "https", + Protocol: "TCP", + Port: 8443, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8443, }, }, }, - ParentRefs: []graph.ParentRef{ - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{ - "testingListener": {"app.example.com", "cafe.example.com"}, - }, + }, + ParentRefs: []graph.ParentRef{ + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + graph.CreateGatewayListenerKey( + gatewayNsName, + "testingListener", + ): {"app.example.com", "cafe.example.com"}, }, - SectionName: nil, - Port: nil, - Gateway: types.NamespacedName{}, - Idx: 0, }, + SectionName: nil, + Port: nil, + Gateway: &graph.ParentRefGateway{ + NamespacedName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, + }, + Idx: 0, }, }, - secureApp2Key: {}, }, + secureApp2Key: {}, }, - { - Name: "testingListener2", - Valid: true, - Source: v1.Listener{ - Protocol: v1.TLSProtocolType, - Port: 443, - Hostname: helpers.GetPointer[v1.Hostname]("cafe.example.com"), - }, - Routes: make(map[graph.RouteKey]*graph.L7Route), - L4Routes: map[graph.L4RouteKey]*graph.L4Route{ - secureApp3Key: { - Valid: true, - Spec: graph.L4RouteSpec{ - Hostnames: []v1.Hostname{"app.example.com", "cafe.example.com"}, - BackendRef: graph.BackendRef{ - Valid: true, - SvcNsName: secureAppKey.NamespacedName, - ServicePort: apiv1.ServicePort{ - Name: "https", - Protocol: "TCP", - Port: 8443, - TargetPort: intstr.IntOrString{ - Type: intstr.Int, - IntVal: 8443, - }, + }, + { + Name: "testingListener2", + Valid: true, + Source: v1.Listener{ + Protocol: v1.TLSProtocolType, + Port: 443, + Hostname: helpers.GetPointer[v1.Hostname]("cafe.example.com"), + }, + Routes: make(map[graph.RouteKey]*graph.L7Route), + L4Routes: map[graph.L4RouteKey]*graph.L4Route{ + secureApp3Key: { + Valid: true, + Spec: graph.L4RouteSpec{ + Hostnames: []v1.Hostname{"app.example.com", "cafe.example.com"}, + BackendRef: graph.BackendRef{ + Valid: true, + SvcNsName: secureAppKey.NamespacedName, + ServicePort: apiv1.ServicePort{ + Name: "https", + Protocol: "TCP", + Port: 8443, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8443, }, }, }, }, }, }, - { - Name: "httpListener", - Valid: true, - Source: v1.Listener{ - Protocol: v1.HTTPProtocolType, - }, + }, + { + Name: "httpListener", + Valid: true, + Source: v1.Listener{ + Protocol: v1.HTTPProtocolType, }, }, }, } - passthroughServers := buildPassthroughServers(&testGraph) + passthroughServers := buildPassthroughServers(gateway) expectedPassthroughServers := []Layer4VirtualServer{ { @@ -3921,79 +4083,107 @@ func TestBuildStreamUpstreams(t *testing.T) { secureApp3Key := getL4RouteKey("secure-app3") secureApp4Key := getL4RouteKey("secure-app4") secureApp5Key := getL4RouteKey("secure-app5") - testGraph := graph.Graph{ - Gateway: &graph.Gateway{ - Listeners: []*graph.Listener{ - { - Name: "testingListener", - Valid: true, - Source: v1.Listener{ - Protocol: v1.TLSProtocolType, - Port: 443, - }, - Routes: make(map[graph.RouteKey]*graph.L7Route), - L4Routes: map[graph.L4RouteKey]*graph.L4Route{ - secureAppKey: { - Valid: true, - Spec: graph.L4RouteSpec{ - Hostnames: []v1.Hostname{"app.example.com", "cafe.example.com"}, - BackendRef: graph.BackendRef{ - Valid: true, - SvcNsName: secureAppKey.NamespacedName, - ServicePort: apiv1.ServicePort{ - Name: "https", - Protocol: "TCP", - Port: 8443, - TargetPort: intstr.IntOrString{ - Type: intstr.Int, - IntVal: 8443, - }, + secureApp6Key := getL4RouteKey("secure-app6") + + gateway := &graph.Gateway{ + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway", + }, + }, + Listeners: []*graph.Listener{ + { + Name: "testingListener", + Valid: true, + Source: v1.Listener{ + Protocol: v1.TLSProtocolType, + Port: 443, + }, + Routes: make(map[graph.RouteKey]*graph.L7Route), + L4Routes: map[graph.L4RouteKey]*graph.L4Route{ + secureAppKey: { + Valid: true, + Spec: graph.L4RouteSpec{ + Hostnames: []v1.Hostname{"app.example.com", "cafe.example.com"}, + BackendRef: graph.BackendRef{ + Valid: true, + SvcNsName: secureAppKey.NamespacedName, + ServicePort: apiv1.ServicePort{ + Name: "https", + Protocol: "TCP", + Port: 8443, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8443, }, }, }, }, - secureApp2Key: {}, - secureApp3Key: { - Valid: true, - Spec: graph.L4RouteSpec{ - Hostnames: []v1.Hostname{"test.example.com"}, - BackendRef: graph.BackendRef{}, + }, + secureApp2Key: {}, + secureApp3Key: { + Valid: true, + Spec: graph.L4RouteSpec{ + Hostnames: []v1.Hostname{"test.example.com"}, + BackendRef: graph.BackendRef{}, + }, + }, + secureApp4Key: { + Valid: true, + Spec: graph.L4RouteSpec{ + Hostnames: []v1.Hostname{"app.example.com", "cafe.example.com"}, + BackendRef: graph.BackendRef{ + Valid: true, + SvcNsName: secureAppKey.NamespacedName, + ServicePort: apiv1.ServicePort{ + Name: "https", + Protocol: "TCP", + Port: 8443, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8443, + }, + }, }, }, - secureApp4Key: { - Valid: true, - Spec: graph.L4RouteSpec{ - Hostnames: []v1.Hostname{"app.example.com", "cafe.example.com"}, - BackendRef: graph.BackendRef{ - Valid: true, - SvcNsName: secureAppKey.NamespacedName, - ServicePort: apiv1.ServicePort{ - Name: "https", - Protocol: "TCP", - Port: 8443, - TargetPort: intstr.IntOrString{ - Type: intstr.Int, - IntVal: 8443, - }, + }, + secureApp5Key: { + Valid: true, + Spec: graph.L4RouteSpec{ + Hostnames: []v1.Hostname{"app2.example.com"}, + BackendRef: graph.BackendRef{ + Valid: true, + SvcNsName: secureApp5Key.NamespacedName, + ServicePort: apiv1.ServicePort{ + Name: "https", + Protocol: "TCP", + Port: 8443, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8443, }, }, }, }, - secureApp5Key: { - Valid: true, - Spec: graph.L4RouteSpec{ - Hostnames: []v1.Hostname{"app2.example.com"}, - BackendRef: graph.BackendRef{ - Valid: true, - SvcNsName: secureApp5Key.NamespacedName, - ServicePort: apiv1.ServicePort{ - Name: "https", - Protocol: "TCP", - Port: 8443, - TargetPort: intstr.IntOrString{ - Type: intstr.Int, - IntVal: 8443, - }, + }, + secureApp6Key: { + Valid: true, + Spec: graph.L4RouteSpec{ + Hostnames: []v1.Hostname{"app2.example.com"}, + BackendRef: graph.BackendRef{ + Valid: true, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{ + {Namespace: "test", Name: "gateway"}: {}, + }, + SvcNsName: secureApp6Key.NamespacedName, + ServicePort: apiv1.ServicePort{ + Name: "https", + Protocol: "TCP", + Port: 8443, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8443, }, }, }, @@ -4021,7 +4211,7 @@ func TestBuildStreamUpstreams(t *testing.T) { return fakeEndpoints, nil } - streamUpstreams := buildStreamUpstreams(context.Background(), testGraph.Gateway.Listeners, &fakeResolver, Dual) + streamUpstreams := buildStreamUpstreams(context.Background(), gateway, &fakeResolver, Dual) expectedStreamUpstreams := []Upstream{ { @@ -4048,8 +4238,10 @@ func TestBuildRewriteIPSettings(t *testing.T) { { msg: "no rewrite IP settings configured", g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{}, + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: { + EffectiveNginxProxy: &graph.EffectiveNginxProxy{}, + }, }, }, expRewriteIPSettings: RewriteClientIPSettings{}, @@ -4057,17 +4249,19 @@ func TestBuildRewriteIPSettings(t *testing.T) { { msg: "rewrite IP settings configured with proxyProtocol", g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - RewriteClientIP: &ngfAPIv1alpha2.RewriteClientIP{ - Mode: helpers.GetPointer(ngfAPIv1alpha2.RewriteClientIPModeProxyProtocol), - TrustedAddresses: []ngfAPIv1alpha2.RewriteClientIPAddress{ - { - Type: ngfAPIv1alpha2.RewriteClientIPCIDRAddressType, - Value: "10.9.9.4/32", + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: { + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + RewriteClientIP: &ngfAPIv1alpha2.RewriteClientIP{ + Mode: helpers.GetPointer(ngfAPIv1alpha2.RewriteClientIPModeProxyProtocol), + TrustedAddresses: []ngfAPIv1alpha2.RewriteClientIPAddress{ + { + Type: ngfAPIv1alpha2.RewriteClientIPCIDRAddressType, + Value: "10.9.9.4/32", + }, }, + SetIPRecursively: helpers.GetPointer(true), }, - SetIPRecursively: helpers.GetPointer(true), }, }, }, @@ -4081,17 +4275,19 @@ func TestBuildRewriteIPSettings(t *testing.T) { { msg: "rewrite IP settings configured with xForwardedFor", g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - RewriteClientIP: &ngfAPIv1alpha2.RewriteClientIP{ - Mode: helpers.GetPointer(ngfAPIv1alpha2.RewriteClientIPModeXForwardedFor), - TrustedAddresses: []ngfAPIv1alpha2.RewriteClientIPAddress{ - { - Type: ngfAPIv1alpha2.RewriteClientIPCIDRAddressType, - Value: "76.89.90.11/24", + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: { + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + RewriteClientIP: &ngfAPIv1alpha2.RewriteClientIP{ + Mode: helpers.GetPointer(ngfAPIv1alpha2.RewriteClientIPModeXForwardedFor), + TrustedAddresses: []ngfAPIv1alpha2.RewriteClientIPAddress{ + { + Type: ngfAPIv1alpha2.RewriteClientIPCIDRAddressType, + Value: "76.89.90.11/24", + }, }, + SetIPRecursively: helpers.GetPointer(true), }, - SetIPRecursively: helpers.GetPointer(true), }, }, }, @@ -4105,29 +4301,31 @@ func TestBuildRewriteIPSettings(t *testing.T) { { msg: "rewrite IP settings configured with recursive set to false and multiple trusted addresses", g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - RewriteClientIP: &ngfAPIv1alpha2.RewriteClientIP{ - Mode: helpers.GetPointer(ngfAPIv1alpha2.RewriteClientIPModeXForwardedFor), - TrustedAddresses: []ngfAPIv1alpha2.RewriteClientIPAddress{ - { - Type: ngfAPIv1alpha2.RewriteClientIPCIDRAddressType, - Value: "5.5.5.5/12", - }, - { - Type: ngfAPIv1alpha2.RewriteClientIPCIDRAddressType, - Value: "1.1.1.1/26", - }, - { - Type: ngfAPIv1alpha2.RewriteClientIPCIDRAddressType, - Value: "2.2.2.2/32", - }, - { - Type: ngfAPIv1alpha2.RewriteClientIPCIDRAddressType, - Value: "3.3.3.3/24", + Gateways: map[types.NamespacedName]*graph.Gateway{ + {}: { + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + RewriteClientIP: &ngfAPIv1alpha2.RewriteClientIP{ + Mode: helpers.GetPointer(ngfAPIv1alpha2.RewriteClientIPModeXForwardedFor), + TrustedAddresses: []ngfAPIv1alpha2.RewriteClientIPAddress{ + { + Type: ngfAPIv1alpha2.RewriteClientIPCIDRAddressType, + Value: "5.5.5.5/12", + }, + { + Type: ngfAPIv1alpha2.RewriteClientIPCIDRAddressType, + Value: "1.1.1.1/26", + }, + { + Type: ngfAPIv1alpha2.RewriteClientIPCIDRAddressType, + Value: "2.2.2.2/32", + }, + { + Type: ngfAPIv1alpha2.RewriteClientIPCIDRAddressType, + Value: "3.3.3.3/24", + }, }, + SetIPRecursively: helpers.GetPointer(false), }, - SetIPRecursively: helpers.GetPointer(false), }, }, }, @@ -4144,7 +4342,7 @@ func TestBuildRewriteIPSettings(t *testing.T) { t.Run(tc.msg, func(t *testing.T) { t.Parallel() g := NewWithT(t) - baseConfig := buildBaseHTTPConfig(tc.g) + baseConfig := buildBaseHTTPConfig(tc.g, tc.g.Gateways[types.NamespacedName{}]) g.Expect(baseConfig.RewriteClientIPSettings).To(Equal(tc.expRewriteIPSettings)) }) } @@ -4156,44 +4354,36 @@ func TestBuildLogging(t *testing.T) { t.Parallel() tests := []struct { msg string - g *graph.Graph + gw *graph.Gateway expLoggingSettings Logging }{ { - msg: "Gateway is nil", - g: &graph.Graph{ - Gateway: nil, - }, + msg: "Gateway is nil", + gw: nil, expLoggingSettings: defaultLogging, }, { msg: "Gateway has no effective NginxProxy", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: nil, - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: nil, }, expLoggingSettings: defaultLogging, }, { msg: "Effective NginxProxy does not specify log level", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - IPFamily: helpers.GetPointer(ngfAPIv1alpha2.Dual), - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + IPFamily: helpers.GetPointer(ngfAPIv1alpha2.Dual), }, }, expLoggingSettings: defaultLogging, }, { msg: "Effective NginxProxy log level set to debug", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - Logging: &ngfAPIv1alpha2.NginxLogging{ - ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelDebug), - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelDebug), }, }, }, @@ -4201,12 +4391,10 @@ func TestBuildLogging(t *testing.T) { }, { msg: "Effective NginxProxy log level set to info", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - Logging: &ngfAPIv1alpha2.NginxLogging{ - ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelInfo), - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelInfo), }, }, }, @@ -4214,12 +4402,10 @@ func TestBuildLogging(t *testing.T) { }, { msg: "Effective NginxProxy log level set to notice", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - Logging: &ngfAPIv1alpha2.NginxLogging{ - ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelNotice), - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelNotice), }, }, }, @@ -4227,12 +4413,10 @@ func TestBuildLogging(t *testing.T) { }, { msg: "Effective NginxProxy log level set to warn", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - Logging: &ngfAPIv1alpha2.NginxLogging{ - ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelWarn), - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelWarn), }, }, }, @@ -4240,12 +4424,10 @@ func TestBuildLogging(t *testing.T) { }, { msg: "Effective NginxProxy log level set to error", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - Logging: &ngfAPIv1alpha2.NginxLogging{ - ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError), - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError), }, }, }, @@ -4253,12 +4435,10 @@ func TestBuildLogging(t *testing.T) { }, { msg: "Effective NginxProxy log level set to crit", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - Logging: &ngfAPIv1alpha2.NginxLogging{ - ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelCrit), - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelCrit), }, }, }, @@ -4266,12 +4446,10 @@ func TestBuildLogging(t *testing.T) { }, { msg: "Effective NginxProxy log level set to alert", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - Logging: &ngfAPIv1alpha2.NginxLogging{ - ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelAlert), - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelAlert), }, }, }, @@ -4279,12 +4457,10 @@ func TestBuildLogging(t *testing.T) { }, { msg: "Effective NginxProxy log level set to emerg", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - Logging: &ngfAPIv1alpha2.NginxLogging{ - ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelEmerg), - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelEmerg), }, }, }, @@ -4297,7 +4473,7 @@ func TestBuildLogging(t *testing.T) { t.Parallel() g := NewWithT(t) - g.Expect(buildLogging(tc.g)).To(Equal(tc.expLoggingSettings)) + g.Expect(buildLogging(tc.gw)).To(Equal(tc.expLoggingSettings)) }) } } @@ -4518,32 +4694,28 @@ func TestBuildNginxPlus(t *testing.T) { t.Parallel() tests := []struct { msg string - g *graph.Graph + gw *graph.Gateway expNginxPlus NginxPlus }{ { msg: "NginxProxy is nil", - g: &graph.Graph{}, + gw: &graph.Gateway{}, expNginxPlus: defaultNginxPlus, }, { msg: "NginxPlus default values are used when NginxProxy doesn't specify NginxPlus settings", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{}, - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{}, }, expNginxPlus: defaultNginxPlus, }, { msg: "NginxProxy specifies one allowed address", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - NginxPlus: &ngfAPIv1alpha2.NginxPlus{ - AllowedAddresses: []ngfAPIv1alpha2.NginxPlusAllowAddress{ - {Type: ngfAPIv1alpha2.NginxPlusAllowIPAddressType, Value: "127.0.0.3"}, - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + NginxPlus: &ngfAPIv1alpha2.NginxPlus{ + AllowedAddresses: []ngfAPIv1alpha2.NginxPlusAllowAddress{ + {Type: ngfAPIv1alpha2.NginxPlusAllowIPAddressType, Value: "127.0.0.3"}, }, }, }, @@ -4552,14 +4724,12 @@ func TestBuildNginxPlus(t *testing.T) { }, { msg: "NginxProxy specifies multiple allowed addresses", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - NginxPlus: &ngfAPIv1alpha2.NginxPlus{ - AllowedAddresses: []ngfAPIv1alpha2.NginxPlusAllowAddress{ - {Type: ngfAPIv1alpha2.NginxPlusAllowIPAddressType, Value: "127.0.0.3"}, - {Type: ngfAPIv1alpha2.NginxPlusAllowIPAddressType, Value: "25.0.0.3"}, - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + NginxPlus: &ngfAPIv1alpha2.NginxPlus{ + AllowedAddresses: []ngfAPIv1alpha2.NginxPlusAllowAddress{ + {Type: ngfAPIv1alpha2.NginxPlusAllowIPAddressType, Value: "127.0.0.3"}, + {Type: ngfAPIv1alpha2.NginxPlusAllowIPAddressType, Value: "25.0.0.3"}, }, }, }, @@ -4568,13 +4738,11 @@ func TestBuildNginxPlus(t *testing.T) { }, { msg: "NginxProxy specifies 127.0.0.1 as allowed address", - g: &graph.Graph{ - Gateway: &graph.Gateway{ - EffectiveNginxProxy: &graph.EffectiveNginxProxy{ - NginxPlus: &ngfAPIv1alpha2.NginxPlus{ - AllowedAddresses: []ngfAPIv1alpha2.NginxPlusAllowAddress{ - {Type: ngfAPIv1alpha2.NginxPlusAllowIPAddressType, Value: "127.0.0.1"}, - }, + gw: &graph.Gateway{ + EffectiveNginxProxy: &graph.EffectiveNginxProxy{ + NginxPlus: &ngfAPIv1alpha2.NginxPlus{ + AllowedAddresses: []ngfAPIv1alpha2.NginxPlusAllowAddress{ + {Type: ngfAPIv1alpha2.NginxPlusAllowIPAddressType, Value: "127.0.0.1"}, }, }, }, @@ -4588,7 +4756,7 @@ func TestBuildNginxPlus(t *testing.T) { t.Parallel() g := NewWithT(t) - g.Expect(buildNginxPlus(tc.g)).To(Equal(tc.expNginxPlus)) + g.Expect(buildNginxPlus(tc.gw)).To(Equal(tc.expNginxPlus)) }) } } diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index cd25073da1..ac8a8cc345 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -22,6 +22,10 @@ import ( type BackendRef struct { // BackendTLSPolicy is the BackendTLSPolicy of the Service which is referenced by the backendRef. BackendTLSPolicy *BackendTLSPolicy + // InvalidForGateways is a map of Gateways for which this BackendRef is invalid for, with the corresponding + // condition. Certain NginxProxy configurations may result in a backend not being valid for some Gateways, + // but not others. + InvalidForGateways map[types.NamespacedName]conditions.Condition // SvcNsName is the NamespacedName of the Service referenced by the backendRef. SvcNsName types.NamespacedName // ServicePort is the ServicePort of the Service which is referenced by the backendRef. @@ -46,10 +50,9 @@ func addBackendRefsToRouteRules( refGrantResolver *referenceGrantResolver, services map[types.NamespacedName]*v1.Service, backendTLSPolicies map[types.NamespacedName]*BackendTLSPolicy, - npCfg *EffectiveNginxProxy, ) { for _, r := range routes { - addBackendRefsToRules(r, refGrantResolver, services, backendTLSPolicies, npCfg) + addBackendRefsToRules(r, refGrantResolver, services, backendTLSPolicies) } } @@ -60,7 +63,6 @@ func addBackendRefsToRules( refGrantResolver *referenceGrantResolver, services map[types.NamespacedName]*v1.Service, backendTLSPolicies map[types.NamespacedName]*BackendTLSPolicy, - npCfg *EffectiveNginxProxy, ) { if !route.Valid { return @@ -85,19 +87,18 @@ func addBackendRefsToRules( refPath := field.NewPath("spec").Child("rules").Index(idx).Child("backendRefs").Index(refIdx) routeNs := route.Source.GetNamespace() - ref, cond := createBackendRef( + ref, conds := createBackendRef( ref, - routeNs, + route, refGrantResolver.refAllowedFrom(getRefGrantFromResourceForRoute(route.RouteType, routeNs)), services, refPath, backendTLSPolicies, - npCfg, ) backendRefs = append(backendRefs, ref) - if cond != nil { - route.Conditions = append(route.Conditions, *cond) + if len(conds) > 0 { + route.Conditions = append(route.Conditions, conds...) } } @@ -117,13 +118,12 @@ func addBackendRefsToRules( func createBackendRef( ref RouteBackendRef, - sourceNamespace string, + route *L7Route, refGrantResolver func(resource toResource) bool, services map[types.NamespacedName]*v1.Service, refPath *field.Path, backendTLSPolicies map[types.NamespacedName]*BackendTLSPolicy, - npCfg *EffectiveNginxProxy, -) (BackendRef, *conditions.Condition) { +) (BackendRef, []conditions.Condition) { // Data plane will handle invalid ref by responding with 500. // Because of that, we always need to add a BackendRef to group.Backends, even if the ref is invalid. // Additionally, we always calculate the weight, even if it is invalid. @@ -137,75 +137,71 @@ func createBackendRef( } } - var backendRef BackendRef - - valid, cond := validateRouteBackendRef(ref, sourceNamespace, refGrantResolver, refPath) + valid, cond := validateRouteBackendRef(ref, route.Source.GetNamespace(), refGrantResolver, refPath) if !valid { - backendRef = BackendRef{ - Weight: weight, - Valid: false, + backendRef := BackendRef{ + Weight: weight, + Valid: false, + InvalidForGateways: make(map[types.NamespacedName]conditions.Condition), } - return backendRef, &cond + return backendRef, []conditions.Condition{cond} } - ns := sourceNamespace + ns := route.Source.GetNamespace() if ref.BackendRef.Namespace != nil { ns = string(*ref.Namespace) } svcNsName := types.NamespacedName{Name: string(ref.BackendRef.Name), Namespace: ns} svcIPFamily, svcPort, err := getIPFamilyAndPortFromRef(ref.BackendRef, svcNsName, services, refPath) if err != nil { - backendRef = BackendRef{ - Weight: weight, - Valid: false, - SvcNsName: svcNsName, - ServicePort: v1.ServicePort{}, + backendRef := BackendRef{ + Weight: weight, + Valid: false, + SvcNsName: svcNsName, + ServicePort: v1.ServicePort{}, + InvalidForGateways: make(map[types.NamespacedName]conditions.Condition), } - cond := staticConds.NewRouteBackendRefRefBackendNotFound(err.Error()) - return backendRef, &cond + return backendRef, []conditions.Condition{staticConds.NewRouteBackendRefRefBackendNotFound(err.Error())} } - if err := verifyIPFamily(npCfg, svcIPFamily); err != nil { - backendRef = BackendRef{ - SvcNsName: svcNsName, - ServicePort: svcPort, - Weight: weight, - Valid: false, + var conds []conditions.Condition + invalidForGateways := make(map[types.NamespacedName]conditions.Condition) + for _, parentRef := range route.ParentRefs { + if err := verifyIPFamily(parentRef.Gateway.EffectiveNginxProxy, svcIPFamily); err != nil { + invalidForGateways[parentRef.Gateway.NamespacedName] = staticConds.NewRouteInvalidIPFamily(err.Error()) } - - cond := staticConds.NewRouteInvalidIPFamily(err.Error()) - return backendRef, &cond } backendTLSPolicy, err := findBackendTLSPolicyForService( backendTLSPolicies, ref.Namespace, string(ref.Name), - sourceNamespace, + route.Source.GetNamespace(), ) if err != nil { - backendRef = BackendRef{ - SvcNsName: svcNsName, - ServicePort: svcPort, - Weight: weight, - Valid: false, + backendRef := BackendRef{ + SvcNsName: svcNsName, + ServicePort: svcPort, + Weight: weight, + Valid: false, + InvalidForGateways: invalidForGateways, } - cond := staticConds.NewRouteBackendRefUnsupportedValue(err.Error()) - return backendRef, &cond + return backendRef, append(conds, staticConds.NewRouteBackendRefUnsupportedValue(err.Error())) } - backendRef = BackendRef{ - SvcNsName: svcNsName, - BackendTLSPolicy: backendTLSPolicy, - ServicePort: svcPort, - Valid: true, - Weight: weight, + backendRef := BackendRef{ + SvcNsName: svcNsName, + BackendTLSPolicy: backendTLSPolicy, + ServicePort: svcPort, + Valid: true, + Weight: weight, + InvalidForGateways: invalidForGateways, } - return backendRef, nil + return backendRef, conds } // validateBackendTLSPolicyMatchingAllBackends validates that all backends in a rule reference the same diff --git a/internal/mode/static/state/graph/backend_refs_test.go b/internal/mode/static/state/graph/backend_refs_test.go index bb6ebf0b0f..6f02ff7a82 100644 --- a/internal/mode/static/state/graph/backend_refs_test.go +++ b/internal/mode/static/state/graph/backend_refs_test.go @@ -377,13 +377,13 @@ func TestVerifyIPFamily(t *testing.T) { } } -func TestAddBackendRefsToRulesTest(t *testing.T) { +func TestAddBackendRefsToRules(t *testing.T) { t.Parallel() sectionNameRefs := []ParentRef{ { Idx: 0, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway"}, + Gateway: &ParentRefGateway{NamespacedName: types.NamespacedName{Namespace: "test", Name: "gateway"}}, Attachment: &ParentRefAttachmentStatus{ Attached: true, }, @@ -589,10 +589,11 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { route: createRoute("hr1", "Service", 1, "svc1"), expectedBackendRefs: []BackendRef{ { - SvcNsName: svc1NsName, - ServicePort: svc1.Spec.Ports[0], - Valid: true, - Weight: 1, + SvcNsName: svc1NsName, + ServicePort: svc1.Spec.Ports[0], + Valid: true, + Weight: 1, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, expectedConditions: nil, @@ -603,16 +604,18 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { route: createRoute("hr2", "Service", 2, "svc1"), expectedBackendRefs: []BackendRef{ { - SvcNsName: svc1NsName, - ServicePort: svc1.Spec.Ports[0], - Valid: true, - Weight: 1, + SvcNsName: svc1NsName, + ServicePort: svc1.Spec.Ports[0], + Valid: true, + Weight: 1, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, { - SvcNsName: svc1NsName, - ServicePort: svc1.Spec.Ports[1], - Valid: true, - Weight: 5, + SvcNsName: svc1NsName, + ServicePort: svc1.Spec.Ports[1], + Valid: true, + Weight: 5, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, expectedConditions: nil, @@ -623,18 +626,20 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { route: createRoute("hr2", "Service", 2, "svc1"), expectedBackendRefs: []BackendRef{ { - SvcNsName: svc1NsName, - ServicePort: svc1.Spec.Ports[0], - Valid: true, - Weight: 1, - BackendTLSPolicy: btp3, + SvcNsName: svc1NsName, + ServicePort: svc1.Spec.Ports[0], + Valid: true, + Weight: 1, + BackendTLSPolicy: btp3, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, { - SvcNsName: svc1NsName, - ServicePort: svc1.Spec.Ports[1], - Valid: true, - Weight: 5, - BackendTLSPolicy: btp3, + SvcNsName: svc1NsName, + ServicePort: svc1.Spec.Ports[1], + Valid: true, + Weight: 5, + BackendTLSPolicy: btp3, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, expectedConditions: nil, @@ -675,7 +680,8 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { route: createRoute("hr3", "NotService", 1, "svc1"), expectedBackendRefs: []BackendRef{ { - Weight: 1, + Weight: 1, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, expectedConditions: []conditions.Condition{ @@ -693,18 +699,20 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }), expectedBackendRefs: []BackendRef{ { - SvcNsName: svc1NsName, - ServicePort: svc1.Spec.Ports[0], - Valid: false, - Weight: 1, - BackendTLSPolicy: btp1, + SvcNsName: svc1NsName, + ServicePort: svc1.Spec.Ports[0], + Valid: false, + Weight: 1, + BackendTLSPolicy: btp1, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, { - SvcNsName: svc2NsName, - ServicePort: svc2.Spec.Ports[1], - Valid: false, - Weight: 5, - BackendTLSPolicy: btp2, + SvcNsName: svc2NsName, + ServicePort: svc2.Spec.Ports[1], + Valid: false, + Weight: 5, + BackendTLSPolicy: btp2, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, expectedConditions: []conditions.Condition{ @@ -732,7 +740,7 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { g := NewWithT(t) resolver := newReferenceGrantResolver(nil) - addBackendRefsToRules(test.route, resolver, services, test.policies, nil) + addBackendRefsToRules(test.route, resolver, services, test.policies) var actual []BackendRef if test.route.Spec.Rules != nil { @@ -824,11 +832,11 @@ func TestCreateBackend(t *testing.T) { } tests := []struct { - expectedCondition *conditions.Condition nginxProxySpec *EffectiveNginxProxy name string expectedServicePortReference string ref gatewayv1.HTTPBackendRef + expectedConditions []conditions.Condition expectedBackend BackendRef }{ { @@ -836,13 +844,14 @@ func TestCreateBackend(t *testing.T) { BackendRef: getNormalRef(), }, expectedBackend: BackendRef{ - SvcNsName: svc1NamespacedName, - ServicePort: svc1.Spec.Ports[0], - Weight: 5, - Valid: true, + SvcNsName: svc1NamespacedName, + ServicePort: svc1.Spec.Ports[0], + Weight: 5, + Valid: true, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, expectedServicePortReference: "test_service1_80", - expectedCondition: nil, + expectedConditions: nil, name: "normal case", }, { @@ -853,13 +862,14 @@ func TestCreateBackend(t *testing.T) { }), }, expectedBackend: BackendRef{ - SvcNsName: svc1NamespacedName, - ServicePort: svc1.Spec.Ports[0], - Weight: 1, - Valid: true, + SvcNsName: svc1NamespacedName, + ServicePort: svc1.Spec.Ports[0], + Weight: 1, + Valid: true, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, expectedServicePortReference: "test_service1_80", - expectedCondition: nil, + expectedConditions: nil, name: "normal with nil weight", }, { @@ -870,17 +880,18 @@ func TestCreateBackend(t *testing.T) { }), }, expectedBackend: BackendRef{ - SvcNsName: types.NamespacedName{}, - ServicePort: v1.ServicePort{}, - Weight: 0, - Valid: false, + SvcNsName: types.NamespacedName{}, + ServicePort: v1.ServicePort{}, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, + Weight: 0, + Valid: false, }, expectedServicePortReference: "", - expectedCondition: helpers.GetPointer( + expectedConditions: []conditions.Condition{ staticConds.NewRouteBackendRefUnsupportedValue( "test.weight: Invalid value: -1: must be in the range [0, 1000000]", ), - ), + }, name: "invalid weight", }, { @@ -891,17 +902,18 @@ func TestCreateBackend(t *testing.T) { }), }, expectedBackend: BackendRef{ - SvcNsName: types.NamespacedName{}, - ServicePort: v1.ServicePort{}, - Weight: 5, - Valid: false, + SvcNsName: types.NamespacedName{}, + ServicePort: v1.ServicePort{}, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, + Weight: 5, + Valid: false, }, expectedServicePortReference: "", - expectedCondition: helpers.GetPointer( + expectedConditions: []conditions.Condition{ staticConds.NewRouteBackendRefInvalidKind( `test.kind: Unsupported value: "NotService": supported values: "Service"`, ), - ), + }, name: "invalid kind", }, { @@ -918,31 +930,33 @@ func TestCreateBackend(t *testing.T) { Namespace: "test", Name: "not-exist", }, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, expectedServicePortReference: "", - expectedCondition: helpers.GetPointer( + expectedConditions: []conditions.Condition{ staticConds.NewRouteBackendRefRefBackendNotFound(`test.name: Not found: "not-exist"`), - ), + }, name: "service doesn't exist", }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Name = "service2" - return backend - }), + BackendRef: getNormalRef(), }, expectedBackend: BackendRef{ - SvcNsName: svc2NamespacedName, + SvcNsName: svc1NamespacedName, ServicePort: svc1.Spec.Ports[0], Weight: 5, - Valid: false, + Valid: true, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{ + {Namespace: "test", Name: "gateway"}: staticConds.NewRouteInvalidIPFamily( + `Service configured with IPv4 family but NginxProxy is configured with IPv6`, + ), + }, }, - nginxProxySpec: &EffectiveNginxProxy{IPFamily: helpers.GetPointer(ngfAPIv1alpha2.IPv6)}, - expectedCondition: helpers.GetPointer( - staticConds.NewRouteInvalidIPFamily(`Service configured with IPv4 family but NginxProxy is configured with IPv6`), - ), - name: "service IPFamily doesn't match NginxProxy IPFamily", + expectedServicePortReference: "test_service1_80", + nginxProxySpec: &EffectiveNginxProxy{IPFamily: helpers.GetPointer(ngfAPIv1alpha2.IPv6)}, + expectedConditions: nil, + name: "service IPFamily doesn't match NginxProxy IPFamily", }, { ref: gatewayv1.HTTPBackendRef{ @@ -952,14 +966,15 @@ func TestCreateBackend(t *testing.T) { }), }, expectedBackend: BackendRef{ - SvcNsName: svc2NamespacedName, - ServicePort: svc1.Spec.Ports[0], - Weight: 5, - Valid: true, - BackendTLSPolicy: &btp, + SvcNsName: svc2NamespacedName, + ServicePort: svc1.Spec.Ports[0], + Weight: 5, + Valid: true, + BackendTLSPolicy: &btp, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, expectedServicePortReference: "test_service2_80", - expectedCondition: nil, + expectedConditions: nil, name: "normal case with policy", }, { @@ -970,17 +985,18 @@ func TestCreateBackend(t *testing.T) { }), }, expectedBackend: BackendRef{ - SvcNsName: svc3NamespacedName, - ServicePort: svc1.Spec.Ports[0], - Weight: 5, - Valid: false, + SvcNsName: svc3NamespacedName, + ServicePort: svc1.Spec.Ports[0], + Weight: 5, + Valid: false, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, expectedServicePortReference: "", - expectedCondition: helpers.GetPointer( + expectedConditions: []conditions.Condition{ staticConds.NewRouteBackendRefUnsupportedValue( "the backend TLS policy is invalid: unsupported value", ), - ), + }, name: "invalid policy", }, } @@ -995,8 +1011,6 @@ func TestCreateBackend(t *testing.T) { client.ObjectKeyFromObject(btp2.Source): &btp2, } - sourceNamespace := "test" - refPath := field.NewPath("test") for _, test := range tests { @@ -1010,18 +1024,36 @@ func TestCreateBackend(t *testing.T) { test.ref.BackendRef, []any{}, } - backend, cond := createBackendRef( + route := &L7Route{ + Source: &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + }, + }, + ParentRefs: []ParentRef{ + { + Gateway: &ParentRefGateway{ + NamespacedName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, + EffectiveNginxProxy: test.nginxProxySpec, + }, + }, + }, + } + + backend, conds := createBackendRef( rbr, - sourceNamespace, + route, alwaysTrueRefGrantResolver, services, refPath, policies, - test.nginxProxySpec, ) g.Expect(helpers.Diff(test.expectedBackend, backend)).To(BeEmpty()) - g.Expect(cond).To(Equal(test.expectedCondition)) + g.Expect(conds).To(Equal(test.expectedConditions)) servicePortRef := backend.ServicePortReference() g.Expect(servicePortRef).To(Equal(test.expectedServicePortReference)) diff --git a/internal/mode/static/state/graph/backend_tls_policy.go b/internal/mode/static/state/graph/backend_tls_policy.go index 4e00eecc56..67563eefe5 100644 --- a/internal/mode/static/state/graph/backend_tls_policy.go +++ b/internal/mode/static/state/graph/backend_tls_policy.go @@ -10,6 +10,7 @@ import ( "sigs.k8s.io/gateway-api/apis/v1alpha3" "github.com/nginx/nginx-gateway-fabric/internal/framework/conditions" + "github.com/nginx/nginx-gateway-fabric/internal/framework/kinds" staticConds "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/conditions" ) @@ -18,8 +19,8 @@ type BackendTLSPolicy struct { Source *v1alpha3.BackendTLSPolicy // CaCertRef is the name of the ConfigMap that contains the CA certificate. CaCertRef types.NamespacedName - // Gateway is the name of the Gateway that is being checked for this BackendTLSPolicy. - Gateway types.NamespacedName + // Gateways are the names of the Gateways that are being checked for this BackendTLSPolicy. + Gateways []types.NamespacedName // Conditions include Conditions for the BackendTLSPolicy. Conditions []conditions.Condition // Valid shows whether the BackendTLSPolicy is valid. @@ -35,9 +36,9 @@ func processBackendTLSPolicies( configMapResolver *configMapResolver, secretResolver *secretResolver, ctlrName string, - gateway *Gateway, + gateways map[types.NamespacedName]*Gateway, ) map[types.NamespacedName]*BackendTLSPolicy { - if len(backendTLSPolicies) == 0 || gateway == nil { + if len(backendTLSPolicies) == 0 || len(gateways) == 0 { return nil } @@ -57,12 +58,8 @@ func processBackendTLSPolicies( Source: backendTLSPolicy, Valid: valid, Conditions: conds, - Gateway: types.NamespacedName{ - Namespace: gateway.Source.Namespace, - Name: gateway.Source.Name, - }, - CaCertRef: caCertRef, - Ignored: ignored, + CaCertRef: caCertRef, + Ignored: ignored, } } return processedBackendTLSPolicies @@ -134,7 +131,7 @@ func validateBackendTLSCACertRef( secretResolver *secretResolver, ) error { if len(btp.Spec.Validation.CACertificateRefs) != 1 { - path := field.NewPath("tls.cacertrefs") + path := field.NewPath("validation.caCertificateRefs") valErr := field.TooMany(path, len(btp.Spec.Validation.CACertificateRefs), 1) return valErr } @@ -143,13 +140,13 @@ func validateBackendTLSCACertRef( allowedCaCertKinds := []v1.Kind{"ConfigMap", "Secret"} if !slices.Contains(allowedCaCertKinds, selectedCertRef.Kind) { - path := field.NewPath("tls.cacertrefs[0].kind") + path := field.NewPath("validation.caCertificateRefs[0].kind") valErr := field.NotSupported(path, btp.Spec.Validation.CACertificateRefs[0].Kind, allowedCaCertKinds) return valErr } if selectedCertRef.Group != "" && selectedCertRef.Group != "core" { - path := field.NewPath("tls.cacertrefs[0].group") + path := field.NewPath("validation.caCertificateRefs[0].group") valErr := field.NotSupported(path, selectedCertRef.Group, []string{"", "core"}) return valErr } @@ -161,12 +158,12 @@ func validateBackendTLSCACertRef( switch selectedCertRef.Kind { case "ConfigMap": if err := configMapResolver.resolve(nsName); err != nil { - path := field.NewPath("tls.cacertrefs[0]") + path := field.NewPath("validation.caCertificateRefs[0]") return field.Invalid(path, selectedCertRef, err.Error()) } case "Secret": if err := secretResolver.resolve(nsName); err != nil { - path := field.NewPath("tls.cacertrefs[0]") + path := field.NewPath("validation.caCertificateRefs[0]") return field.Invalid(path, selectedCertRef, err.Error()) } default: @@ -186,3 +183,32 @@ func validateBackendTLSWellKnownCACerts(btp *v1alpha3.BackendTLSPolicy) error { } return nil } + +func addGatewaysForBackendTLSPolicies( + backendTLSPolicies map[types.NamespacedName]*BackendTLSPolicy, + services map[types.NamespacedName]*ReferencedService, +) { + for _, backendTLSPolicy := range backendTLSPolicies { + gateways := make(map[types.NamespacedName]struct{}) + + for _, refs := range backendTLSPolicy.Source.Spec.TargetRefs { + if refs.Kind != kinds.Service { + continue + } + + for svcNsName, referencedServices := range services { + if svcNsName.Name != string(refs.Name) { + continue + } + + for gateway := range referencedServices.GatewayNsNames { + gateways[gateway] = struct{}{} + } + } + } + + for gateway := range gateways { + backendTLSPolicy.Gateways = append(backendTLSPolicy.Gateways, gateway) + } + } +} diff --git a/internal/mode/static/state/graph/backend_tls_policy_test.go b/internal/mode/static/state/graph/backend_tls_policy_test.go index cea42d64e9..12f0d7264f 100644 --- a/internal/mode/static/state/graph/backend_tls_policy_test.go +++ b/internal/mode/static/state/graph/backend_tls_policy_test.go @@ -46,27 +46,29 @@ func TestProcessBackendTLSPoliciesEmpty(t *testing.T) { }, } - gateway := &Gateway{ - Source: &gatewayv1.Gateway{ObjectMeta: metav1.ObjectMeta{Name: "gateway", Namespace: "test"}}, + gateway := map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway"}: { + Source: &gatewayv1.Gateway{ObjectMeta: metav1.ObjectMeta{Name: "gateway", Namespace: "test"}}, + }, } tests := []struct { expected map[types.NamespacedName]*BackendTLSPolicy - gateway *Gateway + gateways map[types.NamespacedName]*Gateway backendTLSPolicies map[types.NamespacedName]*v1alpha3.BackendTLSPolicy name string }{ { name: "no policies", expected: nil, - gateway: gateway, + gateways: gateway, backendTLSPolicies: nil, }, { name: "nil gateway", expected: nil, backendTLSPolicies: backendTLSPolicies, - gateway: nil, + gateways: nil, }, } @@ -75,7 +77,7 @@ func TestProcessBackendTLSPoliciesEmpty(t *testing.T) { t.Parallel() g := NewWithT(t) - processed := processBackendTLSPolicies(test.backendTLSPolicies, nil, nil, "test", test.gateway) + processed := processBackendTLSPolicies(test.backendTLSPolicies, nil, nil, "test", test.gateways) g.Expect(processed).To(Equal(test.expected)) }) @@ -93,6 +95,15 @@ func TestValidateBackendTLSPolicy(t *testing.T) { }, } + targetRefInvalidKind := []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: "Invalid", + Name: "service1", + }, + }, + } + localObjectRefNormalCase := []gatewayv1.LocalObjectReference{ { Kind: "ConfigMap", @@ -119,7 +130,7 @@ func TestValidateBackendTLSPolicy(t *testing.T) { localObjectRefInvalidKind := []gatewayv1.LocalObjectReference{ { - Kind: "Secret", + Kind: "Invalid", Name: "secret", Group: "", }, @@ -299,7 +310,7 @@ func TestValidateBackendTLSPolicy(t *testing.T) { Namespace: "test", }, Spec: v1alpha3.BackendTLSPolicySpec{ - TargetRefs: targetRefNormalCase, + TargetRefs: targetRefInvalidKind, Validation: v1alpha3.BackendTLSPolicyValidation{ CACertificateRefs: localObjectRefInvalidKind, Hostname: "foo.test.com", @@ -475,3 +486,182 @@ func TestValidateBackendTLSPolicy(t *testing.T) { }) } } + +func TestAddGatewaysForBackendTLSPolicies(t *testing.T) { + t.Parallel() + + btp1 := &BackendTLSPolicy{ + Source: &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "btp1", + Namespace: "test", + }, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: "Service", + Name: "service1", + }, + }, + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: "Service", + Name: "service2", + }, + }, + }, + }, + }, + } + btp1Expected := btp1 + + btp1Expected.Gateways = []types.NamespacedName{ + {Namespace: "test", Name: "gateway1"}, + {Namespace: "test", Name: "gateway2"}, + {Namespace: "test", Name: "gateway3"}, + } + + btp2 := &BackendTLSPolicy{ + Source: &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "btp2", + Namespace: "test", + }, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: "Service", + Name: "service3", + }, + }, + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: "Service", + Name: "service4", + }, + }, + }, + }, + }, + } + + btp2Expected := btp2 + btp2Expected.Gateways = []types.NamespacedName{ + {Namespace: "test", Name: "gateway4"}, + } + + btp3 := &BackendTLSPolicy{ + Source: &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "btp3", + Namespace: "test", + }, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: "Service", + Name: "service-does-not-exist", + }, + }, + }, + }, + }, + } + + btp4 := &BackendTLSPolicy{ + Source: &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "btp4", + Namespace: "test", + }, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: "Gateway", + Name: "gateway", + }, + }, + }, + }, + }, + } + + tests := []struct { + backendTLSPolicies map[types.NamespacedName]*BackendTLSPolicy + services map[types.NamespacedName]*ReferencedService + expected map[types.NamespacedName]*BackendTLSPolicy + name string + }{ + { + name: "add multiple gateways to backend tls policies", + backendTLSPolicies: map[types.NamespacedName]*BackendTLSPolicy{ + {Namespace: "test", Name: "btp1"}: btp1, + {Namespace: "test", Name: "btp2"}: btp2, + }, + services: map[types.NamespacedName]*ReferencedService{ + {Namespace: "test", Name: "service1"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway1"}: {}, + }, + }, + {Namespace: "test", Name: "service2"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway2"}: {}, + {Namespace: "test", Name: "gateway3"}: {}, + }, + }, + {Namespace: "test", Name: "service3"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway4"}: {}, + }, + }, + {Namespace: "test", Name: "service4"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway4"}: {}, + }, + }, + }, + expected: map[types.NamespacedName]*BackendTLSPolicy{ + {Namespace: "test", Name: "btp1"}: btp1Expected, + {Namespace: "test", Name: "btp2"}: btp2Expected, + }, + }, + { + name: "backend tls policy with a service target ref that does not reference a gateway", + backendTLSPolicies: map[types.NamespacedName]*BackendTLSPolicy{ + {Namespace: "test", Name: "btp3"}: btp3, + }, + services: map[types.NamespacedName]*ReferencedService{ + {Namespace: "test", Name: "service1"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{}, + }, + }, + expected: map[types.NamespacedName]*BackendTLSPolicy{ + {Namespace: "test", Name: "btp3"}: btp3, + }, + }, + { + name: "backend tls policy that does not reference a service", + backendTLSPolicies: map[types.NamespacedName]*BackendTLSPolicy{ + {Namespace: "test", Name: "btp4"}: btp4, + }, + services: map[types.NamespacedName]*ReferencedService{}, + expected: map[types.NamespacedName]*BackendTLSPolicy{ + {Namespace: "test", Name: "btp4"}: btp4, + }, + }, + } + + for _, test := range tests { + g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + addGatewaysForBackendTLSPolicies(test.backendTLSPolicies, test.services) + g.Expect(helpers.Diff(test.backendTLSPolicies, test.expected)).To(BeEmpty()) + }) + } +} diff --git a/internal/mode/static/state/graph/gateway.go b/internal/mode/static/state/graph/gateway.go index d41364288d..da9a2a46c8 100644 --- a/internal/mode/static/state/graph/gateway.go +++ b/internal/mode/static/state/graph/gateway.go @@ -1,30 +1,31 @@ package graph import ( - "sort" - "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" - "sigs.k8s.io/controller-runtime/pkg/client" v1 "sigs.k8s.io/gateway-api/apis/v1" "github.com/nginx/nginx-gateway-fabric/internal/framework/conditions" + "github.com/nginx/nginx-gateway-fabric/internal/framework/controller" "github.com/nginx/nginx-gateway-fabric/internal/framework/kinds" "github.com/nginx/nginx-gateway-fabric/internal/mode/static/config" - ngfsort "github.com/nginx/nginx-gateway-fabric/internal/mode/static/sort" staticConds "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/conditions" ) -// Gateway represents the winning Gateway resource. +// Gateway represents a Gateway resource. type Gateway struct { + // LatestReloadResult is the result of the last nginx reload attempt. + LatestReloadResult NginxReloadResult // Source is the corresponding Gateway resource. Source *v1.Gateway // NginxProxy is the NginxProxy referenced by this Gateway. NginxProxy *NginxProxy - /// EffectiveNginxProxy holds the result of merging the NginxProxySpec on this resource with the NginxProxySpec on + // EffectiveNginxProxy holds the result of merging the NginxProxySpec on this resource with the NginxProxySpec on // the GatewayClass resource. This is the effective set of config that should be applied to the Gateway. // If non-nil, then this config is valid. EffectiveNginxProxy *EffectiveNginxProxy + // DeploymentName is the name of the nginx Deployment associated with this Gateway. + DeploymentName types.NamespacedName // Listeners include the listeners of the Gateway. Listeners []*Listener // Conditions holds the conditions for the Gateway. @@ -35,124 +36,95 @@ type Gateway struct { Valid bool } -// processedGateways holds the resources that belong to NGF. -type processedGateways struct { - Winner *v1.Gateway - Ignored map[types.NamespacedName]*v1.Gateway -} - -// GetAllNsNames returns all the NamespacedNames of the Gateway resources that belong to NGF. -func (gws processedGateways) GetAllNsNames() []types.NamespacedName { - winnerCnt := 0 - if gws.Winner != nil { - winnerCnt = 1 - } - - length := winnerCnt + len(gws.Ignored) - if length == 0 { - return nil - } - - allNsNames := make([]types.NamespacedName, 0, length) - - if gws.Winner != nil { - allNsNames = append(allNsNames, client.ObjectKeyFromObject(gws.Winner)) - } - for nsName := range gws.Ignored { - allNsNames = append(allNsNames, nsName) - } - - return allNsNames -} - -// processGateways determines which Gateway resource belong to NGF (determined by the Gateway GatewayClassName field). +// processGateways determines which Gateway resources belong to NGF (determined by the Gateway GatewayClassName field). func processGateways( gws map[types.NamespacedName]*v1.Gateway, gcName string, -) processedGateways { - referencedGws := make([]*v1.Gateway, 0, len(gws)) +) map[types.NamespacedName]*v1.Gateway { + referencedGws := make(map[types.NamespacedName]*v1.Gateway) - for _, gw := range gws { + for gwNsName, gw := range gws { if string(gw.Spec.GatewayClassName) != gcName { continue } - referencedGws = append(referencedGws, gw) + referencedGws[gwNsName] = gw } if len(referencedGws) == 0 { - return processedGateways{} - } - - sort.Slice(referencedGws, func(i, j int) bool { - return ngfsort.LessClientObject(referencedGws[i], referencedGws[j]) - }) - - ignoredGws := make(map[types.NamespacedName]*v1.Gateway) - - for _, gw := range referencedGws[1:] { - ignoredGws[client.ObjectKeyFromObject(gw)] = gw + return nil } - return processedGateways{ - Winner: referencedGws[0], - Ignored: ignoredGws, - } + return referencedGws } -func buildGateway( - gw *v1.Gateway, +func buildGateways( + gws map[types.NamespacedName]*v1.Gateway, secretResolver *secretResolver, gc *GatewayClass, refGrantResolver *referenceGrantResolver, nps map[types.NamespacedName]*NginxProxy, -) *Gateway { - if gw == nil { +) map[types.NamespacedName]*Gateway { + if len(gws) == 0 { return nil } - var np *NginxProxy - if gw.Spec.Infrastructure != nil && gw.Spec.Infrastructure.ParametersRef != nil { - npName := types.NamespacedName{Namespace: gw.Namespace, Name: gw.Spec.Infrastructure.ParametersRef.Name} - np = nps[npName] - } + builtGateways := make(map[types.NamespacedName]*Gateway, len(gws)) - var gcNp *NginxProxy - if gc != nil { - gcNp = gc.NginxProxy - } + for gwNsName, gw := range gws { + var np *NginxProxy + var npNsName types.NamespacedName + if gw.Spec.Infrastructure != nil && gw.Spec.Infrastructure.ParametersRef != nil { + npNsName = types.NamespacedName{Namespace: gw.Namespace, Name: gw.Spec.Infrastructure.ParametersRef.Name} + np = nps[npNsName] + } + + var gcNp *NginxProxy + if gc != nil { + gcNp = gc.NginxProxy + } - effectiveNginxProxy := buildEffectiveNginxProxy(gcNp, np) + effectiveNginxProxy := buildEffectiveNginxProxy(gcNp, np) - conds, valid := validateGateway(gw, gc, np) + conds, valid := validateGateway(gw, gc, np) - if !valid { - return &Gateway{ - Source: gw, - Valid: false, - NginxProxy: np, - EffectiveNginxProxy: effectiveNginxProxy, - Conditions: conds, + protectedPorts := make(ProtectedPorts) + if port, enabled := MetricsEnabledForNginxProxy(effectiveNginxProxy); enabled { + metricsPort := config.DefaultNginxMetricsPort + if port != nil { + metricsPort = *port + } + protectedPorts[metricsPort] = "MetricsPort" } - } - protectedPorts := make(ProtectedPorts) - if port, enabled := MetricsEnabledForNginxProxy(effectiveNginxProxy); enabled { - metricsPort := config.DefaultNginxMetricsPort - if port != nil { - metricsPort = *port + deploymentName := types.NamespacedName{ + Namespace: gw.GetNamespace(), + Name: controller.CreateNginxResourceName(gw.GetName(), string(gw.Spec.GatewayClassName)), } - protectedPorts[metricsPort] = "MetricsPort" - } - return &Gateway{ - Source: gw, - Listeners: buildListeners(gw, secretResolver, refGrantResolver, protectedPorts), - NginxProxy: np, - EffectiveNginxProxy: effectiveNginxProxy, - Valid: true, - Conditions: conds, + if !valid { + builtGateways[gwNsName] = &Gateway{ + Source: gw, + Valid: false, + NginxProxy: np, + EffectiveNginxProxy: effectiveNginxProxy, + Conditions: conds, + DeploymentName: deploymentName, + } + } else { + builtGateways[gwNsName] = &Gateway{ + Source: gw, + Listeners: buildListeners(gw, secretResolver, refGrantResolver, protectedPorts), + NginxProxy: np, + EffectiveNginxProxy: effectiveNginxProxy, + Valid: true, + Conditions: conds, + DeploymentName: deploymentName, + } + } } + + return builtGateways } func validateGatewayParametersRef(npCfg *NginxProxy, ref v1.LocalParametersReference) []conditions.Condition { diff --git a/internal/mode/static/state/graph/gateway_listener.go b/internal/mode/static/state/graph/gateway_listener.go index aa5b7062e1..5bf46bd502 100644 --- a/internal/mode/static/state/graph/gateway_listener.go +++ b/internal/mode/static/state/graph/gateway_listener.go @@ -9,6 +9,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" + "sigs.k8s.io/controller-runtime/pkg/client" v1 "sigs.k8s.io/gateway-api/apis/v1" "github.com/nginx/nginx-gateway-fabric/internal/framework/conditions" @@ -21,6 +22,8 @@ import ( // For now, we only support HTTP and HTTPS listeners. type Listener struct { Name string + // GatewayName is the name of the Gateway resource this Listener belongs to. + GatewayName types.NamespacedName // Source holds the source of the Listener from the Gateway resource. Source v1.Listener // Routes holds the GRPC/HTTPRoutes attached to the Listener. @@ -57,7 +60,7 @@ func buildListeners( for _, gl := range gw.Spec.Listeners { configurator := listenerFactory.getConfiguratorForListener(gl) - listeners = append(listeners, configurator.configure(gl)) + listeners = append(listeners, configurator.configure(gl, client.ObjectKeyFromObject(gw))) } return listeners @@ -167,7 +170,7 @@ type listenerConfigurator struct { externalReferenceResolvers []listenerExternalReferenceResolver } -func (c *listenerConfigurator) configure(listener v1.Listener) *Listener { +func (c *listenerConfigurator) configure(listener v1.Listener, gwNSName types.NamespacedName) *Listener { var conds []conditions.Condition attachable := true @@ -197,6 +200,7 @@ func (c *listenerConfigurator) configure(listener v1.Listener) *Listener { l := &Listener{ Name: string(listener.Name), + GatewayName: gwNSName, Source: listener, Conditions: conds, AllowedRouteLabelSelector: allowedRouteSelector, diff --git a/internal/mode/static/state/graph/gateway_test.go b/internal/mode/static/state/graph/gateway_test.go index 0e50d14380..bb0bbe263c 100644 --- a/internal/mode/static/state/graph/gateway_test.go +++ b/internal/mode/static/state/graph/gateway_test.go @@ -15,66 +15,17 @@ import ( ngfAPIv1alpha2 "github.com/nginx/nginx-gateway-fabric/apis/v1alpha2" "github.com/nginx/nginx-gateway-fabric/internal/framework/conditions" + "github.com/nginx/nginx-gateway-fabric/internal/framework/controller" "github.com/nginx/nginx-gateway-fabric/internal/framework/helpers" "github.com/nginx/nginx-gateway-fabric/internal/framework/kinds" staticConds "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/conditions" ) -func TestProcessedGatewaysGetAllNsNames(t *testing.T) { - t.Parallel() - winner := &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "gateway-1", - }, - } - loser := &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "gateway-2", - }, - } - - tests := []struct { - gws processedGateways - name string - expected []types.NamespacedName - }{ - { - gws: processedGateways{}, - expected: nil, - name: "no gateways", - }, - { - gws: processedGateways{ - Winner: winner, - Ignored: map[types.NamespacedName]*v1.Gateway{ - client.ObjectKeyFromObject(loser): loser, - }, - }, - expected: []types.NamespacedName{ - client.ObjectKeyFromObject(winner), - client.ObjectKeyFromObject(loser), - }, - name: "winner and ignored", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := test.gws.GetAllNsNames() - g.Expect(result).To(Equal(test.expected)) - }) - } -} - func TestProcessGateways(t *testing.T) { t.Parallel() const gcName = "test-gc" - winner := &v1.Gateway{ + gw1 := &v1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", Name: "gateway-1", @@ -83,7 +34,7 @@ func TestProcessGateways(t *testing.T) { GatewayClassName: gcName, }, } - loser := &v1.Gateway{ + gw2 := &v1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", Name: "gateway-2", @@ -95,12 +46,12 @@ func TestProcessGateways(t *testing.T) { tests := []struct { gws map[types.NamespacedName]*v1.Gateway - expected processedGateways + expected map[types.NamespacedName]*v1.Gateway name string }{ { gws: nil, - expected: processedGateways{}, + expected: nil, name: "no gateways", }, { @@ -109,29 +60,26 @@ func TestProcessGateways(t *testing.T) { Spec: v1.GatewaySpec{GatewayClassName: "some-class"}, }, }, - expected: processedGateways{}, + expected: nil, name: "unrelated gateway", }, { gws: map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-1"}: winner, + {Namespace: "test", Name: "gateway-1"}: gw1, }, - expected: processedGateways{ - Winner: winner, - Ignored: map[types.NamespacedName]*v1.Gateway{}, + expected: map[types.NamespacedName]*v1.Gateway{ + {Namespace: "test", Name: "gateway-1"}: gw1, }, name: "one gateway", }, { gws: map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-1"}: winner, - {Namespace: "test", Name: "gateway-2"}: loser, + {Namespace: "test", Name: "gateway-1"}: gw1, + {Namespace: "test", Name: "gateway-2"}: gw2, }, - expected: processedGateways{ - Winner: winner, - Ignored: map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-2"}: loser, - }, + expected: map[types.NamespacedName]*v1.Gateway{ + {Namespace: "test", Name: "gateway-1"}: gw1, + {Namespace: "test", Name: "gateway-2"}: gw2, }, name: "multiple gateways", }, @@ -338,15 +286,18 @@ func TestBuildGateway(t *testing.T) { ) type gatewayCfg struct { + name string ref *v1.LocalParametersReference listeners []v1.Listener addresses []v1.GatewayAddress } var lastCreatedGateway *v1.Gateway - createGateway := func(cfg gatewayCfg) *v1.Gateway { + createGateway := func(cfg gatewayCfg) map[types.NamespacedName]*v1.Gateway { + gatewayMap := make(map[types.NamespacedName]*v1.Gateway) lastCreatedGateway = &v1.Gateway{ ObjectMeta: metav1.ObjectMeta{ + Name: cfg.name, Namespace: "test", }, Spec: v1.GatewaySpec{ @@ -361,8 +312,14 @@ func TestBuildGateway(t *testing.T) { ParametersRef: cfg.ref, } } - return lastCreatedGateway + + gatewayMap[types.NamespacedName{ + Namespace: lastCreatedGateway.Namespace, + Name: lastCreatedGateway.Name, + }] = lastCreatedGateway + return gatewayMap } + getLastCreatedGateway := func() *v1.Gateway { return lastCreatedGateway } @@ -374,6 +331,10 @@ func TestBuildGateway(t *testing.T) { }, Spec: ngfAPIv1alpha2.NginxProxySpec{ Logging: &ngfAPIv1alpha2.NginxLogging{ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError)}, + Metrics: &ngfAPIv1alpha2.Metrics{ + Disable: helpers.GetPointer(false), + Port: helpers.GetPointer(int32(90)), + }, }, } validGwNpRef := &v1.LocalParametersReference{ @@ -434,99 +395,122 @@ func TestBuildGateway(t *testing.T) { } tests := []struct { - gateway *v1.Gateway + gateway map[types.NamespacedName]*v1.Gateway gatewayClass *GatewayClass refGrants map[types.NamespacedName]*v1beta1.ReferenceGrant - expected *Gateway + expected map[types.NamespacedName]*Gateway name string }{ { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{foo80Listener1, foo8080Listener}}), + gateway: createGateway(gatewayCfg{name: "gateway1", listeners: []v1.Listener{foo80Listener1, foo8080Listener}}), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "foo-80-1", - Source: foo80Listener1, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo80Listener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "foo-8080", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo8080Listener, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, }, - { - Name: "foo-8080", - Source: foo8080Listener, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), }, + Valid: true, }, - Valid: true, }, name: "valid http listeners", }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo443HTTPSListener1, foo8443HTTPSListener}}, + gatewayCfg{name: "gateway-https", listeners: []v1.Listener{foo443HTTPSListener1, foo8443HTTPSListener}}, ), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "foo-443-https-1", - Source: foo443HTTPSListener1, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway-https"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-443-https-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo443HTTPSListener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "foo-8443-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo8443HTTPSListener, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), + SupportedKinds: supportedKindsForListeners, + }, }, - { - Name: "foo-8443-https", - Source: foo8443HTTPSListener, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), - SupportedKinds: supportedKindsForListeners, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway-https", gcName), }, + Valid: true, }, - Valid: true, }, name: "valid https listeners", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{listenerAllowedRoutes}}), + gateway: createGateway(gatewayCfg{name: "gateway1", listeners: []v1.Listener{listenerAllowedRoutes}}), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "listener-with-allowed-routes", - Source: listenerAllowedRoutes, - Valid: true, - Attachable: true, - AllowedRouteLabelSelector: labels.SelectorFromSet(labels.Set(labelSet)), - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: []v1.RouteGroupKind{ - {Kind: kinds.HTTPRoute, Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "listener-with-allowed-routes", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: listenerAllowedRoutes, + Valid: true, + Attachable: true, + AllowedRouteLabelSelector: labels.SelectorFromSet(labels.Set(labelSet)), + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: kinds.HTTPRoute, Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: true, }, - Valid: true, }, name: "valid http listener with allowed routes label selector", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{crossNamespaceSecretListener}}), + gateway: createGateway(gatewayCfg{name: "gateway1", listeners: []v1.Listener{crossNamespaceSecretListener}}), gatewayClass: validGC, refGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ {Name: "ref-grant", Namespace: "diff-ns"}: { @@ -552,181 +536,247 @@ func TestBuildGateway(t *testing.T) { }, }, }, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "listener-cross-ns-secret", - Source: crossNamespaceSecretListener, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretDiffNamespace)), - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "listener-cross-ns-secret", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: crossNamespaceSecretListener, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretDiffNamespace)), + SupportedKinds: supportedKindsForListeners, + }, + }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), }, + Valid: true, }, - Valid: true, }, name: "valid https listener with cross-namespace secret; allowed by reference grant", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{foo80Listener1}, ref: validGwNpRef}), + gateway: createGateway(gatewayCfg{ + name: "gateway-valid-np", + listeners: []v1.Listener{foo80Listener1}, + ref: validGwNpRef, + }), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "foo-80-1", - Source: foo80Listener1, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: validGwNp.Namespace, Name: "gateway-valid-np"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo80Listener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, }, - }, - Valid: true, - NginxProxy: &NginxProxy{ - Source: validGwNp, - Valid: true, - }, - EffectiveNginxProxy: &EffectiveNginxProxy{ - Logging: &ngfAPIv1alpha2.NginxLogging{ - ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError), + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway-valid-np", gcName), + }, + Valid: true, + NginxProxy: &NginxProxy{ + Source: validGwNp, + Valid: true, + }, + EffectiveNginxProxy: &EffectiveNginxProxy{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError), + }, + Metrics: &ngfAPIv1alpha2.Metrics{ + Disable: helpers.GetPointer(false), + Port: helpers.GetPointer(int32(90)), + }, }, + Conditions: []conditions.Condition{staticConds.NewGatewayResolvedRefs()}, }, - Conditions: []conditions.Condition{staticConds.NewGatewayResolvedRefs()}, }, name: "valid http listener with valid NginxProxy; GatewayClass has no NginxProxy", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{foo80Listener1}, ref: validGwNpRef}), + gateway: createGateway(gatewayCfg{ + name: "gateway-valid-np", + listeners: []v1.Listener{foo80Listener1}, + ref: validGwNpRef, + }), gatewayClass: validGCWithNp, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "foo-80-1", - Source: foo80Listener1, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: validGwNp.Namespace, Name: "gateway-valid-np"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo80Listener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, }, - }, - Valid: true, - NginxProxy: &NginxProxy{ - Source: validGwNp, - Valid: true, - }, - EffectiveNginxProxy: &EffectiveNginxProxy{ - Logging: &ngfAPIv1alpha2.NginxLogging{ - ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError), + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway-valid-np", gcName), + }, + Valid: true, + NginxProxy: &NginxProxy{ + Source: validGwNp, + Valid: true, }, - IPFamily: helpers.GetPointer(ngfAPIv1alpha2.Dual), + EffectiveNginxProxy: &EffectiveNginxProxy{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError), + }, + IPFamily: helpers.GetPointer(ngfAPIv1alpha2.Dual), + Metrics: &ngfAPIv1alpha2.Metrics{ + Disable: helpers.GetPointer(false), + Port: helpers.GetPointer(int32(90)), + }, + }, + Conditions: []conditions.Condition{staticConds.NewGatewayResolvedRefs()}, }, - Conditions: []conditions.Condition{staticConds.NewGatewayResolvedRefs()}, }, name: "valid http listener with valid NginxProxy; GatewayClass has valid NginxProxy too", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{foo80Listener1}}), + gateway: createGateway(gatewayCfg{name: "gateway1", listeners: []v1.Listener{foo80Listener1}}), gatewayClass: validGCWithNp, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "foo-80-1", - Source: foo80Listener1, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo80Listener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: true, + EffectiveNginxProxy: &EffectiveNginxProxy{ + IPFamily: helpers.GetPointer(ngfAPIv1alpha2.Dual), }, - }, - Valid: true, - EffectiveNginxProxy: &EffectiveNginxProxy{ - IPFamily: helpers.GetPointer(ngfAPIv1alpha2.Dual), }, }, name: "valid http listener; GatewayClass has valid NginxProxy", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{crossNamespaceSecretListener}}), + gateway: createGateway(gatewayCfg{name: "gateway1", listeners: []v1.Listener{crossNamespaceSecretListener}}), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "listener-cross-ns-secret", - Source: crossNamespaceSecretListener, - Valid: false, - Attachable: true, - Conditions: staticConds.NewListenerRefNotPermitted( - `Certificate ref to secret diff-ns/secret not permitted by any ReferenceGrant`, - ), - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "listener-cross-ns-secret", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: crossNamespaceSecretListener, + Valid: false, + Attachable: true, + Conditions: staticConds.NewListenerRefNotPermitted( + `Certificate ref to secret diff-ns/secret not permitted by any ReferenceGrant`, + ), + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: true, }, - Valid: true, }, name: "invalid attachable https listener with cross-namespace secret; no reference grant", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{listenerInvalidSelector}}), + gateway: createGateway(gatewayCfg{name: "gateway1", listeners: []v1.Listener{listenerInvalidSelector}}), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "listener-with-invalid-selector", - Source: listenerInvalidSelector, - Valid: false, - Attachable: true, - Conditions: staticConds.NewListenerUnsupportedValue( - `invalid label selector: "invalid" is not a valid label selector operator`, - ), - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: []v1.RouteGroupKind{ - {Kind: kinds.HTTPRoute, Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "listener-with-invalid-selector", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: listenerInvalidSelector, + Valid: false, + Attachable: true, + Conditions: staticConds.NewListenerUnsupportedValue( + `invalid label selector: "invalid" is not a valid label selector operator`, + ), + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: kinds.HTTPRoute, Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: true, }, - Valid: true, }, name: "attachable http listener with invalid label selector", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{invalidProtocolListener}}), + gateway: createGateway(gatewayCfg{name: "gateway1", listeners: []v1.Listener{invalidProtocolListener}}), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "invalid-protocol", - Source: invalidProtocolListener, - Valid: false, - Attachable: false, - Conditions: staticConds.NewListenerUnsupportedProtocol( - `protocol: Unsupported value: "TCP": supported values: "HTTP", "HTTPS", "TLS"`, - ), - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "invalid-protocol", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: invalidProtocolListener, + Valid: false, + Attachable: false, + Conditions: staticConds.NewListenerUnsupportedProtocol( + `protocol: Unsupported value: "TCP": supported values: "HTTP", "HTTPS", "TLS"`, + ), + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: true, }, - Valid: true, }, name: "invalid listener protocol", }, { gateway: createGateway( gatewayCfg{ + name: "gateway1", listeners: []v1.Listener{ invalidPortListener, invalidHTTPSPortListener, @@ -735,107 +785,132 @@ func TestBuildGateway(t *testing.T) { }, ), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "invalid-port", - Source: invalidPortListener, - Valid: false, - Attachable: true, - Conditions: staticConds.NewListenerUnsupportedValue( - `port: Invalid value: 0: port must be between 1-65535`, - ), - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, - }, - { - Name: "invalid-https-port", - Source: invalidHTTPSPortListener, - Valid: false, - Attachable: true, - Conditions: staticConds.NewListenerUnsupportedValue( - `port: Invalid value: 65536: port must be between 1-65535`, - ), - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "invalid-port", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: invalidPortListener, + Valid: false, + Attachable: true, + Conditions: staticConds.NewListenerUnsupportedValue( + `port: Invalid value: 0: port must be between 1-65535`, + ), + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "invalid-https-port", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: invalidHTTPSPortListener, + Valid: false, + Attachable: true, + Conditions: staticConds.NewListenerUnsupportedValue( + `port: Invalid value: 65536: port must be between 1-65535`, + ), + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "invalid-protected-port", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: invalidProtectedPortListener, + Valid: false, + Attachable: true, + Conditions: staticConds.NewListenerUnsupportedValue( + `port: Invalid value: 9113: port is already in use as MetricsPort`, + ), + SupportedKinds: supportedKindsForListeners, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + }, }, - { - Name: "invalid-protected-port", - Source: invalidProtectedPortListener, - Valid: false, - Attachable: true, - Conditions: staticConds.NewListenerUnsupportedValue( - `port: Invalid value: 9113: port is already in use as MetricsPort`, - ), - SupportedKinds: supportedKindsForListeners, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), }, + Valid: true, }, - Valid: true, }, name: "invalid ports", }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{invalidHostnameListener, invalidHTTPSHostnameListener}}, + gatewayCfg{name: "gateway1", listeners: []v1.Listener{invalidHostnameListener, invalidHTTPSHostnameListener}}, ), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "invalid-hostname", - Source: invalidHostnameListener, - Valid: false, - Conditions: staticConds.NewListenerUnsupportedValue(invalidHostnameMsg), - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "invalid-hostname", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: invalidHostnameListener, + Valid: false, + Conditions: staticConds.NewListenerUnsupportedValue(invalidHostnameMsg), + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "invalid-https-hostname", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: invalidHTTPSHostnameListener, + Valid: false, + Conditions: staticConds.NewListenerUnsupportedValue(invalidHostnameMsg), + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, }, - { - Name: "invalid-https-hostname", - Source: invalidHTTPSHostnameListener, - Valid: false, - Conditions: staticConds.NewListenerUnsupportedValue(invalidHostnameMsg), - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), }, + Valid: true, }, - Valid: true, }, name: "invalid hostnames", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{invalidTLSConfigListener}}), + gateway: createGateway(gatewayCfg{name: "gateway1", listeners: []v1.Listener{invalidTLSConfigListener}}), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "invalid-tls-config", - Source: invalidTLSConfigListener, - Valid: false, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - Conditions: staticConds.NewListenerInvalidCertificateRef( - `tls.certificateRefs[0]: Invalid value: test/does-not-exist: secret does not exist`, - ), - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "invalid-tls-config", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: invalidTLSConfigListener, + Valid: false, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + Conditions: staticConds.NewListenerInvalidCertificateRef( + `tls.certificateRefs[0]: Invalid value: test/does-not-exist: secret does not exist`, + ), + SupportedKinds: supportedKindsForListeners, + }, + }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), }, + Valid: true, }, - Valid: true, }, name: "invalid https listener (secret does not exist)", }, { gateway: createGateway( gatewayCfg{ + name: "gateway1", listeners: []v1.Listener{ foo80Listener1, foo8080Listener, @@ -849,93 +924,108 @@ func TestBuildGateway(t *testing.T) { }, ), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "foo-80-1", - Source: foo80Listener1, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, - }, - { - Name: "foo-8080", - Source: foo8080Listener, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, - }, - { - Name: "foo-8081", - Source: foo8081Listener, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, - }, - { - Name: "foo-443-https-1", - Source: foo443HTTPSListener1, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), - SupportedKinds: supportedKindsForListeners, - }, - { - Name: "foo-8443-https", - Source: foo8443HTTPSListener, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), - SupportedKinds: supportedKindsForListeners, - }, - { - Name: "bar-80", - Source: bar80Listener, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, - }, - { - Name: "bar-443-https", - Source: bar443HTTPSListener, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo80Listener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "foo-8080", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo8080Listener, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "foo-8081", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo8081Listener, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "foo-443-https-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo443HTTPSListener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "foo-8443-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo8443HTTPSListener, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "bar-80", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: bar80Listener, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "bar-443-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: bar443HTTPSListener, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "bar-8443-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: bar8443HTTPSListener, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), + SupportedKinds: supportedKindsForListeners, + }, }, - { - Name: "bar-8443-https", - Source: bar8443HTTPSListener, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), - SupportedKinds: supportedKindsForListeners, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), }, + Valid: true, }, - Valid: true, }, name: "multiple valid http/https listeners", }, { gateway: createGateway( gatewayCfg{ + name: "gateway1", listeners: []v1.Listener{ foo80Listener1, bar80Listener, @@ -947,91 +1037,110 @@ func TestBuildGateway(t *testing.T) { }, ), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "foo-80-1", - Source: foo80Listener1, - Valid: false, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - Conditions: staticConds.NewListenerProtocolConflict(conflict80PortMsg), - SupportedKinds: supportedKindsForListeners, - }, - { - Name: "bar-80", - Source: bar80Listener, - Valid: false, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - Conditions: staticConds.NewListenerProtocolConflict(conflict80PortMsg), - SupportedKinds: supportedKindsForListeners, - }, - { - Name: "foo-443-http", - Source: foo443HTTPListener, - Valid: false, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - Conditions: staticConds.NewListenerProtocolConflict(conflict443PortMsg), - SupportedKinds: supportedKindsForListeners, - }, - { - Name: "foo-80-https", - Source: foo80HTTPSListener, - Valid: false, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - Conditions: staticConds.NewListenerProtocolConflict(conflict80PortMsg), - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), - SupportedKinds: supportedKindsForListeners, - }, - { - Name: "foo-443-https-1", - Source: foo443HTTPSListener1, - Valid: false, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - Conditions: staticConds.NewListenerProtocolConflict(conflict443PortMsg), - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo80Listener1, + Valid: false, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + Conditions: staticConds.NewListenerProtocolConflict(conflict80PortMsg), + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "bar-80", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: bar80Listener, + Valid: false, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + Conditions: staticConds.NewListenerProtocolConflict(conflict80PortMsg), + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "foo-443-http", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo443HTTPListener, + Valid: false, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + Conditions: staticConds.NewListenerProtocolConflict(conflict443PortMsg), + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "foo-80-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo80HTTPSListener, + Valid: false, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + Conditions: staticConds.NewListenerProtocolConflict(conflict80PortMsg), + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "foo-443-https-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo443HTTPSListener1, + Valid: false, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + Conditions: staticConds.NewListenerProtocolConflict(conflict443PortMsg), + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "bar-443-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: bar443HTTPSListener, + Valid: false, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + Conditions: staticConds.NewListenerProtocolConflict(conflict443PortMsg), + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), + SupportedKinds: supportedKindsForListeners, + }, }, - { - Name: "bar-443-https", - Source: bar443HTTPSListener, - Valid: false, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - Conditions: staticConds.NewListenerProtocolConflict(conflict443PortMsg), - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), - SupportedKinds: supportedKindsForListeners, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), }, + Valid: true, }, - Valid: true, }, name: "port/protocol collisions", }, { gateway: createGateway( gatewayCfg{ + name: "gateway1", listeners: []v1.Listener{foo80Listener1, foo443HTTPSListener1}, addresses: []v1.GatewayAddress{{}}, }, ), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Valid: false, - Conditions: staticConds.NewGatewayUnsupportedValue("spec." + - "addresses: Forbidden: addresses are not supported", - ), + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: false, + Conditions: staticConds.NewGatewayUnsupportedValue("spec." + + "addresses: Forbidden: addresses are not supported", + ), + }, }, name: "gateway addresses are not supported", }, @@ -1042,58 +1151,78 @@ func TestBuildGateway(t *testing.T) { }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo80Listener1, invalidProtocolListener}}, + gatewayCfg{name: "gateway1", listeners: []v1.Listener{foo80Listener1, invalidProtocolListener}}, ), gatewayClass: invalidGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Valid: false, - Conditions: staticConds.NewGatewayInvalid("GatewayClass is invalid"), + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: false, + Conditions: staticConds.NewGatewayInvalid("GatewayClass is invalid"), + }, }, name: "invalid gatewayclass", }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo80Listener1, invalidProtocolListener}}, + gatewayCfg{name: "gateway1", listeners: []v1.Listener{foo80Listener1, invalidProtocolListener}}, ), gatewayClass: nil, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Valid: false, - Conditions: staticConds.NewGatewayInvalid("GatewayClass doesn't exist"), + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: false, + Conditions: staticConds.NewGatewayInvalid("GatewayClass doesn't exist"), + }, }, name: "nil gatewayclass", }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo443TLSListener, foo443HTTPListener}}, + gatewayCfg{name: "gateway1", listeners: []v1.Listener{foo443TLSListener, foo443HTTPListener}}, ), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Valid: true, - Listeners: []*Listener{ - { - Name: "foo-443-tls", - Source: foo443TLSListener, - Valid: false, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - Conditions: staticConds.NewListenerProtocolConflict(conflict443PortMsg), - SupportedKinds: []v1.RouteGroupKind{ - {Kind: kinds.TLSRoute, Group: helpers.GetPointer[v1.Group](v1.GroupName)}, - }, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), }, - { - Name: "foo-443-http", - Source: foo443HTTPListener, - Valid: false, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - Conditions: staticConds.NewListenerProtocolConflict(conflict443PortMsg), - SupportedKinds: supportedKindsForListeners, + Valid: true, + Listeners: []*Listener{ + { + Name: "foo-443-tls", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo443TLSListener, + Valid: false, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + Conditions: staticConds.NewListenerProtocolConflict(conflict443PortMsg), + SupportedKinds: []v1.RouteGroupKind{ + {Kind: kinds.TLSRoute, Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: "foo-443-http", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo443HTTPListener, + Valid: false, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + Conditions: staticConds.NewListenerProtocolConflict(conflict443PortMsg), + SupportedKinds: supportedKindsForListeners, + }, }, }, }, @@ -1101,35 +1230,43 @@ func TestBuildGateway(t *testing.T) { }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo443TLSListener, splat443HTTPSListener}}, + gatewayCfg{name: "gateway1", listeners: []v1.Listener{foo443TLSListener, splat443HTTPSListener}}, ), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Valid: true, - Listeners: []*Listener{ - { - Name: "foo-443-tls", - Source: foo443TLSListener, - Valid: false, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - Conditions: staticConds.NewListenerHostnameConflict(conflict443HostnameMsg), - SupportedKinds: []v1.RouteGroupKind{ - {Kind: kinds.TLSRoute, Group: helpers.GetPointer[v1.Group](v1.GroupName)}, - }, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), }, - { - Name: "splat-443-https", - Source: splat443HTTPSListener, - Valid: false, - Attachable: true, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - Conditions: staticConds.NewListenerHostnameConflict(conflict443HostnameMsg), - SupportedKinds: supportedKindsForListeners, + Valid: true, + Listeners: []*Listener{ + { + Name: "foo-443-tls", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo443TLSListener, + Valid: false, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + Conditions: staticConds.NewListenerHostnameConflict(conflict443HostnameMsg), + SupportedKinds: []v1.RouteGroupKind{ + {Kind: kinds.TLSRoute, Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: "splat-443-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: splat443HTTPSListener, + Valid: false, + Attachable: true, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + Conditions: staticConds.NewListenerHostnameConflict(conflict443HostnameMsg), + SupportedKinds: supportedKindsForListeners, + }, }, }, }, @@ -1137,145 +1274,201 @@ func TestBuildGateway(t *testing.T) { }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo443TLSListener, bar443HTTPSListener}}, + gatewayCfg{name: "gateway1", listeners: []v1.Listener{foo443TLSListener, bar443HTTPSListener}}, ), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Valid: true, - Listeners: []*Listener{ - { - Name: "foo-443-tls", - Source: foo443TLSListener, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: []v1.RouteGroupKind{ - {Kind: kinds.TLSRoute, Group: helpers.GetPointer[v1.Group](v1.GroupName)}, - }, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), }, - { - Name: "bar-443-https", - Source: bar443HTTPSListener, - Valid: true, - Attachable: true, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + Valid: true, + Listeners: []*Listener{ + { + Name: "foo-443-tls", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo443TLSListener, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + {Kind: kinds.TLSRoute, Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + }, + }, + { + Name: "bar-443-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: bar443HTTPSListener, + Valid: true, + Attachable: true, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretSameNs)), + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, }, }, }, name: "https listener and tls listener with non overlapping hostnames", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{foo80Listener1}, ref: invalidKindRef}), + gateway: createGateway( + gatewayCfg{ + name: "gateway1", + listeners: []v1.Listener{foo80Listener1}, + ref: invalidKindRef, + }, + ), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "foo-80-1", - Source: foo80Listener1, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo80Listener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: true, // invalid parametersRef does not invalidate Gateway. + Conditions: []conditions.Condition{ + staticConds.NewGatewayRefInvalid( + "spec.infrastructure.parametersRef.kind: Unsupported value: \"Invalid\": " + + "supported values: \"NginxProxy\"", + ), + staticConds.NewGatewayInvalidParameters( + "spec.infrastructure.parametersRef.kind: Unsupported value: \"Invalid\": " + + "supported values: \"NginxProxy\"", + ), }, - }, - Valid: true, // invalid parametersRef does not invalidate Gateway. - Conditions: []conditions.Condition{ - staticConds.NewGatewayRefInvalid( - "spec.infrastructure.parametersRef.kind: Unsupported value: \"Invalid\": " + - "supported values: \"NginxProxy\"", - ), - staticConds.NewGatewayInvalidParameters( - "spec.infrastructure.parametersRef.kind: Unsupported value: \"Invalid\": " + - "supported values: \"NginxProxy\"", - ), }, }, name: "invalid parameters ref kind", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{foo80Listener1}, ref: npDoesNotExistRef}), + gateway: createGateway( + gatewayCfg{ + name: "gateway1", + listeners: []v1.Listener{foo80Listener1}, + ref: npDoesNotExistRef, + }, + ), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "foo-80-1", - Source: foo80Listener1, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo80Listener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: true, // invalid parametersRef does not invalidate Gateway. + Conditions: []conditions.Condition{ + staticConds.NewGatewayRefNotFound(), + staticConds.NewGatewayInvalidParameters( + "spec.infrastructure.parametersRef.name: Not found: \"does-not-exist\"", + ), }, - }, - Valid: true, // invalid parametersRef does not invalidate Gateway. - Conditions: []conditions.Condition{ - staticConds.NewGatewayRefNotFound(), - staticConds.NewGatewayInvalidParameters( - "spec.infrastructure.parametersRef.name: Not found: \"does-not-exist\"", - ), }, }, name: "referenced NginxProxy doesn't exist", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{foo80Listener1}, ref: invalidGwNpRef}), + gateway: createGateway( + gatewayCfg{ + name: "gateway1", + listeners: []v1.Listener{foo80Listener1}, + ref: invalidGwNpRef, + }, + ), gatewayClass: validGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Listeners: []*Listener{ - { - Name: "foo-80-1", - Source: foo80Listener1, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), + Source: foo80Listener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, }, - }, - Valid: true, // invalid NginxProxy does not invalidate Gateway. - NginxProxy: &NginxProxy{ - Source: invalidGwNp, - ErrMsgs: field.ErrorList{ - field.Required(field.NewPath("somePath"), "someField"), // fake error + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: true, // invalid NginxProxy does not invalidate Gateway. + NginxProxy: &NginxProxy{ + Source: invalidGwNp, + ErrMsgs: field.ErrorList{ + field.Required(field.NewPath("somePath"), "someField"), // fake error + }, + Valid: false, + }, + Conditions: []conditions.Condition{ + staticConds.NewGatewayRefInvalid("somePath: Required value: someField"), + staticConds.NewGatewayInvalidParameters("somePath: Required value: someField"), }, - Valid: false, - }, - Conditions: []conditions.Condition{ - staticConds.NewGatewayRefInvalid("somePath: Required value: someField"), - staticConds.NewGatewayInvalidParameters("somePath: Required value: someField"), }, }, name: "invalid NginxProxy", }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo80Listener1, invalidProtocolListener}, ref: invalidGwNpRef}, + gatewayCfg{ + name: "gateway1", + listeners: []v1.Listener{foo80Listener1, invalidProtocolListener}, ref: invalidGwNpRef, + }, ), gatewayClass: invalidGC, - expected: &Gateway{ - Source: getLastCreatedGateway(), - Valid: false, - NginxProxy: &NginxProxy{ - Source: invalidGwNp, - ErrMsgs: field.ErrorList{ - field.Required(field.NewPath("somePath"), "someField"), // fake error + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), }, Valid: false, + NginxProxy: &NginxProxy{ + Source: invalidGwNp, + ErrMsgs: field.ErrorList{ + field.Required(field.NewPath("somePath"), "someField"), // fake error + }, + Valid: false, + }, + Conditions: append( + staticConds.NewGatewayInvalid("GatewayClass is invalid"), + staticConds.NewGatewayRefInvalid("somePath: Required value: someField"), + staticConds.NewGatewayInvalidParameters("somePath: Required value: someField"), + ), }, - Conditions: append( - staticConds.NewGatewayInvalid("GatewayClass is invalid"), - staticConds.NewGatewayRefInvalid("somePath: Required value: someField"), - staticConds.NewGatewayInvalidParameters("somePath: Required value: someField"), - ), }, name: "invalid gatewayclass and invalid NginxProxy", }, @@ -1301,7 +1494,7 @@ func TestBuildGateway(t *testing.T) { t.Run(test.name, func(t *testing.T) { g := NewWithT(t) resolver := newReferenceGrantResolver(test.refGrants) - result := buildGateway(test.gateway, secretResolver, test.gatewayClass, resolver, nginxProxies) + result := buildGateways(test.gateway, secretResolver, test.gatewayClass, resolver, nginxProxies) g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) }) } diff --git a/internal/mode/static/state/graph/graph.go b/internal/mode/static/state/graph/graph.go index 88d6b4abe8..61d39fb44c 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -8,7 +8,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" "sigs.k8s.io/gateway-api/apis/v1alpha2" "sigs.k8s.io/gateway-api/apis/v1alpha3" @@ -16,7 +15,6 @@ import ( ngfAPIv1alpha1 "github.com/nginx/nginx-gateway-fabric/apis/v1alpha1" ngfAPIv1alpha2 "github.com/nginx/nginx-gateway-fabric/apis/v1alpha2" - "github.com/nginx/nginx-gateway-fabric/internal/framework/controller" "github.com/nginx/nginx-gateway-fabric/internal/framework/controller/index" "github.com/nginx/nginx-gateway-fabric/internal/framework/kinds" ngftypes "github.com/nginx/nginx-gateway-fabric/internal/framework/types" @@ -47,16 +45,12 @@ type ClusterState struct { type Graph struct { // GatewayClass holds the GatewayClass resource. GatewayClass *GatewayClass - // Gateway holds the winning Gateway resource. - Gateway *Gateway + // Gateways holds the all Gateway resource. + Gateways map[types.NamespacedName]*Gateway // IgnoredGatewayClasses holds the ignored GatewayClass resources, which reference NGINX Gateway Fabric in the // controllerName, but are not configured via the NGINX Gateway Fabric CLI argument. It doesn't hold the GatewayClass // resources that do not belong to the NGINX Gateway Fabric. IgnoredGatewayClasses map[types.NamespacedName]*gatewayv1.GatewayClass - // IgnoredGateways holds the ignored Gateway resources, which belong to the NGINX Gateway Fabric (based on the - // GatewayClassName field of the resource) but ignored. It doesn't hold the Gateway resources that do not belong to - // the NGINX Gateway Fabric. - IgnoredGateways map[types.NamespacedName]*gatewayv1.Gateway // Routes hold Route resources. Routes map[RouteKey]*L7Route // L4Routes hold L4Route resources. @@ -72,23 +66,16 @@ type Graph struct { ReferencedServices map[types.NamespacedName]*ReferencedService // ReferencedCaCertConfigMaps includes ConfigMaps that have been referenced by any BackendTLSPolicies. ReferencedCaCertConfigMaps map[types.NamespacedName]*CaCertConfigMap - // ReferencedNginxProxies includes NginxProxies that have been referenced by a GatewayClass or the winning Gateway. + // ReferencedNginxProxies includes NginxProxies that have been referenced by a GatewayClass or a Gateway. ReferencedNginxProxies map[types.NamespacedName]*NginxProxy // BackendTLSPolicies holds BackendTLSPolicy resources. BackendTLSPolicies map[types.NamespacedName]*BackendTLSPolicy // NGFPolicies holds all NGF Policies. NGFPolicies map[PolicyKey]*Policy - // GlobalSettings contains global settings from the current state of the graph that may be - // needed for policy validation or generation if certain policies rely on those global settings. - GlobalSettings *policies.GlobalSettings // SnippetsFilters holds all the SnippetsFilters. SnippetsFilters map[types.NamespacedName]*SnippetsFilter // PlusSecrets holds the secrets related to NGINX Plus licensing. PlusSecrets map[types.NamespacedName][]PlusSecretFile - // LatestReloadResult is the latest result of applying config to nginx for this Gateway. - LatestReloadResult NginxReloadResult - // DeploymentName is the name of the nginx Deployment for this Gateway. - DeploymentName types.NamespacedName } // NginxReloadResult describes the result of an NGINX reload. @@ -125,7 +112,7 @@ func (g *Graph) IsReferenced(resourceType ngftypes.ObjectType, nsname types.Name // `exists` does not cover the case highlighted above by `existed` and vice versa so both are needed. _, existed := g.ReferencedNamespaces[nsname] - exists := isNamespaceReferenced(obj, g.Gateway) + exists := isNamespaceReferenced(obj, g.Gateways) return existed || exists // Service reference exists if at least one HTTPRoute references it. case *v1.Service: @@ -138,7 +125,7 @@ func (g *Graph) IsReferenced(resourceType ngftypes.ObjectType, nsname types.Name // Service Namespace should be the same Namespace as the EndpointSlice _, exists := g.ReferencedServices[types.NamespacedName{Namespace: nsname.Namespace, Name: svcName}] return exists - // NginxProxy reference exists if the GatewayClass or winning Gateway references it. + // NginxProxy reference exists if the GatewayClass or Gateway references it. case *ngfAPIv1alpha2.NginxProxy: _, exists := g.ReferencedNginxProxies[nsname] return exists @@ -190,11 +177,11 @@ func (g *Graph) gatewayAPIResourceExist(ref v1alpha2.LocalPolicyTargetReference, switch kind := ref.Kind; kind { case kinds.Gateway: - if g.Gateway == nil { + if len(g.Gateways) == 0 { return false } - return gatewayExists(refNsName, g.Gateway.Source, g.IgnoredGateways) + return gatewayExists(refNsName, g.Gateways) case kinds.HTTPRoute, kinds.GRPCRoute: _, exists := g.Routes[routeKeyForKind(kind, refNsName)] return exists @@ -223,7 +210,7 @@ func BuildGraph( state.NginxProxies, validators.GenericValidator, processedGwClasses.Winner, - processedGws.Winner, + processedGws, ) gc := buildGatewayClass( @@ -237,8 +224,8 @@ func BuildGraph( refGrantResolver := newReferenceGrantResolver(state.ReferenceGrants) - gw := buildGateway( - processedGws.Winner, + gws := buildGateways( + processedGws, secretResolver, gc, refGrantResolver, @@ -250,80 +237,57 @@ func BuildGraph( configMapResolver, secretResolver, controllerName, - gw, + gws, ) processedSnippetsFilters := processSnippetsFilters(state.SnippetsFilters) - var effectiveNginxProxy *EffectiveNginxProxy - if gw != nil { - effectiveNginxProxy = gw.EffectiveNginxProxy - } routes := buildRoutesForGateways( validators.HTTPFieldsValidator, state.HTTPRoutes, state.GRPCRoutes, - processedGws.GetAllNsNames(), - effectiveNginxProxy, + gws, processedSnippetsFilters, ) l4routes := buildL4RoutesForGateways( state.TLSRoutes, - processedGws.GetAllNsNames(), state.Services, - effectiveNginxProxy, + gws, refGrantResolver, ) - bindRoutesToListeners(routes, l4routes, gw, state.Namespaces) addBackendRefsToRouteRules( routes, refGrantResolver, state.Services, processedBackendTLSPolicies, - effectiveNginxProxy, ) + bindRoutesToListeners(routes, l4routes, gws, state.Namespaces) - referencedNamespaces := buildReferencedNamespaces(state.Namespaces, gw) + referencedNamespaces := buildReferencedNamespaces(state.Namespaces, gws) - referencedServices := buildReferencedServices(routes, l4routes, gw) + referencedServices := buildReferencedServices(routes, l4routes, gws) + + addGatewaysForBackendTLSPolicies(processedBackendTLSPolicies, referencedServices) - var globalSettings *policies.GlobalSettings - if gw != nil && gw.EffectiveNginxProxy != nil { - globalSettings = &policies.GlobalSettings{ - NginxProxyValid: true, // for effective nginx proxy to be set, the config must be valid - TelemetryEnabled: telemetryEnabledForNginxProxy(gw.EffectiveNginxProxy), - } - } // policies must be processed last because they rely on the state of the other resources in the graph processedPolicies := processPolicies( state.NGFPolicies, validators.PolicyValidator, - processedGws, routes, referencedServices, - globalSettings, + gws, ) setPlusSecretContent(state.Secrets, plusSecrets) - var deploymentName types.NamespacedName - if gw != nil { - deploymentName = types.NamespacedName{ - Namespace: gw.Source.Namespace, - Name: controller.CreateNginxResourceName(gw.Source.Name, gcName), - } - } - g := &Graph{ GatewayClass: gc, - Gateway: gw, - DeploymentName: deploymentName, + Gateways: gws, Routes: routes, L4Routes: l4routes, IgnoredGatewayClasses: processedGwClasses.Ignored, - IgnoredGateways: processedGws.Ignored, ReferencedSecrets: secretResolver.getResolvedSecrets(), ReferencedNamespaces: referencedNamespaces, ReferencedServices: referencedServices, @@ -331,31 +295,21 @@ func BuildGraph( ReferencedNginxProxies: processedNginxProxies, BackendTLSPolicies: processedBackendTLSPolicies, NGFPolicies: processedPolicies, - GlobalSettings: globalSettings, SnippetsFilters: processedSnippetsFilters, PlusSecrets: plusSecrets, } - g.attachPolicies(controllerName) + g.attachPolicies(validators.PolicyValidator, controllerName) return g } -func gatewayExists( - gwNsName types.NamespacedName, - winner *gatewayv1.Gateway, - ignored map[types.NamespacedName]*gatewayv1.Gateway, -) bool { - if winner == nil { +func gatewayExists(gwNsName types.NamespacedName, gateways map[types.NamespacedName]*Gateway) bool { + if len(gateways) == 0 { return false } - if client.ObjectKeyFromObject(winner) == gwNsName { - return true - } - - _, exists := ignored[gwNsName] - + _, exists := gateways[gwNsName] return exists } diff --git a/internal/mode/static/state/graph/graph_test.go b/internal/mode/static/state/graph/graph_test.go index 3b2fcb5a6f..0fdeecaeb3 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -85,7 +85,7 @@ func TestBuildGraph(t *testing.T) { }, Valid: true, IsReferenced: true, - Gateway: types.NamespacedName{Namespace: testNs, Name: "gateway-1"}, + Gateways: []types.NamespacedName{{Namespace: testNs, Name: "gateway-1"}}, Conditions: btpAcceptedConds, CaCertRef: types.NamespacedName{Namespace: "service", Name: "configmap"}, } @@ -165,11 +165,12 @@ func TestBuildGraph(t *testing.T) { createValidRuleWithBackendRefs := func(matches []gatewayv1.HTTPRouteMatch) RouteRule { refs := []BackendRef{ { - SvcNsName: types.NamespacedName{Namespace: "service", Name: "foo"}, - ServicePort: v1.ServicePort{Port: 80}, - Valid: true, - Weight: 1, - BackendTLSPolicy: &btp, + SvcNsName: types.NamespacedName{Namespace: "service", Name: "foo"}, + ServicePort: v1.ServicePort{Port: 80}, + Valid: true, + Weight: 1, + BackendTLSPolicy: &btp, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, } rbrs := []RouteBackendRef{ @@ -370,73 +371,75 @@ func TestBuildGraph(t *testing.T) { }, } - createGateway := func(name, nginxProxyName string) *gatewayv1.Gateway { - return &gatewayv1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: testNs, - Name: name, - }, - Spec: gatewayv1.GatewaySpec{ - GatewayClassName: gcName, - Infrastructure: &gatewayv1.GatewayInfrastructure{ - ParametersRef: &gatewayv1.LocalParametersReference{ - Group: ngfAPIv1alpha2.GroupName, - Kind: kinds.NginxProxy, - Name: nginxProxyName, - }, + createGateway := func(name, nginxProxyName string) *Gateway { + return &Gateway{ + Source: &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNs, + Name: name, }, - Listeners: []gatewayv1.Listener{ - { - Name: "listener-80-1", - Hostname: nil, - Port: 80, - Protocol: gatewayv1.HTTPProtocolType, - AllowedRoutes: &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "allowed", + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: gcName, + Infrastructure: &gatewayv1.GatewayInfrastructure{ + ParametersRef: &gatewayv1.LocalParametersReference{ + Group: ngfAPIv1alpha2.GroupName, + Kind: kinds.NginxProxy, + Name: nginxProxyName, + }, + }, + Listeners: []gatewayv1.Listener{ + { + Name: "listener-80-1", + Hostname: nil, + Port: 80, + Protocol: gatewayv1.HTTPProtocolType, + AllowedRoutes: &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "allowed", + }, }, }, }, }, - }, - { - Name: "listener-443-1", - Hostname: (*gatewayv1.Hostname)(helpers.GetPointer("*.example.com")), - Port: 443, - TLS: &gatewayv1.GatewayTLSConfig{ - Mode: helpers.GetPointer(gatewayv1.TLSModeTerminate), - CertificateRefs: []gatewayv1.SecretObjectReference{ - { - Kind: helpers.GetPointer[gatewayv1.Kind]("Secret"), - Name: gatewayv1.ObjectName(secret.Name), - Namespace: helpers.GetPointer(gatewayv1.Namespace(secret.Namespace)), + { + Name: "listener-443-1", + Hostname: (*gatewayv1.Hostname)(helpers.GetPointer("*.example.com")), + Port: 443, + TLS: &gatewayv1.GatewayTLSConfig{ + Mode: helpers.GetPointer(gatewayv1.TLSModeTerminate), + CertificateRefs: []gatewayv1.SecretObjectReference{ + { + Kind: helpers.GetPointer[gatewayv1.Kind]("Secret"), + Name: gatewayv1.ObjectName(secret.Name), + Namespace: helpers.GetPointer(gatewayv1.Namespace(secret.Namespace)), + }, }, }, + Protocol: gatewayv1.HTTPSProtocolType, }, - Protocol: gatewayv1.HTTPSProtocolType, - }, - { - Name: "listener-443-2", - Hostname: (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")), - Port: 443, - Protocol: gatewayv1.TLSProtocolType, - TLS: &gatewayv1.GatewayTLSConfig{Mode: helpers.GetPointer(gatewayv1.TLSModePassthrough)}, - AllowedRoutes: &gatewayv1.AllowedRoutes{ - Kinds: []gatewayv1.RouteGroupKind{ - {Kind: kinds.TLSRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + { + Name: "listener-443-2", + Hostname: (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")), + Port: 443, + Protocol: gatewayv1.TLSProtocolType, + TLS: &gatewayv1.GatewayTLSConfig{Mode: helpers.GetPointer(gatewayv1.TLSModePassthrough)}, + AllowedRoutes: &gatewayv1.AllowedRoutes{ + Kinds: []gatewayv1.RouteGroupKind{ + {Kind: kinds.TLSRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + }, }, }, - }, - { - Name: "listener-8443", - Hostname: (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")), - Port: 8443, - Protocol: gatewayv1.TLSProtocolType, - TLS: &gatewayv1.GatewayTLSConfig{Mode: helpers.GetPointer(gatewayv1.TLSModePassthrough)}, + { + Name: "listener-8443", + Hostname: (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")), + Port: 8443, + Protocol: gatewayv1.TLSProtocolType, + TLS: &gatewayv1.GatewayTLSConfig{Mode: helpers.GetPointer(gatewayv1.TLSModePassthrough)}, + }, }, }, }, @@ -447,8 +450,6 @@ func TestBuildGraph(t *testing.T) { gw2 := createGateway("gateway-2", "np-2") // np1 is referenced by gw1 and sets the nginx error log to error. - // Since gw1 is the winning gateway, we expect this nginx proxy to be configured and merged with the gateway class - // nginx proxy configuration. np1 := &ngfAPIv1alpha2.NginxProxy{ ObjectMeta: metav1.ObjectMeta{ Name: "np-1", @@ -462,7 +463,6 @@ func TestBuildGraph(t *testing.T) { } // np2 is referenced by gw2 and sets the IPFamily to IPv6. - // Since gw2 is not the winning gateway, we do not expect this nginx proxy to be configured. np2 := &ngfAPIv1alpha2.NginxProxy{ ObjectMeta: metav1.ObjectMeta{ Name: "np-2", @@ -584,6 +584,25 @@ func TestBuildGraph(t *testing.T) { }, } + // np1Effective is the combined NginxProxy of npGlobal and np1 + np1Effective := &EffectiveNginxProxy{ + Telemetry: &ngfAPIv1alpha2.Telemetry{ + Exporter: &ngfAPIv1alpha2.TelemetryExporter{ + Endpoint: helpers.GetPointer("1.2.3.4:123"), + Interval: helpers.GetPointer(ngfAPIv1alpha1.Duration("5s")), + BatchSize: helpers.GetPointer(int32(512)), + BatchCount: helpers.GetPointer(int32(4)), + }, + ServiceName: helpers.GetPointer("my-svc"), + SpanAttributes: []ngfAPIv1alpha1.SpanAttribute{ + {Key: "key", Value: "value"}, + }, + }, + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError), + }, + } + // NGF Policies // // We have to use real policies here instead of a mocks because the Diff function we use in the test fails when @@ -620,7 +639,8 @@ func TestBuildGraph(t *testing.T) { Nsname: types.NamespacedName{Namespace: testNs, Name: "hr-1"}, }, }, - Valid: true, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: true, } gwPolicyKey := PolicyKey{GVK: polGVK, NsName: types.NamespacedName{Namespace: testNs, Name: "gwPolicy"}} @@ -653,7 +673,8 @@ func TestBuildGraph(t *testing.T) { Nsname: types.NamespacedName{Namespace: testNs, Name: "gateway-1"}, }, }, - Valid: true, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: true, } createStateWithGatewayClass := func(gc *gatewayv1.GatewayClass) ClusterState { @@ -662,8 +683,8 @@ func TestBuildGraph(t *testing.T) { client.ObjectKeyFromObject(gc): gc, }, Gateways: map[types.NamespacedName]*gatewayv1.Gateway{ - client.ObjectKeyFromObject(gw1): gw1, - client.ObjectKeyFromObject(gw2): gw2, + client.ObjectKeyFromObject(gw1.Source): gw1.Source, + client.ObjectKeyFromObject(gw2.Source): gw2.Source, }, HTTPRoutes: map[types.NamespacedName]*gatewayv1.HTTPRoute{ client.ObjectKeyFromObject(hr1): hr1, @@ -722,13 +743,21 @@ func TestBuildGraph(t *testing.T) { Source: hr1, ParentRefs: []ParentRef{ { - Idx: 0, - Gateway: client.ObjectKeyFromObject(gw1), + Idx: 0, + Gateway: &ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw1.Source), + EffectiveNginxProxy: np1Effective, + }, SectionName: hr1.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ - Attached: true, - AcceptedHostnames: map[string][]string{"listener-80-1": {"foo.example.com"}}, - ListenerPort: 80, + Attached: true, + AcceptedHostnames: map[string][]string{ + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw1.Source), + "listener-80-1", + ): {"foo.example.com"}, + }, + ListenerPort: 80, }, }, }, @@ -745,13 +774,22 @@ func TestBuildGraph(t *testing.T) { Source: tr, ParentRefs: []ParentRef{ { - Idx: 0, - Gateway: client.ObjectKeyFromObject(gw1), + Idx: 0, + Gateway: &ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw1.Source), + EffectiveNginxProxy: np1Effective, + }, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-443-2": {"fizz.example.org"}, - "listener-8443": {"fizz.example.org"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw1.Source), + "listener-443-2", + ): {"fizz.example.org"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw1.Source), + "listener-8443", + ): {"fizz.example.org"}, }, }, }, @@ -766,7 +804,8 @@ func TestBuildGraph(t *testing.T) { ServicePort: v1.ServicePort{ Port: 80, }, - Valid: true, + Valid: true, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, } @@ -777,12 +816,15 @@ func TestBuildGraph(t *testing.T) { Source: tr2, ParentRefs: []ParentRef{ { - Idx: 0, - Gateway: client.ObjectKeyFromObject(gw1), + Idx: 0, + Gateway: &ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw1.Source), + EffectiveNginxProxy: np1Effective, + }, Attachment: &ParentRefAttachmentStatus{ Attached: false, AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteHostnameConflict(), + FailedConditions: []conditions.Condition{staticConds.NewRouteHostnameConflict()}, }, }, }, @@ -796,7 +838,8 @@ func TestBuildGraph(t *testing.T) { ServicePort: v1.ServicePort{ Port: 80, }, - Valid: true, + Valid: true, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, } @@ -808,13 +851,21 @@ func TestBuildGraph(t *testing.T) { Source: gr, ParentRefs: []ParentRef{ { - Idx: 0, - Gateway: client.ObjectKeyFromObject(gw1), + Idx: 0, + Gateway: &ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw1.Source), + EffectiveNginxProxy: np1Effective, + }, SectionName: gr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ - Attached: true, - AcceptedHostnames: map[string][]string{"listener-80-1": {"bar.example.com"}}, - ListenerPort: 80, + Attached: true, + AcceptedHostnames: map[string][]string{ + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw1.Source), + "listener-80-1", + ): {"bar.example.com"}, + }, + ListenerPort: 80, }, }, }, @@ -833,13 +884,21 @@ func TestBuildGraph(t *testing.T) { Source: hr3, ParentRefs: []ParentRef{ { - Idx: 0, - Gateway: client.ObjectKeyFromObject(gw1), + Idx: 0, + Gateway: &ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw1.Source), + EffectiveNginxProxy: np1Effective, + }, SectionName: hr3.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ - Attached: true, - AcceptedHostnames: map[string][]string{"listener-443-1": {"foo.example.com"}}, - ListenerPort: 443, + Attached: true, + AcceptedHostnames: map[string][]string{ + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw1.Source), + "listener-443-1", + ): {"foo.example.com"}, + }, + ListenerPort: 443, }, }, }, @@ -865,82 +924,165 @@ func TestBuildGraph(t *testing.T) { Valid: true, }, }, - Gateway: &Gateway{ - Source: gw1, - Listeners: []*Listener{ - { - Name: "listener-80-1", - Source: gw1.Spec.Listeners[0], - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{ - CreateRouteKey(hr1): routeHR1, - CreateRouteKey(gr): routeGR, + Gateways: map[types.NamespacedName]*Gateway{ + {Namespace: testNs, Name: "gateway-1"}: { + Source: gw1.Source, + Listeners: []*Listener{ + { + Name: "listener-80-1", + GatewayName: types.NamespacedName{Namespace: testNs, Name: "gateway-1"}, + Source: gw1.Source.Spec.Listeners[0], + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{ + CreateRouteKey(hr1): routeHR1, + CreateRouteKey(gr): routeGR, + }, + SupportedKinds: supportedKindsForListeners, + L4Routes: map[L4RouteKey]*L4Route{}, + AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"app": "allowed"}), + }, + { + Name: "listener-443-1", + GatewayName: types.NamespacedName{Namespace: testNs, Name: "gateway-1"}, + Source: gw1.Source.Spec.Listeners[1], + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{CreateRouteKey(hr3): routeHR3}, + L4Routes: map[L4RouteKey]*L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secret)), + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "listener-443-2", + GatewayName: types.NamespacedName{Namespace: testNs, Name: "gateway-1"}, + Source: gw1.Source.Spec.Listeners[2], + Valid: true, + Attachable: true, + L4Routes: map[L4RouteKey]*L4Route{CreateRouteKeyL4(tr): routeTR}, + Routes: map[RouteKey]*L7Route{}, + SupportedKinds: []gatewayv1.RouteGroupKind{ + {Kind: kinds.TLSRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + }, + }, + { + Name: "listener-8443", + GatewayName: types.NamespacedName{Namespace: testNs, Name: "gateway-1"}, + Source: gw1.Source.Spec.Listeners[3], + Valid: true, + Attachable: true, + L4Routes: map[L4RouteKey]*L4Route{CreateRouteKeyL4(tr): routeTR}, + Routes: map[RouteKey]*L7Route{}, + SupportedKinds: []gatewayv1.RouteGroupKind{ + {Kind: kinds.TLSRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + }, }, - SupportedKinds: supportedKindsForListeners, - L4Routes: map[L4RouteKey]*L4Route{}, - AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"app": "allowed"}), }, - { - Name: "listener-443-1", - Source: gw1.Spec.Listeners[1], - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{CreateRouteKey(hr3): routeHR3}, - L4Routes: map[L4RouteKey]*L4Route{}, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secret)), - SupportedKinds: supportedKindsForListeners, + Valid: true, + Policies: []*Policy{processedGwPolicy}, + NginxProxy: &NginxProxy{ + Source: np1, + Valid: true, }, - { - Name: "listener-443-2", - Source: gw1.Spec.Listeners[2], - Valid: true, - Attachable: true, - L4Routes: map[L4RouteKey]*L4Route{CreateRouteKeyL4(tr): routeTR}, - Routes: map[RouteKey]*L7Route{}, - SupportedKinds: []gatewayv1.RouteGroupKind{ - {Kind: kinds.TLSRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + EffectiveNginxProxy: &EffectiveNginxProxy{ + Telemetry: &ngfAPIv1alpha2.Telemetry{ + Exporter: &ngfAPIv1alpha2.TelemetryExporter{ + Endpoint: helpers.GetPointer("1.2.3.4:123"), + Interval: helpers.GetPointer(ngfAPIv1alpha1.Duration("5s")), + BatchSize: helpers.GetPointer(int32(512)), + BatchCount: helpers.GetPointer(int32(4)), + }, + ServiceName: helpers.GetPointer("my-svc"), + SpanAttributes: []ngfAPIv1alpha1.SpanAttribute{ + {Key: "key", Value: "value"}, + }, }, - }, - { - Name: "listener-8443", - Source: gw1.Spec.Listeners[3], - Valid: true, - Attachable: true, - L4Routes: map[L4RouteKey]*L4Route{CreateRouteKeyL4(tr): routeTR}, - Routes: map[RouteKey]*L7Route{}, - SupportedKinds: []gatewayv1.RouteGroupKind{ - {Kind: kinds.TLSRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError), }, }, + Conditions: []conditions.Condition{staticConds.NewGatewayResolvedRefs()}, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway-1-my-class", + }, }, - Valid: true, - Policies: []*Policy{processedGwPolicy}, - NginxProxy: &NginxProxy{ - Source: np1, - Valid: true, - }, - EffectiveNginxProxy: &EffectiveNginxProxy{ - Telemetry: &ngfAPIv1alpha2.Telemetry{ - Exporter: &ngfAPIv1alpha2.TelemetryExporter{ - Endpoint: helpers.GetPointer("1.2.3.4:123"), - Interval: helpers.GetPointer(ngfAPIv1alpha1.Duration("5s")), - BatchSize: helpers.GetPointer(int32(512)), - BatchCount: helpers.GetPointer(int32(4)), + {Namespace: testNs, Name: "gateway-2"}: { + Source: gw2.Source, + Listeners: []*Listener{ + { + Name: "listener-80-1", + GatewayName: types.NamespacedName{Namespace: testNs, Name: "gateway-2"}, + Source: gw2.Source.Spec.Listeners[0], + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + SupportedKinds: supportedKindsForListeners, + L4Routes: map[L4RouteKey]*L4Route{}, + AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"app": "allowed"}), + }, + { + Name: "listener-443-1", + GatewayName: types.NamespacedName{Namespace: testNs, Name: "gateway-2"}, + Source: gw2.Source.Spec.Listeners[1], + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secret)), + SupportedKinds: supportedKindsForListeners, + }, + { + Name: "listener-443-2", + GatewayName: types.NamespacedName{Namespace: testNs, Name: "gateway-2"}, + Source: gw2.Source.Spec.Listeners[2], + Valid: true, + Attachable: true, + L4Routes: map[L4RouteKey]*L4Route{}, + Routes: map[RouteKey]*L7Route{}, + SupportedKinds: []gatewayv1.RouteGroupKind{ + {Kind: kinds.TLSRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + }, + }, + { + Name: "listener-8443", + GatewayName: types.NamespacedName{Namespace: testNs, Name: "gateway-2"}, + Source: gw2.Source.Spec.Listeners[3], + Valid: true, + Attachable: true, + L4Routes: map[L4RouteKey]*L4Route{}, + Routes: map[RouteKey]*L7Route{}, + SupportedKinds: []gatewayv1.RouteGroupKind{ + {Kind: kinds.TLSRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + }, }, - ServiceName: helpers.GetPointer("my-svc"), - SpanAttributes: []ngfAPIv1alpha1.SpanAttribute{ - {Key: "key", Value: "value"}, + }, + Valid: true, + NginxProxy: &NginxProxy{ + Source: np2, + Valid: true, + }, + EffectiveNginxProxy: &EffectiveNginxProxy{ + Telemetry: &ngfAPIv1alpha2.Telemetry{ + Exporter: &ngfAPIv1alpha2.TelemetryExporter{ + Endpoint: helpers.GetPointer("1.2.3.4:123"), + Interval: helpers.GetPointer(ngfAPIv1alpha1.Duration("5s")), + BatchSize: helpers.GetPointer(int32(512)), + BatchCount: helpers.GetPointer(int32(4)), + }, + ServiceName: helpers.GetPointer("my-svc"), + SpanAttributes: []ngfAPIv1alpha1.SpanAttribute{ + {Key: "key", Value: "value"}, + }, }, + IPFamily: helpers.GetPointer(ngfAPIv1alpha2.IPv6), }, - Logging: &ngfAPIv1alpha2.NginxLogging{ - ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError), + Conditions: []conditions.Condition{staticConds.NewGatewayResolvedRefs()}, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway-2-my-class", }, }, - Conditions: []conditions.Condition{staticConds.NewGatewayResolvedRefs()}, - }, - IgnoredGateways: map[types.NamespacedName]*gatewayv1.Gateway{ - {Namespace: testNs, Name: "gateway-2"}: gw2, }, Routes: map[RouteKey]*L7Route{ CreateRouteKey(hr1): routeHR1, @@ -964,8 +1106,12 @@ func TestBuildGraph(t *testing.T) { client.ObjectKeyFromObject(ns): ns, }, ReferencedServices: map[types.NamespacedName]*ReferencedService{ - client.ObjectKeyFromObject(svc): {}, - client.ObjectKeyFromObject(svc1): {}, + client.ObjectKeyFromObject(svc): { + GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: testNs, Name: "gateway-1"}: {}}, + }, + client.ObjectKeyFromObject(svc1): { + GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: testNs, Name: "gateway-1"}: {}}, + }, }, ReferencedCaCertConfigMaps: map[types.NamespacedName]*CaCertConfigMap{ client.ObjectKeyFromObject(cm): { @@ -987,15 +1133,15 @@ func TestBuildGraph(t *testing.T) { Source: np1, Valid: true, }, + client.ObjectKeyFromObject(np2): { + Source: np2, + Valid: true, + }, }, NGFPolicies: map[PolicyKey]*Policy{ hrPolicyKey: processedRoutePolicy, gwPolicyKey: processedGwPolicy, }, - GlobalSettings: &policies.GlobalSettings{ - NginxProxyValid: true, - TelemetryEnabled: true, - }, SnippetsFilters: map[types.NamespacedName]*SnippetsFilter{ client.ObjectKeyFromObject(unreferencedSnippetsFilter): processedUnrefSnippetsFilter, client.ObjectKeyFromObject(referencedSnippetsFilter): processedRefSnippetsFilter, @@ -1009,10 +1155,6 @@ func TestBuildGraph(t *testing.T) { }, }, }, - DeploymentName: types.NamespacedName{ - Namespace: "test", - Name: "gateway-1-my-class", - }, } } @@ -1165,15 +1307,17 @@ func TestIsReferenced(t *testing.T) { endpointSliceNotInGraph := createEndpointSlice("endpointSliceNotInGraph", "serviceNotInGraph") emptyEndpointSlice := &discoveryV1.EndpointSlice{} - gw := &Gateway{ - Listeners: []*Listener{ - { - Name: "listener-1", - Valid: true, - AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"apples": "oranges"}), + gw := map[types.NamespacedName]*Gateway{ + {}: { + Listeners: []*Listener{ + { + Name: "listener-1", + Valid: true, + AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"apples": "oranges"}), + }, }, + Valid: true, }, - Valid: true, } nsNotInGraphButInGateway := &v1.Namespace{ @@ -1217,7 +1361,7 @@ func TestIsReferenced(t *testing.T) { } graph := &Graph{ - Gateway: gw, + Gateways: gw, ReferencedSecrets: map[types.NamespacedName]*Secret{ client.ObjectKeyFromObject(baseSecret): { Source: baseSecret, @@ -1413,17 +1557,16 @@ func TestIsNGFPolicyRelevant(t *testing.T) { getGraph := func() *Graph { return &Graph{ - Gateway: &Gateway{ - Source: &gatewayv1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: "test", + Gateways: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gw"}: { + Source: &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Namespace: "test", + }, }, }, }, - IgnoredGateways: map[types.NamespacedName]*gatewayv1.Gateway{ - {Namespace: "test", Name: "ignored"}: {}, - }, Routes: map[RouteKey]*L7Route{ hrKey: {}, grKey: {}, @@ -1482,13 +1625,6 @@ func TestIsNGFPolicyRelevant(t *testing.T) { nsname: types.NamespacedName{Namespace: "test", Name: "ref-gw"}, expRelevant: true, }, - { - name: "relevant; policy references an ignored gateway", - graph: getGraph(), - policy: getPolicy(createTestRef(kinds.Gateway, gatewayv1.GroupName, "ignored")), - nsname: types.NamespacedName{Namespace: "test", Name: "ref-ignored"}, - expRelevant: true, - }, { name: "relevant; policy references an httproute in the graph", graph: getGraph(), @@ -1527,7 +1663,7 @@ func TestIsNGFPolicyRelevant(t *testing.T) { { name: "irrelevant; policy references a Gateway, but the graph's Gateway is nil", graph: getModifiedGraph(func(g *Graph) *Graph { - g.Gateway = nil + g.Gateways = nil return g }), policy: getPolicy(createTestRef(kinds.Gateway, gatewayv1.GroupName, "diff")), @@ -1537,7 +1673,8 @@ func TestIsNGFPolicyRelevant(t *testing.T) { { name: "irrelevant; policy references a Gateway, but the graph's Gateway.Source is nil", graph: getModifiedGraph(func(g *Graph) *Graph { - g.Gateway.Source = nil + gw := g.Gateways[types.NamespacedName{Namespace: "test", Name: "gw"}] + gw.Source = nil return g }), policy: getPolicy(createTestRef(kinds.Gateway, gatewayv1.GroupName, "diff")), @@ -1610,3 +1747,38 @@ func TestIsNGFPolicyRelevantPanics(t *testing.T) { g.Expect(isRelevant).To(Panic()) } + +func TestGatewayExists(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + tests := []struct { + gateways map[types.NamespacedName]*Gateway + gwNsName types.NamespacedName + name string + expectedResult bool + }{ + { + name: "gateway exists", + gwNsName: types.NamespacedName{Namespace: "test", Name: "gw"}, + gateways: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gw"}: {}, + {Namespace: "test", Name: "gw2"}: {}, + }, + expectedResult: true, + }, + { + name: "gateway does not exist", + gwNsName: types.NamespacedName{Namespace: "test", Name: "gw"}, + gateways: nil, + expectedResult: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g.Expect(gatewayExists(test.gwNsName, test.gateways)).To(Equal(test.expectedResult)) + }) + } +} diff --git a/internal/mode/static/state/graph/grpcroute.go b/internal/mode/static/state/graph/grpcroute.go index 7da7bf20d6..7b4ac8a03d 100644 --- a/internal/mode/static/state/graph/grpcroute.go +++ b/internal/mode/static/state/graph/grpcroute.go @@ -14,8 +14,7 @@ import ( func buildGRPCRoute( validator validation.HTTPFieldsValidator, ghr *v1.GRPCRoute, - gatewayNsNames []types.NamespacedName, - http2disabled bool, + gws map[types.NamespacedName]*Gateway, snippetsFilters map[types.NamespacedName]*SnippetsFilter, ) *L7Route { r := &L7Route{ @@ -23,7 +22,7 @@ func buildGRPCRoute( RouteType: RouteTypeGRPC, } - sectionNameRefs, err := buildSectionNameRefs(ghr.Spec.ParentRefs, ghr.Namespace, gatewayNsNames) + sectionNameRefs, err := buildSectionNameRefs(ghr.Spec.ParentRefs, ghr.Namespace, gws) if err != nil { r.Valid = false @@ -35,14 +34,6 @@ func buildGRPCRoute( } r.ParentRefs = sectionNameRefs - if http2disabled { - r.Valid = false - msg := "HTTP2 is disabled - cannot configure GRPCRoutes" - r.Conditions = append(r.Conditions, staticConds.NewRouteUnsupportedConfiguration(msg)) - - return r - } - if err := validateHostnames( ghr.Spec.Hostnames, field.NewPath("spec").Child("hostnames"), diff --git a/internal/mode/static/state/graph/grpcroute_test.go b/internal/mode/static/state/graph/grpcroute_test.go index a5b0c5a605..502adebc30 100644 --- a/internal/mode/static/state/graph/grpcroute_test.go +++ b/internal/mode/static/state/graph/grpcroute_test.go @@ -83,6 +83,21 @@ func TestBuildGRPCRoutes(t *testing.T) { t.Parallel() gwNsName := types.NamespacedName{Namespace: "test", Name: "gateway"} + gateways := map[types.NamespacedName]*Gateway{ + gwNsName: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway", + }, + }, + Valid: true, + EffectiveNginxProxy: &EffectiveNginxProxy{ + DisableHTTP2: helpers.GetPointer(false), + }, + }, + } + snippetsFilterRef := v1.GRPCRouteFilter{ Type: v1.GRPCRouteFilterExtensionRef, ExtensionRef: &v1.LocalObjectReference{ @@ -126,12 +141,12 @@ func TestBuildGRPCRoutes(t *testing.T) { } tests := []struct { - expected map[RouteKey]*L7Route - name string - gwNsNames []types.NamespacedName + expected map[RouteKey]*L7Route + gateways map[types.NamespacedName]*Gateway + name string }{ { - gwNsNames: []types.NamespacedName{gwNsName}, + gateways: gateways, expected: map[RouteKey]*L7Route{ CreateRouteKey(gr): { RouteType: RouteTypeGRPC, @@ -139,7 +154,7 @@ func TestBuildGRPCRoutes(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gwNsName, + Gateway: CreateParentRefGateway(gateways[gwNsName]), SectionName: gr.Spec.ParentRefs[0].SectionName, }, }, @@ -186,18 +201,14 @@ func TestBuildGRPCRoutes(t *testing.T) { name: "normal case", }, { - gwNsNames: []types.NamespacedName{}, - expected: nil, - name: "no gateways", + gateways: nil, + expected: nil, + name: "no gateways", }, } validator := &validationfakes.FakeHTTPFieldsValidator{} - npCfg := &EffectiveNginxProxy{ - DisableHTTP2: helpers.GetPointer(false), - } - for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -217,8 +228,7 @@ func TestBuildGRPCRoutes(t *testing.T) { validator, map[types.NamespacedName]*v1.HTTPRoute{}, grRoutes, - test.gwNsNames, - npCfg, + test.gateways, snippetsFilters, ) g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) @@ -228,7 +238,20 @@ func TestBuildGRPCRoutes(t *testing.T) { func TestBuildGRPCRoute(t *testing.T) { t.Parallel() - gatewayNsName := types.NamespacedName{Namespace: "test", Name: "gateway"} + + gw := &Gateway{ + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway", + }, + }, + Valid: true, + EffectiveNginxProxy: &EffectiveNginxProxy{ + DisableHTTP2: helpers.GetPointer(false), + }, + } + gatewayNsName := client.ObjectKeyFromObject(gw.Source) methodMatchRule := createGRPCMethodMatch("myService", "myMethod", "Exact") headersMatchRule := createGRPCHeadersMatch("Exact", "MyHeader", "SomeValue") @@ -487,11 +510,10 @@ func TestBuildGRPCRoute(t *testing.T) { } tests := []struct { - validator *validationfakes.FakeHTTPFieldsValidator - gr *v1.GRPCRoute - expected *L7Route - name string - http2disabled bool + validator *validationfakes.FakeHTTPFieldsValidator + gr *v1.GRPCRoute + expected *L7Route + name string }{ { validator: createAllValidValidator(), @@ -502,7 +524,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grBoth.Spec.ParentRefs[0].SectionName, }, }, @@ -543,7 +565,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grEmptyMatch.Spec.ParentRefs[0].SectionName, }, }, @@ -575,7 +597,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grValidFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -618,7 +640,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidMatchesEmptyMethodFields.Spec.ParentRefs[0].SectionName, }, }, @@ -662,7 +684,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidMatchesInvalidMethodFields.Spec.ParentRefs[0].SectionName, }, }, @@ -699,28 +721,6 @@ func TestBuildGRPCRoute(t *testing.T) { }, name: "invalid route with duplicate sectionName", }, - { - validator: createAllValidValidator(), - gr: grBoth, - expected: &L7Route{ - RouteType: RouteTypeGRPC, - Source: grBoth, - ParentRefs: []ParentRef{ - { - Idx: 0, - Gateway: gatewayNsName, - SectionName: grBoth.Spec.ParentRefs[0].SectionName, - }, - }, - Conditions: []conditions.Condition{ - staticConds.NewRouteUnsupportedConfiguration( - `HTTP2 is disabled - cannot configure GRPCRoutes`, - ), - }, - }, - http2disabled: true, - name: "invalid route with disabled http2", - }, { validator: createAllValidValidator(), gr: grOneInvalid, @@ -732,7 +732,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grOneInvalid.Spec.ParentRefs[0].SectionName, }, }, @@ -778,7 +778,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidHeadersInvalidType.Spec.ParentRefs[0].SectionName, }, }, @@ -816,7 +816,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidHeadersEmptyType.Spec.ParentRefs[0].SectionName, }, }, @@ -854,7 +854,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidMatchesNilMethodType.Spec.ParentRefs[0].SectionName, }, }, @@ -891,7 +891,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -936,7 +936,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidHostname.Spec.ParentRefs[0].SectionName, }, }, @@ -959,7 +959,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -997,7 +997,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grUnresolvableSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -1036,7 +1036,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidAndUnresolvableSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -1070,7 +1070,9 @@ func TestBuildGRPCRoute(t *testing.T) { }, } - gatewayNsNames := []types.NamespacedName{gatewayNsName} + gws := map[types.NamespacedName]*Gateway{ + gatewayNsName: gw, + } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -1080,7 +1082,7 @@ func TestBuildGRPCRoute(t *testing.T) { snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ {Namespace: "test", Name: "sf"}: {Valid: true}, } - route := buildGRPCRoute(test.validator, test.gr, gatewayNsNames, test.http2disabled, snippetsFilters) + route := buildGRPCRoute(test.validator, test.gr, gws, snippetsFilters) g.Expect(helpers.Diff(test.expected, route)).To(BeEmpty()) }) } diff --git a/internal/mode/static/state/graph/httproute.go b/internal/mode/static/state/graph/httproute.go index dd3258a4cd..48310e1411 100644 --- a/internal/mode/static/state/graph/httproute.go +++ b/internal/mode/static/state/graph/httproute.go @@ -24,7 +24,7 @@ var ( func buildHTTPRoute( validator validation.HTTPFieldsValidator, ghr *v1.HTTPRoute, - gatewayNsNames []types.NamespacedName, + gws map[types.NamespacedName]*Gateway, snippetsFilters map[types.NamespacedName]*SnippetsFilter, ) *L7Route { r := &L7Route{ @@ -32,7 +32,7 @@ func buildHTTPRoute( RouteType: RouteTypeHTTP, } - sectionNameRefs, err := buildSectionNameRefs(ghr.Spec.ParentRefs, ghr.Namespace, gatewayNsNames) + sectionNameRefs, err := buildSectionNameRefs(ghr.Spec.ParentRefs, ghr.Namespace, gws) if err != nil { r.Valid = false diff --git a/internal/mode/static/state/graph/httproute_test.go b/internal/mode/static/state/graph/httproute_test.go index d08f00b9ed..5ef6500b6a 100644 --- a/internal/mode/static/state/graph/httproute_test.go +++ b/internal/mode/static/state/graph/httproute_test.go @@ -90,8 +90,21 @@ func addFilterToPath(hr *gatewayv1.HTTPRoute, path string, filter gatewayv1.HTTP func TestBuildHTTPRoutes(t *testing.T) { t.Parallel() + gwNsName := types.NamespacedName{Namespace: "test", Name: "gateway"} + gateways := map[types.NamespacedName]*Gateway{ + gwNsName: { + Source: &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway", + }, + }, + Valid: true, + }, + } + hr := createHTTPRoute("hr-1", gwNsName.Name, "example.com", "/") snippetsFilterRef := gatewayv1.HTTPRouteFilter{ Type: gatewayv1.HTTPRouteFilterExtensionRef, @@ -132,12 +145,12 @@ func TestBuildHTTPRoutes(t *testing.T) { } tests := []struct { - expected map[RouteKey]*L7Route - name string - gwNsNames []types.NamespacedName + expected map[RouteKey]*L7Route + gateways map[types.NamespacedName]*Gateway + name string }{ { - gwNsNames: []types.NamespacedName{gwNsName}, + gateways: gateways, expected: map[RouteKey]*L7Route{ CreateRouteKey(hr): { Source: hr, @@ -145,7 +158,7 @@ func TestBuildHTTPRoutes(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gwNsName, + Gateway: CreateParentRefGateway(gateways[gwNsName]), SectionName: hr.Spec.ParentRefs[0].SectionName, }, }, @@ -192,9 +205,9 @@ func TestBuildHTTPRoutes(t *testing.T) { name: "normal case", }, { - gwNsNames: []types.NamespacedName{}, - expected: nil, - name: "no gateways", + gateways: map[types.NamespacedName]*Gateway{}, + expected: nil, + name: "no gateways", }, } @@ -219,8 +232,7 @@ func TestBuildHTTPRoutes(t *testing.T) { validator, hrRoutes, map[types.NamespacedName]*gatewayv1.GRPCRoute{}, - test.gwNsNames, - nil, + test.gateways, snippetsFilters, ) g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) @@ -235,7 +247,16 @@ func TestBuildHTTPRoute(t *testing.T) { invalidRedirectHostname = "invalid.example.com" ) - gatewayNsName := types.NamespacedName{Namespace: "test", Name: "gateway"} + gw := &Gateway{ + Source: &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway", + }, + }, + Valid: true, + } + gatewayNsName := client.ObjectKeyFromObject(gw.Source) // route with valid filter validFilter := gatewayv1.HTTPRouteFilter{ @@ -357,7 +378,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hr.Spec.ParentRefs[0].SectionName, }, }, @@ -400,7 +421,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidMatchesEmptyPathType.Spec.ParentRefs[0].SectionName, }, }, @@ -446,7 +467,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidMatchesEmptyPathValue.Spec.ParentRefs[0].SectionName, }, }, @@ -489,7 +510,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidHostname.Spec.ParentRefs[0].SectionName, }, }, @@ -512,7 +533,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidMatches.Spec.ParentRefs[0].SectionName, }, }, @@ -549,7 +570,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidFilters.Spec.ParentRefs[0].SectionName, }, }, @@ -587,7 +608,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrDroppedInvalidMatches.Spec.ParentRefs[0].SectionName, }, }, @@ -634,7 +655,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrDroppedInvalidMatchesAndInvalidFilters.Spec.ParentRefs[0].SectionName, }, }, @@ -693,7 +714,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrDroppedInvalidFilters.Spec.ParentRefs[0].SectionName, }, }, @@ -740,7 +761,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrValidSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -782,7 +803,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -820,7 +841,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrUnresolvableSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -859,7 +880,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidAndUnresolvableSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -895,7 +916,9 @@ func TestBuildHTTPRoute(t *testing.T) { }, } - gatewayNsNames := []types.NamespacedName{gatewayNsName} + gws := map[types.NamespacedName]*Gateway{ + gatewayNsName: gw, + } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -906,7 +929,7 @@ func TestBuildHTTPRoute(t *testing.T) { {Namespace: "test", Name: "sf"}: {Valid: true}, } - route := buildHTTPRoute(test.validator, test.hr, gatewayNsNames, snippetsFilters) + route := buildHTTPRoute(test.validator, test.hr, gws, snippetsFilters) g.Expect(helpers.Diff(test.expected, route)).To(BeEmpty()) }) } diff --git a/internal/mode/static/state/graph/multiple_gateways_test.go b/internal/mode/static/state/graph/multiple_gateways_test.go new file mode 100644 index 0000000000..497fbd9680 --- /dev/null +++ b/internal/mode/static/state/graph/multiple_gateways_test.go @@ -0,0 +1,895 @@ +package graph + +import ( + "testing" + + . "github.com/onsi/gomega" + "github.com/onsi/gomega/format" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + "sigs.k8s.io/gateway-api/apis/v1beta1" + + ngfAPIv1alpha2 "github.com/nginx/nginx-gateway-fabric/apis/v1alpha2" + "github.com/nginx/nginx-gateway-fabric/internal/framework/conditions" + "github.com/nginx/nginx-gateway-fabric/internal/framework/helpers" + "github.com/nginx/nginx-gateway-fabric/internal/framework/kinds" + staticConds "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/conditions" + "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/validation" + "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/validation/validationfakes" +) + +const ( + controllerName = "nginx" + gcName = "my-gateway-class" +) + +var ( + plusSecret = &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ngf", + Name: "plus-secret", + }, + Data: map[string][]byte{ + "license.jwt": []byte("license"), + }, + } + convertedPlusSecret = map[types.NamespacedName][]PlusSecretFile{ + client.ObjectKeyFromObject(plusSecret): { + { + Type: PlusReportJWTToken, + Content: []byte("license"), + FieldName: "license.jwt", + }, + }, + } + + supportedHTTPGRPC = []gatewayv1.RouteGroupKind{ + {Kind: gatewayv1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + {Kind: gatewayv1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + } + supportedTLS = []gatewayv1.RouteGroupKind{ + {Kind: gatewayv1.Kind(kinds.TLSRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + } + + allowedRoutesHTTPGRPC = &gatewayv1.AllowedRoutes{ + Kinds: []gatewayv1.RouteGroupKind{ + {Kind: kinds.HTTPRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + {Kind: kinds.GRPCRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + }, + } + allowedRoutesTLS = &gatewayv1.AllowedRoutes{ + Kinds: []gatewayv1.RouteGroupKind{ + {Kind: kinds.TLSRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + }, + } +) + +func createGateway(name, namespace, nginxProxyName string, listeners []gatewayv1.Listener) *gatewayv1.Gateway { + gateway := &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + Spec: gatewayv1.GatewaySpec{ + GatewayClassName: gcName, + Listeners: listeners, + }, + } + + if nginxProxyName != "" { + gateway.Spec.Infrastructure = &gatewayv1.GatewayInfrastructure{ + ParametersRef: &gatewayv1.LocalParametersReference{ + Group: ngfAPIv1alpha2.GroupName, + Kind: kinds.NginxProxy, + Name: nginxProxyName, + }, + } + } + + return gateway +} + +func createGatewayClass(name, controllerName, npName, npNamespace string) *gatewayv1.GatewayClass { + if npName == "" { + return &gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: gatewayv1.GatewayController(controllerName), + }, + } + } + return &gatewayv1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: gatewayv1.GatewayClassSpec{ + ControllerName: gatewayv1.GatewayController(controllerName), + ParametersRef: &gatewayv1.ParametersReference{ + Group: ngfAPIv1alpha2.GroupName, + Kind: kinds.NginxProxy, + Name: npName, + Namespace: helpers.GetPointer(gatewayv1.Namespace(npNamespace)), + }, + }, + } +} + +func convertedGatewayClass( + gc *gatewayv1.GatewayClass, + nginxProxy ngfAPIv1alpha2.NginxProxy, + cond ...conditions.Condition, +) *GatewayClass { + return &GatewayClass{ + Source: gc, + NginxProxy: &NginxProxy{ + Source: &nginxProxy, + Valid: true, + }, + Valid: true, + Conditions: cond, + } +} + +func createNginxProxy(name, namespace string, spec ngfAPIv1alpha2.NginxProxySpec) *ngfAPIv1alpha2.NginxProxy { + return &ngfAPIv1alpha2.NginxProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: spec, + } +} + +func convertedGateway( + gw *gatewayv1.Gateway, + nginxProxy *NginxProxy, + effectiveNp *EffectiveNginxProxy, + listeners []*Listener, + conds []conditions.Condition, +) *Gateway { + return &Gateway{ + Source: gw, + Valid: true, + NginxProxy: nginxProxy, + EffectiveNginxProxy: effectiveNp, + Listeners: listeners, + Conditions: conds, + DeploymentName: types.NamespacedName{ + Name: gw.Name + "-" + gcName, + Namespace: gw.Namespace, + }, + } +} + +func createListener( + name, hostname string, + port int32, + protocol gatewayv1.ProtocolType, + tlsConfig *gatewayv1.GatewayTLSConfig, + allowedRoutes *gatewayv1.AllowedRoutes, +) gatewayv1.Listener { + listener := gatewayv1.Listener{ + Name: gatewayv1.SectionName(name), + Hostname: (*gatewayv1.Hostname)(helpers.GetPointer(hostname)), + Port: gatewayv1.PortNumber(port), + Protocol: protocol, + AllowedRoutes: allowedRoutes, + } + + if tlsConfig != nil { + listener.TLS = tlsConfig + } + + return listener +} + +func convertListener( + listener gatewayv1.Listener, + gatewayNSName types.NamespacedName, + secret *v1.Secret, + supportedKinds []gatewayv1.RouteGroupKind, + l7Route map[RouteKey]*L7Route, + l4Route map[L4RouteKey]*L4Route, +) *Listener { + l := &Listener{ + Name: string(listener.Name), + GatewayName: gatewayNSName, + Source: listener, + L4Routes: l4Route, + Routes: l7Route, + Valid: true, + SupportedKinds: supportedKinds, + Attachable: true, + } + + if secret != nil { + l.ResolvedSecret = helpers.GetPointer(client.ObjectKeyFromObject(secret)) + } + return l +} + +// Test_MultipleGateways_WithNginxProxy tests how nginx proxy config is inherited or overwritten +// when multiple gateways are present in the cluster. +func Test_MultipleGateways_WithNginxProxy(t *testing.T) { + nginxProxyGlobal := createNginxProxy("nginx-proxy", testNs, ngfAPIv1alpha2.NginxProxySpec{ + DisableHTTP2: helpers.GetPointer(true), + }) + + nginxProxyGateway1 := createNginxProxy("nginx-proxy-gateway-1", testNs, ngfAPIv1alpha2.NginxProxySpec{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelDebug), + AgentLevel: helpers.GetPointer(ngfAPIv1alpha2.AgentLogLevelDebug), + }, + }) + + nginxProxyGateway3 := createNginxProxy("nginx-proxy-gateway-3", "test2", ngfAPIv1alpha2.NginxProxySpec{ + Kubernetes: &ngfAPIv1alpha2.KubernetesSpec{ + Deployment: &ngfAPIv1alpha2.DeploymentSpec{ + Replicas: helpers.GetPointer(int32(3)), + }, + }, + DisableHTTP2: helpers.GetPointer(false), + }) + + gatewayClass := createGatewayClass(gcName, controllerName, "nginx-proxy", testNs) + gateway1 := createGateway("gateway-1", testNs, "", []gatewayv1.Listener{}) + gateway2 := createGateway("gateway-2", testNs, "", []gatewayv1.Listener{}) + gateway3 := createGateway("gateway-3", "test2", "", []gatewayv1.Listener{}) + + gateway1withNP := createGateway("gateway-1", testNs, "nginx-proxy-gateway-1", []gatewayv1.Listener{}) + gateway3withNP := createGateway("gateway-3", "test2", "nginx-proxy-gateway-3", []gatewayv1.Listener{}) + + gcConditions := []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()} + + tests := []struct { + clusterState ClusterState + expGraph *Graph + name string + }{ + { + name: "gateway class with nginx proxy, multiple gateways inheriting settings from global nginx proxy", + clusterState: ClusterState{ + GatewayClasses: map[types.NamespacedName]*gatewayv1.GatewayClass{ + client.ObjectKeyFromObject(gatewayClass): gatewayClass, + }, + Gateways: map[types.NamespacedName]*gatewayv1.Gateway{ + client.ObjectKeyFromObject(gateway1): gateway1, + client.ObjectKeyFromObject(gateway2): gateway2, + client.ObjectKeyFromObject(gateway3): gateway3, + }, + NginxProxies: map[types.NamespacedName]*ngfAPIv1alpha2.NginxProxy{ + client.ObjectKeyFromObject(nginxProxyGlobal): nginxProxyGlobal, + }, + Secrets: map[types.NamespacedName]*v1.Secret{ + client.ObjectKeyFromObject(plusSecret): plusSecret, + }, + }, + expGraph: &Graph{ + GatewayClass: convertedGatewayClass(gatewayClass, *nginxProxyGlobal, gcConditions...), + Gateways: map[types.NamespacedName]*Gateway{ + client.ObjectKeyFromObject(gateway1): convertedGateway( + gateway1, + nil, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{}, + nil, + ), + client.ObjectKeyFromObject(gateway2): convertedGateway( + gateway2, + nil, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{}, + nil, + ), + client.ObjectKeyFromObject(gateway3): convertedGateway( + gateway3, + nil, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{}, + nil, + ), + }, + ReferencedNginxProxies: map[types.NamespacedName]*NginxProxy{ + client.ObjectKeyFromObject(nginxProxyGlobal): { + Source: nginxProxyGlobal, + Valid: true, + }, + }, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + PlusSecrets: convertedPlusSecret, + }, + }, + { + name: "gateway class with nginx proxy, multiple gateways with their own referenced nginx proxy", + clusterState: ClusterState{ + GatewayClasses: map[types.NamespacedName]*gatewayv1.GatewayClass{ + client.ObjectKeyFromObject(gatewayClass): gatewayClass, + }, + Gateways: map[types.NamespacedName]*gatewayv1.Gateway{ + client.ObjectKeyFromObject(gateway1withNP): gateway1withNP, + client.ObjectKeyFromObject(gateway2): gateway2, + client.ObjectKeyFromObject(gateway3withNP): gateway3withNP, + }, + NginxProxies: map[types.NamespacedName]*ngfAPIv1alpha2.NginxProxy{ + client.ObjectKeyFromObject(nginxProxyGlobal): nginxProxyGlobal, + client.ObjectKeyFromObject(nginxProxyGateway1): nginxProxyGateway1, + client.ObjectKeyFromObject(nginxProxyGateway3): nginxProxyGateway3, + }, + Secrets: map[types.NamespacedName]*v1.Secret{ + client.ObjectKeyFromObject(plusSecret): plusSecret, + }, + }, + expGraph: &Graph{ + GatewayClass: convertedGatewayClass(gatewayClass, *nginxProxyGlobal, gcConditions...), + Gateways: map[types.NamespacedName]*Gateway{ + client.ObjectKeyFromObject(gateway1withNP): convertedGateway( + gateway1withNP, + &NginxProxy{Source: nginxProxyGateway1, Valid: true}, + &EffectiveNginxProxy{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelDebug), + AgentLevel: helpers.GetPointer(ngfAPIv1alpha2.AgentLogLevelDebug), + }, + DisableHTTP2: helpers.GetPointer(true), + }, + []*Listener{}, + gcConditions, + ), + client.ObjectKeyFromObject(gateway2): convertedGateway( + gateway2, + nil, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{}, + nil, + ), + client.ObjectKeyFromObject(gateway3withNP): convertedGateway( + gateway3withNP, + &NginxProxy{Source: nginxProxyGateway3, Valid: true}, + &EffectiveNginxProxy{ + Kubernetes: &ngfAPIv1alpha2.KubernetesSpec{ + Deployment: &ngfAPIv1alpha2.DeploymentSpec{ + Replicas: helpers.GetPointer(int32(3)), + }, + }, + DisableHTTP2: helpers.GetPointer(false), + }, + []*Listener{}, + gcConditions, + ), + }, + ReferencedNginxProxies: map[types.NamespacedName]*NginxProxy{ + client.ObjectKeyFromObject(nginxProxyGlobal): {Source: nginxProxyGlobal, Valid: true}, + client.ObjectKeyFromObject(nginxProxyGateway1): {Source: nginxProxyGateway1, Valid: true}, + client.ObjectKeyFromObject(nginxProxyGateway3): {Source: nginxProxyGateway3, Valid: true}, + }, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + PlusSecrets: convertedPlusSecret, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewWithT(t) + format.MaxLength = 10000000 + + fakePolicyValidator := &validationfakes.FakePolicyValidator{} + + result := BuildGraph( + test.clusterState, + controllerName, + gcName, + map[types.NamespacedName][]PlusSecretFile{ + client.ObjectKeyFromObject(plusSecret): { + { + Type: PlusReportJWTToken, + FieldName: "license.jwt", + }, + }, + }, + validation.Validators{ + HTTPFieldsValidator: &validationfakes.FakeHTTPFieldsValidator{}, + GenericValidator: &validationfakes.FakeGenericValidator{}, + PolicyValidator: fakePolicyValidator, + }, + ) + + g.Expect(helpers.Diff(test.expGraph, result)).To(BeEmpty()) + }) + } +} + +// Test_MultipleGateways_WithListeners tests how listeners attach and interact with multiple gateways. +func Test_MultipleGateways_WithListeners(t *testing.T) { + nginxProxyGlobal := createNginxProxy("nginx-proxy", testNs, ngfAPIv1alpha2.NginxProxySpec{ + DisableHTTP2: helpers.GetPointer(true), + }) + gatewayClass := createGatewayClass(gcName, controllerName, "nginx-proxy", testNs) + + secretDiffNs := &v1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "secret-ns", + Name: "secret", + }, + Data: map[string][]byte{ + v1.TLSCertKey: cert, + v1.TLSPrivateKeyKey: key, + }, + Type: v1.SecretTypeTLS, + } + + rgSecretsToGateway := &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rg-secret-to-gateway", + Namespace: "secret-ns", + }, + Spec: v1beta1.ReferenceGrantSpec{ + From: []v1beta1.ReferenceGrantFrom{ + { + Group: gatewayv1.GroupName, + Kind: kinds.Gateway, + Namespace: gatewayv1.Namespace(testNs), + }, + }, + To: []v1beta1.ReferenceGrantTo{ + { + Group: "core", + Kind: "Secret", + Name: helpers.GetPointer[gatewayv1.ObjectName]("secret"), + }, + }, + }, + } + + tlsConfigDiffNsSecret := &gatewayv1.GatewayTLSConfig{ + Mode: helpers.GetPointer(gatewayv1.TLSModeTerminate), + CertificateRefs: []gatewayv1.SecretObjectReference{ + { + Kind: helpers.GetPointer[gatewayv1.Kind]("Secret"), + Name: gatewayv1.ObjectName(secretDiffNs.Name), + Namespace: helpers.GetPointer(gatewayv1.Namespace(secretDiffNs.Namespace)), + }, + }, + } + + gateway1 := createGateway("gateway-1", testNs, "nginx-proxy", []gatewayv1.Listener{ + createListener( + "listener-tls-mode-terminate", + "*.example.com", + 443, + gatewayv1.HTTPSProtocolType, + tlsConfigDiffNsSecret, + allowedRoutesHTTPGRPC, + ), + }) + gateway2 := createGateway("gateway-2", testNs, "nginx-proxy", []gatewayv1.Listener{ + createListener( + "listener-tls-mode-terminate", + "*.example.com", + 443, + gatewayv1.HTTPSProtocolType, + tlsConfigDiffNsSecret, + allowedRoutesHTTPGRPC, + ), + }) + + tlsConfigPassthrough := &gatewayv1.GatewayTLSConfig{ + Mode: helpers.GetPointer(gatewayv1.TLSModePassthrough), + } + + secretSameNs := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "secret", + }, + Data: map[string][]byte{ + v1.TLSCertKey: cert, + v1.TLSPrivateKeyKey: key, + }, + Type: v1.SecretTypeTLS, + } + + gatewayTLSConfigSameNs := &gatewayv1.GatewayTLSConfig{ + Mode: helpers.GetPointer(gatewayv1.TLSModeTerminate), + CertificateRefs: []gatewayv1.SecretObjectReference{ + { + Kind: helpers.GetPointer[gatewayv1.Kind]("Secret"), + Name: gatewayv1.ObjectName(secretSameNs.Name), + Namespace: (*gatewayv1.Namespace)(&secretSameNs.Namespace), + }, + }, + } + + // valid http, https and tls listeners + listeners := []gatewayv1.Listener{ + createListener( + "foo-listener-http", + "foo.example.com", + 80, + gatewayv1.HTTPProtocolType, + nil, + allowedRoutesHTTPGRPC, + ), + createListener( + "foo-listener-https", + "tea.example.com", + 443, + gatewayv1.HTTPSProtocolType, + gatewayTLSConfigSameNs, + allowedRoutesHTTPGRPC, + ), + createListener( + "listener-tls-mode-passthrough", + "cafe.example.com", + 8443, + gatewayv1.TLSProtocolType, + tlsConfigPassthrough, + allowedRoutesTLS, + ), + } + gatewayMultipleListeners1 := createGateway("gateway-multiple-listeners-1", testNs, "nginx-proxy", listeners) + gatewayMultipleListeners2 := createGateway("gateway-multiple-listeners-2", testNs, "nginx-proxy", listeners) + gatewayMultipleListeners3 := createGateway("gateway-multiple-listeners-3", testNs, "nginx-proxy", listeners) + + // valid TLS and https listener same port and hostname + gatewayTLSSamePortHostname := createGateway( + "gateway-tls-foo", + testNs, + "nginx-proxy", + []gatewayv1.Listener{ + createListener( + "foo-listener-tls", + "foo.example.com", + 443, + gatewayv1.TLSProtocolType, + tlsConfigPassthrough, + allowedRoutesTLS, + ), + }, + ) + + gatewayHTTPSSamePortHostname := createGateway( + "gateway-http-foo", + testNs, + "nginx-proxy", + []gatewayv1.Listener{ + createListener( + "foo-listener-tls", + "foo.example.com", + 443, + gatewayv1.HTTPSProtocolType, + gatewayTLSConfigSameNs, + allowedRoutesHTTPGRPC, + ), + }, + ) + + tests := []struct { + clusterState ClusterState + expGraph *Graph + name string + }{ + { + name: "multiple gateways with tls listeners, have reference grants to access the secret", + clusterState: ClusterState{ + GatewayClasses: map[types.NamespacedName]*gatewayv1.GatewayClass{ + client.ObjectKeyFromObject(gatewayClass): gatewayClass, + }, + Secrets: map[types.NamespacedName]*v1.Secret{ + client.ObjectKeyFromObject(plusSecret): plusSecret, + client.ObjectKeyFromObject(secretDiffNs): secretDiffNs, + }, + Gateways: map[types.NamespacedName]*gatewayv1.Gateway{ + client.ObjectKeyFromObject(gateway1): gateway1, + client.ObjectKeyFromObject(gateway2): gateway2, + }, + NginxProxies: map[types.NamespacedName]*ngfAPIv1alpha2.NginxProxy{ + client.ObjectKeyFromObject(nginxProxyGlobal): nginxProxyGlobal, + }, + ReferenceGrants: map[types.NamespacedName]*v1beta1.ReferenceGrant{ + client.ObjectKeyFromObject(rgSecretsToGateway): rgSecretsToGateway, + }, + }, + expGraph: &Graph{ + GatewayClass: convertedGatewayClass(gatewayClass, *nginxProxyGlobal, staticConds.NewGatewayClassResolvedRefs()), + Gateways: map[types.NamespacedName]*Gateway{ + client.ObjectKeyFromObject(gateway1): convertedGateway( + gateway1, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{ + convertListener( + gateway1.Spec.Listeners[0], + client.ObjectKeyFromObject(gateway1), + secretDiffNs, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, + ), + client.ObjectKeyFromObject(gateway2): convertedGateway( + gateway2, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{ + convertListener( + gateway2.Spec.Listeners[0], + client.ObjectKeyFromObject(gateway2), + secretDiffNs, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, + ), + }, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + PlusSecrets: convertedPlusSecret, + ReferencedNginxProxies: map[types.NamespacedName]*NginxProxy{ + client.ObjectKeyFromObject(nginxProxyGlobal): {Source: nginxProxyGlobal, Valid: true}, + }, + ReferencedSecrets: map[types.NamespacedName]*Secret{ + client.ObjectKeyFromObject(secretDiffNs): { + Source: secretDiffNs, + CertBundle: NewCertificateBundle(client.ObjectKeyFromObject(secretDiffNs), "Secret", &Certificate{ + TLSCert: cert, + TLSPrivateKey: key, + }), + }, + }, + }, + }, + { + name: "valid http, https and tls listeners across multiple gateways with same port references," + + "leads to no port conflict", + clusterState: ClusterState{ + GatewayClasses: map[types.NamespacedName]*gatewayv1.GatewayClass{ + client.ObjectKeyFromObject(gatewayClass): gatewayClass, + }, + Gateways: map[types.NamespacedName]*gatewayv1.Gateway{ + client.ObjectKeyFromObject(gatewayMultipleListeners1): gatewayMultipleListeners1, + client.ObjectKeyFromObject(gatewayMultipleListeners2): gatewayMultipleListeners2, + client.ObjectKeyFromObject(gatewayMultipleListeners3): gatewayMultipleListeners3, + }, + NginxProxies: map[types.NamespacedName]*ngfAPIv1alpha2.NginxProxy{ + client.ObjectKeyFromObject(nginxProxyGlobal): nginxProxyGlobal, + }, + Secrets: map[types.NamespacedName]*v1.Secret{ + client.ObjectKeyFromObject(plusSecret): plusSecret, + client.ObjectKeyFromObject(secretSameNs): secretSameNs, + }, + }, + expGraph: &Graph{ + GatewayClass: convertedGatewayClass(gatewayClass, *nginxProxyGlobal, staticConds.NewGatewayClassResolvedRefs()), + Gateways: map[types.NamespacedName]*Gateway{ + client.ObjectKeyFromObject(gatewayMultipleListeners1): convertedGateway( + gatewayMultipleListeners1, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{ + convertListener( + gatewayMultipleListeners1.Spec.Listeners[0], + client.ObjectKeyFromObject(gatewayMultipleListeners1), + nil, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + convertListener( + gatewayMultipleListeners1.Spec.Listeners[1], + client.ObjectKeyFromObject(gatewayMultipleListeners1), + secretSameNs, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + convertListener( + gatewayMultipleListeners1.Spec.Listeners[2], + client.ObjectKeyFromObject(gatewayMultipleListeners1), + nil, + supportedTLS, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, + ), + client.ObjectKeyFromObject(gatewayMultipleListeners2): convertedGateway( + gatewayMultipleListeners2, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{ + convertListener( + gatewayMultipleListeners2.Spec.Listeners[0], + client.ObjectKeyFromObject(gatewayMultipleListeners2), + nil, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + convertListener( + gatewayMultipleListeners2.Spec.Listeners[1], + client.ObjectKeyFromObject(gatewayMultipleListeners2), + secretSameNs, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + convertListener( + gatewayMultipleListeners2.Spec.Listeners[2], + client.ObjectKeyFromObject(gatewayMultipleListeners2), + nil, + supportedTLS, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, + ), + client.ObjectKeyFromObject(gatewayMultipleListeners3): convertedGateway( + gatewayMultipleListeners3, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{ + convertListener( + gatewayMultipleListeners3.Spec.Listeners[0], + client.ObjectKeyFromObject(gatewayMultipleListeners3), + nil, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + convertListener( + gatewayMultipleListeners3.Spec.Listeners[1], + client.ObjectKeyFromObject(gatewayMultipleListeners3), + secretSameNs, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + convertListener( + gatewayMultipleListeners3.Spec.Listeners[2], + client.ObjectKeyFromObject(gatewayMultipleListeners3), + nil, + supportedTLS, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, + ), + }, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + PlusSecrets: convertedPlusSecret, + ReferencedNginxProxies: map[types.NamespacedName]*NginxProxy{ + client.ObjectKeyFromObject(nginxProxyGlobal): {Source: nginxProxyGlobal, Valid: true}, + }, + ReferencedSecrets: map[types.NamespacedName]*Secret{ + client.ObjectKeyFromObject(secretSameNs): { + Source: secretSameNs, + CertBundle: NewCertificateBundle(client.ObjectKeyFromObject(secretSameNs), "Secret", &Certificate{ + TLSCert: cert, + TLSPrivateKey: key, + }), + }, + }, + }, + }, + { + name: "valid tls and https listeners across multiple gateways with same port and hostname causes no conflict", + clusterState: ClusterState{ + GatewayClasses: map[types.NamespacedName]*gatewayv1.GatewayClass{ + client.ObjectKeyFromObject(gatewayClass): gatewayClass, + }, + Gateways: map[types.NamespacedName]*gatewayv1.Gateway{ + client.ObjectKeyFromObject(gatewayTLSSamePortHostname): gatewayTLSSamePortHostname, + client.ObjectKeyFromObject(gatewayHTTPSSamePortHostname): gatewayHTTPSSamePortHostname, + }, + NginxProxies: map[types.NamespacedName]*ngfAPIv1alpha2.NginxProxy{ + client.ObjectKeyFromObject(nginxProxyGlobal): nginxProxyGlobal, + }, + Secrets: map[types.NamespacedName]*v1.Secret{ + client.ObjectKeyFromObject(plusSecret): plusSecret, + client.ObjectKeyFromObject(secretSameNs): secretSameNs, + }, + }, + expGraph: &Graph{ + GatewayClass: convertedGatewayClass(gatewayClass, *nginxProxyGlobal, staticConds.NewGatewayClassResolvedRefs()), + Gateways: map[types.NamespacedName]*Gateway{ + client.ObjectKeyFromObject(gatewayTLSSamePortHostname): convertedGateway( + gatewayTLSSamePortHostname, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{ + convertListener( + gatewayTLSSamePortHostname.Spec.Listeners[0], + client.ObjectKeyFromObject(gatewayTLSSamePortHostname), + nil, + supportedTLS, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, + ), + client.ObjectKeyFromObject(gatewayHTTPSSamePortHostname): convertedGateway( + gatewayHTTPSSamePortHostname, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{ + convertListener( + gatewayHTTPSSamePortHostname.Spec.Listeners[0], + client.ObjectKeyFromObject(gatewayHTTPSSamePortHostname), + secretSameNs, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, + ), + }, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + PlusSecrets: convertedPlusSecret, + ReferencedNginxProxies: map[types.NamespacedName]*NginxProxy{ + client.ObjectKeyFromObject(nginxProxyGlobal): {Source: nginxProxyGlobal, Valid: true}, + }, + ReferencedSecrets: map[types.NamespacedName]*Secret{ + client.ObjectKeyFromObject(secretSameNs): { + Source: secretSameNs, + CertBundle: NewCertificateBundle(client.ObjectKeyFromObject(secretSameNs), "Secret", &Certificate{ + TLSCert: cert, + TLSPrivateKey: key, + }), + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewWithT(t) + format.MaxLength = 10000000 + + fakePolicyValidator := &validationfakes.FakePolicyValidator{} + + result := BuildGraph( + test.clusterState, + controllerName, + gcName, + map[types.NamespacedName][]PlusSecretFile{ + client.ObjectKeyFromObject(plusSecret): { + { + Type: PlusReportJWTToken, + FieldName: "license.jwt", + }, + }, + }, + validation.Validators{ + HTTPFieldsValidator: &validationfakes.FakeHTTPFieldsValidator{}, + GenericValidator: &validationfakes.FakeGenericValidator{}, + PolicyValidator: fakePolicyValidator, + }, + ) + + g.Expect(helpers.Diff(test.expGraph, result)).To(BeEmpty()) + }) + } +} diff --git a/internal/mode/static/state/graph/namespace.go b/internal/mode/static/state/graph/namespace.go index 481e4d749b..8cbda90f7e 100644 --- a/internal/mode/static/state/graph/namespace.go +++ b/internal/mode/static/state/graph/namespace.go @@ -10,12 +10,12 @@ import ( // a label that matches any of the Gateway Listener's label selector. func buildReferencedNamespaces( clusterNamespaces map[types.NamespacedName]*v1.Namespace, - gw *Gateway, + gateways map[types.NamespacedName]*Gateway, ) map[types.NamespacedName]*v1.Namespace { referencedNamespaces := make(map[types.NamespacedName]*v1.Namespace) for name, ns := range clusterNamespaces { - if isNamespaceReferenced(ns, gw) { + if isNamespaceReferenced(ns, gateways) { referencedNamespaces[name] = ns } } @@ -28,19 +28,21 @@ func buildReferencedNamespaces( // isNamespaceReferenced returns true if a given Namespace resource has a label // that matches any of the Gateway Listener's label selector. -func isNamespaceReferenced(ns *v1.Namespace, gw *Gateway) bool { - if gw == nil || ns == nil { +func isNamespaceReferenced(ns *v1.Namespace, gws map[types.NamespacedName]*Gateway) bool { + if ns == nil || len(gws) == 0 { return false } nsLabels := labels.Set(ns.GetLabels()) - for _, listener := range gw.Listeners { - if listener.AllowedRouteLabelSelector == nil { - // Can have listeners with AllowedRouteLabelSelector not set. - continue - } - if listener.AllowedRouteLabelSelector.Matches(nsLabels) { - return true + for _, gw := range gws { + for _, listener := range gw.Listeners { + if listener.AllowedRouteLabelSelector == nil { + // Can have listeners with AllowedRouteLabelSelector not set. + continue + } + if listener.AllowedRouteLabelSelector.Matches(nsLabels) { + return true + } } } diff --git a/internal/mode/static/state/graph/namespace_test.go b/internal/mode/static/state/graph/namespace_test.go index 372fd3d12d..af2e2ecc2b 100644 --- a/internal/mode/static/state/graph/namespace_test.go +++ b/internal/mode/static/state/graph/namespace_test.go @@ -4,9 +4,8 @@ import ( "testing" . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" ) @@ -44,20 +43,22 @@ func TestBuildReferencedNamespaces(t *testing.T) { } tests := []struct { - gw *Gateway + gws map[types.NamespacedName]*Gateway expectedRefNS map[types.NamespacedName]*v1.Namespace name string }{ { - gw: &Gateway{ - Listeners: []*Listener{ - { - Name: "listener-2", - Valid: true, - AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"apples": "oranges"}), + gws: map[types.NamespacedName]*Gateway{ + {}: { + Listeners: []*Listener{ + { + Name: "listener-2", + Valid: true, + AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"apples": "oranges"}), + }, }, + Valid: true, }, - Valid: true, }, expectedRefNS: map[types.NamespacedName]*v1.Namespace{ {Name: "ns2"}: ns2, @@ -65,20 +66,22 @@ func TestBuildReferencedNamespaces(t *testing.T) { name: "gateway matches labels with one namespace", }, { - gw: &Gateway{ - Listeners: []*Listener{ - { - Name: "listener-1", - Valid: true, - AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"apples": "oranges"}), - }, - { - Name: "listener-2", - Valid: true, - AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"peaches": "bananas"}), + gws: map[types.NamespacedName]*Gateway{ + {}: { + Listeners: []*Listener{ + { + Name: "listener-1", + Valid: true, + AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"apples": "oranges"}), + }, + { + Name: "listener-2", + Valid: true, + AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"peaches": "bananas"}), + }, }, + Valid: true, }, - Valid: true, }, expectedRefNS: map[types.NamespacedName]*v1.Namespace{ {Name: "ns2"}: ns2, @@ -87,60 +90,67 @@ func TestBuildReferencedNamespaces(t *testing.T) { name: "gateway matches labels with two namespaces", }, { - gw: &Gateway{ - Listeners: []*Listener{}, - Valid: true, + gws: map[types.NamespacedName]*Gateway{ + {}: { + Listeners: []*Listener{}, + Valid: true, + }, }, expectedRefNS: nil, name: "gateway has no Listeners", }, { - gw: &Gateway{ - Listeners: []*Listener{ - { - Name: "listener-1", - Valid: true, - }, - { - Name: "listener-2", - Valid: true, + gws: map[types.NamespacedName]*Gateway{ + {}: { + Listeners: []*Listener{ + { + Name: "listener-1", + Valid: true, + }, + { + Name: "listener-2", + Valid: true, + }, }, + Valid: true, }, - Valid: true, }, expectedRefNS: nil, name: "gateway has multiple listeners with no AllowedRouteLabelSelector set", }, { - gw: &Gateway{ - Listeners: []*Listener{ - { - Name: "listener-1", - Valid: true, - AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"not": "matching"}), + gws: map[types.NamespacedName]*Gateway{ + {}: { + Listeners: []*Listener{ + { + Name: "listener-1", + Valid: true, + AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"not": "matching"}), + }, }, + Valid: true, }, - Valid: true, }, - expectedRefNS: nil, name: "gateway doesn't match labels with any namespace", }, { - gw: &Gateway{ - Listeners: []*Listener{ - { - Name: "listener-1", - Valid: true, - AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"apples": "oranges"}), - }, - { - Name: "listener-2", - Valid: true, - AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"not": "matching"}), + gws: map[types.NamespacedName]*Gateway{ + {}: { + Listeners: []*Listener{ + { + Name: "listener-1", + Valid: true, + AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"apples": "oranges"}), + }, + { + Name: "listener-2", + Valid: true, + AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"not": "matching"}), + }, }, + Valid: true, }, - Valid: true, }, expectedRefNS: map[types.NamespacedName]*v1.Namespace{ {Name: "ns2"}: ns2, @@ -148,19 +158,21 @@ func TestBuildReferencedNamespaces(t *testing.T) { name: "gateway has two listeners and only matches labels with one namespace", }, { - gw: &Gateway{ - Listeners: []*Listener{ - { - Name: "listener-1", - Valid: true, - AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"apples": "oranges"}), - }, - { - Name: "listener-2", - Valid: true, + gws: map[types.NamespacedName]*Gateway{ + {}: { + Listeners: []*Listener{ + { + Name: "listener-1", + Valid: true, + AllowedRouteLabelSelector: labels.SelectorFromSet(map[string]string{"apples": "oranges"}), + }, + { + Name: "listener-2", + Valid: true, + }, }, + Valid: true, }, - Valid: true, }, expectedRefNS: map[types.NamespacedName]*v1.Namespace{ {Name: "ns2"}: ns2, @@ -173,7 +185,7 @@ func TestBuildReferencedNamespaces(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() g := NewWithT(t) - g.Expect(buildReferencedNamespaces(clusterNamespaces, test.gw)).To(Equal(test.expectedRefNS)) + g.Expect(buildReferencedNamespaces(clusterNamespaces, test.gws)).To(Equal(test.expectedRefNS)) }) } } @@ -182,13 +194,13 @@ func TestIsNamespaceReferenced(t *testing.T) { t.Parallel() tests := []struct { ns *v1.Namespace - gw *Gateway + gws map[types.NamespacedName]*Gateway name string exp bool }{ { ns: nil, - gw: nil, + gws: nil, exp: false, name: "namespace and gateway are nil", }, @@ -198,15 +210,17 @@ func TestIsNamespaceReferenced(t *testing.T) { Name: "ns1", }, }, - gw: nil, + gws: nil, exp: false, name: "namespace is valid but gateway is nil", }, { ns: nil, - gw: &Gateway{ - Listeners: []*Listener{}, - Valid: true, + gws: map[types.NamespacedName]*Gateway{ + {Name: "ns1"}: { + Listeners: []*Listener{}, + Valid: true, + }, }, exp: false, name: "gateway is valid but namespace is nil", @@ -218,7 +232,7 @@ func TestIsNamespaceReferenced(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() g := NewWithT(t) - g.Expect(isNamespaceReferenced(test.ns, test.gw)).To(Equal(test.exp)) + g.Expect(isNamespaceReferenced(test.ns, test.gws)).To(Equal(test.exp)) }) } } diff --git a/internal/mode/static/state/graph/nginxproxy.go b/internal/mode/static/state/graph/nginxproxy.go index e9993ab73f..3b72161233 100644 --- a/internal/mode/static/state/graph/nginxproxy.go +++ b/internal/mode/static/state/graph/nginxproxy.go @@ -127,7 +127,7 @@ func processNginxProxies( nps map[types.NamespacedName]*ngfAPIv1alpha2.NginxProxy, validator validation.GenericValidator, gc *v1.GatewayClass, - winningGateway *v1.Gateway, + gws map[types.NamespacedName]*v1.Gateway, ) map[types.NamespacedName]*NginxProxy { referencedNginxProxies := make(map[types.NamespacedName]*NginxProxy) @@ -146,14 +146,17 @@ func processNginxProxies( } } - if gwReferencesAnyNginxProxy(winningGateway) { - refNp := types.NamespacedName{ - Name: winningGateway.Spec.Infrastructure.ParametersRef.Name, - Namespace: winningGateway.Namespace, - } - - if np, ok := nps[refNp]; ok { - referencedNginxProxies[refNp] = buildNginxProxy(np, validator) + for _, gw := range gws { + if gwReferencesAnyNginxProxy(gw) { + refNp := types.NamespacedName{ + Name: gw.Spec.Infrastructure.ParametersRef.Name, + Namespace: gw.Namespace, + } + if np, ok := nps[refNp]; ok { + referencedNginxProxies[refNp] = buildNginxProxy(np, validator) + } else { + referencedNginxProxies[refNp] = nil + } } } diff --git a/internal/mode/static/state/graph/nginxproxy_test.go b/internal/mode/static/state/graph/nginxproxy_test.go index bf074ab562..4e21c7283d 100644 --- a/internal/mode/static/state/graph/nginxproxy_test.go +++ b/internal/mode/static/state/graph/nginxproxy_test.go @@ -481,16 +481,18 @@ func TestProcessNginxProxies(t *testing.T) { } } - gateway := &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "gw-ns", - }, - Spec: v1.GatewaySpec{ - Infrastructure: &v1.GatewayInfrastructure{ - ParametersRef: &v1.LocalParametersReference{ - Group: ngfAPIv1alpha2.GroupName, - Kind: kinds.NginxProxy, - Name: gatewayNpName.Name, + gateway := map[types.NamespacedName]*v1.Gateway{ + gatewayNpName: { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "gw-ns", + }, + Spec: v1.GatewaySpec{ + Infrastructure: &v1.GatewayInfrastructure{ + ParametersRef: &v1.LocalParametersReference{ + Group: ngfAPIv1alpha2.GroupName, + Kind: kinds.NginxProxy, + Name: gatewayNpName.Name, + }, }, }, }, @@ -551,7 +553,7 @@ func TestProcessNginxProxies(t *testing.T) { validator validation.GenericValidator nps map[types.NamespacedName]*ngfAPIv1alpha2.NginxProxy gc *v1.GatewayClass - gw *v1.Gateway + gws map[types.NamespacedName]*v1.Gateway expResult map[types.NamespacedName]*NginxProxy name string }{ @@ -559,9 +561,9 @@ func TestProcessNginxProxies(t *testing.T) { name: "no nginx proxies", nps: nil, gc: gatewayClass, - gw: gateway, + gws: gateway, validator: createValidValidator(), - expResult: nil, + expResult: map[types.NamespacedName]*NginxProxy{gatewayNpName: nil}, }, { name: "gateway class param ref is missing namespace", @@ -570,7 +572,7 @@ func TestProcessNginxProxies(t *testing.T) { gatewayNpName: getTestNp(gatewayNpName), }, gc: gatewayClassRefMissingNs, - gw: gateway, + gws: gateway, validator: createValidValidator(), expResult: map[types.NamespacedName]*NginxProxy{ gatewayNpName: { @@ -583,7 +585,7 @@ func TestProcessNginxProxies(t *testing.T) { name: "normal case; both nginx proxies are valid", nps: getNpMap(), gc: gatewayClass, - gw: gateway, + gws: gateway, validator: createValidValidator(), expResult: getExpResult(true), }, @@ -591,7 +593,7 @@ func TestProcessNginxProxies(t *testing.T) { name: "normal case; both nginx proxies are invalid", nps: getNpMap(), gc: gatewayClass, - gw: gateway, + gws: gateway, validator: createInvalidValidator(), expResult: getExpResult(false), }, @@ -606,7 +608,7 @@ func TestProcessNginxProxies(t *testing.T) { test.nps, test.validator, test.gc, - test.gw, + test.gws, ) g.Expect(helpers.Diff(test.expResult, result)).To(BeEmpty()) diff --git a/internal/mode/static/state/graph/policies.go b/internal/mode/static/state/graph/policies.go index 04fb6a0767..50dd3c3601 100644 --- a/internal/mode/static/state/graph/policies.go +++ b/internal/mode/static/state/graph/policies.go @@ -21,6 +21,9 @@ import ( type Policy struct { // Source is the corresponding Policy resource. Source policies.Policy + // InvalidForGateways is a map of Gateways for which this Policy is invalid for. Certain NginxProxy + // configurations may result in a policy not being valid for some Gateways, but not others. + InvalidForGateways map[types.NamespacedName]struct{} // Ancestors is a list of ancestor objects of the Policy. Used in status. Ancestors []PolicyAncestor // TargetRefs are the resources that the Policy targets. @@ -67,8 +70,8 @@ const ( ) // attachPolicies attaches the graph's processed policies to the resources they target. It modifies the graph in place. -func (g *Graph) attachPolicies(ctlrName string) { - if g.Gateway == nil { +func (g *Graph) attachPolicies(validator validation.PolicyValidator, ctlrName string) { + if len(g.Gateways) == 0 { return } @@ -76,21 +79,21 @@ func (g *Graph) attachPolicies(ctlrName string) { for _, ref := range policy.TargetRefs { switch ref.Kind { case kinds.Gateway: - attachPolicyToGateway(policy, ref, g.Gateway, g.IgnoredGateways, ctlrName) + attachPolicyToGateway(policy, ref, g.Gateways, ctlrName) case kinds.HTTPRoute, kinds.GRPCRoute: route, exists := g.Routes[routeKeyForKind(ref.Kind, ref.Nsname)] if !exists { continue } - attachPolicyToRoute(policy, route, ctlrName) + attachPolicyToRoute(policy, route, validator, ctlrName) case kinds.Service: svc, exists := g.ReferencedServices[ref.Nsname] if !exists { continue } - attachPolicyToService(policy, svc, g.Gateway, ctlrName) + attachPolicyToService(policy, svc, g.Gateways, ctlrName) } } } @@ -99,35 +102,51 @@ func (g *Graph) attachPolicies(ctlrName string) { func attachPolicyToService( policy *Policy, svc *ReferencedService, - gw *Gateway, + gws map[types.NamespacedName]*Gateway, ctlrName string, ) { if ngfPolicyAncestorsFull(policy, ctlrName) { return } - ancestor := PolicyAncestor{ - Ancestor: createParentReference(v1.GroupName, kinds.Gateway, client.ObjectKeyFromObject(gw.Source)), - } + var validForAGateway bool + for gwNsName, gw := range gws { + if _, belongsToGw := svc.GatewayNsNames[gwNsName]; !belongsToGw { + continue + } - if !gw.Valid { - ancestor.Conditions = []conditions.Condition{staticConds.NewPolicyTargetNotFound("Parent Gateway is invalid")} - if ancestorsContainsAncestorRef(policy.Ancestors, ancestor.Ancestor) { - return + ancestor := PolicyAncestor{ + Ancestor: createParentReference(v1.GroupName, kinds.Gateway, client.ObjectKeyFromObject(gw.Source)), } - policy.Ancestors = append(policy.Ancestors, ancestor) - return - } + if !gw.Valid { + policy.InvalidForGateways[gwNsName] = struct{}{} + ancestor.Conditions = []conditions.Condition{staticConds.NewPolicyTargetNotFound("Parent Gateway is invalid")} + if ancestorsContainsAncestorRef(policy.Ancestors, ancestor.Ancestor) { + continue + } - if !ancestorsContainsAncestorRef(policy.Ancestors, ancestor.Ancestor) { - policy.Ancestors = append(policy.Ancestors, ancestor) + policy.Ancestors = append(policy.Ancestors, ancestor) + continue + } + + if !ancestorsContainsAncestorRef(policy.Ancestors, ancestor.Ancestor) { + policy.Ancestors = append(policy.Ancestors, ancestor) + } + validForAGateway = true } - svc.Policies = append(svc.Policies, policy) + if validForAGateway { + svc.Policies = append(svc.Policies, policy) + } } -func attachPolicyToRoute(policy *Policy, route *L7Route, ctlrName string) { +func attachPolicyToRoute(policy *Policy, route *L7Route, validator validation.PolicyValidator, ctlrName string) { + if ngfPolicyAncestorsFull(policy, ctlrName) { + // FIXME (kate-osborn): https://github.com/nginx/nginx-gateway-fabric/issues/1987 + return + } + kind := v1.Kind(kinds.HTTPRoute) if route.RouteType == RouteTypeGRPC { kind = kinds.GRPCRoute @@ -139,31 +158,43 @@ func attachPolicyToRoute(policy *Policy, route *L7Route, ctlrName string) { Ancestor: createParentReference(v1.GroupName, kind, routeNsName), } - if ngfPolicyAncestorsFull(policy, ctlrName) { - // FIXME (kate-osborn): https://github.com/nginx/nginx-gateway-fabric/issues/1987 - return - } - if !route.Valid || !route.Attachable || len(route.ParentRefs) == 0 { ancestor.Conditions = []conditions.Condition{staticConds.NewPolicyTargetNotFound("TargetRef is invalid")} policy.Ancestors = append(policy.Ancestors, ancestor) return } + // as of now, ObservabilityPolicy is the only policy that needs this check, and it only attaches to Routes + for _, parentRef := range route.ParentRefs { + if parentRef.Gateway != nil && parentRef.Gateway.EffectiveNginxProxy != nil { + gw := parentRef.Gateway + globalSettings := &policies.GlobalSettings{ + TelemetryEnabled: telemetryEnabledForNginxProxy(gw.EffectiveNginxProxy), + } + + if conds := validator.ValidateGlobalSettings(policy.Source, globalSettings); len(conds) > 0 { + policy.InvalidForGateways[gw.NamespacedName] = struct{}{} + ancestor.Conditions = append(ancestor.Conditions, conds...) + } + } + } + policy.Ancestors = append(policy.Ancestors, ancestor) + if len(policy.InvalidForGateways) == len(route.ParentRefs) { + return + } + route.Policies = append(route.Policies, policy) } func attachPolicyToGateway( policy *Policy, ref PolicyTargetRef, - gw *Gateway, - ignoredGateways map[types.NamespacedName]*v1.Gateway, + gateways map[types.NamespacedName]*Gateway, ctlrName string, ) { - _, ignored := ignoredGateways[ref.Nsname] - - if !ignored && ref.Nsname != client.ObjectKeyFromObject(gw.Source) { + if ngfPolicyAncestorsFull(policy, ctlrName) { + // FIXME (kate-osborn): https://github.com/nginx/nginx-gateway-fabric/issues/1987 return } @@ -171,18 +202,17 @@ func attachPolicyToGateway( Ancestor: createParentReference(v1.GroupName, kinds.Gateway, ref.Nsname), } - if ngfPolicyAncestorsFull(policy, ctlrName) { - // FIXME (kate-osborn): https://github.com/nginx/nginx-gateway-fabric/issues/1987 - return - } + gw, exists := gateways[ref.Nsname] - if ignored { - ancestor.Conditions = []conditions.Condition{staticConds.NewPolicyTargetNotFound("TargetRef is ignored")} + if !exists || (gw != nil && gw.Source == nil) { + policy.InvalidForGateways[ref.Nsname] = struct{}{} + ancestor.Conditions = []conditions.Condition{staticConds.NewPolicyTargetNotFound("TargetRef is not found")} policy.Ancestors = append(policy.Ancestors, ancestor) return } if !gw.Valid { + policy.InvalidForGateways[ref.Nsname] = struct{}{} ancestor.Conditions = []conditions.Condition{staticConds.NewPolicyTargetNotFound("TargetRef is invalid")} policy.Ancestors = append(policy.Ancestors, ancestor) return @@ -195,12 +225,11 @@ func attachPolicyToGateway( func processPolicies( pols map[PolicyKey]policies.Policy, validator validation.PolicyValidator, - gateways processedGateways, routes map[RouteKey]*L7Route, services map[types.NamespacedName]*ReferencedService, - globalSettings *policies.GlobalSettings, + gws map[types.NamespacedName]*Gateway, ) map[PolicyKey]*Policy { - if len(pols) == 0 || gateways.Winner == nil { + if len(pols) == 0 || len(gws) == 0 { return nil } @@ -217,7 +246,7 @@ func processPolicies( switch refGroupKind(ref.Group, ref.Kind) { case gatewayGroupKind: - if !gatewayExists(refNsName, gateways.Winner, gateways.Ignored) { + if !gatewayExists(refNsName, gws) { continue } case hrGroupKind, grpcGroupKind: @@ -249,14 +278,15 @@ func processPolicies( overlapConds := checkTargetRoutesForOverlap(targetedRoutes, routes) conds = append(conds, overlapConds...) - conds = append(conds, validator.Validate(policy, globalSettings)...) + conds = append(conds, validator.Validate(policy)...) processedPolicies[key] = &Policy{ - Source: policy, - Valid: len(conds) == 0, - Conditions: conds, - TargetRefs: targetRefs, - Ancestors: make([]PolicyAncestor, 0, len(targetRefs)), + Source: policy, + Valid: len(conds) == 0, + Conditions: conds, + TargetRefs: targetRefs, + Ancestors: make([]PolicyAncestor, 0, len(targetRefs)), + InvalidForGateways: make(map[types.NamespacedName]struct{}), } } diff --git a/internal/mode/static/state/graph/policies_test.go b/internal/mode/static/state/graph/policies_test.go index a7a8b71dc2..4adda19357 100644 --- a/internal/mode/static/state/graph/policies_test.go +++ b/internal/mode/static/state/graph/policies_test.go @@ -11,6 +11,7 @@ import ( v1 "sigs.k8s.io/gateway-api/apis/v1" "sigs.k8s.io/gateway-api/apis/v1alpha2" + ngfAPIv1alpha2 "github.com/nginx/nginx-gateway-fabric/apis/v1alpha2" "github.com/nginx/nginx-gateway-fabric/internal/framework/conditions" "github.com/nginx/nginx-gateway-fabric/internal/framework/helpers" "github.com/nginx/nginx-gateway-fabric/internal/framework/kinds" @@ -75,8 +76,10 @@ func TestAttachPolicies(t *testing.T) { } expectNoGatewayPolicyAttachment := func(g *WithT, graph *Graph) { - if graph.Gateway != nil { - g.Expect(graph.Gateway.Policies).To(BeNil()) + for _, gw := range graph.Gateways { + if gw != nil { + g.Expect(gw.Policies).To(BeNil()) + } } } @@ -93,8 +96,10 @@ func TestAttachPolicies(t *testing.T) { } expectGatewayPolicyAttachment := func(g *WithT, graph *Graph) { - if graph.Gateway != nil { - g.Expect(graph.Gateway.Policies).To(HaveLen(1)) + for _, gw := range graph.Gateways { + if gw != nil { + g.Expect(gw.Policies).To(HaveLen(1)) + } } } @@ -144,26 +149,43 @@ func TestAttachPolicies(t *testing.T) { ) } - getGateway := func() *Gateway { - return &Gateway{ - Source: &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Namespace: testNs, + getGateways := func() map[types.NamespacedName]*Gateway { + return map[types.NamespacedName]*Gateway{ + {Namespace: testNs, Name: "gateway"}: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Namespace: testNs, + }, }, + Valid: true, + }, + {Namespace: testNs, Name: "gateway1"}: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway1", + Namespace: testNs, + }, + }, + Valid: true, }, - Valid: true, } } getServices := func() map[types.NamespacedName]*ReferencedService { return map[types.NamespacedName]*ReferencedService{ - {Namespace: testNs, Name: "svc-1"}: {}, + {Namespace: testNs, Name: "svc-1"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: testNs, Name: "gateway"}: {}, + {Namespace: testNs, Name: "gateway1"}: {}, + }, + Policies: nil, + }, } } tests := []struct { - gateway *Gateway + gateway map[types.NamespacedName]*Gateway routes map[RouteKey]*L7Route svcs map[types.NamespacedName]*ReferencedService ngfPolicies map[PolicyKey]*Policy @@ -178,7 +200,7 @@ func TestAttachPolicies(t *testing.T) { }, { name: "nil Routes; gateway and service policies attach", - gateway: getGateway(), + gateway: getGateways(), svcs: getServices(), ngfPolicies: getPolicies(), expects: []func(g *WithT, graph *Graph){ @@ -191,7 +213,7 @@ func TestAttachPolicies(t *testing.T) { name: "nil ReferencedServices; gateway and route policies attach", routes: getRoutes(), ngfPolicies: getPolicies(), - gateway: getGateway(), + gateway: getGateways(), expects: []func(g *WithT, graph *Graph){ expectGatewayPolicyAttachment, expectRoutePolicyAttachment, @@ -203,7 +225,7 @@ func TestAttachPolicies(t *testing.T) { routes: getRoutes(), svcs: getServices(), ngfPolicies: getPolicies(), - gateway: getGateway(), + gateway: getGateways(), expects: expectAllAttachmentList, }, } @@ -214,13 +236,13 @@ func TestAttachPolicies(t *testing.T) { g := NewWithT(t) graph := &Graph{ - Gateway: test.gateway, + Gateways: test.gateway, Routes: test.routes, ReferencedServices: test.svcs, NGFPolicies: test.ngfPolicies, } - graph.attachPolicies("nginx-gateway") + graph.attachPolicies(nil, "nginx-gateway") for _, expect := range test.expects { expect(g, graph) } @@ -275,34 +297,49 @@ func TestAttachPolicyToRoute(t *testing.T) { } } + validatorError := &policiesfakes.FakeValidator{ + ValidateGlobalSettingsStub: func(_ policies.Policy, gs *policies.GlobalSettings) []conditions.Condition { + if !gs.TelemetryEnabled { + return []conditions.Condition{ + staticConds.NewPolicyNotAcceptedNginxProxyNotSet(staticConds.PolicyMessageTelemetryNotEnabled), + } + } + return nil + }, + } + tests := []struct { route *L7Route policy *Policy + validator policies.Validator name string expAncestors []PolicyAncestor expAttached bool }{ { - name: "policy attaches to http route", - route: createHTTPRoute(true /*valid*/, true /*attachable*/, true /*parentRefs*/), - policy: &Policy{Source: &policiesfakes.FakePolicy{}}, + name: "policy attaches to http route", + route: createHTTPRoute(true /*valid*/, true /*attachable*/, true /*parentRefs*/), + validator: &policiesfakes.FakeValidator{}, + policy: &Policy{Source: &policiesfakes.FakePolicy{}}, expAncestors: []PolicyAncestor{ {Ancestor: createExpAncestor(kinds.HTTPRoute)}, }, expAttached: true, }, { - name: "policy attaches to grpc route", - route: createGRPCRoute(true /*valid*/, true /*attachable*/, true /*parentRefs*/), - policy: &Policy{Source: &policiesfakes.FakePolicy{}}, + name: "policy attaches to grpc route", + route: createGRPCRoute(true /*valid*/, true /*attachable*/, true /*parentRefs*/), + validator: &policiesfakes.FakeValidator{}, + policy: &Policy{Source: &policiesfakes.FakePolicy{}}, expAncestors: []PolicyAncestor{ {Ancestor: createExpAncestor(kinds.GRPCRoute)}, }, expAttached: true, }, { - name: "attachment with existing ancestor", - route: createHTTPRoute(true /*valid*/, true /*attachable*/, true /*parentRefs*/), + name: "attachment with existing ancestor", + route: createHTTPRoute(true /*valid*/, true /*attachable*/, true /*parentRefs*/), + validator: &policiesfakes.FakeValidator{}, policy: &Policy{ Source: &policiesfakes.FakePolicy{}, Ancestors: []PolicyAncestor{ @@ -316,9 +353,10 @@ func TestAttachPolicyToRoute(t *testing.T) { expAttached: true, }, { - name: "no attachment; unattachable route", - route: createHTTPRoute(true /*valid*/, false /*attachable*/, true /*parentRefs*/), - policy: &Policy{Source: &policiesfakes.FakePolicy{}}, + name: "no attachment; unattachable route", + route: createHTTPRoute(true /*valid*/, false /*attachable*/, true /*parentRefs*/), + validator: &policiesfakes.FakeValidator{}, + policy: &Policy{Source: &policiesfakes.FakePolicy{}}, expAncestors: []PolicyAncestor{ { Ancestor: createExpAncestor(kinds.HTTPRoute), @@ -328,9 +366,10 @@ func TestAttachPolicyToRoute(t *testing.T) { expAttached: false, }, { - name: "no attachment; missing parentRefs", - route: createHTTPRoute(true /*valid*/, true /*attachable*/, false /*parentRefs*/), - policy: &Policy{Source: &policiesfakes.FakePolicy{}}, + name: "no attachment; missing parentRefs", + route: createHTTPRoute(true /*valid*/, true /*attachable*/, false /*parentRefs*/), + validator: &policiesfakes.FakeValidator{}, + policy: &Policy{Source: &policiesfakes.FakePolicy{}}, expAncestors: []PolicyAncestor{ { Ancestor: createExpAncestor(kinds.HTTPRoute), @@ -340,9 +379,10 @@ func TestAttachPolicyToRoute(t *testing.T) { expAttached: false, }, { - name: "no attachment; invalid route", - route: createHTTPRoute(false /*valid*/, true /*attachable*/, true /*parentRefs*/), - policy: &Policy{Source: &policiesfakes.FakePolicy{}}, + name: "no attachment; invalid route", + route: createHTTPRoute(false /*valid*/, true /*attachable*/, true /*parentRefs*/), + validator: &policiesfakes.FakeValidator{}, + policy: &Policy{Source: &policiesfakes.FakePolicy{}}, expAncestors: []PolicyAncestor{ { Ancestor: createExpAncestor(kinds.HTTPRoute), @@ -354,10 +394,104 @@ func TestAttachPolicyToRoute(t *testing.T) { { name: "no attachment; max ancestors", route: createHTTPRoute(true /*valid*/, true /*attachable*/, true /*parentRefs*/), + validator: &policiesfakes.FakeValidator{}, policy: &Policy{Source: createTestPolicyWithAncestors(16)}, expAncestors: nil, expAttached: false, }, + { + name: "invalid for some ParentRefs", + route: &L7Route{ + Source: &v1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: routeNsName.Name, + Namespace: routeNsName.Namespace, + }, + }, + Valid: true, + Attachable: true, + RouteType: RouteTypeHTTP, + ParentRefs: []ParentRef{ + { + Gateway: &ParentRefGateway{ + NamespacedName: types.NamespacedName{Name: "gateway1", Namespace: "test"}, + EffectiveNginxProxy: &EffectiveNginxProxy{ + Telemetry: &ngfAPIv1alpha2.Telemetry{ + Exporter: &ngfAPIv1alpha2.TelemetryExporter{ + Endpoint: helpers.GetPointer("test-endpoint"), + }, + }, + }, + }, + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + { + Gateway: &ParentRefGateway{ + NamespacedName: types.NamespacedName{Name: "gateway2", Namespace: "test"}, + EffectiveNginxProxy: &EffectiveNginxProxy{}, + }, + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + }, + }, + validator: validatorError, + policy: &Policy{ + Source: &policiesfakes.FakePolicy{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + }, + expAncestors: []PolicyAncestor{ + { + Ancestor: createExpAncestor(kinds.HTTPRoute), + Conditions: []conditions.Condition{ + staticConds.NewPolicyNotAcceptedNginxProxyNotSet(staticConds.PolicyMessageTelemetryNotEnabled), + }, + }, + }, + expAttached: true, + }, + { + name: "invalid for all ParentRefs", + route: &L7Route{ + Source: &v1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Name: routeNsName.Name, + Namespace: routeNsName.Namespace, + }, + }, + Valid: true, + Attachable: true, + RouteType: RouteTypeHTTP, + ParentRefs: []ParentRef{ + { + Gateway: &ParentRefGateway{ + NamespacedName: types.NamespacedName{Name: "gateway1", Namespace: "test"}, + EffectiveNginxProxy: &EffectiveNginxProxy{}, + }, + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + }, + }, + validator: validatorError, + policy: &Policy{ + Source: &policiesfakes.FakePolicy{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + }, + expAncestors: []PolicyAncestor{ + { + Ancestor: createExpAncestor(kinds.HTTPRoute), + Conditions: []conditions.Condition{ + staticConds.NewPolicyNotAcceptedNginxProxyNotSet(staticConds.PolicyMessageTelemetryNotEnabled), + }, + }, + }, + expAttached: false, + }, } for _, test := range tests { @@ -365,7 +499,7 @@ func TestAttachPolicyToRoute(t *testing.T) { t.Parallel() g := NewWithT(t) - attachPolicyToRoute(test.policy, test.route, "nginx-gateway") + attachPolicyToRoute(test.policy, test.route, test.validator, "nginx-gateway") if test.expAttached { g.Expect(test.route.Policies).To(HaveLen(1)) @@ -382,23 +516,26 @@ func TestAttachPolicyToGateway(t *testing.T) { t.Parallel() gatewayNsName := types.NamespacedName{Namespace: testNs, Name: "gateway"} gateway2NsName := types.NamespacedName{Namespace: testNs, Name: "gateway2"} - ignoredGatewayNsName := types.NamespacedName{Namespace: testNs, Name: "ignored"} - newGateway := func(valid bool, nsname types.NamespacedName) *Gateway { - return &Gateway{ - Source: &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: nsname.Namespace, - Name: nsname.Name, + newGatewayMap := func(valid bool, nsname []types.NamespacedName) map[types.NamespacedName]*Gateway { + gws := make(map[types.NamespacedName]*Gateway) + for _, name := range nsname { + gws[name] = &Gateway{ + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: name.Name, + Namespace: name.Namespace, + }, }, - }, - Valid: valid, + Valid: valid, + } } + return gws } tests := []struct { policy *Policy - gw *Gateway + gws map[types.NamespacedName]*Gateway name string expAncestors []PolicyAncestor expAttached bool @@ -413,8 +550,9 @@ func TestAttachPolicyToGateway(t *testing.T) { Kind: "Gateway", }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, - gw: newGateway(true, gatewayNsName), + gws: newGatewayMap(true, []types.NamespacedName{gatewayNsName}), expAncestors: []PolicyAncestor{ {Ancestor: getGatewayParentRef(gatewayNsName)}, }, @@ -430,11 +568,12 @@ func TestAttachPolicyToGateway(t *testing.T) { Kind: "Gateway", }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, Ancestors: []PolicyAncestor{ {Ancestor: getGatewayParentRef(gatewayNsName)}, }, }, - gw: newGateway(true, gatewayNsName), + gws: newGatewayMap(true, []types.NamespacedName{gatewayNsName}), expAncestors: []PolicyAncestor{ {Ancestor: getGatewayParentRef(gatewayNsName)}, {Ancestor: getGatewayParentRef(gatewayNsName)}, @@ -442,21 +581,22 @@ func TestAttachPolicyToGateway(t *testing.T) { expAttached: true, }, { - name: "not attached; gateway ignored", + name: "not attached; gateway is not found", policy: &Policy{ Source: &policiesfakes.FakePolicy{}, TargetRefs: []PolicyTargetRef{ { - Nsname: ignoredGatewayNsName, + Nsname: gateway2NsName, Kind: "Gateway", }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, - gw: newGateway(true, gatewayNsName), + gws: newGatewayMap(true, []types.NamespacedName{gatewayNsName}), expAncestors: []PolicyAncestor{ { - Ancestor: getGatewayParentRef(ignoredGatewayNsName), - Conditions: []conditions.Condition{staticConds.NewPolicyTargetNotFound("TargetRef is ignored")}, + Ancestor: getGatewayParentRef(gateway2NsName), + Conditions: []conditions.Condition{staticConds.NewPolicyTargetNotFound("TargetRef is not found")}, }, }, expAttached: false, @@ -471,8 +611,9 @@ func TestAttachPolicyToGateway(t *testing.T) { Kind: "Gateway", }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, - gw: newGateway(false, gatewayNsName), + gws: newGatewayMap(false, []types.NamespacedName{gatewayNsName}), expAncestors: []PolicyAncestor{ { Ancestor: getGatewayParentRef(gatewayNsName), @@ -481,21 +622,6 @@ func TestAttachPolicyToGateway(t *testing.T) { }, expAttached: false, }, - { - name: "not attached; non-NGF gateway", - policy: &Policy{ - Source: &policiesfakes.FakePolicy{}, - TargetRefs: []PolicyTargetRef{ - { - Nsname: gateway2NsName, - Kind: "Gateway", - }, - }, - }, - gw: newGateway(true, gatewayNsName), - expAncestors: nil, - expAttached: false, - }, { name: "not attached; max ancestors", policy: &Policy{ @@ -506,28 +632,29 @@ func TestAttachPolicyToGateway(t *testing.T) { Kind: "Gateway", }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, - gw: newGateway(true, gatewayNsName), + gws: newGatewayMap(true, []types.NamespacedName{gatewayNsName}), expAncestors: nil, expAttached: false, }, } for _, test := range tests { - ignoredGateways := map[types.NamespacedName]*v1.Gateway{ - ignoredGatewayNsName: nil, - } - t.Run(test.name, func(t *testing.T) { t.Parallel() g := NewWithT(t) - attachPolicyToGateway(test.policy, test.policy.TargetRefs[0], test.gw, ignoredGateways, "nginx-gateway") + attachPolicyToGateway(test.policy, test.policy.TargetRefs[0], test.gws, "nginx-gateway") if test.expAttached { - g.Expect(test.gw.Policies).To(HaveLen(1)) + for _, gw := range test.gws { + g.Expect(gw.Policies).To(HaveLen(1)) + } } else { - g.Expect(test.gw.Policies).To(BeEmpty()) + for _, gw := range test.gws { + g.Expect(gw.Policies).To(BeEmpty()) + } } g.Expect(test.policy.Ancestors).To(BeEquivalentTo(test.expAncestors)) @@ -541,31 +668,37 @@ func TestAttachPolicyToService(t *testing.T) { gwNsname := types.NamespacedName{Namespace: testNs, Name: "gateway"} gw2Nsname := types.NamespacedName{Namespace: testNs, Name: "gateway2"} - getGateway := func(valid bool) *Gateway { - return &Gateway{ - Source: &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: gwNsname.Name, - Namespace: gwNsname.Namespace, + getGateway := func(valid bool) map[types.NamespacedName]*Gateway { + return map[types.NamespacedName]*Gateway{ + gwNsname: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: gwNsname.Name, + Namespace: gwNsname.Namespace, + }, }, + Valid: valid, }, - Valid: valid, } } tests := []struct { policy *Policy svc *ReferencedService - gw *Gateway + gws map[types.NamespacedName]*Gateway name string expAncestors []PolicyAncestor expAttached bool }{ { - name: "attachment", - policy: &Policy{Source: &policiesfakes.FakePolicy{}}, - svc: &ReferencedService{}, - gw: getGateway(true /*valid*/), + name: "attachment", + policy: &Policy{Source: &policiesfakes.FakePolicy{}, InvalidForGateways: map[types.NamespacedName]struct{}{}}, + svc: &ReferencedService{ + GatewayNsNames: map[types.NamespacedName]struct{}{ + gwNsname: {}, + }, + }, + gws: getGateway(true /*valid*/), expAttached: true, expAncestors: []PolicyAncestor{ { @@ -582,9 +715,14 @@ func TestAttachPolicyToService(t *testing.T) { Ancestor: getGatewayParentRef(gwNsname), }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, - svc: &ReferencedService{}, - gw: getGateway(true /*valid*/), + svc: &ReferencedService{ + GatewayNsNames: map[types.NamespacedName]struct{}{ + gwNsname: {}, + }, + }, + gws: getGateway(true /*valid*/), expAttached: true, expAncestors: []PolicyAncestor{ { @@ -601,9 +739,15 @@ func TestAttachPolicyToService(t *testing.T) { Ancestor: getGatewayParentRef(gw2Nsname), }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + }, + svc: &ReferencedService{ + GatewayNsNames: map[types.NamespacedName]struct{}{ + gw2Nsname: {}, + gwNsname: {}, + }, }, - svc: &ReferencedService{}, - gw: getGateway(true /*valid*/), + gws: getGateway(true /*valid*/), expAttached: true, expAncestors: []PolicyAncestor{ { @@ -615,10 +759,14 @@ func TestAttachPolicyToService(t *testing.T) { }, }, { - name: "no attachment; gateway is invalid", - policy: &Policy{Source: &policiesfakes.FakePolicy{}}, - svc: &ReferencedService{}, - gw: getGateway(false /*invalid*/), + name: "no attachment; gateway is invalid", + policy: &Policy{Source: &policiesfakes.FakePolicy{}, InvalidForGateways: map[types.NamespacedName]struct{}{}}, + svc: &ReferencedService{ + GatewayNsNames: map[types.NamespacedName]struct{}{ + gwNsname: {}, + }, + }, + gws: getGateway(false /*invalid*/), expAttached: false, expAncestors: []PolicyAncestor{ { @@ -628,13 +776,55 @@ func TestAttachPolicyToService(t *testing.T) { }, }, { - name: "no attachment; max ancestor", - policy: &Policy{Source: createTestPolicyWithAncestors(16)}, - svc: &ReferencedService{}, - gw: getGateway(true /*valid*/), + name: "no attachment; max ancestor", + policy: &Policy{Source: createTestPolicyWithAncestors(16), InvalidForGateways: map[types.NamespacedName]struct{}{}}, + svc: &ReferencedService{ + GatewayNsNames: map[types.NamespacedName]struct{}{ + gwNsname: {}, + }, + }, + gws: getGateway(true /*valid*/), + expAttached: false, + expAncestors: nil, + }, + { + name: "no attachment; does not belong to gateway", + policy: &Policy{Source: &policiesfakes.FakePolicy{}, InvalidForGateways: map[types.NamespacedName]struct{}{}}, + svc: &ReferencedService{ + GatewayNsNames: map[types.NamespacedName]struct{}{ + gw2Nsname: {}, + }, + }, + gws: getGateway(true /*valid*/), expAttached: false, expAncestors: nil, }, + { + name: "no attachment; gateway is invalid", + policy: &Policy{ + Source: &policiesfakes.FakePolicy{}, + InvalidForGateways: map[types.NamespacedName]struct{}{ + gwNsname: {}, + }, + Ancestors: []PolicyAncestor{ + { + Ancestor: getGatewayParentRef(gwNsname), + }, + }, + }, + svc: &ReferencedService{ + GatewayNsNames: map[types.NamespacedName]struct{}{ + gwNsname: {}, + }, + }, + gws: getGateway(false), + expAttached: false, + expAncestors: []PolicyAncestor{ + { + Ancestor: getGatewayParentRef(gwNsname), + }, + }, + }, } for _, test := range tests { @@ -642,7 +832,7 @@ func TestAttachPolicyToService(t *testing.T) { t.Parallel() g := NewWithT(t) - attachPolicyToService(test.policy, test.svc, test.gw, "ctlr") + attachPolicyToService(test.policy, test.svc, test.gws, "ctlr") if test.expAttached { g.Expect(test.svc.Policies).To(HaveLen(1)) } else { @@ -663,7 +853,7 @@ func TestProcessPolicies(t *testing.T) { hrRef := createTestRef(kinds.HTTPRoute, v1.GroupName, "hr") grpcRef := createTestRef(kinds.GRPCRoute, v1.GroupName, "grpc") gatewayRef := createTestRef(kinds.Gateway, v1.GroupName, "gw") - ignoredGatewayRef := createTestRef(kinds.Gateway, v1.GroupName, "ignored") + gatewayRef2 := createTestRef(kinds.Gateway, v1.GroupName, "gw2") svcRef := createTestRef(kinds.Service, "core", "svc") // These refs reference objects that do not belong to NGF. @@ -677,7 +867,7 @@ func TestProcessPolicies(t *testing.T) { pol1, pol1Key := createTestPolicyAndKey(policyGVK, "pol1", hrRef) pol2, pol2Key := createTestPolicyAndKey(policyGVK, "pol2", grpcRef) pol3, pol3Key := createTestPolicyAndKey(policyGVK, "pol3", gatewayRef) - pol4, pol4Key := createTestPolicyAndKey(policyGVK, "pol4", ignoredGatewayRef) + pol4, pol4Key := createTestPolicyAndKey(policyGVK, "pol4", gatewayRef2) pol5, pol5Key := createTestPolicyAndKey(policyGVK, "pol5", hrDoesNotExistRef) pol6, pol6Key := createTestPolicyAndKey(policyGVK, "pol6", hrWrongGroup) pol7, pol7Key := createTestPolicyAndKey(policyGVK, "pol7", gatewayWrongGroupRef) @@ -724,8 +914,9 @@ func TestProcessPolicies(t *testing.T) { Group: v1.GroupName, }, }, - Ancestors: []PolicyAncestor{}, - Valid: true, + Ancestors: []PolicyAncestor{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: true, }, pol2Key: { Source: pol2, @@ -736,8 +927,9 @@ func TestProcessPolicies(t *testing.T) { Group: v1.GroupName, }, }, - Ancestors: []PolicyAncestor{}, - Valid: true, + Ancestors: []PolicyAncestor{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: true, }, pol3Key: { Source: pol3, @@ -748,20 +940,22 @@ func TestProcessPolicies(t *testing.T) { Group: v1.GroupName, }, }, - Ancestors: []PolicyAncestor{}, - Valid: true, + Ancestors: []PolicyAncestor{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: true, }, pol4Key: { Source: pol4, TargetRefs: []PolicyTargetRef{ { - Nsname: types.NamespacedName{Namespace: testNs, Name: "ignored"}, + Nsname: types.NamespacedName{Namespace: testNs, Name: "gw2"}, Kind: kinds.Gateway, Group: v1.GroupName, }, }, - Ancestors: []PolicyAncestor{}, - Valid: true, + Ancestors: []PolicyAncestor{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: true, }, pol10Key: { Source: pol10, @@ -772,18 +966,16 @@ func TestProcessPolicies(t *testing.T) { Group: "core", }, }, - Ancestors: []PolicyAncestor{}, - Valid: true, + Ancestors: []PolicyAncestor{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: true, }, }, }, { name: "invalid and valid policies", validator: &policiesfakes.FakeValidator{ - ValidateStub: func( - policy policies.Policy, - _ *policies.GlobalSettings, - ) []conditions.Condition { + ValidateStub: func(policy policies.Policy) []conditions.Condition { if policy.GetName() == "pol1" { return []conditions.Condition{staticConds.NewPolicyInvalid("invalid error")} } @@ -808,8 +1000,9 @@ func TestProcessPolicies(t *testing.T) { Conditions: []conditions.Condition{ staticConds.NewPolicyInvalid("invalid error"), }, - Ancestors: []PolicyAncestor{}, - Valid: false, + Ancestors: []PolicyAncestor{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: false, }, pol2Key: { Source: pol2, @@ -820,8 +1013,9 @@ func TestProcessPolicies(t *testing.T) { Group: v1.GroupName, }, }, - Ancestors: []PolicyAncestor{}, - Valid: true, + Ancestors: []PolicyAncestor{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: true, }, }, }, @@ -846,8 +1040,9 @@ func TestProcessPolicies(t *testing.T) { Group: v1.GroupName, }, }, - Ancestors: []PolicyAncestor{}, - Valid: true, + Ancestors: []PolicyAncestor{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: true, }, pol1ConflictKey: { Source: pol1Conflict, @@ -861,27 +1056,32 @@ func TestProcessPolicies(t *testing.T) { Conditions: []conditions.Condition{ staticConds.NewPolicyConflicted("Conflicts with another MyPolicy"), }, - Ancestors: []PolicyAncestor{}, - Valid: false, + Ancestors: []PolicyAncestor{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: false, }, }, }, } - gateways := processedGateways{ - Winner: &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: testNs, + gateways := map[types.NamespacedName]*Gateway{ + {Namespace: testNs, Name: "gw"}: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Namespace: testNs, + }, }, + Valid: true, }, - Ignored: map[types.NamespacedName]*v1.Gateway{ - {Namespace: testNs, Name: "ignored"}: { + {Namespace: testNs, Name: "gw2"}: { + Source: &v1.Gateway{ ObjectMeta: metav1.ObjectMeta{ - Name: "gw", + Name: "gw2", Namespace: testNs, }, }, + Valid: true, }, } @@ -913,7 +1113,7 @@ func TestProcessPolicies(t *testing.T) { t.Parallel() g := NewWithT(t) - processed := processPolicies(test.policies, test.validator, gateways, routes, services, nil) + processed := processPolicies(test.policies, test.validator, routes, services, gateways) g.Expect(processed).To(BeEquivalentTo(test.expProcessedPolicies)) }) } @@ -1039,12 +1239,15 @@ func TestProcessPolicies_RouteOverlap(t *testing.T) { }, } - gateways := processedGateways{ - Winner: &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: testNs, + gateways := map[types.NamespacedName]*Gateway{ + {Namespace: testNs, Name: "gw"}: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Namespace: testNs, + }, }, + Valid: true, }, } @@ -1053,7 +1256,7 @@ func TestProcessPolicies_RouteOverlap(t *testing.T) { t.Parallel() g := NewWithT(t) - processed := processPolicies(test.policies, test.validator, gateways, test.routes, nil, nil) + processed := processPolicies(test.policies, test.validator, test.routes, nil, gateways) g.Expect(processed).To(HaveLen(1)) for _, pol := range processed { diff --git a/internal/mode/static/state/graph/route_common.go b/internal/mode/static/state/graph/route_common.go index 67df2f8c8b..8e84ae0c32 100644 --- a/internal/mode/static/state/graph/route_common.go +++ b/internal/mode/static/state/graph/route_common.go @@ -31,8 +31,8 @@ type ParentRef struct { SectionName *v1.SectionName // Port is the network port this Route targets. Port *v1.PortNumber - // Gateway is the NamespacedName of the referenced Gateway - Gateway types.NamespacedName + // Gateway is the metadata about the parent Gateway. + Gateway *ParentRefGateway // Idx is the index of the corresponding ParentReference in the Route. Idx int } @@ -40,17 +40,32 @@ type ParentRef struct { // ParentRefAttachmentStatus describes the attachment status of a ParentRef. type ParentRefAttachmentStatus struct { // AcceptedHostnames is an intersection between the hostnames supported by an attached Listener - // and the hostnames from this Route. Key is listener name, value is list of hostnames. + // and the hostnames from this Route. Key is , value is list of hostnames. AcceptedHostnames map[string][]string - // FailedCondition is the condition that describes why the ParentRef is not attached to the Gateway. It is set - // when Attached is false. - FailedCondition conditions.Condition + // FailedConditions are the conditions that describe why the ParentRef is not attached to the Gateway, or other + // failures that may lead to partial attachments. For example, a backendRef could be invalid, but the route can + // still attach. The backendRef condition would be displayed here. + FailedConditions []conditions.Condition // ListenerPort is the port on the Listener that the Route is attached to. ListenerPort v1.PortNumber // Attached indicates if the ParentRef is attached to the Gateway. Attached bool } +// ParentRefGateway contains the NamespacedName and EffectiveNginxProxy of the parent Gateway. +type ParentRefGateway struct { + EffectiveNginxProxy *EffectiveNginxProxy + NamespacedName types.NamespacedName +} + +// CreateParentRefGateway creates a new ParentRefGateway object using a graph.Gateway object. +func CreateParentRefGateway(gateway *Gateway) *ParentRefGateway { + return &ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gateway.Source), + EffectiveNginxProxy: gateway.EffectiveNginxProxy, + } +} + type RouteType string const ( @@ -171,6 +186,11 @@ func CreateRouteKeyL4(obj client.Object) L4RouteKey { } } +// CreateGatewayListenerKey creates a key using the Gateway NamespacedName and Listener name. +func CreateGatewayListenerKey(gwNSName types.NamespacedName, listenerName string) string { + return fmt.Sprintf("%s/%s/%s", gwNSName.Namespace, gwNSName.Name, listenerName) +} + type routeRuleErrors struct { invalid field.ErrorList resolve field.ErrorList @@ -185,12 +205,11 @@ func (e routeRuleErrors) append(newErrors routeRuleErrors) routeRuleErrors { func buildL4RoutesForGateways( tlsRoutes map[types.NamespacedName]*v1alpha.TLSRoute, - gatewayNsNames []types.NamespacedName, services map[types.NamespacedName]*apiv1.Service, - npCfg *EffectiveNginxProxy, + gws map[types.NamespacedName]*Gateway, resolver *referenceGrantResolver, ) map[L4RouteKey]*L4Route { - if len(gatewayNsNames) == 0 { + if len(gws) == 0 { return nil } @@ -198,15 +217,15 @@ func buildL4RoutesForGateways( for _, route := range tlsRoutes { r := buildTLSRoute( route, - gatewayNsNames, + gws, services, - npCfg, resolver.refAllowedFrom(fromTLSRoute(route.Namespace)), ) if r != nil { routes[CreateRouteKeyL4(route)] = r } } + return routes } @@ -215,27 +234,24 @@ func buildRoutesForGateways( validator validation.HTTPFieldsValidator, httpRoutes map[types.NamespacedName]*v1.HTTPRoute, grpcRoutes map[types.NamespacedName]*v1.GRPCRoute, - gatewayNsNames []types.NamespacedName, - effectiveNginxProxy *EffectiveNginxProxy, + gateways map[types.NamespacedName]*Gateway, snippetsFilters map[types.NamespacedName]*SnippetsFilter, ) map[RouteKey]*L7Route { - if len(gatewayNsNames) == 0 { + if len(gateways) == 0 { return nil } routes := make(map[RouteKey]*L7Route) - http2disabled := isHTTP2Disabled(effectiveNginxProxy) - for _, route := range httpRoutes { - r := buildHTTPRoute(validator, route, gatewayNsNames, snippetsFilters) + r := buildHTTPRoute(validator, route, gateways, snippetsFilters) if r != nil { routes[CreateRouteKey(route)] = r } } for _, route := range grpcRoutes { - r := buildGRPCRoute(validator, route, gatewayNsNames, http2disabled, snippetsFilters) + r := buildGRPCRoute(validator, route, gateways, snippetsFilters) if r != nil { routes[CreateRouteKey(route)] = r } @@ -244,22 +260,10 @@ func buildRoutesForGateways( return routes } -func isHTTP2Disabled(npCfg *EffectiveNginxProxy) bool { - if npCfg == nil { - return false - } - - if npCfg.DisableHTTP2 == nil { - return false - } - - return *npCfg.DisableHTTP2 -} - func buildSectionNameRefs( parentRefs []v1.ParentReference, routeNamespace string, - gatewayNsNames []types.NamespacedName, + gws map[types.NamespacedName]*Gateway, ) ([]ParentRef, error) { sectionNameRefs := make([]ParentRef, 0, len(parentRefs)) @@ -270,8 +274,8 @@ func buildSectionNameRefs( uniqueSectionsPerGateway := make(map[key]struct{}) for i, p := range parentRefs { - gw, found := findGatewayForParentRef(p, routeNamespace, gatewayNsNames) - if !found { + gw := findGatewayForParentRef(p, routeNamespace, gws) + if gw == nil { continue } @@ -280,19 +284,20 @@ func buildSectionNameRefs( sectionName = string(*p.SectionName) } + gwNsName := client.ObjectKeyFromObject(gw.Source) k := key{ - gwNsName: gw, + gwNsName: gwNsName, sectionName: sectionName, } if _, exist := uniqueSectionsPerGateway[k]; exist { - return nil, fmt.Errorf("duplicate section name %q for Gateway %s", sectionName, gw.String()) + return nil, fmt.Errorf("duplicate section name %q for Gateway %s", sectionName, gwNsName.String()) } uniqueSectionsPerGateway[k] = struct{}{} sectionNameRefs = append(sectionNameRefs, ParentRef{ Idx: i, - Gateway: gw, + Gateway: CreateParentRefGateway(gw), SectionName: p.SectionName, Port: p.Port, }) @@ -304,85 +309,98 @@ func buildSectionNameRefs( func findGatewayForParentRef( ref v1.ParentReference, routeNamespace string, - gatewayNsNames []types.NamespacedName, -) (gwNsName types.NamespacedName, found bool) { + gws map[types.NamespacedName]*Gateway, +) *Gateway { if ref.Kind != nil && *ref.Kind != kinds.Gateway { - return types.NamespacedName{}, false + return nil } if ref.Group != nil && *ref.Group != v1.GroupName { - return types.NamespacedName{}, false + return nil } - // if the namespace is missing, assume the namespace of the HTTPRoute + // if the namespace is missing, assume the namespace of the Route ns := routeNamespace if ref.Namespace != nil { ns = string(*ref.Namespace) } - for _, gw := range gatewayNsNames { - if gw.Namespace == ns && gw.Name == string(ref.Name) { - return gw, true - } + key := types.NamespacedName{ + Namespace: ns, + Name: string(ref.Name), } - return types.NamespacedName{}, false + if gw, exists := gws[key]; exists { + return gw + } + + return nil } func bindRoutesToListeners( l7Routes map[RouteKey]*L7Route, l4Routes map[L4RouteKey]*L4Route, - gw *Gateway, + gws map[types.NamespacedName]*Gateway, namespaces map[types.NamespacedName]*apiv1.Namespace, ) { - if gw == nil { + if len(gws) == 0 { return } - for _, r := range l7Routes { - bindL7RouteToListeners(r, gw, namespaces) - } + for _, gw := range gws { + for _, r := range l7Routes { + bindL7RouteToListeners(r, gw, namespaces) + } - routes := make([]*L7Route, 0, len(l7Routes)) - for _, r := range l7Routes { - routes = append(routes, r) - } + routes := make([]*L7Route, 0, len(l7Routes)) + for _, r := range l7Routes { + routes = append(routes, r) + } - listenerMap := getListenerHostPortMap(gw.Listeners) - isolateL7RouteListeners(routes, listenerMap) + listenerMap := getListenerHostPortMap(gw.Listeners, gw) + isolateL7RouteListeners(routes, listenerMap) - l4RouteSlice := make([]*L4Route, 0, len(l4Routes)) - for _, r := range l4Routes { - l4RouteSlice = append(l4RouteSlice, r) - } + l4RouteSlice := make([]*L4Route, 0, len(l4Routes)) + for _, r := range l4Routes { + l4RouteSlice = append(l4RouteSlice, r) + } - // Sort the slice by timestamp and name so that we process the routes in the priority order - sort.Slice(l4RouteSlice, func(i, j int) bool { - return ngfSort.LessClientObject(l4RouteSlice[i].Source, l4RouteSlice[j].Source) - }) + // Sort the slice by timestamp and name so that we process the routes in the priority order + sort.Slice(l4RouteSlice, func(i, j int) bool { + return ngfSort.LessClientObject(l4RouteSlice[i].Source, l4RouteSlice[j].Source) + }) - // portHostnamesMap exists to detect duplicate hostnames on the same port - portHostnamesMap := make(map[string]struct{}) + // portHostnamesMap exists to detect duplicate hostnames on the same port + portHostnamesMap := make(map[string]struct{}) - for _, r := range l4RouteSlice { - bindL4RouteToListeners(r, gw, namespaces, portHostnamesMap) - } + for _, r := range l4RouteSlice { + bindL4RouteToListeners(r, gw, namespaces, portHostnamesMap) + } - isolateL4RouteListeners(l4RouteSlice, listenerMap) + isolateL4RouteListeners(l4RouteSlice, listenerMap) + } } type hostPort struct { + gwNsName types.NamespacedName hostname string port v1.PortNumber } -func getListenerHostPortMap(listeners []*Listener) map[string]hostPort { +func getListenerHostPortMap(listeners []*Listener, gw *Gateway) map[string]hostPort { listenerHostPortMap := make(map[string]hostPort, len(listeners)) + gwNsName := types.NamespacedName{ + Name: gw.Source.Name, + Namespace: gw.Source.Namespace, + } for _, l := range listeners { - listenerHostPortMap[l.Name] = hostPort{ + key := CreateGatewayListenerKey(client.ObjectKeyFromObject(gw.Source), l.Name) + listenerHostPortMap[key] = hostPort{ hostname: getHostname(l.Source.Hostname), port: l.Source.Port, + gwNsName: gwNsName, } } + return listenerHostPortMap } @@ -412,22 +430,31 @@ func isolateHostnamesForParentRefs(parentRef []ParentRef, listenerHostnameMap ma continue } + if ref.Attachment == nil { + continue + } + acceptedHostnames := ref.Attachment.AcceptedHostnames hostnamesToRemoves := make(map[string]struct{}) - for listenerName, hostnames := range acceptedHostnames { + for key, hostnames := range acceptedHostnames { if len(hostnames) == 0 { continue } for _, h := range hostnames { for lName, lHostPort := range listenerHostnameMap { + // skip comparison if not part of the same gateway + if lHostPort.gwNsName != ref.Gateway.NamespacedName { + continue + } + // skip comparison if it is a catch all listener block if lHostPort.hostname == "" { continue } - // for L7Routes, we compare the hostname, port and listener name combination + // for L7Routes, we compare the hostname, port and listenerName combination // to identify if hostname needs to be isolated. - if h == lHostPort.hostname && listenerName != lName { + if h == lHostPort.hostname && key != lName { // for L4Routes, we only compare the hostname and listener name combination // because we do not allow l4Routes to attach to the same listener // if they share the same port and hostname. @@ -439,7 +466,7 @@ func isolateHostnamesForParentRefs(parentRef []ParentRef, listenerHostnameMap ma } isolatedHostnames := removeHostnames(hostnames, hostnamesToRemoves) - ref.Attachment.AcceptedHostnames[listenerName] = isolatedHostnames + ref.Attachment.AcceptedHostnames[key] = isolatedHostnames } } } @@ -475,7 +502,7 @@ func validateParentRef( // Case 1: Attachment is not possible because the specified SectionName does not match any Listeners in the // Gateway. if !listenerExists { - attachment.FailedCondition = staticConds.NewRouteNoMatchingParent() + attachment.FailedConditions = append(attachment.FailedConditions, staticConds.NewRouteNoMatchingParent()) return attachment, nil } @@ -483,25 +510,19 @@ func validateParentRef( if ref.Port != nil { valErr := field.Forbidden(path.Child("port"), "cannot be set") - attachment.FailedCondition = staticConds.NewRouteUnsupportedValue(valErr.Error()) - return attachment, attachableListeners - } - - // Case 3: the parentRef references an ignored Gateway resource. - - referencesWinningGw := ref.Gateway.Namespace == gw.Source.Namespace && ref.Gateway.Name == gw.Source.Name - - if !referencesWinningGw { - attachment.FailedCondition = staticConds.NewRouteNotAcceptedGatewayIgnored() + attachment.FailedConditions = append( + attachment.FailedConditions, staticConds.NewRouteUnsupportedValue(valErr.Error()), + ) return attachment, attachableListeners } - // Case 4: Attachment is not possible because Gateway is invalid + // Case 3: Attachment is not possible because Gateway is invalid if !gw.Valid { - attachment.FailedCondition = staticConds.NewRouteInvalidGateway() + attachment.FailedConditions = append(attachment.FailedConditions, staticConds.NewRouteInvalidGateway()) return attachment, attachableListeners } + return attachment, attachableListeners } @@ -518,13 +539,25 @@ func bindL4RouteToListeners( for i := range route.ParentRefs { ref := &(route.ParentRefs)[i] + gwNsName := types.NamespacedName{ + Name: gw.Source.Name, + Namespace: gw.Source.Namespace, + } + + if ref.Gateway.NamespacedName != gwNsName { + continue + } + attachment, attachableListeners := validateParentRef(ref, gw) - if attachment.FailedCondition != (conditions.Condition{}) { + if len(attachment.FailedConditions) > 0 { continue } - // Winning Gateway + if cond, ok := route.Spec.BackendRef.InvalidForGateways[gwNsName]; ok { + attachment.FailedConditions = append(attachment.FailedConditions, cond) + } + // Try to attach Route to all matching listeners cond, attached := tryToAttachL4RouteToListeners( @@ -536,7 +569,7 @@ func bindL4RouteToListeners( portHostnamesMap, ) if !attached { - attachment.FailedCondition = cond + attachment.FailedConditions = append(attachment.FailedConditions, cond) continue } if cond != (conditions.Condition{}) { @@ -653,7 +686,7 @@ func bindToListenerL4( return true, false, true } - refStatus.AcceptedHostnames[string(l.Source.Name)] = hostnames + refStatus.AcceptedHostnames[CreateGatewayListenerKey(l.GatewayName, l.Name)] = hostnames l.L4Routes[CreateRouteKeyL4(route.Source)] = route return true, true, true @@ -671,13 +704,36 @@ func bindL7RouteToListeners( for i := range route.ParentRefs { ref := &(route.ParentRefs)[i] + gwNsName := types.NamespacedName{ + Name: gw.Source.Name, + Namespace: gw.Source.Namespace, + } + + if ref.Gateway.NamespacedName != gwNsName { + continue + } + attachment, attachableListeners := validateParentRef(ref, gw) - if attachment.FailedCondition != (conditions.Condition{}) { + if route.RouteType == RouteTypeGRPC && isHTTP2Disabled(gw.EffectiveNginxProxy) { + msg := "HTTP2 is disabled - cannot configure GRPCRoutes" + attachment.FailedConditions = append( + attachment.FailedConditions, staticConds.NewRouteUnsupportedConfiguration(msg), + ) + } + + if len(attachment.FailedConditions) > 0 { continue } - // Winning Gateway + for _, rule := range route.Spec.Rules { + for _, backendRef := range rule.BackendRefs { + if cond, ok := backendRef.InvalidForGateways[gwNsName]; ok { + attachment.FailedConditions = append(attachment.FailedConditions, cond) + } + } + } + // Try to attach Route to all matching listeners cond, attached := tryToAttachL7RouteToListeners( @@ -688,7 +744,7 @@ func bindL7RouteToListeners( namespaces, ) if !attached { - attachment.FailedCondition = cond + attachment.FailedConditions = append(attachment.FailedConditions, cond) continue } if cond != (conditions.Condition{}) { @@ -699,6 +755,18 @@ func bindL7RouteToListeners( } } +func isHTTP2Disabled(npCfg *EffectiveNginxProxy) bool { + if npCfg == nil { + return false + } + + if npCfg.DisableHTTP2 == nil { + return false + } + + return *npCfg.DisableHTTP2 +} + // tryToAttachRouteToListeners tries to attach the route to the listeners that match the parentRef and the hostnames. // There are two cases: // (1) If it succeeds in attaching at least one listener it will return true. The returned condition will be empty if @@ -731,7 +799,7 @@ func tryToAttachL7RouteToListeners( return true, false } - refStatus.AcceptedHostnames[string(l.Source.Name)] = hostnames + refStatus.AcceptedHostnames[CreateGatewayListenerKey(l.GatewayName, l.Name)] = hostnames refStatus.ListenerPort = l.Source.Port l.Routes[rk] = route diff --git a/internal/mode/static/state/graph/route_common_test.go b/internal/mode/static/state/graph/route_common_test.go index 8268af036e..dc127bb3b8 100644 --- a/internal/mode/static/state/graph/route_common_test.go +++ b/internal/mode/static/state/graph/route_common_test.go @@ -54,27 +54,44 @@ func TestBuildSectionNameRefs(t *testing.T) { }, } - gwNsNames := []types.NamespacedName{gwNsName1, gwNsName2} + gws := map[types.NamespacedName]*Gateway{ + gwNsName1: { + Source: &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: gwNsName1.Name, + Namespace: gwNsName1.Namespace, + }, + }, + }, + gwNsName2: { + Source: &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: gwNsName2.Name, + Namespace: gwNsName2.Namespace, + }, + }, + }, + } expected := []ParentRef{ { Idx: 0, - Gateway: gwNsName1, + Gateway: CreateParentRefGateway(gws[gwNsName1]), SectionName: parentRefs[0].SectionName, }, { Idx: 2, - Gateway: gwNsName2, + Gateway: CreateParentRefGateway(gws[gwNsName2]), SectionName: parentRefs[2].SectionName, }, { Idx: 3, - Gateway: gwNsName1, + Gateway: CreateParentRefGateway(gws[gwNsName1]), SectionName: parentRefs[3].SectionName, }, { Idx: 4, - Gateway: gwNsName2, + Gateway: CreateParentRefGateway(gws[gwNsName2]), SectionName: parentRefs[4].SectionName, }, } @@ -126,7 +143,7 @@ func TestBuildSectionNameRefs(t *testing.T) { t.Parallel() g := NewWithT(t) - result, err := buildSectionNameRefs(test.parentRefs, routeNamespace, gwNsNames) + result, err := buildSectionNameRefs(test.parentRefs, routeNamespace, gws) g.Expect(result).To(Equal(test.expectedRefs)) if test.expectedError != nil { g.Expect(err).To(Equal(test.expectedError)) @@ -181,35 +198,46 @@ func TestFindGatewayForParentRef(t *testing.T) { Kind: helpers.GetPointer[gatewayv1.Kind]("NotGateway"), Name: gatewayv1.ObjectName(gwNsName2.Name), }, - expectedFound: false, - expectedGwNsName: types.NamespacedName{}, - name: "wrong kind", + expectedFound: false, + name: "wrong kind", }, { ref: gatewayv1.ParentReference{ Group: helpers.GetPointer[gatewayv1.Group]("wrong-group"), Name: gatewayv1.ObjectName(gwNsName2.Name), }, - expectedFound: false, - expectedGwNsName: types.NamespacedName{}, - name: "wrong group", + expectedFound: false, + name: "wrong group", }, { ref: gatewayv1.ParentReference{ Namespace: helpers.GetPointer(gatewayv1.Namespace(gwNsName1.Namespace)), Name: "some-gateway", }, - expectedFound: false, - expectedGwNsName: types.NamespacedName{}, - name: "not found", + expectedFound: false, + name: "not found", }, } routeNamespace := "test-2" - gwNsNames := []types.NamespacedName{ - gwNsName1, - gwNsName2, + gws := map[types.NamespacedName]*Gateway{ + gwNsName1: { + Source: &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: gwNsName1.Name, + Namespace: gwNsName1.Namespace, + }, + }, + }, + gwNsName2: { + Source: &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: gwNsName2.Name, + Namespace: gwNsName2.Namespace, + }, + }, + }, } for _, test := range tests { @@ -217,9 +245,13 @@ func TestFindGatewayForParentRef(t *testing.T) { t.Parallel() g := NewWithT(t) - gw, found := findGatewayForParentRef(test.ref, routeNamespace, gwNsNames) - g.Expect(found).To(Equal(test.expectedFound)) - g.Expect(gw).To(Equal(test.expectedGwNsName)) + gw := findGatewayForParentRef(test.ref, routeNamespace, gws) + if test.expectedFound { + g.Expect(gw).ToNot(BeNil()) + g.Expect(client.ObjectKeyFromObject(gw.Source)).To(Equal(test.expectedGwNsName)) + } else { + g.Expect(gw).To(BeNil()) + } }) } } @@ -229,6 +261,10 @@ func TestBindRouteToListeners(t *testing.T) { createListener := func(name string) *Listener { return &Listener{ Name: name, + GatewayName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Source: gatewayv1.Listener{ Name: gatewayv1.SectionName(name), Hostname: (*gatewayv1.Hostname)(helpers.GetPointer("foo.example.com")), @@ -316,7 +352,7 @@ func TestBindRouteToListeners(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gateway), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gateway)}, SectionName: hr.Spec.ParentRefs[0].SectionName, }, }, @@ -336,7 +372,7 @@ func TestBindRouteToListeners(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, }, }, @@ -349,7 +385,7 @@ func TestBindRouteToListeners(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, }, }, @@ -363,7 +399,7 @@ func TestBindRouteToListeners(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrWithNilSectionName.Spec.ParentRefs[0].SectionName, }, }, @@ -376,7 +412,7 @@ func TestBindRouteToListeners(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrWithEmptySectionName.Spec.ParentRefs[0].SectionName, }, }, @@ -389,7 +425,7 @@ func TestBindRouteToListeners(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrWithNonExistingListener.Spec.ParentRefs[0].SectionName, }, }, @@ -402,26 +438,12 @@ func TestBindRouteToListeners(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrWithPort.Spec.ParentRefs[0].SectionName, Port: hrWithPort.Spec.ParentRefs[0].Port, }, }, } - ignoredGwNsName := types.NamespacedName{Namespace: "test", Name: "ignored-gateway"} - routeWithIgnoredGateway := &L7Route{ - RouteType: RouteTypeHTTP, - Source: hr, - Valid: true, - Attachable: true, - ParentRefs: []ParentRef{ - { - Idx: 0, - Gateway: ignoredGwNsName, - SectionName: hr.Spec.ParentRefs[0].SectionName, - }, - }, - } invalidRoute := &L7Route{ RouteType: RouteTypeHTTP, Valid: false, @@ -429,7 +451,7 @@ func TestBindRouteToListeners(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, }, }, @@ -443,6 +465,19 @@ func TestBindRouteToListeners(t *testing.T) { l.Source.Hostname = helpers.GetPointer[gatewayv1.Hostname]("bar.example.com") }) + routeWithInvalidBackendRefs := createNormalHTTPRoute(gw) + routeWithInvalidBackendRefs.Spec.Rules = []RouteRule{ + { + BackendRefs: []BackendRef{ + { + InvalidForGateways: map[types.NamespacedName]conditions.Condition{ + client.ObjectKeyFromObject(gw): {Message: "invalid backend"}, + }, + }, + }, + }, + } + createGRPCRouteWithSectionNameAndPort := func( sectionName *gatewayv1.SectionName, port *gatewayv1.PortNumber, @@ -487,7 +522,7 @@ func TestBindRouteToListeners(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gateway), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gateway)}, SectionName: gr.Spec.ParentRefs[0].SectionName, }, }, @@ -519,12 +554,15 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-80-1": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-80-1", + ): {"foo.example.com"}, }, }, }, @@ -550,12 +588,15 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrWithNilSectionName.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-80-1": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-80-1", + ): {"foo.example.com"}, }, }, }, @@ -582,13 +623,19 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrWithEmptySectionName.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-80": {"foo.example.com"}, - "listener-8080": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-80", + ): {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-8080", + ): {"foo.example.com"}, }, }, }, @@ -619,11 +666,11 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrWithEmptySectionName.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: false, - FailedCondition: staticConds.NewRouteInvalidListener(), + FailedConditions: []conditions.Condition{staticConds.NewRouteInvalidListener()}, AcceptedHostnames: map[string][]string{}, }, }, @@ -645,13 +692,15 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrWithPort.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: false, - FailedCondition: staticConds.NewRouteUnsupportedValue( - `spec.parentRefs[0].port: Forbidden: cannot be set`, - ), + FailedConditions: []conditions.Condition{ + staticConds.NewRouteUnsupportedValue( + `spec.parentRefs[0].port: Forbidden: cannot be set`, + ), + }, AcceptedHostnames: map[string][]string{}, }, Port: hrWithPort.Spec.ParentRefs[0].Port, @@ -674,11 +723,11 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrWithNonExistingListener.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: false, - FailedCondition: staticConds.NewRouteNoMatchingParent(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingParent()}, AcceptedHostnames: map[string][]string{}, }, }, @@ -700,11 +749,11 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: false, - FailedCondition: staticConds.NewRouteInvalidListener(), + FailedConditions: []conditions.Condition{staticConds.NewRouteInvalidListener()}, AcceptedHostnames: map[string][]string{}, }, }, @@ -726,11 +775,11 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: false, - FailedCondition: staticConds.NewRouteNoMatchingListenerHostname(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingListenerHostname()}, AcceptedHostnames: map[string][]string{}, }, }, @@ -740,32 +789,6 @@ func TestBindRouteToListeners(t *testing.T) { }, name: "no matching listener hostname", }, - { - route: routeWithIgnoredGateway, - gateway: &Gateway{ - Source: gw, - Valid: true, - Listeners: []*Listener{ - createListener("listener-80-1"), - }, - }, - expectedSectionNameRefs: []ParentRef{ - { - Idx: 0, - Gateway: ignoredGwNsName, - SectionName: hr.Spec.ParentRefs[0].SectionName, - Attachment: &ParentRefAttachmentStatus{ - Attached: false, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - AcceptedHostnames: map[string][]string{}, - }, - }, - }, - expectedGatewayListeners: []*Listener{ - createListener("listener-80-1"), - }, - name: "gateway is ignored", - }, { route: invalidRoute, gateway: &Gateway{ @@ -778,7 +801,7 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Attachment: nil, SectionName: hr.Spec.ParentRefs[0].SectionName, }, @@ -800,11 +823,11 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: false, - FailedCondition: staticConds.NewRouteInvalidGateway(), + FailedConditions: []conditions.Condition{staticConds.NewRouteInvalidGateway()}, AcceptedHostnames: map[string][]string{}, }, }, @@ -828,12 +851,15 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-80-1": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-80-1", + ): {"foo.example.com"}, }, }, }, @@ -861,12 +887,15 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-80-1": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-80-1", + ): {"foo.example.com"}, }, }, }, @@ -894,12 +923,15 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-80-1": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-80-1", + ): {"foo.example.com"}, }, }, }, @@ -935,11 +967,11 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: false, - FailedCondition: staticConds.NewRouteNotAllowedByListeners(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNotAllowedByListeners()}, AcceptedHostnames: map[string][]string{}, }, }, @@ -977,12 +1009,15 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-80-1": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-80-1", + ): {"foo.example.com"}, }, }, }, @@ -1021,11 +1056,11 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gwDiffNamespace), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gwDiffNamespace)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: false, - FailedCondition: staticConds.NewRouteNotAllowedByListeners(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNotAllowedByListeners()}, AcceptedHostnames: map[string][]string{}, }, }, @@ -1059,12 +1094,15 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-80-1": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-80-1", + ): {"foo.example.com"}, }, }, }, @@ -1101,12 +1139,15 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gwDiffNamespace), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gwDiffNamespace)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-80-1": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-80-1", + ): {"foo.example.com"}, }, }, }, @@ -1144,11 +1185,11 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: gr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: false, - FailedCondition: staticConds.NewRouteNotAllowedByListeners(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNotAllowedByListeners()}, AcceptedHostnames: map[string][]string{}, }, }, @@ -1165,6 +1206,55 @@ func TestBindRouteToListeners(t *testing.T) { }, name: "grpc route not allowed when listener kind is HTTPRoute", }, + { + route: createNormalGRPCRoute(gw), + gateway: &Gateway{ + Source: gw, + Valid: true, + Listeners: []*Listener{ + createModifiedListener("listener-80-1", func(l *Listener) { + l.SupportedKinds = []gatewayv1.RouteGroupKind{ + {Kind: gatewayv1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(gr): getLastNormalGRPCRoute(), + } + }), + }, + EffectiveNginxProxy: &EffectiveNginxProxy{ + DisableHTTP2: helpers.GetPointer(true), + }, + }, + expectedSectionNameRefs: []ParentRef{ + { + Idx: 0, + Gateway: &ParentRefGateway{ + NamespacedName: client.ObjectKeyFromObject(gw), + }, + SectionName: gr.Spec.ParentRefs[0].SectionName, + Attachment: &ParentRefAttachmentStatus{ + Attached: false, + FailedConditions: []conditions.Condition{ + staticConds.NewRouteUnsupportedConfiguration( + `HTTP2 is disabled - cannot configure GRPCRoutes`, + ), + }, + AcceptedHostnames: map[string][]string{}, + }, + }, + }, + expectedGatewayListeners: []*Listener{ + createModifiedListener("listener-80-1", func(l *Listener) { + l.SupportedKinds = []gatewayv1.RouteGroupKind{ + {Kind: gatewayv1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(gr): getLastNormalGRPCRoute(), + } + }), + }, + name: "grpc route not allowed when HTTP2 is disabled", + }, { route: createNormalHTTPRoute(gw), gateway: &Gateway{ @@ -1186,12 +1276,15 @@ func TestBindRouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-80-1": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-80-1", + ): {"foo.example.com"}, }, }, }, @@ -1210,6 +1303,43 @@ func TestBindRouteToListeners(t *testing.T) { }, name: "http route allowed when listener kind is HTTPRoute", }, + { + route: routeWithInvalidBackendRefs, + gateway: &Gateway{ + Source: gw, + Valid: true, + Listeners: []*Listener{ + createListener("listener-80-1"), + }, + }, + expectedSectionNameRefs: []ParentRef{ + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: hr.Spec.ParentRefs[0].SectionName, + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + FailedConditions: []conditions.Condition{ + {Message: "invalid backend"}, + }, + AcceptedHostnames: map[string][]string{ + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-80-1", + ): {"foo.example.com"}, + }, + }, + }, + }, + expectedGatewayListeners: []*Listener{ + createModifiedListener("listener-80-1", func(l *Listener) { + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): routeWithInvalidBackendRefs, + } + }), + }, + name: "route still allowed if backendRef failure conditions exist", + }, } namespaces := map[types.NamespacedName]*v1.Namespace{ @@ -1220,6 +1350,7 @@ func TestBindRouteToListeners(t *testing.T) { }, }, } + for _, test := range tests { t.Run(test.name, func(t *testing.T) { g := NewWithT(t) @@ -1490,6 +1621,10 @@ func TestBindL4RouteToListeners(t *testing.T) { createListener := func(name string) *Listener { return &Listener{ Name: name, + GatewayName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Source: gatewayv1.Listener{ Name: gatewayv1.SectionName(name), Hostname: (*gatewayv1.Hostname)(helpers.GetPointer("foo.example.com")), @@ -1568,7 +1703,7 @@ func TestBindL4RouteToListeners(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gateway), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gateway)}, SectionName: tr.Spec.ParentRefs[0].SectionName, }, }, @@ -1587,7 +1722,7 @@ func TestBindL4RouteToListeners(t *testing.T) { noMatchingParentAttachment := ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingParent()}, } notAttachableRoute := &L4Route{ @@ -1599,7 +1734,7 @@ func TestBindL4RouteToListeners(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tr.Spec.ParentRefs[0].SectionName, }, }, @@ -1613,27 +1748,19 @@ func TestBindL4RouteToListeners(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tr.Spec.ParentRefs[0].SectionName, Port: helpers.GetPointer[gatewayv1.PortNumber](80), }, }, Attachable: true, } - routeReferencesWrongNamespace := &L4Route{ - Source: tr, - Spec: L4RouteSpec{ - Hostnames: tr.Spec.Hostnames, - }, - Valid: true, - ParentRefs: []ParentRef{ - { - Idx: 0, - Gateway: client.ObjectKeyFromObject(gwWrongNamespace), - SectionName: tr.Spec.ParentRefs[0].SectionName, - }, + + routeWithInvalidBackendRefs := createNormalRoute(gw) + routeWithInvalidBackendRefs.Spec.BackendRef = BackendRef{ + InvalidForGateways: map[types.NamespacedName]conditions.Condition{ + client.ObjectKeyFromObject(gw): {Message: "invalid backend"}, }, - Attachable: true, } tests := []struct { @@ -1649,6 +1776,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-443"), }, @@ -1656,12 +1787,15 @@ func TestBindL4RouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-443": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-443", + ): {"foo.example.com"}, }, }, }, @@ -1680,6 +1814,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-443"), }, @@ -1687,7 +1825,7 @@ func TestBindL4RouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tr.Spec.ParentRefs[0].SectionName, }, }, @@ -1701,6 +1839,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-444"), }, @@ -1709,7 +1851,7 @@ func TestBindL4RouteToListeners(t *testing.T) { { Attachment: &noMatchingParentAttachment, SectionName: tr.Spec.ParentRefs[0].SectionName, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Idx: 0, }, }, @@ -1723,6 +1865,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-443"), }, @@ -1731,16 +1877,15 @@ func TestBindL4RouteToListeners(t *testing.T) { { Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: conditions.Condition{ - Type: "Accepted", - Status: "False", - Reason: "UnsupportedValue", - Message: "spec.parentRefs[0].port: Forbidden: cannot be set", + FailedConditions: []conditions.Condition{ + staticConds.NewRouteUnsupportedValue( + `spec.parentRefs[0].port: Forbidden: cannot be set`, + ), }, Attached: false, }, SectionName: tr.Spec.ParentRefs[0].SectionName, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Idx: 0, Port: helpers.GetPointer[gatewayv1.PortNumber](80), }, @@ -1750,42 +1895,15 @@ func TestBindL4RouteToListeners(t *testing.T) { }, name: "port is not nil", }, - { - route: routeReferencesWrongNamespace, - gateway: &Gateway{ - Source: gw, - Valid: true, - Listeners: []*Listener{ - createListener("listener-443"), - }, - }, - expectedSectionNameRefs: []ParentRef{ - { - Attachment: &ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: conditions.Condition{ - Type: "Accepted", - Status: "False", - Reason: "GatewayIgnored", - Message: "The Gateway is ignored by the controller", - }, - Attached: false, - }, - SectionName: tr.Spec.ParentRefs[0].SectionName, - Gateway: client.ObjectKeyFromObject(gwWrongNamespace), - Idx: 0, - }, - }, - expectedGatewayListeners: []*Listener{ - createListener("listener-443"), - }, - name: "ignored gateway", - }, { route: createNormalRoute(gw), gateway: &Gateway{ Source: gw, Valid: false, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-443"), }, @@ -1794,16 +1912,11 @@ func TestBindL4RouteToListeners(t *testing.T) { { Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: conditions.Condition{ - Type: "Accepted", - Status: "False", - Reason: "InvalidGateway", - Message: "Gateway is invalid", - }, - Attached: false, + FailedConditions: []conditions.Condition{staticConds.NewRouteInvalidGateway()}, + Attached: false, }, SectionName: tr.Spec.ParentRefs[0].SectionName, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Idx: 0, }, }, @@ -1817,8 +1930,13 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gwWrongNamespace, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "wrong", + Name: "gateway", + }, Listeners: []*Listener{ createModifiedListener("listener-443", func(l *Listener) { + l.GatewayName = client.ObjectKeyFromObject(gwWrongNamespace) l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ Namespaces: &gatewayv1.RouteNamespaces{From: helpers.GetPointer( gatewayv1.FromNamespaces("Same"), @@ -1830,21 +1948,17 @@ func TestBindL4RouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gwWrongNamespace), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gwWrongNamespace)}, SectionName: tr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: conditions.Condition{ - Type: "Accepted", - Status: "False", - Reason: "NotAllowedByListeners", - Message: "Route is not allowed by any listener", - }, + FailedConditions: []conditions.Condition{staticConds.NewRouteNotAllowedByListeners()}, }, }, }, expectedGatewayListeners: []*Listener{ createModifiedListener("listener-443", func(l *Listener) { + l.GatewayName = client.ObjectKeyFromObject(gwWrongNamespace) l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ Namespaces: &gatewayv1.RouteNamespaces{From: helpers.GetPointer( gatewayv1.FromNamespaces("Same"), @@ -1859,6 +1973,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createModifiedListener("listener-443", func(l *Listener) { l.Valid = false @@ -1868,11 +1986,14 @@ func TestBindL4RouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "listener-443": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-443", + ): {"foo.example.com"}, }, Attached: true, }, @@ -1886,11 +2007,14 @@ func TestBindL4RouteToListeners(t *testing.T) { r.ParentRefs = []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "listener-443": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-443", + ): {"foo.example.com"}, }, Attached: true, }, @@ -1908,7 +2032,11 @@ func TestBindL4RouteToListeners(t *testing.T) { route: createNormalRoute(gw), gateway: &Gateway{ Source: gw, - Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, + Valid: true, Listeners: []*Listener{ createModifiedListener("listener-443", func(l *Listener) { l.Source.Hostname = (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")) @@ -1918,11 +2046,11 @@ func TestBindL4RouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tr.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingListenerHostname(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingListenerHostname()}, }, }, }, @@ -1940,6 +2068,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-443"), }, @@ -1947,11 +2079,14 @@ func TestBindL4RouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-443": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-443", + ): {"foo.example.com"}, }, }, }, @@ -1972,6 +2107,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-443"), }, @@ -1979,11 +2118,14 @@ func TestBindL4RouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-443": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-443", + ): {"foo.example.com"}, }, }, SectionName: helpers.GetPointer[gatewayv1.SectionName](""), @@ -2001,7 +2143,11 @@ func TestBindL4RouteToListeners(t *testing.T) { { route: createNormalRoute(gw), gateway: &Gateway{ - Source: gw, + Source: gw, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Valid: true, Listeners: []*Listener{}, }, @@ -2009,7 +2155,7 @@ func TestBindL4RouteToListeners(t *testing.T) { { Attachment: &noMatchingParentAttachment, SectionName: tr.Spec.ParentRefs[0].SectionName, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Idx: 0, }, }, @@ -2022,7 +2168,11 @@ func TestBindL4RouteToListeners(t *testing.T) { }), gateway: &Gateway{ Source: gw, - Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, + Valid: true, Listeners: []*Listener{ createListener("listener-443"), }, @@ -2030,11 +2180,14 @@ func TestBindL4RouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Attachment: &ParentRefAttachmentStatus{ Attached: true, AcceptedHostnames: map[string][]string{ - "listener-443": {"foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-443", + ): {"foo.example.com"}, }, }, SectionName: helpers.GetPointer[gatewayv1.SectionName]("listener-443"), @@ -2054,6 +2207,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createModifiedListener("listener-443", func(l *Listener) { l.SupportedKinds = nil @@ -2063,10 +2220,10 @@ func TestBindL4RouteToListeners(t *testing.T) { expectedSectionNameRefs: []ParentRef{ { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAllowedByListeners(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNotAllowedByListeners()}, }, SectionName: helpers.GetPointer[gatewayv1.SectionName]("listener-443"), }, @@ -2078,6 +2235,47 @@ func TestBindL4RouteToListeners(t *testing.T) { }, name: "route kind not allowed", }, + { + route: routeWithInvalidBackendRefs, + gateway: &Gateway{ + Source: gw, + Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, + Listeners: []*Listener{ + createListener("listener-443"), + }, + }, + expectedSectionNameRefs: []ParentRef{ + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: tr.Spec.ParentRefs[0].SectionName, + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + FailedConditions: []conditions.Condition{ + {Message: "invalid backend"}, + }, + AcceptedHostnames: map[string][]string{ + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "listener-443", + ): {"foo.example.com"}, + }, + }, + }, + }, + expectedGatewayListeners: []*Listener{ + createModifiedListener("listener-443", func(l *Listener) { + l.L4Routes = map[L4RouteKey]*L4Route{ + CreateRouteKeyL4(tr): routeWithInvalidBackendRefs, + } + }), + }, + name: "route still allowed if backendRef failure conditions exist", + }, } namespaces := map[types.NamespacedName]*v1.Namespace{ @@ -2133,7 +2331,6 @@ func TestBuildL4RoutesForGateways_NoGateways(t *testing.T) { g.Expect(buildL4RoutesForGateways( tlsRoutes, - nil, services, nil, refGrantResolver, @@ -2176,6 +2373,11 @@ func TestTryToAttachL4RouteToListeners_NoAttachableListeners(t *testing.T) { g.Expect(attachable).To(BeFalse()) } +type parentRef struct { + sectionName *gatewayv1.SectionName + gw types.NamespacedName +} + func TestIsolateL4Listeners(t *testing.T) { t.Parallel() gw := &gatewayv1.Gateway{ @@ -2185,12 +2387,26 @@ func TestIsolateL4Listeners(t *testing.T) { }, } + gw1 := &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway1", + }, + } + createTLSRouteWithSectionNameAndPort := func( name string, - sectionName *gatewayv1.SectionName, + parentRef []parentRef, ns string, hostnames ...gatewayv1.Hostname, ) *v1alpha2.TLSRoute { + var parentRefs []gatewayv1.ParentReference + for _, p := range parentRef { + parentRefs = append(parentRefs, gatewayv1.ParentReference{ + Name: gatewayv1.ObjectName(p.gw.Name), + SectionName: p.sectionName, + }) + } return &v1alpha2.TLSRoute{ ObjectMeta: metav1.ObjectMeta{ Namespace: ns, @@ -2198,12 +2414,7 @@ func TestIsolateL4Listeners(t *testing.T) { }, Spec: v1alpha2.TLSRouteSpec{ CommonRouteSpec: gatewayv1.CommonRouteSpec{ - ParentRefs: []gatewayv1.ParentReference{ - { - Name: gatewayv1.ObjectName(gw.Name), - SectionName: sectionName, - }, - }, + ParentRefs: parentRefs, }, Hostnames: hostnames, }, @@ -2213,31 +2424,56 @@ func TestIsolateL4Listeners(t *testing.T) { routeHostnames := []gatewayv1.Hostname{"bar.com", "*.example.com", "*.foo.example.com", "abc.foo.example.com"} tr1 := createTLSRouteWithSectionNameAndPort( "tr1", - helpers.GetPointer[gatewayv1.SectionName]("empty-hostname"), + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("empty-hostname"), + }, + }, "test", routeHostnames..., ) tr2 := createTLSRouteWithSectionNameAndPort( "tr2", - helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + }, + }, "test", routeHostnames..., ) tr3 := createTLSRouteWithSectionNameAndPort( "tr3", - helpers.GetPointer[gatewayv1.SectionName]("foo-wildcard-example-com"), + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("foo-wildcard-example-com"), + }, + }, "test", routeHostnames..., ) tr4 := createTLSRouteWithSectionNameAndPort( "tr4", - helpers.GetPointer[gatewayv1.SectionName]("abc-com"), + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("abc-com"), + }, + }, "test", routeHostnames..., ) tr5 := createTLSRouteWithSectionNameAndPort( "tr5", - helpers.GetPointer[gatewayv1.SectionName]("no-match"), + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("no-match"), + }, + }, "test", routeHostnames..., ) @@ -2256,11 +2492,8 @@ func TestIsolateL4Listeners(t *testing.T) { }, ParentRefs: []ParentRef{ { - Idx: 0, - Gateway: client.ObjectKey{ - Namespace: gw.Namespace, - Name: gw.Name, - }, + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: sectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: acceptedHostnames, @@ -2273,29 +2506,29 @@ func TestIsolateL4Listeners(t *testing.T) { } acceptedHostnamesEmptyHostname := map[string][]string{ - "empty-hostname": { + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "empty-hostname"): { "bar.com", "*.example.com", "*.foo.example.com", "abc.foo.example.com", }, } acceptedHostnamesWildcardExample := map[string][]string{ - "wildcard-example-com": { + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "wildcard-example-com"): { "*.example.com", "*.foo.example.com", "abc.foo.example.com", }, } acceptedHostnamesFooWildcardExample := map[string][]string{ - "foo-wildcard-example-com": { + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "foo-wildcard-example-com"): { "*.foo.example.com", "abc.foo.example.com", }, } acceptedHostnamesAbcCom := map[string][]string{ - "abc-com": { + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "abc-com"): { "abc.foo.example.com", }, } acceptedHostnamesNoMatch := map[string][]string{ - "no-match": {}, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "no-match"): {}, } routesHostnameIntersection := []*L4Route{ @@ -2336,22 +2569,42 @@ func TestIsolateL4Listeners(t *testing.T) { } listenerMapHostnameIntersection := map[string]hostPort{ - "empty-hostname": {hostname: "", port: 80}, - "wildcard-example-com": {hostname: "*.example.com", port: 80}, - "foo-wildcard-example-com": {hostname: "*.foo.example.com", port: 80}, - "abc-com": {hostname: "abc.foo.example.com", port: 80}, - "no-match": {hostname: "no-match.cafe.com", port: 80}, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "empty-hostname"): { + hostname: "", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), + }, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "wildcard-example-com"): { + hostname: "*.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), + }, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "foo-wildcard-example-com"): { + hostname: "*.foo.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), + }, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "abc-com"): { + hostname: "abc.foo.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), + }, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "no-match"): { + hostname: "no-match.cafe.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), + }, } expectedResultHostnameIntersection := map[string][]ParentRef{ "tr1": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tr1.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "empty-hostname": {"bar.com"}, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "empty-hostname"): {"bar.com"}, }, Attached: true, ListenerPort: 80, @@ -2361,11 +2614,14 @@ func TestIsolateL4Listeners(t *testing.T) { "tr2": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tr2.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "wildcard-example-com": {"*.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "wildcard-example-com", + ): {"*.example.com"}, }, Attached: true, ListenerPort: 80, @@ -2375,11 +2631,14 @@ func TestIsolateL4Listeners(t *testing.T) { "tr3": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tr3.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "foo-wildcard-example-com": {"*.foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "foo-wildcard-example-com", + ): {"*.foo.example.com"}, }, Attached: true, ListenerPort: 80, @@ -2389,11 +2648,11 @@ func TestIsolateL4Listeners(t *testing.T) { "tr4": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tr4.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "abc-com": {"abc.foo.example.com"}, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "abc-com"): {"abc.foo.example.com"}, }, Attached: true, ListenerPort: 80, @@ -2403,11 +2662,11 @@ func TestIsolateL4Listeners(t *testing.T) { "tr5": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tr5.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "no-match": {}, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "no-match"): {}, }, Attached: true, ListenerPort: 80, @@ -2444,6 +2703,43 @@ func TestIsolateL4Listeners(t *testing.T) { "tls_flavor": {"flavor.example.com"}, } + routeHostname := []gatewayv1.Hostname{"coffee.example.com", "flavor.example.com"} + acceptedHostanamesMultipleGateways := map[string][]string{ + "tls_coffee": {"coffee.example.com", "flavor.example.com"}, + "tls_flavor": {"coffee.example.com", "flavor.example.com"}, + } + tlsCoffeeRoute1 := createTLSRouteWithSectionNameAndPort( + "tls_coffee", + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + }, + { + gw: client.ObjectKeyFromObject(gw1), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + }, + }, + "test", + routeHostname..., + ) + + tlsFlavorRoute1 := createTLSRouteWithSectionNameAndPort( + "tls_flavor", + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + }, + { + gw: client.ObjectKeyFromObject(gw1), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + }, + }, + "test", + routeHostname..., + ) + tests := []struct { expectedResult map[string][]ParentRef listenerMap map[string]hostPort @@ -2482,15 +2778,15 @@ func TestIsolateL4Listeners(t *testing.T) { ), }, listenerMap: map[string]hostPort{ - "tls_coffee": {hostname: "coffee.example.com", port: 443}, - "tls_tea": {hostname: "tea.example.com", port: 443}, - "tls_flavor": {hostname: "flavor.example.com", port: 443}, + "tls_coffee,test,gateway": {hostname: "coffee.example.com", port: 443}, + "tls_tea,test,gateway": {hostname: "tea.example.com", port: 443}, + "tls_flavor,test,gateway": {hostname: "flavor.example.com", port: 443}, }, expectedResult: map[string][]ParentRef{ "tls_coffee": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ "tls_coffee": {"coffee.example.com"}, @@ -2505,7 +2801,7 @@ func TestIsolateL4Listeners(t *testing.T) { "tls_tea": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ "tls_coffee": {"coffee.example.com"}, @@ -2520,7 +2816,7 @@ func TestIsolateL4Listeners(t *testing.T) { "tls_flavor": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ "tls_coffee": {"coffee.example.com"}, @@ -2534,6 +2830,137 @@ func TestIsolateL4Listeners(t *testing.T) { }, }, }, + { + name: "no listener isolation for routes with overlapping hostnames but different gateways", + routes: []*L4Route{ + { + Source: tlsCoffeeRoute1, + Spec: L4RouteSpec{ + Hostnames: routeHostname, + }, + ParentRefs: []ParentRef{ + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: acceptedHostanamesMultipleGateways, + Attached: true, + ListenerPort: gatewayv1.PortNumber(443), + }, + }, + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: acceptedHostanamesMultipleGateways, + Attached: true, + ListenerPort: gatewayv1.PortNumber(443), + }, + }, + }, + }, + { + Source: tlsFlavorRoute1, + Spec: L4RouteSpec{ + Hostnames: routeHostname, + }, + ParentRefs: []ParentRef{ + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: acceptedHostanamesMultipleGateways, + Attached: true, + ListenerPort: gatewayv1.PortNumber(443), + }, + }, + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: acceptedHostanamesMultipleGateways, + Attached: true, + ListenerPort: gatewayv1.PortNumber(443), + }, + }, + }, + }, + }, + listenerMap: map[string]hostPort{ + "wildcard-example-com,test,gateway": { + hostname: "*.example.com", + port: 443, + gwNsName: client.ObjectKeyFromObject(gw), + }, + "wildcard-example-com,test,gateway1": { + hostname: "*.example.com", + port: 443, + gwNsName: client.ObjectKeyFromObject(gw), + }, + }, + expectedResult: map[string][]ParentRef{ + "tls_coffee": { + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: tlsCoffeeRoute1.Spec.ParentRefs[0].SectionName, + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + "tls_coffee": {"coffee.example.com", "flavor.example.com"}, + "tls_flavor": {"coffee.example.com", "flavor.example.com"}, + }, + ListenerPort: 443, + Attached: true, + }, + }, + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: tlsCoffeeRoute1.Spec.ParentRefs[0].SectionName, + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + "tls_coffee": {"coffee.example.com", "flavor.example.com"}, + "tls_flavor": {"coffee.example.com", "flavor.example.com"}, + }, + ListenerPort: 443, + Attached: true, + }, + }, + }, + "tls_flavor": { + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: tlsFlavorRoute1.Spec.ParentRefs[0].SectionName, + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + "tls_coffee": {"coffee.example.com", "flavor.example.com"}, + "tls_flavor": {"coffee.example.com", "flavor.example.com"}, + }, + ListenerPort: 443, + Attached: true, + }, + }, + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: tlsCoffeeRoute1.Spec.ParentRefs[0].SectionName, + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + "tls_coffee": {"coffee.example.com", "flavor.example.com"}, + "tls_flavor": {"coffee.example.com", "flavor.example.com"}, + }, + ListenerPort: 443, + Attached: true, + }, + }, + }, + }, + }, } for _, test := range tests { @@ -2560,12 +2987,26 @@ func TestIsolateL7Listeners(t *testing.T) { }, } + gw1 := &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway1", + }, + } + createHTTPRouteWithSectionNameAndPort := func( name string, - sectionName *gatewayv1.SectionName, + parentRef []parentRef, ns string, hostnames ...gatewayv1.Hostname, ) *gatewayv1.HTTPRoute { + var parentRefs []gatewayv1.ParentReference + for _, p := range parentRef { + parentRefs = append(parentRefs, gatewayv1.ParentReference{ + Name: gatewayv1.ObjectName(p.gw.Name), + SectionName: p.sectionName, + }) + } return &gatewayv1.HTTPRoute{ ObjectMeta: metav1.ObjectMeta{ Namespace: ns, @@ -2573,12 +3014,7 @@ func TestIsolateL7Listeners(t *testing.T) { }, Spec: gatewayv1.HTTPRouteSpec{ CommonRouteSpec: gatewayv1.CommonRouteSpec{ - ParentRefs: []gatewayv1.ParentReference{ - { - Name: gatewayv1.ObjectName(gw.Name), - SectionName: sectionName, - }, - }, + ParentRefs: parentRefs, }, Hostnames: hostnames, }, @@ -2599,11 +3035,8 @@ func TestIsolateL7Listeners(t *testing.T) { }, ParentRefs: []ParentRef{ { - Idx: 0, - Gateway: client.ObjectKey{ - Namespace: gw.Namespace, - Name: gw.Name, - }, + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: sectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: acceptedHostnames, @@ -2618,59 +3051,84 @@ func TestIsolateL7Listeners(t *testing.T) { routeHostnames := []gatewayv1.Hostname{"bar.com", "*.example.com", "*.foo.example.com", "abc.foo.example.com"} hr1 := createHTTPRouteWithSectionNameAndPort( "hr1", - helpers.GetPointer[gatewayv1.SectionName]("empty-hostname"), + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("empty-hostname"), + }, + }, "test", routeHostnames..., ) hr2 := createHTTPRouteWithSectionNameAndPort( "hr2", - helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + }, + }, "test", routeHostnames..., ) hr3 := createHTTPRouteWithSectionNameAndPort( "hr3", - helpers.GetPointer[gatewayv1.SectionName]("foo-wildcard-example-com"), + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("foo-wildcard-example-com"), + }, + }, "test", routeHostnames..., ) hr4 := createHTTPRouteWithSectionNameAndPort( "hr4", - helpers.GetPointer[gatewayv1.SectionName]("abc-com"), + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("abc-com"), + }, + }, "test", routeHostnames..., ) hr5 := createHTTPRouteWithSectionNameAndPort( "hr5", - helpers.GetPointer[gatewayv1.SectionName]("no-match"), + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("no-match"), + }, + }, "test", routeHostnames..., // no matching hostname ) acceptedHostnamesEmptyHostname := map[string][]string{ - "empty-hostname": { + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "empty-hostname"): { "bar.com", "*.example.com", "*.foo.example.com", "abc.foo.example.com", }, } acceptedHostnamesWildcardExample := map[string][]string{ - "wildcard-example-com": { + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "wildcard-example-com"): { "*.example.com", "*.foo.example.com", "abc.foo.example.com", }, } acceptedHostnamesFooWildcardExample := map[string][]string{ - "foo-wildcard-example-com": { + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "foo-wildcard-example-com"): { "*.foo.example.com", "abc.foo.example.com", }, } acceptedHostnamesAbcCom := map[string][]string{ - "abc-com": { + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "abc-com"): { "abc.foo.example.com", }, } acceptedHostnamesNoMatch := map[string][]string{ - "no-match": {}, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "no-match"): {}, } routesHostnameIntersection := []*L7Route{ @@ -2712,22 +3170,42 @@ func TestIsolateL7Listeners(t *testing.T) { } listenerMapHostnameIntersection := map[string]hostPort{ - "empty-hostname": {hostname: "", port: 80}, - "wildcard-example-com": {hostname: "*.example.com", port: 80}, - "foo-wildcard-example-com": {hostname: "*.foo.example.com", port: 80}, - "abc-com": {hostname: "abc.foo.example.com", port: 80}, - "no-match": {hostname: "no-match.cafe.com", port: 80}, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "empty-hostname"): { + hostname: "", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), + }, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "wildcard-example-com"): { + hostname: "*.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), + }, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "foo-wildcard-example-com"): { + hostname: "*.foo.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), + }, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "abc-com"): { + hostname: "abc.foo.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), + }, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "no-match"): { + hostname: "no-match.cafe.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), + }, } expectedResultHostnameIntersection := map[string][]ParentRef{ "hr1": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr1.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "empty-hostname": {"bar.com"}, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "empty-hostname"): {"bar.com"}, }, Attached: true, ListenerPort: 80, @@ -2737,11 +3215,14 @@ func TestIsolateL7Listeners(t *testing.T) { "hr2": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr2.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "wildcard-example-com": {"*.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "wildcard-example-com", + ): {"*.example.com"}, }, Attached: true, ListenerPort: 80, @@ -2751,11 +3232,14 @@ func TestIsolateL7Listeners(t *testing.T) { "hr3": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr3.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "foo-wildcard-example-com": {"*.foo.example.com"}, + CreateGatewayListenerKey( + client.ObjectKeyFromObject(gw), + "foo-wildcard-example-com", + ): {"*.foo.example.com"}, }, Attached: true, ListenerPort: 80, @@ -2765,11 +3249,11 @@ func TestIsolateL7Listeners(t *testing.T) { "hr4": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr4.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "abc-com": {"abc.foo.example.com"}, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "abc-com"): {"abc.foo.example.com"}, }, Attached: true, ListenerPort: 80, @@ -2779,11 +3263,11 @@ func TestIsolateL7Listeners(t *testing.T) { "hr5": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hr5.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "no-match": {}, + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "no-match"): {}, }, Attached: true, ListenerPort: 80, @@ -2795,7 +3279,12 @@ func TestIsolateL7Listeners(t *testing.T) { routeHostnameCafeExample := []gatewayv1.Hostname{"cafe.example.com"} httpListenerRoute := createHTTPRouteWithSectionNameAndPort( "hr_cafe", - helpers.GetPointer[gatewayv1.SectionName]("http"), + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("http"), + }, + }, "test", routeHostnameCafeExample..., ) @@ -2834,6 +3323,45 @@ func TestIsolateL7Listeners(t *testing.T) { "hr_flavor": {"flavor.example.com"}, } + routeHostname := []gatewayv1.Hostname{"cafe.example.com", "flavor.example.com"} + + acceptedHostNamesMultipleGateway := map[string][]string{ + "hr_cafe": {"cafe.example.com", "flavor.example.com"}, + "hr_flavor": {"cafe.example.com", "flavor.example.com"}, + } + + hrCoffeeRoute1 := createHTTPRouteWithSectionNameAndPort( + "hr_coffee", + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + }, + { + gw: client.ObjectKeyFromObject(gw1), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + }, + }, + "test", + routeHostname..., + ) + + hrFlavorRoute1 := createHTTPRouteWithSectionNameAndPort( + "hr_flavor", + []parentRef{ + { + gw: client.ObjectKeyFromObject(gw), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + }, + { + gw: client.ObjectKeyFromObject(gw1), + sectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + }, + }, + "test", + routeHostname..., + ) + tests := []struct { expectedResult map[string][]ParentRef listenersMap map[string]hostPort @@ -2858,14 +3386,14 @@ func TestIsolateL7Listeners(t *testing.T) { ), }, listenersMap: map[string]hostPort{ - "http": {hostname: "cafe.example.com", port: 80}, - "http-different": {hostname: "cafe.example.com", port: 8080}, + "http,test,gateway": {hostname: "cafe.example.com", port: 80}, + "http-different,test,gateway": {hostname: "cafe.example.com", port: 8080}, }, expectedResult: map[string][]ParentRef{ "hr_cafe": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: httpListenerRoute.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ @@ -2904,15 +3432,15 @@ func TestIsolateL7Listeners(t *testing.T) { ), }, listenersMap: map[string]hostPort{ - "hr_coffee": {hostname: "coffee.example.com", port: 80}, - "hr_tea": {hostname: "tea.example.com", port: 80}, - "hr_flavor": {hostname: "flavor.example.com", port: 80}, + "hr_coffee,test,gateway": {hostname: "coffee.example.com", port: 80}, + "hr_tea,test,gateway": {hostname: "tea.example.com", port: 80}, + "hr_flavor,test,gateway": {hostname: "flavor.example.com", port: 80}, }, expectedResult: map[string][]ParentRef{ "hr_coffee": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ "hr_coffee": {"coffee.example.com"}, @@ -2927,7 +3455,7 @@ func TestIsolateL7Listeners(t *testing.T) { "hr_tea": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ "hr_coffee": {"coffee.example.com"}, @@ -2942,7 +3470,7 @@ func TestIsolateL7Listeners(t *testing.T) { "hr_flavor": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ "hr_coffee": {"coffee.example.com"}, @@ -2956,6 +3484,137 @@ func TestIsolateL7Listeners(t *testing.T) { }, }, }, + { + name: "no listener isolation for routes with same hostname, associated with different gateways", + routes: []*L7Route{ + { + Source: hrCoffeeRoute1, + Spec: L7RouteSpec{ + Hostnames: routeHostname, + }, + ParentRefs: []ParentRef{ + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: acceptedHostNamesMultipleGateway, + Attached: true, + ListenerPort: gatewayv1.PortNumber(80), + }, + }, + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: acceptedHostNamesMultipleGateway, + Attached: true, + ListenerPort: gatewayv1.PortNumber(80), + }, + }, + }, + }, + { + Source: hrFlavorRoute1, + Spec: L7RouteSpec{ + Hostnames: routeHostname, + }, + ParentRefs: []ParentRef{ + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: acceptedHostNamesMultipleGateway, + Attached: true, + ListenerPort: gatewayv1.PortNumber(80), + }, + }, + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: acceptedHostNamesMultipleGateway, + Attached: true, + ListenerPort: gatewayv1.PortNumber(80), + }, + }, + }, + }, + }, + listenersMap: map[string]hostPort{ + "wildcard-example-com,test,gateway": { + hostname: "*.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), + }, + "wildcard-example-com,test,gateway1": { + hostname: "*.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw1), + }, + }, + expectedResult: map[string][]ParentRef{ + "hr_coffee": { + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: hrCoffeeRoute1.Spec.ParentRefs[0].SectionName, + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + "hr_cafe": {"cafe.example.com", "flavor.example.com"}, + "hr_flavor": {"cafe.example.com", "flavor.example.com"}, + }, + ListenerPort: 80, + Attached: true, + }, + }, + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: hrCoffeeRoute1.Spec.ParentRefs[1].SectionName, + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + "hr_cafe": {"cafe.example.com", "flavor.example.com"}, + "hr_flavor": {"cafe.example.com", "flavor.example.com"}, + }, + ListenerPort: 80, + Attached: true, + }, + }, + }, + "hr_flavor": { + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: hrFlavorRoute1.Spec.ParentRefs[0].SectionName, + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + "hr_cafe": {"cafe.example.com", "flavor.example.com"}, + "hr_flavor": {"cafe.example.com", "flavor.example.com"}, + }, + ListenerPort: 80, + Attached: true, + }, + }, + { + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, + SectionName: hrFlavorRoute1.Spec.ParentRefs[0].SectionName, + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + "hr_cafe": {"cafe.example.com", "flavor.example.com"}, + "hr_flavor": {"cafe.example.com", "flavor.example.com"}, + }, + ListenerPort: 80, + Attached: true, + }, + }, + }, + }, + }, } for _, test := range tests { @@ -3018,3 +3677,12 @@ func TestRemoveHostnames(t *testing.T) { }) } } + +func TestBindRoutesToListeners(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + g.Expect(func() { + bindRoutesToListeners(nil, nil, nil, nil) + }).ToNot(Panic()) +} diff --git a/internal/mode/static/state/graph/service.go b/internal/mode/static/state/graph/service.go index ad6fb817ef..7a41b07132 100644 --- a/internal/mode/static/state/graph/service.go +++ b/internal/mode/static/state/graph/service.go @@ -5,10 +5,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -// A ReferencedService represents a Kubernetes Service that is referenced by a Route and that belongs to the -// winning Gateway. It does not contain the v1.Service object, because Services are resolved when building +// A ReferencedService represents a Kubernetes Service that is referenced by a Route and the Gateways it belongs to. +// It does not contain the v1.Service object, because Services are resolved when building // the dataplane.Configuration. type ReferencedService struct { + // GatewayNsNames are all the Gateways that this Service indirectly attaches to through a Route. + GatewayNsNames map[types.NamespacedName]struct{} // Policies is a list of NGF Policies that target this Service. Policies []*Policy } @@ -16,72 +18,43 @@ type ReferencedService struct { func buildReferencedServices( l7routes map[RouteKey]*L7Route, l4Routes map[L4RouteKey]*L4Route, - gw *Gateway, + gws map[types.NamespacedName]*Gateway, ) map[types.NamespacedName]*ReferencedService { - if gw == nil { - return nil - } - referencedServices := make(map[types.NamespacedName]*ReferencedService) - - belongsToWinningGw := func(refs []ParentRef) bool { - for _, ref := range refs { - if ref.Gateway == client.ObjectKeyFromObject(gw.Source) { - return true - } + for gwNsName, gw := range gws { + if gw == nil { + continue } - return false - } - - // Processes both valid and invalid BackendRefs as invalid ones still have referenced services - // we may want to track. - addServicesForL7Routes := func(routeRules []RouteRule) { - for _, rule := range routeRules { - for _, ref := range rule.BackendRefs { - if ref.SvcNsName != (types.NamespacedName{}) { - referencedServices[ref.SvcNsName] = &ReferencedService{ - Policies: nil, - } + belongsToGw := func(refs []ParentRef) bool { + for _, ref := range refs { + if ref.Gateway.NamespacedName == client.ObjectKeyFromObject(gw.Source) { + return true } } + return false } - } - addServicesForL4Routes := func(route *L4Route) { - nsname := route.Spec.BackendRef.SvcNsName - if nsname != (types.NamespacedName{}) { - referencedServices[nsname] = &ReferencedService{ - Policies: nil, + // routes all have populated ParentRefs from when they were created. + // + // Get all the service names referenced from all the l7 and l4 routes. + for _, route := range l7routes { + if !route.Valid || !belongsToGw(route.ParentRefs) { + continue } - } - } - - // routes all have populated ParentRefs from when they were created. - // - // Get all the service names referenced from all the l7 and l4 routes. - for _, route := range l7routes { - if !route.Valid { - continue - } - if !belongsToWinningGw(route.ParentRefs) { - continue + // Processes both valid and invalid BackendRefs as invalid ones still have referenced services + // we may want to track. + addServicesAndGatewayForL7Routes(route.Spec.Rules, gwNsName, referencedServices) } - addServicesForL7Routes(route.Spec.Rules) - } - - for _, route := range l4Routes { - if !route.Valid { - continue - } + for _, route := range l4Routes { + if !route.Valid || !belongsToGw(route.ParentRefs) { + continue + } - if !belongsToWinningGw(route.ParentRefs) { - continue + addServicesAndGatewayForL4Routes(route, gwNsName, referencedServices) } - - addServicesForL4Routes(route) } if len(referencedServices) == 0 { @@ -90,3 +63,41 @@ func buildReferencedServices( return referencedServices } + +func addServicesAndGatewayForL4Routes( + route *L4Route, + gwNsName types.NamespacedName, + referencedServices map[types.NamespacedName]*ReferencedService, +) { + nsname := route.Spec.BackendRef.SvcNsName + if nsname != (types.NamespacedName{}) { + if _, ok := referencedServices[nsname]; !ok { + referencedServices[nsname] = &ReferencedService{ + Policies: nil, + GatewayNsNames: make(map[types.NamespacedName]struct{}), + } + } + referencedServices[nsname].GatewayNsNames[gwNsName] = struct{}{} + } +} + +func addServicesAndGatewayForL7Routes( + routeRules []RouteRule, + gwNsName types.NamespacedName, + referencedServices map[types.NamespacedName]*ReferencedService, +) { + for _, rule := range routeRules { + for _, ref := range rule.BackendRefs { + if ref.SvcNsName != (types.NamespacedName{}) { + if _, ok := referencedServices[ref.SvcNsName]; !ok { + referencedServices[ref.SvcNsName] = &ReferencedService{ + Policies: nil, + GatewayNsNames: make(map[types.NamespacedName]struct{}), + } + } + + referencedServices[ref.SvcNsName].GatewayNsNames[gwNsName] = struct{}{} + } + } + } +} diff --git a/internal/mode/static/state/graph/service_test.go b/internal/mode/static/state/graph/service_test.go index 0fa316e73f..e0ef7180ce 100644 --- a/internal/mode/static/state/graph/service_test.go +++ b/internal/mode/static/state/graph/service_test.go @@ -12,25 +12,49 @@ import ( func TestBuildReferencedServices(t *testing.T) { t.Parallel() - gwNsname := types.NamespacedName{Namespace: "test", Name: "gwNsname"} - gw := &Gateway{ - Source: &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gwNsname.Namespace, - Name: gwNsname.Name, + gwNsName := types.NamespacedName{Namespace: "test", Name: "gwNsname"} + gw2NsName := types.NamespacedName{Namespace: "test", Name: "gw2Nsname"} + gw3NsName := types.NamespacedName{Namespace: "test", Name: "gw3Nsname"} + gw := map[types.NamespacedName]*Gateway{ + gwNsName: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: gwNsName.Namespace, + Name: gwNsName.Name, + }, + }, + }, + gw2NsName: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: gw2NsName.Namespace, + Name: gw2NsName.Name, + }, + }, + }, + gw3NsName: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: gw3NsName.Namespace, + Name: gw3NsName.Name, + }, }, }, } - ignoredGw := types.NamespacedName{Namespace: "test", Name: "ignoredGw"} + + parentRefs := []ParentRef{ + { + Gateway: &ParentRefGateway{NamespacedName: gwNsName}, + }, + { + Gateway: &ParentRefGateway{NamespacedName: gw2NsName}, + }, + } getNormalL7Route := func() *L7Route { return &L7Route{ - ParentRefs: []ParentRef{ - { - Gateway: gwNsname, - }, - }, - Valid: true, + ParentRefs: parentRefs, + Valid: true, Spec: L7RouteSpec{ Rules: []RouteRule{ { @@ -57,12 +81,8 @@ func TestBuildReferencedServices(t *testing.T) { SvcNsName: types.NamespacedName{Namespace: "tlsroute-ns", Name: "service"}, }, }, - Valid: true, - ParentRefs: []ParentRef{ - { - Gateway: gwNsname, - }, - }, + Valid: true, + ParentRefs: parentRefs, } } @@ -137,56 +157,16 @@ func TestBuildReferencedServices(t *testing.T) { return route }) - normalL4RouteWinningAndIgnoredGws := getModifiedL4Route(func(route *L4Route) *L4Route { - route.ParentRefs = []ParentRef{ - { - Gateway: ignoredGw, - }, - { - Gateway: ignoredGw, - }, - { - Gateway: gwNsname, - }, - } - return route - }) - - normalRouteWinningAndIgnoredGws := getModifiedL7Route(func(route *L7Route) *L7Route { - route.ParentRefs = []ParentRef{ - { - Gateway: ignoredGw, - }, - { - Gateway: gwNsname, - }, - { - Gateway: ignoredGw, - }, - } - return route - }) - - normalL4RouteIgnoredGw := getModifiedL4Route(func(route *L4Route) *L4Route { - route.ParentRefs[0].Gateway = ignoredGw - return route - }) - - normalL7RouteIgnoredGw := getModifiedL7Route(func(route *L7Route) *L7Route { - route.ParentRefs[0].Gateway = ignoredGw - return route - }) - tests := []struct { l7Routes map[RouteKey]*L7Route l4Routes map[L4RouteKey]*L4Route exp map[types.NamespacedName]*ReferencedService - gw *Gateway + gws map[types.NamespacedName]*Gateway name string }{ { name: "normal routes", - gw: gw, + gws: gw, l7Routes: map[RouteKey]*L7Route{ {NamespacedName: types.NamespacedName{Name: "normal-route"}}: normalRoute, }, @@ -194,35 +174,65 @@ func TestBuildReferencedServices(t *testing.T) { {NamespacedName: types.NamespacedName{Name: "normal-l4-route"}}: normalL4Route, }, exp: map[types.NamespacedName]*ReferencedService{ - {Namespace: "banana-ns", Name: "service"}: {}, - {Namespace: "tlsroute-ns", Name: "service"}: {}, + {Namespace: "banana-ns", Name: "service"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gwNsname"}: {}, + {Namespace: "test", Name: "gw2Nsname"}: {}, + }, + }, + {Namespace: "tlsroute-ns", Name: "service"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gwNsname"}: {}, + {Namespace: "test", Name: "gw2Nsname"}: {}, + }, + }, }, }, { name: "l7 route with two services in one Rule", // l4 routes don't support multiple services right now - gw: gw, + gws: gw, l7Routes: map[RouteKey]*L7Route{ {NamespacedName: types.NamespacedName{Name: "two-svc-one-rule"}}: validRouteTwoServicesOneRule, }, exp: map[types.NamespacedName]*ReferencedService{ - {Namespace: "service-ns", Name: "service"}: {}, - {Namespace: "service-ns2", Name: "service2"}: {}, + {Namespace: "service-ns", Name: "service"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gwNsname"}: {}, + {Namespace: "test", Name: "gw2Nsname"}: {}, + }, + }, + {Namespace: "service-ns2", Name: "service2"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gwNsname"}: {}, + {Namespace: "test", Name: "gw2Nsname"}: {}, + }, + }, }, }, { name: "route with one service per rule", // l4 routes don't support multiple rules right now - gw: gw, + gws: gw, l7Routes: map[RouteKey]*L7Route{ {NamespacedName: types.NamespacedName{Name: "one-svc-per-rule"}}: validRouteTwoServicesTwoRules, }, exp: map[types.NamespacedName]*ReferencedService{ - {Namespace: "service-ns", Name: "service"}: {}, - {Namespace: "service-ns2", Name: "service2"}: {}, + {Namespace: "service-ns", Name: "service"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gwNsname"}: {}, + {Namespace: "test", Name: "gw2Nsname"}: {}, + }, + }, + {Namespace: "service-ns2", Name: "service2"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gwNsname"}: {}, + {Namespace: "test", Name: "gw2Nsname"}: {}, + }, + }, }, }, { name: "multiple valid routes with same services", - gw: gw, + gws: gw, l7Routes: map[RouteKey]*L7Route{ {NamespacedName: types.NamespacedName{Name: "one-svc-per-rule"}}: validRouteTwoServicesTwoRules, {NamespacedName: types.NamespacedName{Name: "two-svc-one-rule"}}: validRouteTwoServicesOneRule, @@ -233,57 +243,35 @@ func TestBuildReferencedServices(t *testing.T) { {NamespacedName: types.NamespacedName{Name: "l4-route-same-svc-as-l7-route"}}: normalL4RouteWithSameSvcAsL7Route, }, exp: map[types.NamespacedName]*ReferencedService{ - {Namespace: "service-ns", Name: "service"}: {}, - {Namespace: "service-ns2", Name: "service2"}: {}, - {Namespace: "tlsroute-ns", Name: "service"}: {}, - {Namespace: "tlsroute-ns", Name: "service2"}: {}, - }, - }, - { - name: "valid routes that do not belong to winning gateway", - gw: gw, - l7Routes: map[RouteKey]*L7Route{ - {NamespacedName: types.NamespacedName{Name: "belongs-to-ignored-gws"}}: normalL7RouteIgnoredGw, - }, - l4Routes: map[L4RouteKey]*L4Route{ - {NamespacedName: types.NamespacedName{Name: "belongs-to-ignored-gw"}}: normalL4RouteIgnoredGw, - }, - exp: nil, - }, - { - name: "valid routes that belong to both winning and ignored gateways", - gw: gw, - l7Routes: map[RouteKey]*L7Route{ - {NamespacedName: types.NamespacedName{Name: "belongs-to-ignored-gws"}}: normalRouteWinningAndIgnoredGws, - }, - l4Routes: map[L4RouteKey]*L4Route{ - {NamespacedName: types.NamespacedName{Name: "ignored-gw"}}: normalL4RouteWinningAndIgnoredGws, - }, - exp: map[types.NamespacedName]*ReferencedService{ - {Namespace: "banana-ns", Name: "service"}: {}, - {Namespace: "tlsroute-ns", Name: "service"}: {}, - }, - }, - { - name: "valid routes with different services", - gw: gw, - l7Routes: map[RouteKey]*L7Route{ - {NamespacedName: types.NamespacedName{Name: "one-svc-per-rule"}}: validRouteTwoServicesTwoRules, - {NamespacedName: types.NamespacedName{Name: "normal-route"}}: normalRoute, - }, - l4Routes: map[L4RouteKey]*L4Route{ - {NamespacedName: types.NamespacedName{Name: "normal-l4-route"}}: normalL4Route, - }, - exp: map[types.NamespacedName]*ReferencedService{ - {Namespace: "service-ns", Name: "service"}: {}, - {Namespace: "service-ns2", Name: "service2"}: {}, - {Namespace: "banana-ns", Name: "service"}: {}, - {Namespace: "tlsroute-ns", Name: "service"}: {}, + {Namespace: "service-ns", Name: "service"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gwNsname"}: {}, + {Namespace: "test", Name: "gw2Nsname"}: {}, + }, + }, + {Namespace: "service-ns2", Name: "service2"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gwNsname"}: {}, + {Namespace: "test", Name: "gw2Nsname"}: {}, + }, + }, + {Namespace: "tlsroute-ns", Name: "service"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gwNsname"}: {}, + {Namespace: "test", Name: "gw2Nsname"}: {}, + }, + }, + {Namespace: "tlsroute-ns", Name: "service2"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gwNsname"}: {}, + {Namespace: "test", Name: "gw2Nsname"}: {}, + }, + }, }, }, { name: "invalid routes", - gw: gw, + gws: gw, l7Routes: map[RouteKey]*L7Route{ {NamespacedName: types.NamespacedName{Name: "invalid-route"}}: invalidRoute, }, @@ -294,7 +282,7 @@ func TestBuildReferencedServices(t *testing.T) { }, { name: "combination of valid and invalid routes", - gw: gw, + gws: gw, l7Routes: map[RouteKey]*L7Route{ {NamespacedName: types.NamespacedName{Name: "normal-route"}}: normalRoute, {NamespacedName: types.NamespacedName{Name: "invalid-route"}}: invalidRoute, @@ -304,13 +292,23 @@ func TestBuildReferencedServices(t *testing.T) { {NamespacedName: types.NamespacedName{Name: "normal-l4-route"}}: normalL4Route, }, exp: map[types.NamespacedName]*ReferencedService{ - {Namespace: "banana-ns", Name: "service"}: {}, - {Namespace: "tlsroute-ns", Name: "service"}: {}, + {Namespace: "banana-ns", Name: "service"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gwNsname"}: {}, + {Namespace: "test", Name: "gw2Nsname"}: {}, + }, + }, + {Namespace: "tlsroute-ns", Name: "service"}: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gwNsname"}: {}, + {Namespace: "test", Name: "gw2Nsname"}: {}, + }, + }, }, }, { name: "valid route no service nsname", - gw: gw, + gws: gw, l7Routes: map[RouteKey]*L7Route{ {NamespacedName: types.NamespacedName{Name: "no-service-nsname"}}: validRouteNoServiceNsName, }, @@ -321,7 +319,9 @@ func TestBuildReferencedServices(t *testing.T) { }, { name: "nil gateway", - gw: nil, + gws: map[types.NamespacedName]*Gateway{ + gwNsName: nil, + }, l7Routes: map[RouteKey]*L7Route{ {NamespacedName: types.NamespacedName{Name: "no-service-nsname"}}: validRouteNoServiceNsName, }, @@ -337,7 +337,7 @@ func TestBuildReferencedServices(t *testing.T) { t.Parallel() g := NewWithT(t) - g.Expect(buildReferencedServices(test.l7Routes, test.l4Routes, test.gw)).To(Equal(test.exp)) + g.Expect(buildReferencedServices(test.l7Routes, test.l4Routes, test.gws)).To(Equal(test.exp)) }) } } diff --git a/internal/mode/static/state/graph/tlsroute.go b/internal/mode/static/state/graph/tlsroute.go index da0749f9ce..0a58525d33 100644 --- a/internal/mode/static/state/graph/tlsroute.go +++ b/internal/mode/static/state/graph/tlsroute.go @@ -7,22 +7,20 @@ import ( "sigs.k8s.io/gateway-api/apis/v1alpha2" "github.com/nginx/nginx-gateway-fabric/internal/framework/conditions" - "github.com/nginx/nginx-gateway-fabric/internal/framework/helpers" staticConds "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/conditions" ) func buildTLSRoute( gtr *v1alpha2.TLSRoute, - gatewayNsNames []types.NamespacedName, + gws map[types.NamespacedName]*Gateway, services map[types.NamespacedName]*apiv1.Service, - npCfg *EffectiveNginxProxy, refGrantResolver func(resource toResource) bool, ) *L4Route { r := &L4Route{ Source: gtr, } - sectionNameRefs, err := buildSectionNameRefs(gtr.Spec.ParentRefs, gtr.Namespace, gatewayNsNames) + sectionNameRefs, err := buildSectionNameRefs(gtr.Spec.ParentRefs, gtr.Namespace, gws) if err != nil { r.Valid = false @@ -54,14 +52,14 @@ func buildTLSRoute( return r } - br, cond := validateBackendRefTLSRoute(gtr, services, npCfg, refGrantResolver) + br, conds := validateBackendRefTLSRoute(gtr, services, r.ParentRefs, refGrantResolver) r.Spec.BackendRef = br r.Valid = true r.Attachable = true - if cond != nil { - r.Conditions = append(r.Conditions, *cond) + if len(conds) > 0 { + r.Conditions = append(r.Conditions, conds...) } return r @@ -69,9 +67,9 @@ func buildTLSRoute( func validateBackendRefTLSRoute(gtr *v1alpha2.TLSRoute, services map[types.NamespacedName]*apiv1.Service, - npCfg *EffectiveNginxProxy, + parentRefs []ParentRef, refGrantResolver func(resource toResource) bool, -) (BackendRef, *conditions.Condition) { +) (BackendRef, []conditions.Condition) { // Length of BackendRefs and Rules is guaranteed to be one due to earlier check in buildTLSRoute refPath := field.NewPath("spec").Child("rules").Index(0).Child("backendRefs").Index(0) @@ -84,10 +82,11 @@ func validateBackendRefTLSRoute(gtr *v1alpha2.TLSRoute, refPath, ); !valid { backendRef := BackendRef{ - Valid: false, + Valid: false, + InvalidForGateways: make(map[types.NamespacedName]conditions.Condition), } - return backendRef, &cond + return backendRef, []conditions.Condition{cond} } ns := gtr.Namespace @@ -108,22 +107,25 @@ func validateBackendRefTLSRoute(gtr *v1alpha2.TLSRoute, ) backendRef := BackendRef{ - SvcNsName: svcNsName, - ServicePort: svcPort, - Valid: true, + SvcNsName: svcNsName, + ServicePort: svcPort, + Valid: true, + InvalidForGateways: make(map[types.NamespacedName]conditions.Condition), } if err != nil { backendRef.Valid = false - return backendRef, helpers.GetPointer(staticConds.NewRouteBackendRefRefBackendNotFound(err.Error())) + return backendRef, []conditions.Condition{staticConds.NewRouteBackendRefRefBackendNotFound(err.Error())} } - if err := verifyIPFamily(npCfg, svcIPFamily); err != nil { - backendRef.Valid = false - - return backendRef, helpers.GetPointer(staticConds.NewRouteInvalidIPFamily(err.Error())) + var conds []conditions.Condition + for _, parentRef := range parentRefs { + if err := verifyIPFamily(parentRef.Gateway.EffectiveNginxProxy, svcIPFamily); err != nil { + backendRef.Valid = backendRef.Valid || false + backendRef.InvalidForGateways[parentRef.Gateway.NamespacedName] = staticConds.NewRouteInvalidIPFamily(err.Error()) + } } - return backendRef, nil + return backendRef, conds } diff --git a/internal/mode/static/state/graph/tlsroute_test.go b/internal/mode/static/state/graph/tlsroute_test.go index 9bd57f6ac2..fa0dfaa4b6 100644 --- a/internal/mode/static/state/graph/tlsroute_test.go +++ b/internal/mode/static/state/graph/tlsroute_test.go @@ -7,6 +7,7 @@ import ( apiv1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -44,13 +45,31 @@ func TestBuildTLSRoute(t *testing.T) { Name: "gateway", SectionName: helpers.GetPointer[gatewayv1.SectionName]("l1"), } - gatewayNsName := types.NamespacedName{ - Namespace: "test", - Name: "gateway", + + createGateway := func() *Gateway { + return &Gateway{ + Source: &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway", + }, + }, + Valid: true, + } + } + + modGateway := func(gw *Gateway, mod func(*Gateway) *Gateway) *Gateway { + return mod(gw) } + parentRefGraph := ParentRef{ SectionName: helpers.GetPointer[gatewayv1.SectionName]("l1"), - Gateway: gatewayNsName, + Gateway: &ParentRefGateway{ + NamespacedName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, + }, } duplicateParentRefsGtr := createTLSRoute( "hi.example.com", @@ -267,13 +286,12 @@ func TestBuildTLSRoute(t *testing.T) { alwaysFalseRefGrantResolver := func(_ toResource) bool { return false } tests := []struct { - expected *L4Route - gtr *v1alpha2.TLSRoute - services map[types.NamespacedName]*apiv1.Service - resolver func(resource toResource) bool - npCfg *EffectiveNginxProxy - name string - gatewayNsNames []types.NamespacedName + expected *L4Route + gtr *v1alpha2.TLSRoute + services map[types.NamespacedName]*apiv1.Service + resolver func(resource toResource) bool + gateway *Gateway + name string }{ { gtr: duplicateParentRefsGtr, @@ -281,18 +299,18 @@ func TestBuildTLSRoute(t *testing.T) { Source: duplicateParentRefsGtr, Valid: false, }, - gatewayNsNames: []types.NamespacedName{gatewayNsName}, - services: map[types.NamespacedName]*apiv1.Service{}, - resolver: alwaysTrueRefGrantResolver, - name: "duplicate parent refs", + gateway: createGateway(), + services: map[types.NamespacedName]*apiv1.Service{}, + resolver: alwaysTrueRefGrantResolver, + name: "duplicate parent refs", }, { - gtr: noParentRefsGtr, - expected: nil, - gatewayNsNames: []types.NamespacedName{gatewayNsName}, - services: map[types.NamespacedName]*apiv1.Service{}, - resolver: alwaysTrueRefGrantResolver, - name: "no parent refs", + gtr: noParentRefsGtr, + expected: nil, + gateway: createGateway(), + services: map[types.NamespacedName]*apiv1.Service{}, + resolver: alwaysTrueRefGrantResolver, + name: "no parent refs", }, { gtr: invalidHostnameGtr, @@ -308,10 +326,10 @@ func TestBuildTLSRoute(t *testing.T) { )}, Valid: false, }, - gatewayNsNames: []types.NamespacedName{gatewayNsName}, - services: map[types.NamespacedName]*apiv1.Service{}, - resolver: alwaysTrueRefGrantResolver, - name: "invalid hostname", + gateway: createGateway(), + services: map[types.NamespacedName]*apiv1.Service{}, + resolver: alwaysTrueRefGrantResolver, + name: "invalid hostname", }, { gtr: noRulesGtr, @@ -328,10 +346,10 @@ func TestBuildTLSRoute(t *testing.T) { )}, Valid: false, }, - gatewayNsNames: []types.NamespacedName{gatewayNsName}, - services: map[types.NamespacedName]*apiv1.Service{}, - resolver: alwaysTrueRefGrantResolver, - name: "invalid rule", + gateway: createGateway(), + services: map[types.NamespacedName]*apiv1.Service{}, + resolver: alwaysTrueRefGrantResolver, + name: "invalid rule", }, { gtr: backedRefDNEGtr, @@ -347,7 +365,8 @@ func TestBuildTLSRoute(t *testing.T) { Namespace: "test", Name: "hi", }, - Valid: false, + Valid: false, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, Conditions: []conditions.Condition{staticConds.NewRouteBackendRefRefBackendNotFound( @@ -356,10 +375,10 @@ func TestBuildTLSRoute(t *testing.T) { Attachable: true, Valid: true, }, - gatewayNsNames: []types.NamespacedName{gatewayNsName}, - services: map[types.NamespacedName]*apiv1.Service{}, - resolver: alwaysTrueRefGrantResolver, - name: "BackendRef not found", + gateway: createGateway(), + services: map[types.NamespacedName]*apiv1.Service{}, + resolver: alwaysTrueRefGrantResolver, + name: "BackendRef not found", }, { gtr: wrongBackendRefGroupGtr, @@ -371,7 +390,8 @@ func TestBuildTLSRoute(t *testing.T) { "app.example.com", }, BackendRef: BackendRef{ - Valid: false, + Valid: false, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, Conditions: []conditions.Condition{staticConds.NewRouteBackendRefInvalidKind( @@ -381,7 +401,7 @@ func TestBuildTLSRoute(t *testing.T) { Attachable: true, Valid: true, }, - gatewayNsNames: []types.NamespacedName{gatewayNsName}, + gateway: createGateway(), services: map[types.NamespacedName]*apiv1.Service{ svcNsName: createSvc("hi", 80), }, @@ -398,7 +418,8 @@ func TestBuildTLSRoute(t *testing.T) { "app.example.com", }, BackendRef: BackendRef{ - Valid: false, + Valid: false, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, Conditions: []conditions.Condition{staticConds.NewRouteBackendRefInvalidKind( @@ -408,7 +429,7 @@ func TestBuildTLSRoute(t *testing.T) { Attachable: true, Valid: true, }, - gatewayNsNames: []types.NamespacedName{gatewayNsName}, + gateway: createGateway(), services: map[types.NamespacedName]*apiv1.Service{ svcNsName: createSvc("hi", 80), }, @@ -425,7 +446,8 @@ func TestBuildTLSRoute(t *testing.T) { "app.example.com", }, BackendRef: BackendRef{ - Valid: false, + Valid: false, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, Conditions: []conditions.Condition{staticConds.NewRouteBackendRefRefNotPermitted( @@ -434,7 +456,7 @@ func TestBuildTLSRoute(t *testing.T) { Attachable: true, Valid: true, }, - gatewayNsNames: []types.NamespacedName{gatewayNsName}, + gateway: createGateway(), services: map[types.NamespacedName]*apiv1.Service{ diffSvcNsName: diffNsSvc, }, @@ -451,7 +473,8 @@ func TestBuildTLSRoute(t *testing.T) { "app.example.com", }, BackendRef: BackendRef{ - Valid: false, + Valid: false, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, Conditions: []conditions.Condition{staticConds.NewRouteBackendRefUnsupportedValue( @@ -460,7 +483,7 @@ func TestBuildTLSRoute(t *testing.T) { Attachable: true, Valid: true, }, - gatewayNsNames: []types.NamespacedName{gatewayNsName}, + gateway: createGateway(), services: map[types.NamespacedName]*apiv1.Service{ diffSvcNsName: createSvc("hi", 80), }, @@ -470,8 +493,19 @@ func TestBuildTLSRoute(t *testing.T) { { gtr: ipFamilyMismatchGtr, expected: &L4Route{ - Source: ipFamilyMismatchGtr, - ParentRefs: []ParentRef{parentRefGraph}, + Source: ipFamilyMismatchGtr, + ParentRefs: []ParentRef{ + { + SectionName: helpers.GetPointer[gatewayv1.SectionName]("l1"), + Gateway: &ParentRefGateway{ + NamespacedName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, + EffectiveNginxProxy: &EffectiveNginxProxy{IPFamily: helpers.GetPointer(ngfAPI.IPv6)}, + }, + }, + }, Spec: L4RouteSpec{ Hostnames: []gatewayv1.Hostname{ "app.example.com", @@ -479,19 +513,24 @@ func TestBuildTLSRoute(t *testing.T) { BackendRef: BackendRef{ SvcNsName: svcNsName, ServicePort: apiv1.ServicePort{Port: 80}, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{ + {Namespace: "test", Name: "gateway"}: staticConds.NewRouteInvalidIPFamily( + "Service configured with IPv4 family but NginxProxy is configured with IPv6", + ), + }, + Valid: true, }, }, - Conditions: []conditions.Condition{staticConds.NewRouteInvalidIPFamily( - "Service configured with IPv4 family but NginxProxy is configured with IPv6", - )}, Attachable: true, Valid: true, }, - gatewayNsNames: []types.NamespacedName{gatewayNsName}, + gateway: modGateway(createGateway(), func(gw *Gateway) *Gateway { + gw.EffectiveNginxProxy = &EffectiveNginxProxy{IPFamily: helpers.GetPointer(ngfAPI.IPv6)} + return gw + }), services: map[types.NamespacedName]*apiv1.Service{ svcNsName: ipv4Svc, }, - npCfg: &EffectiveNginxProxy{IPFamily: helpers.GetPointer(ngfAPI.IPv6)}, resolver: alwaysTrueRefGrantResolver, name: "service and npcfg ip family mismatch", }, @@ -505,15 +544,16 @@ func TestBuildTLSRoute(t *testing.T) { "app.example.com", }, BackendRef: BackendRef{ - SvcNsName: diffSvcNsName, - ServicePort: apiv1.ServicePort{Port: 80}, - Valid: true, + SvcNsName: diffSvcNsName, + ServicePort: apiv1.ServicePort{Port: 80}, + Valid: true, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, Attachable: true, Valid: true, }, - gatewayNsNames: []types.NamespacedName{gatewayNsName}, + gateway: createGateway(), services: map[types.NamespacedName]*apiv1.Service{ diffSvcNsName: diffNsSvc, }, @@ -530,15 +570,16 @@ func TestBuildTLSRoute(t *testing.T) { "app.example.com", }, BackendRef: BackendRef{ - SvcNsName: svcNsName, - ServicePort: apiv1.ServicePort{Port: 80}, - Valid: true, + SvcNsName: svcNsName, + ServicePort: apiv1.ServicePort{Port: 80}, + Valid: true, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, Attachable: true, Valid: true, }, - gatewayNsNames: []types.NamespacedName{gatewayNsName}, + gateway: createGateway(), services: map[types.NamespacedName]*apiv1.Service{ svcNsName: ipv4Svc, }, @@ -554,9 +595,8 @@ func TestBuildTLSRoute(t *testing.T) { r := buildTLSRoute( test.gtr, - test.gatewayNsNames, + map[types.NamespacedName]*Gateway{client.ObjectKeyFromObject(test.gateway.Source): test.gateway}, test.services, - test.npCfg, test.resolver, ) g.Expect(helpers.Diff(test.expected, r)).To(BeEmpty()) diff --git a/internal/mode/static/state/validation/validationfakes/fake_policy_validator.go b/internal/mode/static/state/validation/validationfakes/fake_policy_validator.go index 4460ec36e3..59883a9fc7 100644 --- a/internal/mode/static/state/validation/validationfakes/fake_policy_validator.go +++ b/internal/mode/static/state/validation/validationfakes/fake_policy_validator.go @@ -22,11 +22,10 @@ type FakePolicyValidator struct { conflictsReturnsOnCall map[int]struct { result1 bool } - ValidateStub func(policies.Policy, *policies.GlobalSettings) []conditions.Condition + ValidateStub func(policies.Policy) []conditions.Condition validateMutex sync.RWMutex validateArgsForCall []struct { arg1 policies.Policy - arg2 *policies.GlobalSettings } validateReturns struct { result1 []conditions.Condition @@ -34,6 +33,18 @@ type FakePolicyValidator struct { validateReturnsOnCall map[int]struct { result1 []conditions.Condition } + ValidateGlobalSettingsStub func(policies.Policy, *policies.GlobalSettings) []conditions.Condition + validateGlobalSettingsMutex sync.RWMutex + validateGlobalSettingsArgsForCall []struct { + arg1 policies.Policy + arg2 *policies.GlobalSettings + } + validateGlobalSettingsReturns struct { + result1 []conditions.Condition + } + validateGlobalSettingsReturnsOnCall map[int]struct { + result1 []conditions.Condition + } invocations map[string][][]interface{} invocationsMutex sync.RWMutex } @@ -100,19 +111,18 @@ func (fake *FakePolicyValidator) ConflictsReturnsOnCall(i int, result1 bool) { }{result1} } -func (fake *FakePolicyValidator) Validate(arg1 policies.Policy, arg2 *policies.GlobalSettings) []conditions.Condition { +func (fake *FakePolicyValidator) Validate(arg1 policies.Policy) []conditions.Condition { fake.validateMutex.Lock() ret, specificReturn := fake.validateReturnsOnCall[len(fake.validateArgsForCall)] fake.validateArgsForCall = append(fake.validateArgsForCall, struct { arg1 policies.Policy - arg2 *policies.GlobalSettings - }{arg1, arg2}) + }{arg1}) stub := fake.ValidateStub fakeReturns := fake.validateReturns - fake.recordInvocation("Validate", []interface{}{arg1, arg2}) + fake.recordInvocation("Validate", []interface{}{arg1}) fake.validateMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1) } if specificReturn { return ret.result1 @@ -126,17 +136,17 @@ func (fake *FakePolicyValidator) ValidateCallCount() int { return len(fake.validateArgsForCall) } -func (fake *FakePolicyValidator) ValidateCalls(stub func(policies.Policy, *policies.GlobalSettings) []conditions.Condition) { +func (fake *FakePolicyValidator) ValidateCalls(stub func(policies.Policy) []conditions.Condition) { fake.validateMutex.Lock() defer fake.validateMutex.Unlock() fake.ValidateStub = stub } -func (fake *FakePolicyValidator) ValidateArgsForCall(i int) (policies.Policy, *policies.GlobalSettings) { +func (fake *FakePolicyValidator) ValidateArgsForCall(i int) policies.Policy { fake.validateMutex.RLock() defer fake.validateMutex.RUnlock() argsForCall := fake.validateArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1 } func (fake *FakePolicyValidator) ValidateReturns(result1 []conditions.Condition) { @@ -162,6 +172,68 @@ func (fake *FakePolicyValidator) ValidateReturnsOnCall(i int, result1 []conditio }{result1} } +func (fake *FakePolicyValidator) ValidateGlobalSettings(arg1 policies.Policy, arg2 *policies.GlobalSettings) []conditions.Condition { + fake.validateGlobalSettingsMutex.Lock() + ret, specificReturn := fake.validateGlobalSettingsReturnsOnCall[len(fake.validateGlobalSettingsArgsForCall)] + fake.validateGlobalSettingsArgsForCall = append(fake.validateGlobalSettingsArgsForCall, struct { + arg1 policies.Policy + arg2 *policies.GlobalSettings + }{arg1, arg2}) + stub := fake.ValidateGlobalSettingsStub + fakeReturns := fake.validateGlobalSettingsReturns + fake.recordInvocation("ValidateGlobalSettings", []interface{}{arg1, arg2}) + fake.validateGlobalSettingsMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakePolicyValidator) ValidateGlobalSettingsCallCount() int { + fake.validateGlobalSettingsMutex.RLock() + defer fake.validateGlobalSettingsMutex.RUnlock() + return len(fake.validateGlobalSettingsArgsForCall) +} + +func (fake *FakePolicyValidator) ValidateGlobalSettingsCalls(stub func(policies.Policy, *policies.GlobalSettings) []conditions.Condition) { + fake.validateGlobalSettingsMutex.Lock() + defer fake.validateGlobalSettingsMutex.Unlock() + fake.ValidateGlobalSettingsStub = stub +} + +func (fake *FakePolicyValidator) ValidateGlobalSettingsArgsForCall(i int) (policies.Policy, *policies.GlobalSettings) { + fake.validateGlobalSettingsMutex.RLock() + defer fake.validateGlobalSettingsMutex.RUnlock() + argsForCall := fake.validateGlobalSettingsArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakePolicyValidator) ValidateGlobalSettingsReturns(result1 []conditions.Condition) { + fake.validateGlobalSettingsMutex.Lock() + defer fake.validateGlobalSettingsMutex.Unlock() + fake.ValidateGlobalSettingsStub = nil + fake.validateGlobalSettingsReturns = struct { + result1 []conditions.Condition + }{result1} +} + +func (fake *FakePolicyValidator) ValidateGlobalSettingsReturnsOnCall(i int, result1 []conditions.Condition) { + fake.validateGlobalSettingsMutex.Lock() + defer fake.validateGlobalSettingsMutex.Unlock() + fake.ValidateGlobalSettingsStub = nil + if fake.validateGlobalSettingsReturnsOnCall == nil { + fake.validateGlobalSettingsReturnsOnCall = make(map[int]struct { + result1 []conditions.Condition + }) + } + fake.validateGlobalSettingsReturnsOnCall[i] = struct { + result1 []conditions.Condition + }{result1} +} + func (fake *FakePolicyValidator) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() @@ -169,6 +241,8 @@ func (fake *FakePolicyValidator) Invocations() map[string][][]interface{} { defer fake.conflictsMutex.RUnlock() fake.validateMutex.RLock() defer fake.validateMutex.RUnlock() + fake.validateGlobalSettingsMutex.RLock() + defer fake.validateGlobalSettingsMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/internal/mode/static/state/validation/validator.go b/internal/mode/static/state/validation/validator.go index dd48573902..6e78928b6d 100644 --- a/internal/mode/static/state/validation/validator.go +++ b/internal/mode/static/state/validation/validator.go @@ -54,7 +54,9 @@ type GenericValidator interface { //counterfeiter:generate . PolicyValidator type PolicyValidator interface { // Validate validates an NGF Policy. - Validate(policy policies.Policy, globalSettings *policies.GlobalSettings) []conditions.Condition + Validate(policy policies.Policy) []conditions.Condition + // ValidateGlobalSettings validates an NGF Policy with the NginxProxy settings. + ValidateGlobalSettings(policy policies.Policy, globalSettings *policies.GlobalSettings) []conditions.Condition // Conflicts returns true if the two Policies conflict. Conflicts(a, b policies.Policy) bool } diff --git a/internal/mode/static/status/prepare_requests.go b/internal/mode/static/status/prepare_requests.go index f3bd39c2a3..fc0cfe358a 100644 --- a/internal/mode/static/status/prepare_requests.go +++ b/internal/mode/static/status/prepare_requests.go @@ -111,8 +111,8 @@ func prepareRouteStatus( for _, ref := range parentRefs { failedAttachmentCondCount := 0 - if ref.Attachment != nil && !ref.Attachment.Attached { - failedAttachmentCondCount = 1 + if ref.Attachment != nil { + failedAttachmentCondCount = len(ref.Attachment.FailedConditions) } allConds := make([]conditions.Condition, 0, len(conds)+len(defaultConds)+failedAttachmentCondCount) @@ -120,8 +120,8 @@ func prepareRouteStatus( // ensured by DeduplicateConditions. allConds = append(allConds, defaultConds...) allConds = append(allConds, conds...) - if failedAttachmentCondCount == 1 { - allConds = append(allConds, ref.Attachment.FailedCondition) + if failedAttachmentCondCount > 0 { + allConds = append(allConds, ref.Attachment.FailedConditions...) } if nginxReloadRes.Error != nil { @@ -136,8 +136,8 @@ func prepareRouteStatus( ps := v1.RouteParentStatus{ ParentRef: v1.ParentReference{ - Namespace: helpers.GetPointer(v1.Namespace(ref.Gateway.Namespace)), - Name: v1.ObjectName(ref.Gateway.Name), + Namespace: helpers.GetPointer(v1.Namespace(ref.Gateway.NamespacedName.Namespace)), + Name: v1.ObjectName(ref.Gateway.NamespacedName.Name), SectionName: ref.SectionName, }, ControllerName: v1.GatewayController(gatewayCtlrName), @@ -205,28 +205,16 @@ func PrepareGatewayClassRequests( // PrepareGatewayRequests prepares status UpdateRequests for the given Gateways. func PrepareGatewayRequests( gateway *graph.Gateway, - ignoredGateways map[types.NamespacedName]*v1.Gateway, transitionTime metav1.Time, gwAddresses []v1.GatewayStatusAddress, nginxReloadRes graph.NginxReloadResult, ) []frameworkStatus.UpdateRequest { - reqs := make([]frameworkStatus.UpdateRequest, 0, 1+len(ignoredGateways)) + reqs := make([]frameworkStatus.UpdateRequest, 0, 1) if gateway != nil { reqs = append(reqs, prepareGatewayRequest(gateway, transitionTime, gwAddresses, nginxReloadRes)) } - for nsname, gw := range ignoredGateways { - apiConds := conditions.ConvertConditions(staticConds.NewGatewayConflict(), gw.Generation, transitionTime) - reqs = append(reqs, frameworkStatus.UpdateRequest{ - NsName: nsname, - ResourceType: &v1.Gateway{}, - Setter: newGatewayStatusSetter(v1.GatewayStatus{ - Conditions: apiConds, - }), - }) - } - return reqs } @@ -383,19 +371,24 @@ func PrepareBackendTLSPolicyRequests( conds := conditions.DeduplicateConditions(pol.Conditions) apiConds := conditions.ConvertConditions(conds, pol.Source.Generation, transitionTime) - status := v1alpha2.PolicyStatus{ - Ancestors: []v1alpha2.PolicyAncestorStatus{ - { - AncestorRef: v1.ParentReference{ - Namespace: (*v1.Namespace)(&pol.Gateway.Namespace), - Name: v1alpha2.ObjectName(pol.Gateway.Name), - Group: helpers.GetPointer[v1.Group](v1.GroupName), - Kind: helpers.GetPointer[v1.Kind](kinds.Gateway), - }, - ControllerName: v1alpha2.GatewayController(gatewayCtlrName), - Conditions: apiConds, + policyAncestors := make([]v1alpha2.PolicyAncestorStatus, 0, len(pol.Gateways)) + for _, gwNsName := range pol.Gateways { + policyAncestorStatus := v1alpha2.PolicyAncestorStatus{ + AncestorRef: v1.ParentReference{ + Namespace: helpers.GetPointer(v1.Namespace(gwNsName.Namespace)), + Name: v1.ObjectName(gwNsName.Name), + Group: helpers.GetPointer[v1.Group](v1.GroupName), + Kind: helpers.GetPointer[v1.Kind](kinds.Gateway), }, - }, + ControllerName: v1alpha2.GatewayController(gatewayCtlrName), + Conditions: apiConds, + } + + policyAncestors = append(policyAncestors, policyAncestorStatus) + } + + status := v1alpha2.PolicyStatus{ + Ancestors: policyAncestors, } reqs = append(reqs, frameworkStatus.UpdateRequest{ diff --git a/internal/mode/static/status/prepare_requests_test.go b/internal/mode/static/status/prepare_requests_test.go index 8bb8ca34f7..fbc5ede98e 100644 --- a/internal/mode/static/status/prepare_requests_test.go +++ b/internal/mode/static/status/prepare_requests_test.go @@ -71,6 +71,9 @@ var ( { SectionName: helpers.GetPointer[v1.SectionName]("listener-80-2"), }, + { + SectionName: helpers.GetPointer[v1.SectionName]("listener-80-3"), + }, }, } @@ -85,7 +88,7 @@ var ( parentRefsValid = []graph.ParentRef{ { Idx: 0, - Gateway: gwNsName, + Gateway: &graph.ParentRefGateway{NamespacedName: gwNsName}, SectionName: commonRouteSpecValid.ParentRefs[0].SectionName, Attachment: &graph.ParentRefAttachmentStatus{ Attached: true, @@ -93,11 +96,20 @@ var ( }, { Idx: 1, - Gateway: gwNsName, + Gateway: &graph.ParentRefGateway{NamespacedName: gwNsName}, SectionName: commonRouteSpecValid.ParentRefs[1].SectionName, Attachment: &graph.ParentRefAttachmentStatus{ - Attached: false, - FailedCondition: invalidAttachmentCondition, + Attached: false, + FailedConditions: []conditions.Condition{invalidAttachmentCondition}, + }, + }, + { + Idx: 2, + Gateway: &graph.ParentRefGateway{NamespacedName: gwNsName}, + SectionName: commonRouteSpecValid.ParentRefs[2].SectionName, + Attachment: &graph.ParentRefAttachmentStatus{ + Attached: true, + FailedConditions: []conditions.Condition{invalidAttachmentCondition}, }, }, } @@ -105,7 +117,7 @@ var ( parentRefsInvalid = []graph.ParentRef{ { Idx: 0, - Gateway: gwNsName, + Gateway: &graph.ParentRefGateway{NamespacedName: gwNsName}, Attachment: nil, SectionName: commonRouteSpecInvalid.ParentRefs[0].SectionName, }, @@ -171,6 +183,38 @@ var ( }, }, }, + { + ParentRef: v1.ParentReference{ + Namespace: helpers.GetPointer(v1.Namespace(gwNsName.Namespace)), + Name: v1.ObjectName(gwNsName.Name), + SectionName: helpers.GetPointer[v1.SectionName]("listener-80-3"), + }, + ControllerName: gatewayCtlrName, + Conditions: []metav1.Condition{ + { + Type: string(v1.RouteConditionAccepted), + Status: metav1.ConditionTrue, + ObservedGeneration: 3, + LastTransitionTime: transitionTime, + Reason: string(v1.RouteReasonAccepted), + Message: "The route is accepted", + }, + { + Type: string(v1.RouteConditionResolvedRefs), + Status: metav1.ConditionTrue, + ObservedGeneration: 3, + LastTransitionTime: transitionTime, + Reason: string(v1.RouteReasonResolvedRefs), + Message: "All references are resolved", + }, + { + Type: invalidAttachmentCondition.Type, + Status: metav1.ConditionTrue, + ObservedGeneration: 3, + LastTransitionTime: transitionTime, + }, + }, + }, }, } @@ -475,7 +519,7 @@ func TestBuildRouteStatusesNginxErr(t *testing.T) { ParentRefs: []graph.ParentRef{ { Idx: 0, - Gateway: gwNsName, + Gateway: &graph.ParentRefGateway{NamespacedName: gwNsName}, Attachment: &graph.ParentRefAttachmentStatus{ Attached: true, }, @@ -741,77 +785,15 @@ func TestBuildGatewayStatuses(t *testing.T) { routeKey := graph.RouteKey{NamespacedName: types.NamespacedName{Namespace: "test", Name: "hr-1"}} tests := []struct { - nginxReloadRes graph.NginxReloadResult - gateway *graph.Gateway - ignoredGateways map[types.NamespacedName]*v1.Gateway - expected map[types.NamespacedName]v1.GatewayStatus - name string + nginxReloadRes graph.NginxReloadResult + gateway *graph.Gateway + expected map[types.NamespacedName]v1.GatewayStatus + name string }{ { name: "nil gateway and no ignored gateways", expected: map[types.NamespacedName]v1.GatewayStatus{}, }, - { - name: "nil gateway and ignored gateways", - ignoredGateways: map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "ignored-1"}: { - ObjectMeta: metav1.ObjectMeta{ - Name: "ignored-1", - Namespace: "test", - Generation: 1, - }, - }, - {Namespace: "test", Name: "ignored-2"}: { - ObjectMeta: metav1.ObjectMeta{ - Name: "ignored-2", - Namespace: "test", - Generation: 2, - }, - }, - }, - expected: map[types.NamespacedName]v1.GatewayStatus{ - {Namespace: "test", Name: "ignored-1"}: { - Conditions: []metav1.Condition{ - { - Type: string(v1.GatewayConditionAccepted), - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - LastTransitionTime: transitionTime, - Reason: string(staticConds.GatewayReasonGatewayConflict), - Message: staticConds.GatewayMessageGatewayConflict, - }, - { - Type: string(v1.GatewayConditionProgrammed), - Status: metav1.ConditionFalse, - ObservedGeneration: 1, - LastTransitionTime: transitionTime, - Reason: string(staticConds.GatewayReasonGatewayConflict), - Message: staticConds.GatewayMessageGatewayConflict, - }, - }, - }, - {Namespace: "test", Name: "ignored-2"}: { - Conditions: []metav1.Condition{ - { - Type: string(v1.GatewayConditionAccepted), - Status: metav1.ConditionFalse, - ObservedGeneration: 2, - LastTransitionTime: transitionTime, - Reason: string(staticConds.GatewayReasonGatewayConflict), - Message: staticConds.GatewayMessageGatewayConflict, - }, - { - Type: string(v1.GatewayConditionProgrammed), - Status: metav1.ConditionFalse, - ObservedGeneration: 2, - LastTransitionTime: transitionTime, - Reason: string(staticConds.GatewayReasonGatewayConflict), - Message: staticConds.GatewayMessageGatewayConflict, - }, - }, - }, - }, - }, { name: "valid gateway; all valid listeners", gateway: &graph.Gateway{ @@ -1264,17 +1246,10 @@ func TestBuildGatewayStatuses(t *testing.T) { expectedTotalReqs++ } - for _, gw := range test.ignoredGateways { - err := k8sClient.Create(context.Background(), gw) - g.Expect(err).ToNot(HaveOccurred()) - expectedTotalReqs++ - } - updater := statusFramework.NewUpdater(k8sClient, logr.Discard()) reqs := PrepareGatewayRequests( test.gateway, - test.ignoredGateways, transitionTime, addr, test.nginxReloadRes, @@ -1304,6 +1279,7 @@ func TestBuildBackendTLSPolicyStatuses(t *testing.T) { type policyCfg struct { Name string Conditions []conditions.Condition + Gateways []types.NamespacedName Valid bool Ignored bool IsReferenced bool @@ -1322,7 +1298,7 @@ func TestBuildBackendTLSPolicyStatuses(t *testing.T) { Ignored: policyCfg.Ignored, IsReferenced: policyCfg.IsReferenced, Conditions: policyCfg.Conditions, - Gateway: types.NamespacedName{Name: "gateway", Namespace: "test"}, + Gateways: policyCfg.Gateways, } } @@ -1334,12 +1310,19 @@ func TestBuildBackendTLSPolicyStatuses(t *testing.T) { Valid: true, IsReferenced: true, Conditions: attachedConds, + Gateways: []types.NamespacedName{ + {Namespace: "test", Name: "gateway"}, + {Namespace: "test", Name: "gateway-2"}, + }, } invalidPolicyCfg := policyCfg{ Name: "invalid-bt", IsReferenced: true, Conditions: invalidConds, + Gateways: []types.NamespacedName{ + {Namespace: "test", Name: "gateway"}, + }, } ignoredPolicyCfg := policyCfg{ @@ -1392,6 +1375,25 @@ func TestBuildBackendTLSPolicyStatuses(t *testing.T) { }, }, }, + { + AncestorRef: v1.ParentReference{ + Namespace: helpers.GetPointer[v1.Namespace]("test"), + Name: "gateway-2", + Group: helpers.GetPointer[v1.Group](v1.GroupName), + Kind: helpers.GetPointer[v1.Kind](kinds.Gateway), + }, + ControllerName: gatewayCtlrName, + Conditions: []metav1.Condition{ + { + Type: string(v1alpha2.PolicyConditionAccepted), + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + LastTransitionTime: transitionTime, + Reason: string(v1alpha2.PolicyReasonAccepted), + Message: "Policy is accepted", + }, + }, + }, }, }, }, @@ -1470,6 +1472,25 @@ func TestBuildBackendTLSPolicyStatuses(t *testing.T) { }, }, }, + { + AncestorRef: v1.ParentReference{ + Namespace: helpers.GetPointer[v1.Namespace]("test"), + Name: "gateway-2", + Group: helpers.GetPointer[v1.Group](v1.GroupName), + Kind: helpers.GetPointer[v1.Kind](kinds.Gateway), + }, + ControllerName: gatewayCtlrName, + Conditions: []metav1.Condition{ + { + Type: string(v1alpha2.PolicyConditionAccepted), + Status: metav1.ConditionTrue, + ObservedGeneration: 1, + LastTransitionTime: transitionTime, + Reason: string(v1alpha2.PolicyReasonAccepted), + Message: "Policy is accepted", + }, + }, + }, }, }, }, diff --git a/internal/mode/static/telemetry/collector.go b/internal/mode/static/telemetry/collector.go index 1be3accf14..cfa3614df7 100644 --- a/internal/mode/static/telemetry/collector.go +++ b/internal/mode/static/telemetry/collector.go @@ -199,10 +199,7 @@ func collectGraphResourceCount( ngfResourceCounts.GatewayClassCount++ } - ngfResourceCounts.GatewayCount = int64(len(g.IgnoredGateways)) - if g.Gateway != nil { - ngfResourceCounts.GatewayCount++ - } + ngfResourceCounts.GatewayCount = int64(len(g.Gateways)) routeCounts := computeRouteCount(g.Routes, g.L4Routes) ngfResourceCounts.HTTPRouteCount = routeCounts.HTTPRouteCount diff --git a/internal/mode/static/telemetry/collector_test.go b/internal/mode/static/telemetry/collector_test.go index 80dcd8b077..8077606d95 100644 --- a/internal/mode/static/telemetry/collector_test.go +++ b/internal/mode/static/telemetry/collector_test.go @@ -272,15 +272,15 @@ var _ = Describe("Collector", Ordered, func() { graph := &graph.Graph{ GatewayClass: &graph.GatewayClass{}, - Gateway: &graph.Gateway{}, + Gateways: map[types.NamespacedName]*graph.Gateway{ + {Name: "gateway1"}: {}, + {Name: "gateway2"}: {}, + {Name: "gateway3"}: {}, + }, IgnoredGatewayClasses: map[types.NamespacedName]*gatewayv1.GatewayClass{ {Name: "ignoredGC1"}: {}, {Name: "ignoredGC2"}: {}, }, - IgnoredGateways: map[types.NamespacedName]*gatewayv1.Gateway{ - {Name: "ignoredGw1"}: {}, - {Name: "ignoredGw2"}: {}, - }, Routes: map[graph.RouteKey]*graph.L7Route{ {NamespacedName: types.NamespacedName{Namespace: "test", Name: "hr-1"}}: {RouteType: graph.RouteTypeHTTP}, {NamespacedName: types.NamespacedName{Namespace: "test", Name: "hr-2"}}: {RouteType: graph.RouteTypeHTTP}, @@ -578,7 +578,9 @@ var _ = Describe("Collector", Ordered, func() { graph1 = &graph.Graph{ GatewayClass: &graph.GatewayClass{}, - Gateway: &graph.Gateway{}, + Gateways: map[types.NamespacedName]*graph.Gateway{ + {Name: "gateway1"}: {}, + }, Routes: map[graph.RouteKey]*graph.L7Route{ {NamespacedName: types.NamespacedName{Namespace: "test", Name: "hr-1"}}: {RouteType: graph.RouteTypeHTTP}, }, diff --git a/tests/conformance/conformance-rbac.yaml b/tests/conformance/conformance-rbac.yaml index c1c0d54185..26572dbe94 100644 --- a/tests/conformance/conformance-rbac.yaml +++ b/tests/conformance/conformance-rbac.yaml @@ -16,6 +16,7 @@ rules: - pods - secrets - services + - serviceaccounts verbs: - create - delete diff --git a/tests/framework/resourcemanager.go b/tests/framework/resourcemanager.go index 50aab0653e..912c321090 100644 --- a/tests/framework/resourcemanager.go +++ b/tests/framework/resourcemanager.go @@ -692,25 +692,34 @@ func GetReadyNGFPodNames( ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - var podList core.PodList - if err := k8sClient.List( + var ngfPodNames []string + + err := wait.PollUntilContextCancel( ctx, - &podList, - client.InNamespace(namespace), - client.MatchingLabels{ - "app.kubernetes.io/instance": releaseName, - }, - ); err != nil { - return nil, fmt.Errorf("error getting list of NGF Pods: %w", err) - } + 500*time.Millisecond, + true, // poll immediately + func(ctx context.Context) (bool, error) { + var podList core.PodList + if err := k8sClient.List( + ctx, + &podList, + client.InNamespace(namespace), + client.MatchingLabels{ + "app.kubernetes.io/instance": releaseName, + }, + ); err != nil { + return false, fmt.Errorf("error getting list of NGF Pods: %w", err) + } - if len(podList.Items) == 0 { - return nil, errors.New("unable to find NGF Pod(s)") + ngfPodNames = getReadyPodNames(podList) + return len(ngfPodNames) > 0, nil + }, + ) + if err != nil { + return nil, fmt.Errorf("timed out waiting for NGF Pods to be ready: %w", err) } - names := getReadyPodNames(podList) - - return names, nil + return ngfPodNames, nil } // GetReadyNginxPodNames returns the name(s) of the NGINX Pod(s). @@ -722,23 +731,32 @@ func GetReadyNginxPodNames( ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - var podList core.PodList - if err := k8sClient.List( + var nginxPodNames []string + + err := wait.PollUntilContextCancel( ctx, - &podList, - client.InNamespace(namespace), - client.HasLabels{"gateway.networking.k8s.io/gateway-name"}, - ); err != nil { - return nil, fmt.Errorf("error getting list of NGINX Pods: %w", err) - } + 500*time.Millisecond, + true, // poll immediately + func(ctx context.Context) (bool, error) { + var podList core.PodList + if err := k8sClient.List( + ctx, + &podList, + client.InNamespace(namespace), + client.HasLabels{"gateway.networking.k8s.io/gateway-name"}, + ); err != nil { + return false, fmt.Errorf("error getting list of NGINX Pods: %w", err) + } - if len(podList.Items) == 0 { - return nil, errors.New("unable to find NGINX Pod(s)") + nginxPodNames = getReadyPodNames(podList) + return len(nginxPodNames) > 0, nil + }, + ) + if err != nil { + return nil, fmt.Errorf("timed out waiting for NGINX Pods to be ready: %w", err) } - names := getReadyPodNames(podList) - - return names, nil + return nginxPodNames, nil } func getReadyPodNames(podList core.PodList) []string { diff --git a/tests/suite/advanced_routing_test.go b/tests/suite/advanced_routing_test.go index 844e1db02c..1359beb795 100644 --- a/tests/suite/advanced_routing_test.go +++ b/tests/suite/advanced_routing_test.go @@ -40,7 +40,7 @@ var _ = Describe("AdvancedRouting", Ordered, Label("functional", "routing"), fun Expect(resourceManager.ApplyFromFiles(files, namespace)).To(Succeed()) Expect(resourceManager.WaitForAppsToBeReady(namespace)).To(Succeed()) - nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, namespace, timeoutConfig.GetTimeout) + nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, namespace, timeoutConfig.GetStatusTimeout) Expect(err).ToNot(HaveOccurred()) Expect(nginxPodNames).To(HaveLen(1)) diff --git a/tests/suite/client_settings_test.go b/tests/suite/client_settings_test.go index 835f3a9896..7bd5f4ed57 100644 --- a/tests/suite/client_settings_test.go +++ b/tests/suite/client_settings_test.go @@ -47,7 +47,7 @@ var _ = Describe("ClientSettingsPolicy", Ordered, Label("functional", "cspolicy" Expect(resourceManager.ApplyFromFiles(files, namespace)).To(Succeed()) Expect(resourceManager.WaitForAppsToBeReady(namespace)).To(Succeed()) - nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, namespace, timeoutConfig.GetTimeout) + nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, namespace, timeoutConfig.GetStatusTimeout) Expect(err).ToNot(HaveOccurred()) Expect(nginxPodNames).To(HaveLen(1)) @@ -255,13 +255,12 @@ var _ = Describe("ClientSettingsPolicy", Ordered, Label("functional", "cspolicy" When("a ClientSettingsPolicy targets an invalid resources", func() { Specify("their accepted condition is set to TargetNotFound", func() { files := []string{ - "clientsettings/ignored-gateway.yaml", - "clientsettings/invalid-csp.yaml", + "clientsettings/invalid-route-csp.yaml", } Expect(resourceManager.ApplyFromFiles(files, namespace)).To(Succeed()) - nsname := types.NamespacedName{Name: "invalid-csp", Namespace: namespace} + nsname := types.NamespacedName{Name: "invalid-route-csp", Namespace: namespace} Expect(waitForCSPolicyToHaveTargetNotFoundAcceptedCond(nsname)).To(Succeed()) Expect(resourceManager.DeleteFromFiles(files, namespace)).To(Succeed()) diff --git a/tests/suite/graceful_recovery_test.go b/tests/suite/graceful_recovery_test.go index 2e844f46e6..bd71551933 100644 --- a/tests/suite/graceful_recovery_test.go +++ b/tests/suite/graceful_recovery_test.go @@ -210,7 +210,7 @@ var _ = Describe("Graceful Recovery test", Ordered, Label("graceful-recovery"), var err error Eventually( func() bool { - nginxPodNames, err = framework.GetReadyNginxPodNames(k8sClient, ns.Name, timeoutConfig.GetTimeout) + nginxPodNames, err = framework.GetReadyNginxPodNames(k8sClient, ns.Name, timeoutConfig.GetStatusTimeout) return len(nginxPodNames) == 1 && err == nil }). WithTimeout(timeoutConfig.CreateTimeout). @@ -310,7 +310,7 @@ var _ = Describe("Graceful Recovery test", Ordered, Label("graceful-recovery"), var nginxPodNames []string Eventually( func() bool { - nginxPodNames, err = framework.GetReadyNginxPodNames(k8sClient, ns.Name, timeoutConfig.GetTimeout) + nginxPodNames, err = framework.GetReadyNginxPodNames(k8sClient, ns.Name, timeoutConfig.GetStatusTimeout) return len(nginxPodNames) == 1 && err == nil }). WithTimeout(timeoutConfig.CreateTimeout * 2). @@ -349,7 +349,7 @@ var _ = Describe("Graceful Recovery test", Ordered, Label("graceful-recovery"), } getLeaderElectionLeaseHolderName := func() (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), timeoutConfig.GetTimeout) + ctx, cancel := context.WithTimeout(context.Background(), timeoutConfig.GetStatusTimeout) defer cancel() var lease coordination.Lease @@ -384,7 +384,7 @@ var _ = Describe("Graceful Recovery test", Ordered, Label("graceful-recovery"), } BeforeAll(func() { - podNames, err := framework.GetReadyNGFPodNames(k8sClient, ngfNamespace, releaseName, timeoutConfig.GetTimeout) + podNames, err := framework.GetReadyNGFPodNames(k8sClient, ngfNamespace, releaseName, timeoutConfig.GetStatusTimeout) Expect(err).ToNot(HaveOccurred()) Expect(podNames).To(HaveLen(1)) @@ -400,7 +400,7 @@ var _ = Describe("Graceful Recovery test", Ordered, Label("graceful-recovery"), Expect(resourceManager.ApplyFromFiles(files, ns.Name)).To(Succeed()) Expect(resourceManager.WaitForAppsToBeReady(ns.Name)).To(Succeed()) - nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, ns.Name, timeoutConfig.GetTimeout) + nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, ns.Name, timeoutConfig.GetStatusTimeout) Expect(err).ToNot(HaveOccurred()) Expect(nginxPodNames).To(HaveLen(1)) @@ -433,7 +433,7 @@ var _ = Describe("Graceful Recovery test", Ordered, Label("graceful-recovery"), It("recovers when nginx container is restarted", func() { restartNginxContainer(activeNginxPodName, ns.Name, nginxContainerName) - nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, ns.Name, timeoutConfig.GetTimeout) + nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, ns.Name, timeoutConfig.GetStatusTimeout) Expect(err).ToNot(HaveOccurred()) Expect(nginxPodNames).To(HaveLen(1)) activeNginxPodName = nginxPodNames[0] diff --git a/tests/suite/manifests/clientsettings/ignored-gateway.yaml b/tests/suite/manifests/clientsettings/ignored-gateway.yaml deleted file mode 100644 index 74d8317b01..0000000000 --- a/tests/suite/manifests/clientsettings/ignored-gateway.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: gateway.networking.k8s.io/v1 -kind: Gateway -metadata: - name: ignored-gateway -spec: - gatewayClassName: nginx - listeners: - - name: http - port: 80 - protocol: HTTP - hostname: "*.example.com" diff --git a/tests/suite/manifests/clientsettings/invalid-csp.yaml b/tests/suite/manifests/clientsettings/invalid-csp.yaml deleted file mode 100644 index cedfb52e46..0000000000 --- a/tests/suite/manifests/clientsettings/invalid-csp.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: gateway.nginx.org/v1alpha1 -kind: ClientSettingsPolicy -metadata: - name: invalid-csp -spec: - targetRef: - group: gateway.networking.k8s.io - kind: Gateway - name: ignored-gateway - body: - maxSize: 10m - timeout: 30s - keepAlive: - requests: 100 - time: 5s - timeout: - server: 2s - header: 1s diff --git a/tests/suite/manifests/clientsettings/invalid-route-csp.yaml b/tests/suite/manifests/clientsettings/invalid-route-csp.yaml new file mode 100644 index 0000000000..e856d6e5e9 --- /dev/null +++ b/tests/suite/manifests/clientsettings/invalid-route-csp.yaml @@ -0,0 +1,33 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: invalid-route +spec: + parentRefs: + - name: gateway + sectionName: http + hostnames: + - "cafe.example.com" + rules: + - matches: + - path: + type: PathPrefix + value: /invalid + headers: + - name: host_name + value: v2 + backendRefs: + - name: coffee + port: 80 +--- +apiVersion: gateway.nginx.org/v1alpha1 +kind: ClientSettingsPolicy +metadata: + name: invalid-route-csp +spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: invalid-route + keepAlive: + requests: 200 diff --git a/tests/suite/nginxgateway_test.go b/tests/suite/nginxgateway_test.go index 1129310fab..a2d44e3a77 100644 --- a/tests/suite/nginxgateway_test.go +++ b/tests/suite/nginxgateway_test.go @@ -98,7 +98,7 @@ var _ = Describe("NginxGateway", Ordered, Label("functional", "nginxGateway"), f k8sClient, ngfNamespace, releaseName, - timeoutConfig.GetTimeout, + timeoutConfig.GetStatusTimeout, ) if err != nil { return "", err diff --git a/tests/suite/sample_test.go b/tests/suite/sample_test.go index 3426ed973f..d4c265ec37 100644 --- a/tests/suite/sample_test.go +++ b/tests/suite/sample_test.go @@ -38,7 +38,7 @@ var _ = Describe("Basic test example", Label("functional"), func() { Expect(resourceManager.ApplyFromFiles(files, namespace)).To(Succeed()) Expect(resourceManager.WaitForAppsToBeReady(namespace)).To(Succeed()) - nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, namespace, timeoutConfig.GetTimeout) + nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, namespace, timeoutConfig.GetStatusTimeout) Expect(err).ToNot(HaveOccurred()) Expect(nginxPodNames).To(HaveLen(1)) diff --git a/tests/suite/snippets_filter_test.go b/tests/suite/snippets_filter_test.go index 39e099a8f6..397f1e8c58 100644 --- a/tests/suite/snippets_filter_test.go +++ b/tests/suite/snippets_filter_test.go @@ -42,7 +42,7 @@ var _ = Describe("SnippetsFilter", Ordered, Label("functional", "snippets-filter Expect(resourceManager.ApplyFromFiles(files, namespace)).To(Succeed()) Expect(resourceManager.WaitForAppsToBeReady(namespace)).To(Succeed()) - nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, namespace, timeoutConfig.GetTimeout) + nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, namespace, timeoutConfig.GetStatusTimeout) Expect(err).ToNot(HaveOccurred()) Expect(nginxPodNames).To(HaveLen(1)) diff --git a/tests/suite/tracing_test.go b/tests/suite/tracing_test.go index 83c46d4cb4..19b8a55f5f 100644 --- a/tests/suite/tracing_test.go +++ b/tests/suite/tracing_test.go @@ -92,7 +92,7 @@ var _ = Describe("Tracing", FlakeAttempts(2), Ordered, Label("functional", "trac Expect(resourceManager.ApplyFromFiles(files, namespace)).To(Succeed()) Expect(resourceManager.WaitForAppsToBeReady(namespace)).To(Succeed()) - nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, namespace, timeoutConfig.GetTimeout) + nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, namespace, timeoutConfig.GetStatusTimeout) Expect(err).ToNot(HaveOccurred()) Expect(nginxPodNames).To(HaveLen(1)) diff --git a/tests/suite/upstream_settings_test.go b/tests/suite/upstream_settings_test.go index dad10bc0e5..4243a432c9 100644 --- a/tests/suite/upstream_settings_test.go +++ b/tests/suite/upstream_settings_test.go @@ -51,7 +51,7 @@ var _ = Describe("UpstreamSettingsPolicy", Ordered, Label("functional", "uspolic Expect(resourceManager.ApplyFromFiles(files, namespace)).To(Succeed()) Expect(resourceManager.WaitForAppsToBeReady(namespace)).To(Succeed()) - nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, namespace, timeoutConfig.GetTimeout) + nginxPodNames, err := framework.GetReadyNginxPodNames(k8sClient, namespace, timeoutConfig.GetStatusTimeout) Expect(err).ToNot(HaveOccurred()) Expect(nginxPodNames).To(HaveLen(1))