From c76ad1480708f76a63047ddbd050b34180ba4134 Mon Sep 17 00:00:00 2001 From: salonichf5 <146118978+salonichf5@users.noreply.github.com> Date: Thu, 27 Mar 2025 23:49:29 -0600 Subject: [PATCH 01/19] support multiple gateways --- internal/mode/static/handler.go | 166 +- internal/mode/static/handler_test.go | 33 +- .../static/state/change_processor_test.go | 4382 +++++++++-------- .../static/state/dataplane/configuration.go | 63 +- .../state/dataplane/configuration_test.go | 749 +-- .../mode/static/state/graph/backend_refs.go | 11 +- .../static/state/graph/backend_tls_policy.go | 70 +- .../state/graph/backend_tls_policy_test.go | 14 +- internal/mode/static/state/graph/gateway.go | 167 +- .../mode/static/state/graph/gateway_test.go | 1256 ++--- internal/mode/static/state/graph/graph.go | 91 +- .../mode/static/state/graph/graph_test.go | 276 +- .../mode/static/state/graph/grpcroute_test.go | 21 +- .../mode/static/state/graph/httproute_test.go | 14 +- .../state/graph/multiple_gateways_test.go | 4 + internal/mode/static/state/graph/namespace.go | 24 +- .../mode/static/state/graph/namespace_test.go | 162 +- .../mode/static/state/graph/nginxproxy.go | 18 +- .../static/state/graph/nginxproxy_test.go | 34 +- internal/mode/static/state/graph/policies.go | 157 +- .../mode/static/state/graph/policies_test.go | 242 +- .../mode/static/state/graph/route_common.go | 157 +- .../static/state/graph/route_common_test.go | 144 +- internal/mode/static/state/graph/service.go | 119 +- .../mode/static/state/graph/service_test.go | 242 +- .../mode/static/status/prepare_requests.go | 43 +- .../static/status/prepare_requests_test.go | 499 +- internal/mode/static/telemetry/collector.go | 5 +- .../mode/static/telemetry/collector_test.go | 14 +- tests/suite/client_settings_test.go | 5 +- .../clientsettings/invalid-route-csp.yaml | 33 + 31 files changed, 4792 insertions(+), 4423 deletions(-) create mode 100644 internal/mode/static/state/graph/multiple_gateways_test.go create mode 100644 tests/suite/manifests/clientsettings/invalid-route-csp.yaml diff --git a/internal/mode/static/handler.go b/internal/mode/static/handler.go index c22b182e8e..06707007a3 100644 --- a/internal/mode/static/handler.go +++ b/internal/mode/static/handler.go @@ -185,49 +185,51 @@ func (h *eventHandlerImpl) sendNginxConfig( return } - if gr.Gateway == nil { - // still need to update GatewayClass status - obj := &status.QueueObject{ - UpdateType: status.UpdateAll, + for _, gw := range gr.Gateways { + if gw == nil { + // still need to update GatewayClass status + obj := &status.QueueObject{ + UpdateType: status.UpdateAll, + } + h.cfg.statusQueue.Enqueue(obj) + return } - h.cfg.statusQueue.Enqueue(obj) - return - } - go func() { - if err := h.cfg.nginxProvisioner.RegisterGateway(ctx, gr.Gateway, gr.DeploymentName.Name); err != nil { - logger.Error(err, "error from provisioner") - } - }() + 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 +237,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 +245,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 +263,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") @@ -292,56 +295,58 @@ func (h *eventHandlerImpl) waitForStatusUpdates(ctx context.Context) { } var nginxReloadRes graph.NginxReloadResult - switch { - case item.Error != nil: - h.cfg.logger.Error(item.Error, "Failed to update NGINX configuration") - nginxReloadRes.Error = item.Error - case gr.Gateway != nil: - h.cfg.logger.Info("NGINX configuration was successfully updated") - } - gr.LatestReloadResult = nginxReloadRes - - switch item.UpdateType { - case status.UpdateAll: - h.updateStatuses(ctx, gr) - case status.UpdateGateway: - gwAddresses, err := getGatewayAddresses( - ctx, - h.cfg.k8sClient, - item.GatewayService, - gr.Gateway, - h.cfg.gatewayClassName, - ) - if err != nil { - msg := "error getting Gateway Service IP address" - h.cfg.logger.Error(err, msg) - h.cfg.eventRecorder.Eventf( + for _, gw := range gr.Gateways { + switch { + case item.Error != nil: + h.cfg.logger.Error(item.Error, "Failed to update NGINX configuration") + nginxReloadRes.Error = item.Error + case gw != nil: + h.cfg.logger.Info("NGINX configuration was successfully updated") + } + gr.LatestReloadResult = nginxReloadRes + + switch item.UpdateType { + case status.UpdateAll: + h.updateStatuses(ctx, gr, gw) + case status.UpdateGateway: + gwAddresses, err := getGatewayAddresses( + ctx, + h.cfg.k8sClient, item.GatewayService, - v1.EventTypeWarning, - "GetServiceIPFailed", - msg+": %s", - err.Error(), + gw, + h.cfg.gatewayClassName, ) - continue + if err != nil { + msg := "error getting Gateway Service IP address" + h.cfg.logger.Error(err, msg) + h.cfg.eventRecorder.Eventf( + item.GatewayService, + v1.EventTypeWarning, + "GetServiceIPFailed", + msg+": %s", + err.Error(), + ) + continue + } + + transitionTime := metav1.Now() + + gatewayStatuses := status.PrepareGatewayRequests( + gw, + transitionTime, + gwAddresses, + gr.LatestReloadResult, + ) + h.cfg.statusUpdater.UpdateGroup(ctx, groupGateways, gatewayStatuses...) + default: + panic(fmt.Sprintf("unknown event type %T", item.UpdateType)) } - - transitionTime := metav1.Now() - gatewayStatuses := status.PrepareGatewayRequests( - gr.Gateway, - gr.IgnoredGateways, - transitionTime, - gwAddresses, - gr.LatestReloadResult, - ) - h.cfg.statusUpdater.UpdateGroup(ctx, groupGateways, gatewayStatuses...) - default: - panic(fmt.Sprintf("unknown event type %T", item.UpdateType)) } } } -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) { + 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) @@ -392,8 +397,7 @@ 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, diff --git a/internal/mode/static/handler_test.go b/internal/mode/static/handler_test.go index 5175195a7f..9e9e25c0c0 100644 --- a/internal/mode/static/handler_test.go +++ b/internal/mode/static/handler_test.go @@ -96,9 +96,11 @@ 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{ + {}: { + Valid: true, + Source: &gatewayv1.Gateway{}, + }, }, } @@ -185,7 +187,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{}) checkUpsertEventExpectations(e) expectReconfig(dcfg, fakeCfgFiles) @@ -201,7 +203,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) @@ -238,7 +240,7 @@ 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()) }) }) @@ -268,6 +270,7 @@ var _ = Describe("eventHandler", func() { IgnoredGatewayClasses: map[types.NamespacedName]*gatewayv1.GatewayClass{ client.ObjectKeyFromObject(ignoredGC): ignoredGC, }, + Gateways: map[types.NamespacedName]*graph.Gateway{}, } fakeProcessor.ProcessReturns(state.ClusterStateChange, gr) @@ -392,7 +395,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 +408,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 +421,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)) @@ -460,12 +467,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/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 626f87a0aa..e3c4fca5f4 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -10,7 +10,6 @@ import ( apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -22,11 +21,9 @@ 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/controller/index" "github.com/nginx/nginx-gateway-fabric/internal/framework/gatewayclass" "github.com/nginx/nginx-gateway-fabric/internal/framework/helpers" "github.com/nginx/nginx-gateway-fabric/internal/framework/kinds" - ngftypes "github.com/nginx/nginx-gateway-fabric/internal/framework/types" "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state" staticConds "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/conditions" "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/graph" @@ -230,41 +227,41 @@ func createGateway(name string, listeners ...v1.Listener) *v1.Gateway { } } -func createRouteWithMultipleRules( - name, gateway, hostname string, - rules []v1.HTTPRouteRule, -) *v1.HTTPRoute { - hr := createHTTPRoute(name, gateway, hostname) - hr.Spec.Rules = rules - - return hr -} - -func createHTTPRule(path string, backendRefs ...v1.HTTPBackendRef) v1.HTTPRouteRule { - return v1.HTTPRouteRule{ - Matches: []v1.HTTPRouteMatch{ - { - Path: &v1.HTTPPathMatch{ - Type: helpers.GetPointer(v1.PathMatchPathPrefix), - Value: &path, - }, - }, - }, - BackendRefs: backendRefs, - } -} - -func createHTTPBackendRef( - kind *v1.Kind, - name v1.ObjectName, - namespace *v1.Namespace, -) v1.HTTPBackendRef { - return v1.HTTPBackendRef{ - BackendRef: v1.BackendRef{ - BackendObjectReference: createBackendRefObj(kind, name, namespace), - }, - } -} +// func createRouteWithMultipleRules( +// name, gateway, hostname string, +// rules []v1.HTTPRouteRule, +// ) *v1.HTTPRoute { +// hr := createHTTPRoute(name, gateway, hostname) +// hr.Spec.Rules = rules + +// return hr +// } + +// func createHTTPRule(path string, backendRefs ...v1.HTTPBackendRef) v1.HTTPRouteRule { +// return v1.HTTPRouteRule{ +// Matches: []v1.HTTPRouteMatch{ +// { +// Path: &v1.HTTPPathMatch{ +// Type: helpers.GetPointer(v1.PathMatchPathPrefix), +// Value: &path, +// }, +// }, +// }, +// BackendRefs: backendRefs, +// } +// } + +// func createHTTPBackendRef( +// kind *v1.Kind, +// name v1.ObjectName, +// namespace *v1.Namespace, +// ) v1.HTTPBackendRef { +// return v1.HTTPBackendRef{ +// BackendRef: v1.BackendRef{ +// BackendObjectReference: createBackendRefObj(kind, name, namespace), +// }, +// } +// } func createTLSBackendRef(name, namespace string) v1.BackendRef { kindSvc := v1.Kind("Service") @@ -422,23 +419,24 @@ var _ = Describe("ChangeProcessor", func() { Describe("Process gateway resources", Ordered, func() { var ( - gcUpdated *v1.GatewayClass - diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret - diffNsTLSCert, sameNsTLSCert *graph.CertificateBundle - hr1, hr1Updated, hr2 *v1.HTTPRoute - gr1, gr1Updated, gr2 *v1.GRPCRoute - tr1, tr1Updated, tr2 *v1alpha2.TLSRoute - gw1, gw1Updated, gw2 *v1.Gateway - secretRefGrant, hrServiceRefGrant *v1beta1.ReferenceGrant - grServiceRefGrant, trServiceRefGrant *v1beta1.ReferenceGrant - expGraph *graph.Graph - expRouteHR1, expRouteHR2 *graph.L7Route - expRouteGR1, expRouteGR2 *graph.L7Route - expRouteTR1, expRouteTR2 *graph.L4Route - gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata - httpRouteKey1, httpRouteKey2, grpcRouteKey1, grpcRouteKey2 graph.RouteKey - trKey1, trKey2 graph.L4RouteKey - refSvc, refGRPCSvc, refTLSSvc types.NamespacedName + gcUpdated *v1.GatewayClass + diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret + diffNsTLSCert, sameNsTLSCert *graph.CertificateBundle + hr1, hr1Updated *v1.HTTPRoute + gr1, gr1Updated *v1.GRPCRoute + tr1, tr1Updated *v1alpha2.TLSRoute + gw1, gw1Updated, gw2, gw2Updated *v1.Gateway + secretRefGrant, hrServiceRefGrant *v1beta1.ReferenceGrant + grServiceRefGrant, trServiceRefGrant *v1beta1.ReferenceGrant + expGraph *graph.Graph + expRouteHR1 *graph.L7Route + expRouteGR1 *graph.L7Route + expRouteTR1 *graph.L4Route + gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata + httpRouteKey1, grpcRouteKey1 graph.RouteKey + // httpRouteKey2 graph.RouteKey + trKey1 graph.L4RouteKey + refSvc, refGRPCSvc, refTLSSvc types.NamespacedName ) processAndValidateGraph := func(expGraph *graph.Graph) { @@ -482,23 +480,23 @@ var _ = Describe("ChangeProcessor", func() { httpRouteKey1 = graph.CreateRouteKey(hr1) hr1Updated = hr1.DeepCopy() hr1Updated.Generation++ - hr2 = createHTTPRoute("hr-2", "gateway-2", "bar.example.com") - httpRouteKey2 = graph.CreateRouteKey(hr2) + // hr2 = createHTTPRoute("hr-2", "gateway-2", "bar.example.com") + // 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") - grpcRouteKey2 = graph.CreateRouteKey(gr2) + // gr2 = createGRPCRoute("gr-2", "gateway-2", "bar.example.com") + // grpcRouteKey2 = graph.CreateRouteKey(gr2) tlsBackendRef := createTLSBackendRef(refTLSSvc.Name, refTLSSvc.Namespace) tr1 = createTLSRoute("tr-1", "gateway-1", "foo.tls.com", tlsBackendRef) trKey1 = graph.CreateRouteKeyL4(tr1) tr1Updated = tr1.DeepCopy() tr1Updated.Generation++ - tr2 = createTLSRoute("tr-2", "gateway-2", "bar.tls.com", tlsBackendRef) - trKey2 = graph.CreateRouteKeyL4(tr2) + // tr2 = createTLSRoute("tr-2", "gateway-2", "bar.tls.com", tlsBackendRef) + // trKey2 = graph.CreateRouteKeyL4(tr2) secretRefGrant = &v1beta1.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ @@ -645,6 +643,9 @@ var _ = Describe("ChangeProcessor", func() { createTLSListener(tlsListenerName), ) + gw2Updated = gw2.DeepCopy() + gw2Updated.Generation++ + gatewayAPICRD = &metav1.PartialObjectMetadata{ TypeMeta: metav1.TypeMeta{ Kind: "CustomResourceDefinition", @@ -715,47 +716,47 @@ var _ = Describe("ChangeProcessor", func() { }, } - expRouteHR2 = &graph.L7Route{ - Source: hr2, - RouteType: graph.RouteTypeHTTP, - ParentRefs: []graph.ParentRef{ - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, - Attached: true, - ListenerPort: 80, - }, - 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, - }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - Idx: 1, - SectionName: hr2.Spec.ParentRefs[1].SectionName, - }, - }, - Spec: graph.L7RouteSpec{ - Hostnames: hr2.Spec.Hostnames, - Rules: []graph.RouteRule{ - { - ValidMatches: true, - Filters: graph.RouteRuleFilters{ - Valid: true, - Filters: []graph.Filter{}, - }, - Matches: hr2.Spec.Rules[0].Matches, - RouteBackendRefs: []graph.RouteBackendRef{}, - }, - }, - }, - Valid: true, - Attachable: true, - } + // expRouteHR2 = &graph.L7Route{ + // Source: hr2, + // RouteType: graph.RouteTypeHTTP, + // ParentRefs: []graph.ParentRef{ + // { + // Attachment: &graph.ParentRefAttachmentStatus{ + // AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, + // Attached: true, + // ListenerPort: 80, + // }, + // 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, + // }, + // Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + // Idx: 1, + // SectionName: hr2.Spec.ParentRefs[1].SectionName, + // }, + // }, + // Spec: graph.L7RouteSpec{ + // Hostnames: hr2.Spec.Hostnames, + // Rules: []graph.RouteRule{ + // { + // ValidMatches: true, + // Filters: graph.RouteRuleFilters{ + // Valid: true, + // Filters: []graph.Filter{}, + // }, + // Matches: hr2.Spec.Rules[0].Matches, + // RouteBackendRefs: []graph.RouteBackendRef{}, + // }, + // }, + // }, + // Valid: true, + // Attachable: true, + // } expRouteGR1 = &graph.L7Route{ Source: gr1, @@ -810,47 +811,47 @@ var _ = Describe("ChangeProcessor", func() { }, } - expRouteGR2 = &graph.L7Route{ - Source: gr2, - RouteType: graph.RouteTypeGRPC, - ParentRefs: []graph.ParentRef{ - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, - Attached: true, - ListenerPort: 80, - }, - 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, - }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - Idx: 1, - SectionName: gr2.Spec.ParentRefs[1].SectionName, - }, - }, - Spec: graph.L7RouteSpec{ - Hostnames: gr2.Spec.Hostnames, - Rules: []graph.RouteRule{ - { - ValidMatches: true, - Filters: graph.RouteRuleFilters{ - Valid: true, - Filters: []graph.Filter{}, - }, - Matches: graph.ConvertGRPCMatches(gr2.Spec.Rules[0].Matches), - RouteBackendRefs: []graph.RouteBackendRef{}, - }, - }, - }, - Valid: true, - Attachable: true, - } + // expRouteGR2 = &graph.L7Route{ + // Source: gr2, + // RouteType: graph.RouteTypeGRPC, + // ParentRefs: []graph.ParentRef{ + // { + // Attachment: &graph.ParentRefAttachmentStatus{ + // AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, + // Attached: true, + // ListenerPort: 80, + // }, + // 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, + // }, + // Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + // Idx: 1, + // SectionName: gr2.Spec.ParentRefs[1].SectionName, + // }, + // }, + // Spec: graph.L7RouteSpec{ + // Hostnames: gr2.Spec.Hostnames, + // Rules: []graph.RouteRule{ + // { + // ValidMatches: true, + // Filters: graph.RouteRuleFilters{ + // Valid: true, + // Filters: []graph.Filter{}, + // }, + // Matches: graph.ConvertGRPCMatches(gr2.Spec.Rules[0].Matches), + // RouteBackendRefs: []graph.RouteBackendRef{}, + // }, + // }, + // }, + // Valid: true, + // Attachable: true, + // } expRouteTR1 = &graph.L4Route{ Source: tr1, @@ -880,33 +881,33 @@ var _ = Describe("ChangeProcessor", func() { }, } - expRouteTR2 = &graph.L4Route{ - Source: tr2, - ParentRefs: []graph.ParentRef{ - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{tlsListenerName: {"bar.tls.com"}}, - Attached: true, - }, - 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, - }, - }, - Valid: true, - Attachable: true, - Conditions: []conditions.Condition{ - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"tls-service\"", - ), - }, - } + // expRouteTR2 = &graph.L4Route{ + // Source: tr2, + // ParentRefs: []graph.ParentRef{ + // { + // Attachment: &graph.ParentRefAttachmentStatus{ + // AcceptedHostnames: map[string][]string{tlsListenerName: {"bar.tls.com"}}, + // Attached: true, + // }, + // 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, + // }, + // }, + // Valid: true, + // Attachable: true, + // Conditions: []conditions.Condition{ + // staticConds.NewRouteBackendRefRefBackendNotFound( + // "spec.rules[0].backendRefs[0].name: Not found: \"tls-service\"", + // ), + // }, + // } // This is the base case expected graph. Tests will manipulate this to add or remove elements // to fit the expected output of the input under test. @@ -915,60 +916,67 @@ 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, + 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, + 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, + 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: {}, - }, - DeploymentName: types.NamespacedName{ - Namespace: "test", - Name: "gateway-1-test-class", + 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 +1034,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{ @@ -1089,7 +1098,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( @@ -1112,7 +1122,7 @@ var _ = Describe("ChangeProcessor", func() { 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 @@ -1234,6 +1244,13 @@ var _ = Describe("ChangeProcessor", func() { ), } + expGraph.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ + refSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-1"}: {}, + }, + }, + } processAndValidateGraph(expGraph) }) }) @@ -1262,6 +1279,19 @@ var _ = Describe("ChangeProcessor", func() { ), } + expGraph.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ + refGRPCSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-1"}: {}, + }, + }, + refSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-1"}: {}, + }, + }, + } + processAndValidateGraph(expGraph) }) }) @@ -1331,10 +1361,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 +1380,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 +1398,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 +1414,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, @@ -1447,107 +1481,68 @@ var _ = Describe("ChangeProcessor", func() { It("returns populated graph using first 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) - }) - }) - 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, - } - 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(), - } - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - CertBundle: diffNsTLSCert, - } - - processAndValidateGraph(expGraph) - }) - }) - 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(), - } - - expGraph.Routes[grpcRouteKey2] = expRouteGR2 - expGraph.Routes[grpcRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - expGraph.Routes[grpcRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - CertBundle: diffNsTLSCert, - } - - processAndValidateGraph(expGraph) - }) - }) - When("the second TLSRoute is upserted", func() { - It("returns populated graph", func() { - processor.CaptureUpsertChange(tr2) + grpcRoute := expGraph.Routes[grpcRouteKey1] + grpcRoute.Conditions = append(grpcRoute.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", + )) - 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(), - } + httpRoute := expGraph.Routes[httpRouteKey1] + httpRoute.Conditions = append(httpRoute.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"service\"", + )) - expGraph.Routes[grpcRouteKey2] = expRouteGR2 - expGraph.Routes[grpcRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - expGraph.Routes[grpcRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + expGraph.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] = &graph.Gateway{ + Source: gw2, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + 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, + 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, + 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.L4Routes[trKey2] = expRouteTR2 - expGraph.L4Routes[trKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ + Source: sameNsTLSSecret, + CertBundle: sameNsTLSCert, } expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ @@ -1558,1824 +1553,1931 @@ var _ = Describe("ChangeProcessor", func() { processAndValidateGraph(expGraph) }) }) - When("the first Gateway is deleted", func() { - It("returns updated graph", func() { - processor.CaptureDeleteChange( - &v1.Gateway{}, - 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{} - - processAndValidateGraph(expGraph) - }) - }) - When("the second HTTPRoute is deleted", func() { - It("returns updated graph", func() { - processor.CaptureDeleteChange( - &v1.HTTPRoute{}, - 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{} + // When("the second HTTPRoute is upserted", func() { + // It("returns populated graph", func() { + // processor.CaptureUpsertChange(hr2) + + // grpcRoute := expGraph.Routes[grpcRouteKey1] + // grpcRoute.Conditions = append(grpcRoute.Conditions, + // staticConds.NewRouteBackendRefRefBackendNotFound( + // "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", + // )) + + // httpRoute := expGraph.Routes[httpRouteKey1] + // httpRoute.Conditions = append(httpRoute.Conditions, + // staticConds.NewRouteBackendRefRefBackendNotFound( + // "spec.rules[0].backendRefs[0].name: Not found: \"service\"", + // )) + + // expGraph.Routes[httpRouteKey2] = expRouteHR2 + // expGraph.Routes[httpRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + // AcceptedHostnames: map[string][]string{}, + // } + // expGraph.Routes[httpRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + // AcceptedHostnames: map[string][]string{}, + // } + // expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + // Source: diffNsTLSSecret, + // CertBundle: diffNsTLSCert, + // } + + // processAndValidateGraph(expGraph) + // }) + // }) + // 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(), + // } + + // expGraph.Routes[grpcRouteKey2] = expRouteGR2 + // expGraph.Routes[grpcRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + // AcceptedHostnames: map[string][]string{}, + // FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + // } + // expGraph.Routes[grpcRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + // AcceptedHostnames: map[string][]string{}, + // FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + // } + + // expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + // Source: diffNsTLSSecret, + // CertBundle: diffNsTLSCert, + // } + + // processAndValidateGraph(expGraph) + // }) + // }) + // 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(), + // } + + // expGraph.Routes[grpcRouteKey2] = expRouteGR2 + // expGraph.Routes[grpcRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + // AcceptedHostnames: map[string][]string{}, + // FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + // } + // expGraph.Routes[grpcRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + // AcceptedHostnames: map[string][]string{}, + // FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + // } + + // expGraph.L4Routes[trKey2] = expRouteTR2 + // expGraph.L4Routes[trKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + // AcceptedHostnames: map[string][]string{}, + // FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + // } + + // expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + // Source: diffNsTLSSecret, + // CertBundle: diffNsTLSCert, + // } + + // processAndValidateGraph(expGraph) + // }) + // }) + // When("the first Gateway is deleted", func() { + // It("returns updated graph", func() { + // processor.CaptureDeleteChange( + // &v1.Gateway{}, + // 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{} + + // processAndValidateGraph(expGraph) + // }) + // }) + // When("the second HTTPRoute is deleted", func() { + // It("returns updated graph", func() { + // processor.CaptureDeleteChange( + // &v1.HTTPRoute{}, + // 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{} + + // processAndValidateGraph(expGraph) + // }) + // }) + // When("the second GRPCRoute is deleted", func() { + // It("returns updated graph", func() { + // processor.CaptureDeleteChange( + // &v1.GRPCRoute{}, + // 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] + + // delete(listener80.Routes, httpRouteKey1) + // delete(listener443.Routes, httpRouteKey1) + // delete(listener80.Routes, grpcRouteKey1) + // delete(listener443.Routes, grpcRouteKey1) + // delete(tlsListener.L4Routes, trKey1) + + // tlsListener.L4Routes[trKey2] = expRouteTR2 + // expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} + + // 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{} + + // processAndValidateGraph(expGraph) + // }) + // }) + // When("the second TLSRoute is deleted", func() { + // It("returns updated graph", func() { + // processor.CaptureDeleteChange( + // &v1alpha2.TLSRoute{}, + // 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) + + // 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) + + // expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} + // expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} + + // sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) + // listener443.ResolvedSecret = sameNsTLSSecretRef + // expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ + // Source: sameNsTLSSecret, + // CertBundle: sameNsTLSCert, + // } + + // expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + // expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + // expGraph.ReferencedServices = nil + + // processAndValidateGraph(expGraph) + // }) + // }) + // When("the GatewayClass is deleted", func() { + // It("returns updated graph", func() { + // processor.CaptureDeleteChange( + // &v1.GatewayClass{}, + // types.NamespacedName{Name: gcName}, + // ) + + // expGraph.GatewayClass = nil + // expGraph.Gateway = &graph.Gateway{ + // Source: gw2, + // Conditions: staticConds.NewGatewayInvalid("GatewayClass doesn't exist"), + // } + // expGraph.DeploymentName.Name = "gateway-2-test-class" + // expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} + // expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} + // expGraph.ReferencedSecrets = nil + + // expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + // expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + // expGraph.ReferencedServices = nil + + // processAndValidateGraph(expGraph) + // }) + // }) + // When("the second Gateway is deleted", func() { + // It("returns empty graph", func() { + // processor.CaptureDeleteChange( + // &v1.Gateway{}, + // types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + // ) + + // expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + // expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + // expGraph.ReferencedServices = nil + + // processAndValidateGraph(&graph.Graph{}) + // }) + // }) + // When("the first HTTPRoute is deleted", func() { + // It("returns empty graph", func() { + // processor.CaptureDeleteChange( + // &v1.HTTPRoute{}, + // types.NamespacedName{Namespace: "test", Name: "hr-1"}, + // ) + + // expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + // expGraph.ReferencedServices = nil + + // processAndValidateGraph(&graph.Graph{}) + // }) + // }) + // When("the first GRPCRoute is deleted", func() { + // It("returns empty graph", func() { + // processor.CaptureDeleteChange( + // &v1.GRPCRoute{}, + // types.NamespacedName{Namespace: "test", Name: "gr-1"}, + // ) + + // expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + // expGraph.ReferencedServices = nil + + // processAndValidateGraph(&graph.Graph{}) + // }) + // }) + // When("the first TLSRoute is deleted", func() { + // It("returns empty graph", func() { + // processor.CaptureDeleteChange( + // &v1alpha2.TLSRoute{}, + // types.NamespacedName{Namespace: "test", Name: "tr-1"}, + // ) + + // expGraph.ReferencedServices = nil + + // processAndValidateGraph(&graph.Graph{}) + // }) + // }) + }) - processAndValidateGraph(expGraph) - }) - }) - When("the second GRPCRoute is deleted", func() { - It("returns updated graph", func() { - processor.CaptureDeleteChange( - &v1.GRPCRoute{}, - 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] - - delete(listener80.Routes, httpRouteKey1) - delete(listener443.Routes, httpRouteKey1) - delete(listener80.Routes, grpcRouteKey1) - delete(listener443.Routes, grpcRouteKey1) - delete(tlsListener.L4Routes, trKey1) - - tlsListener.L4Routes[trKey2] = expRouteTR2 - expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - - 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{} - - processAndValidateGraph(expGraph) - }) - }) - When("the second TLSRoute is deleted", func() { - It("returns updated graph", func() { - processor.CaptureDeleteChange( - &v1alpha2.TLSRoute{}, - 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) - - 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) - - expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} - - sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) - listener443.ResolvedSecret = sameNsTLSSecretRef - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, - CertBundle: sameNsTLSCert, - } - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - processAndValidateGraph(expGraph) - }) - }) - When("the GatewayClass is deleted", func() { - It("returns updated graph", func() { - processor.CaptureDeleteChange( - &v1.GatewayClass{}, - types.NamespacedName{Name: gcName}, - ) - - expGraph.GatewayClass = nil - expGraph.Gateway = &graph.Gateway{ - Source: gw2, - Conditions: staticConds.NewGatewayInvalid("GatewayClass doesn't exist"), - } - expGraph.DeploymentName.Name = "gateway-2-test-class" - expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} - expGraph.ReferencedSecrets = nil - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - processAndValidateGraph(expGraph) - }) - }) - When("the second Gateway is deleted", func() { - It("returns empty graph", func() { - processor.CaptureDeleteChange( - &v1.Gateway{}, - types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - ) - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - processAndValidateGraph(&graph.Graph{}) - }) - }) - When("the first HTTPRoute is deleted", func() { - It("returns empty graph", func() { - processor.CaptureDeleteChange( - &v1.HTTPRoute{}, - types.NamespacedName{Namespace: "test", Name: "hr-1"}, - ) - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - processAndValidateGraph(&graph.Graph{}) - }) - }) - When("the first GRPCRoute is deleted", func() { - It("returns empty graph", func() { - processor.CaptureDeleteChange( - &v1.GRPCRoute{}, - types.NamespacedName{Namespace: "test", Name: "gr-1"}, - ) - - expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - processAndValidateGraph(&graph.Graph{}) - }) - }) - When("the first TLSRoute is deleted", func() { - It("returns empty graph", func() { - processor.CaptureDeleteChange( - &v1alpha2.TLSRoute{}, - types.NamespacedName{Namespace: "test", Name: "tr-1"}, - ) - - expGraph.ReferencedServices = nil - - processAndValidateGraph(&graph.Graph{}) - }) - }) - }) - - Describe("Process services and endpoints", Ordered, func() { - var ( - hr1, hr2, hr3, hrInvalidBackendRef, hrMultipleRules *v1.HTTPRoute - hr1svc, sharedSvc, bazSvc1, bazSvc2, bazSvc3, invalidSvc, notRefSvc *apiv1.Service - hr1slice1, hr1slice2, noRefSlice, missingSvcNameSlice *discoveryV1.EndpointSlice - gw *v1.Gateway - btls *v1alpha3.BackendTLSPolicy - ) - - createSvc := func(name string) *apiv1.Service { - return &apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: name, - }, - } - } - - createEndpointSlice := func(name string, svcName string) *discoveryV1.EndpointSlice { - return &discoveryV1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: name, - Labels: map[string]string{index.KubernetesServiceNameLabel: svcName}, - }, - } - } - - createBackendTLSPolicy := func(name string, svcName string) *v1alpha3.BackendTLSPolicy { - return &v1alpha3.BackendTLSPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: name, - }, - Spec: v1alpha3.BackendTLSPolicySpec{ - TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ - { - LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ - Kind: v1.Kind("Service"), - Name: v1.ObjectName(svcName), - }, - }, - }, - }, - } - } - - BeforeAll(func() { - testNamespace := v1.Namespace("test") - kindService := v1.Kind("Service") - kindInvalid := v1.Kind("Invalid") - - // backend Refs - fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) - baz1NilNamespace := createHTTPBackendRef(&kindService, "baz-svc-v1", &testNamespace) - barRef := createHTTPBackendRef(&kindService, "bar-svc", nil) - baz2Ref := createHTTPBackendRef(&kindService, "baz-svc-v2", &testNamespace) - baz3Ref := createHTTPBackendRef(&kindService, "baz-svc-v3", &testNamespace) - invalidKindRef := createHTTPBackendRef(&kindInvalid, "bar-svc", &testNamespace) - - // httproutes - hr1 = createHTTPRoute("hr1", "gw", "foo.example.com", fooRef) - hr2 = createHTTPRoute("hr2", "gw", "bar.example.com", barRef) - // hr3 shares the same backendRef as hr2 - hr3 = createHTTPRoute("hr3", "gw", "bar.2.example.com", barRef) - hrInvalidBackendRef = createHTTPRoute("hr-invalid", "gw", "invalid.com", invalidKindRef) - hrMultipleRules = createRouteWithMultipleRules( - "hr-multiple-rules", - "gw", - "mutli.example.com", - []v1.HTTPRouteRule{ - createHTTPRule("/baz-v1", baz1NilNamespace), - createHTTPRule("/baz-v2", baz2Ref), - createHTTPRule("/baz-v3", baz3Ref), - }, - ) - - // services - hr1svc = createSvc("foo-svc") - sharedSvc = createSvc("bar-svc") // shared between hr2 and hr3 - invalidSvc = createSvc("invalid") // nsname matches invalid BackendRef - notRefSvc = createSvc("not-ref") - bazSvc1 = createSvc("baz-svc-v1") - bazSvc2 = createSvc("baz-svc-v2") - bazSvc3 = createSvc("baz-svc-v3") - - // endpoint slices - hr1slice1 = createEndpointSlice("hr1-1", "foo-svc") - hr1slice2 = createEndpointSlice("hr1-2", "foo-svc") - noRefSlice = createEndpointSlice("no-ref", "no-ref") - missingSvcNameSlice = createEndpointSlice("missing-svc-name", "") - - // backendTLSPolicy - btls = createBackendTLSPolicy("btls", "foo-svc") - - gw = createGateway("gw", createHTTPListener()) - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - - testProcessChangedVal := func(expChanged state.ChangeType) { - changed, _ := processor.Process() - Expect(changed).To(Equal(expChanged)) - } - - testUpsertTriggersChange := func(obj client.Object, expChanged state.ChangeType) { - processor.CaptureUpsertChange(obj) - testProcessChangedVal(expChanged) - } - - testDeleteTriggersChange := func(obj client.Object, nsname types.NamespacedName, expChanged state.ChangeType) { - processor.CaptureDeleteChange(obj, nsname) - testProcessChangedVal(expChanged) - } - - When("hr1 is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1, state.ClusterStateChange) - }) - }) - When("a hr1 service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1svc, state.ClusterStateChange) - }) - }) - When("a backendTLSPolicy is added for referenced service", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(btls, state.ClusterStateChange) - }) - }) - When("an hr1 endpoint slice is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1slice1, state.EndpointsOnlyChange) - }) - }) - When("an hr1 service is updated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1svc, state.ClusterStateChange) - }) - }) - When("another hr1 endpoint slice is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) - }) - }) - When("an endpoint slice with a missing svc name label is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(missingSvcNameSlice, state.NoChange) - }) - }) - When("an hr1 endpoint slice is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr1slice1, - types.NamespacedName{Namespace: hr1slice1.Namespace, Name: hr1slice1.Name}, - state.EndpointsOnlyChange, - ) - }) - }) - When("the second hr1 endpoint slice is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr1slice2, - types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, - state.EndpointsOnlyChange, - ) - }) - }) - When("the second hr1 endpoint slice is recreated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) - }) - }) - When("hr1 is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr1, - types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name}, - state.ClusterStateChange, - ) - }) - }) - When("hr1 service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - hr1svc, - types.NamespacedName{Namespace: hr1svc.Namespace, Name: hr1svc.Name}, - state.NoChange, - ) - }) - }) - When("the second hr1 endpoint slice is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - hr1slice2, - types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, - state.NoChange, - ) - }) - }) - When("hr2 is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr2, state.ClusterStateChange) - }) - }) - When("a hr3, that shares a backend service with hr2, is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr3, state.ClusterStateChange) - }) - }) - When("sharedSvc, a service referenced by both hr2 and hr3, is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) - }) - }) - When("hr2 is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr2, - types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name}, - state.ClusterStateChange, - ) - }) - }) - When("sharedSvc is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - sharedSvc, - types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, - state.ClusterStateChange, - ) - }) - }) - When("sharedSvc is recreated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) - }) - }) - When("hr3 is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr3, - types.NamespacedName{Namespace: hr3.Namespace, Name: hr3.Name}, - state.ClusterStateChange, - ) - }) - }) - When("sharedSvc is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - sharedSvc, - types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, - state.NoChange, - ) - }) - }) - When("a service that is not referenced by any route is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(notRefSvc, state.NoChange) - }) - }) - When("a route with an invalid backend ref type is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hrInvalidBackendRef, state.ClusterStateChange) - }) - }) - When("a service with a namespace name that matches invalid backend ref is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(invalidSvc, state.NoChange) - }) - }) - When("an endpoint slice that is not owned by a referenced service is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(noRefSlice, state.NoChange) - }) - }) - When("an endpoint slice that is not owned by a referenced service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - noRefSlice, - types.NamespacedName{Namespace: noRefSlice.Namespace, Name: noRefSlice.Name}, - state.NoChange, - ) - }) - }) - Context("processing a route with multiple rules and three unique backend services", func() { - When("route is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hrMultipleRules, state.ClusterStateChange) - }) - }) - When("first referenced service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) - }) - }) - When("second referenced service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc2, state.ClusterStateChange) - }) - }) - When("first referenced service is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - bazSvc1, - types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, - state.ClusterStateChange, - ) - }) - }) - When("first referenced service is recreated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) - }) - }) - When("third referenced service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) - }) - }) - When("third referenced service is updated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) - }) - }) - When("route is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hrMultipleRules, - types.NamespacedName{ - Namespace: hrMultipleRules.Namespace, - Name: hrMultipleRules.Name, - }, - state.ClusterStateChange, - ) - }) - }) - When("first referenced service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc1, - types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, - state.NoChange, - ) - }) - }) - When("second referenced service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc2, - types.NamespacedName{Namespace: bazSvc2.Namespace, Name: bazSvc2.Name}, - state.NoChange, - ) - }) - }) - When("final referenced service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc3, - types.NamespacedName{Namespace: bazSvc3.Namespace, Name: bazSvc3.Name}, - state.NoChange, - ) - }) - }) - }) - }) - - Describe("namespace changes", Ordered, func() { - var ( - ns, nsDifferentLabels, nsNoLabels *apiv1.Namespace - gw *v1.Gateway - ) - - BeforeAll(func() { - ns = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ns", - Labels: map[string]string{ - "app": "allowed", - }, - }, - } - nsDifferentLabels = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ns-different-labels", - Labels: map[string]string{ - "oranges": "bananas", - }, - }, - } - nsNoLabels = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "no-labels", - }, - } - gw = &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: v1.GatewaySpec{ - GatewayClassName: gcName, - Listeners: []v1.Listener{ - { - Port: 80, - Protocol: v1.HTTPProtocolType, - AllowedRoutes: &v1.AllowedRoutes{ - Namespaces: &v1.RouteNamespaces{ - From: helpers.GetPointer(v1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "allowed", - }, - }, - }, - }, - }, - }, - }, - } - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - Logger: logr.Discard(), - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }) - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw) - processor.Process() - }) - - When("a namespace is created that is not linked to a listener", func() { - It("does not trigger an update", func() { - processor.CaptureUpsertChange(nsNoLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - }) - When("a namespace is created that is linked to a listener", func() { - It("triggers an update", func() { - processor.CaptureUpsertChange(ns) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - When("a namespace is deleted that is not linked to a listener", func() { - It("does not trigger an update", func() { - processor.CaptureDeleteChange(nsNoLabels, types.NamespacedName{Name: "no-labels"}) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - }) - When("a namespace is deleted that is linked to a listener", func() { - It("triggers an update", func() { - processor.CaptureDeleteChange(ns, types.NamespacedName{Name: "ns"}) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - When("a namespace that is not linked to a listener has its labels changed to match a listener", func() { - It("triggers an update", func() { - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - - nsDifferentLabels.Labels = map[string]string{ - "app": "allowed", - } - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ = processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - When( - "a namespace that is linked to a listener has its labels changed to no longer match a listener", - func() { - It("triggers an update", func() { - nsDifferentLabels.Labels = map[string]string{ - "oranges": "bananas", - } - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }, - ) - When("a gateway changes its listener's labels", func() { - It("triggers an update when a namespace that matches the new labels is created", func() { - gwChangedLabel := gw.DeepCopy() - gwChangedLabel.Spec.Listeners[0].AllowedRoutes.Namespaces.Selector.MatchLabels = map[string]string{ - "oranges": "bananas", - } - gwChangedLabel.Generation++ - processor.CaptureUpsertChange(gwChangedLabel) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - - // After changing the gateway's labels and generation, the processor should be marked to update - // the nginx configuration and build a new graph. When processor.Process() gets called, - // the nginx configuration gets updated and a new graph is built with an updated - // referencedNamespaces. Thus, when the namespace "ns" is upserted with labels that no longer match - // the new labels on the gateway, it would not trigger a change as the namespace would no longer - // be in the updated referencedNamespaces and the labels no longer match the new labels on the - // gateway. - processor.CaptureUpsertChange(ns) - changed, _ = processor.Process() - Expect(changed).To(Equal(state.NoChange)) - - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ = processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - When("a namespace that is not linked to a listener has its labels removed", func() { - It("does not trigger an update", func() { - ns.Labels = nil - processor.CaptureUpsertChange(ns) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - }) - When("a namespace that is linked to a listener has its labels removed", func() { - It("triggers an update when labels are removed", func() { - nsDifferentLabels.Labels = nil - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - }) - - Describe("NginxProxy resource changes", Ordered, func() { - Context("referenced by a GatewayClass", func() { - paramGC := gc.DeepCopy() - paramGC.Spec.ParametersRef = &v1beta1.ParametersReference{ - Group: ngfAPIv1alpha1.GroupName, - Kind: kinds.NginxProxy, - Name: "np", - Namespace: helpers.GetPointer[v1.Namespace]("test"), - } - - np := &ngfAPIv1alpha2.NginxProxy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "np", - Namespace: "test", - }, - } - - npUpdated := &ngfAPIv1alpha2.NginxProxy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "np", - Namespace: "test", - }, - Spec: ngfAPIv1alpha2.NginxProxySpec{ - Telemetry: &ngfAPIv1alpha2.Telemetry{ - Exporter: &ngfAPIv1alpha2.TelemetryExporter{ - Endpoint: helpers.GetPointer("my-svc:123"), - BatchSize: helpers.GetPointer(int32(512)), - BatchCount: helpers.GetPointer(int32(4)), - Interval: helpers.GetPointer(ngfAPIv1alpha1.Duration("5s")), - }, - }, - }, - } - It("handles upserts for an NginxProxy", func() { - processor.CaptureUpsertChange(np) - processor.CaptureUpsertChange(paramGC) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.GatewayClass.NginxProxy.Source).To(Equal(np)) - }) - It("captures changes for an NginxProxy", func() { - processor.CaptureUpsertChange(npUpdated) - processor.CaptureUpsertChange(paramGC) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.GatewayClass.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.GatewayClass.NginxProxy).To(BeNil()) - }) - }) - Context("referenced by a Gateway", func() { - paramGW := &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "param-gw", - Generation: 1, - }, - Spec: v1.GatewaySpec{ - GatewayClassName: gcName, - Listeners: []v1.Listener{ - { - Name: httpListenerName, - Hostname: nil, - Port: 80, - Protocol: v1.HTTPProtocolType, - }, - }, - Infrastructure: &v1.GatewayInfrastructure{ - ParametersRef: &v1.LocalParametersReference{ - Group: ngfAPIv1alpha1.GroupName, - Kind: kinds.NginxProxy, - Name: "np-gw", - }, - }, - }, - } - - np := &ngfAPIv1alpha2.NginxProxy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "np-gw", - Namespace: "test", - }, - } - - npUpdated := &ngfAPIv1alpha2.NginxProxy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "np-gw", - Namespace: "test", - }, - Spec: ngfAPIv1alpha2.NginxProxySpec{ - Telemetry: &ngfAPIv1alpha2.Telemetry{ - Exporter: &ngfAPIv1alpha2.TelemetryExporter{ - Endpoint: helpers.GetPointer("my-svc:123"), - BatchSize: helpers.GetPointer(int32(512)), - BatchCount: helpers.GetPointer(int32(4)), - Interval: helpers.GetPointer(ngfAPIv1alpha1.Duration("5s")), - }, - }, - }, - } - It("handles upserts for an NginxProxy", func() { - processor.CaptureUpsertChange(np) - processor.CaptureUpsertChange(paramGW) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.Gateway.NginxProxy.Source).To(Equal(np)) - }) - It("captures changes for an NginxProxy", func() { - processor.CaptureUpsertChange(npUpdated) - processor.CaptureUpsertChange(paramGW) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.Gateway.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()) - }) - }) - }) - - Describe("NGF Policy resource changes", Ordered, func() { - var ( - gw *v1.Gateway - route *v1.HTTPRoute - svc *apiv1.Service - csp, cspUpdated *ngfAPIv1alpha1.ClientSettingsPolicy - obs, obsUpdated *ngfAPIv1alpha2.ObservabilityPolicy - usp, uspUpdated *ngfAPIv1alpha1.UpstreamSettingsPolicy - cspKey, obsKey, uspKey graph.PolicyKey - ) - - BeforeAll(func() { - processor.CaptureUpsertChange(gc) - changed, newGraph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(newGraph.GatewayClass.Source).To(Equal(gc)) - Expect(newGraph.NGFPolicies).To(BeEmpty()) - - gw = createGateway("gw", createHTTPListener()) - route = createHTTPRoute( - "hr-1", - "gw", - "foo.example.com", - v1.HTTPBackendRef{ - BackendRef: v1.BackendRef{ - BackendObjectReference: v1.BackendObjectReference{ - Group: helpers.GetPointer[v1.Group](""), - Kind: helpers.GetPointer[v1.Kind](kinds.Service), - Name: "svc", - Port: helpers.GetPointer[v1.PortNumber](80), - }, - }, - }, - ) - - svc = &apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "svc", - Namespace: "test", - }, - } - - csp = &ngfAPIv1alpha1.ClientSettingsPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "csp", - Namespace: "test", - }, - Spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ - TargetRef: v1alpha2.LocalPolicyTargetReference{ - Group: v1.GroupName, - Kind: kinds.Gateway, - Name: "gw", - }, - Body: &ngfAPIv1alpha1.ClientBody{ - MaxSize: helpers.GetPointer[ngfAPIv1alpha1.Size]("10m"), - }, - }, - } - - cspUpdated = csp.DeepCopy() - cspUpdated.Spec.Body.MaxSize = helpers.GetPointer[ngfAPIv1alpha1.Size]("20m") - - cspKey = graph.PolicyKey{ - NsName: types.NamespacedName{Name: "csp", Namespace: "test"}, - GVK: schema.GroupVersionKind{ - Group: ngfAPIv1alpha1.GroupName, - Kind: kinds.ClientSettingsPolicy, - Version: "v1alpha1", - }, - } - - obs = &ngfAPIv1alpha2.ObservabilityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "obs", - Namespace: "test", - }, - Spec: ngfAPIv1alpha2.ObservabilityPolicySpec{ - TargetRefs: []v1alpha2.LocalPolicyTargetReference{ - { - Group: v1.GroupName, - Kind: kinds.HTTPRoute, - Name: "hr-1", - }, - }, - Tracing: &ngfAPIv1alpha2.Tracing{ - Strategy: ngfAPIv1alpha2.TraceStrategyRatio, - }, - }, - } - - obsUpdated = obs.DeepCopy() - obsUpdated.Spec.Tracing.Strategy = ngfAPIv1alpha2.TraceStrategyParent - - obsKey = graph.PolicyKey{ - NsName: types.NamespacedName{Name: "obs", Namespace: "test"}, - GVK: schema.GroupVersionKind{ - Group: ngfAPIv1alpha1.GroupName, - Kind: kinds.ObservabilityPolicy, - Version: "v1alpha2", - }, - } - - usp = &ngfAPIv1alpha1.UpstreamSettingsPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "usp", - Namespace: "test", - }, - Spec: ngfAPIv1alpha1.UpstreamSettingsPolicySpec{ - ZoneSize: helpers.GetPointer[ngfAPIv1alpha1.Size]("10m"), - TargetRefs: []v1alpha2.LocalPolicyTargetReference{ - { - Group: "core", - Kind: kinds.Service, - Name: "svc", - }, - }, - }, - } - - uspUpdated = usp.DeepCopy() - uspUpdated.Spec.ZoneSize = helpers.GetPointer[ngfAPIv1alpha1.Size]("20m") - - uspKey = graph.PolicyKey{ - NsName: types.NamespacedName{Name: "usp", Namespace: "test"}, - GVK: schema.GroupVersionKind{ - Group: ngfAPIv1alpha1.GroupName, - Kind: kinds.UpstreamSettingsPolicy, - Version: "v1alpha1", - }, - } - }) - - /* - NOTE: When adding a new NGF policy to the change processor, - update the following tests to make sure that the change processor can track changes for multiple NGF - policies. - */ - - When("a policy is created that references a resource that is not in the last graph", func() { - It("reports no changes", func() { - processor.CaptureUpsertChange(csp) - processor.CaptureUpsertChange(obs) - processor.CaptureUpsertChange(usp) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - }) - When("the resource the policy references is created", func() { - It("populates the graph with the policy", func() { - processor.CaptureUpsertChange(gw) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(HaveKey(cspKey)) - Expect(graph.NGFPolicies[cspKey].Source).To(Equal(csp)) - Expect(graph.NGFPolicies).ToNot(HaveKey(obsKey)) - - processor.CaptureUpsertChange(route) - changed, graph = processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(HaveKey(obsKey)) - Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obs)) - - processor.CaptureUpsertChange(svc) - changed, graph = processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(HaveKey(uspKey)) - Expect(graph.NGFPolicies[uspKey].Source).To(Equal(usp)) - }) - }) - When("the policy is updated", func() { - It("captures changes for a policy", func() { - processor.CaptureUpsertChange(cspUpdated) - processor.CaptureUpsertChange(obsUpdated) - processor.CaptureUpsertChange(uspUpdated) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(HaveKey(cspKey)) - Expect(graph.NGFPolicies[cspKey].Source).To(Equal(cspUpdated)) - Expect(graph.NGFPolicies).To(HaveKey(obsKey)) - Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obsUpdated)) - Expect(graph.NGFPolicies).To(HaveKey(uspKey)) - Expect(graph.NGFPolicies[uspKey].Source).To(Equal(uspUpdated)) - }) - }) - When("the policy is deleted", func() { - It("removes the policy from the graph", func() { - processor.CaptureDeleteChange(&ngfAPIv1alpha1.ClientSettingsPolicy{}, client.ObjectKeyFromObject(csp)) - processor.CaptureDeleteChange(&ngfAPIv1alpha2.ObservabilityPolicy{}, client.ObjectKeyFromObject(obs)) - processor.CaptureDeleteChange(&ngfAPIv1alpha1.UpstreamSettingsPolicy{}, client.ObjectKeyFromObject(usp)) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(BeEmpty()) - }) - }) - }) - - Describe("SnippetsFilter resource changed", Ordered, func() { - sfNsName := types.NamespacedName{ - Name: "sf", - Namespace: "test", - } - - sf := &ngfAPIv1alpha1.SnippetsFilter{ - ObjectMeta: metav1.ObjectMeta{ - Name: sfNsName.Name, - Namespace: sfNsName.Namespace, - }, - Spec: ngfAPIv1alpha1.SnippetsFilterSpec{ - Snippets: []ngfAPIv1alpha1.Snippet{ - { - Context: ngfAPIv1alpha1.NginxContextMain, - Value: "main snippet", - }, - }, - }, - } - - sfUpdated := &ngfAPIv1alpha1.SnippetsFilter{ - ObjectMeta: metav1.ObjectMeta{ - Name: sfNsName.Name, - Namespace: sfNsName.Namespace, - }, - Spec: ngfAPIv1alpha1.SnippetsFilterSpec{ - Snippets: []ngfAPIv1alpha1.Snippet{ - { - Context: ngfAPIv1alpha1.NginxContextMain, - Value: "main snippet", - }, - { - Context: ngfAPIv1alpha1.NginxContextHTTP, - Value: "http snippet", - }, - }, - }, - } - It("handles upserts for a SnippetsFilter", func() { - processor.CaptureUpsertChange(sf) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - - processedSf, exists := graph.SnippetsFilters[sfNsName] - Expect(exists).To(BeTrue()) - Expect(processedSf.Source).To(Equal(sf)) - Expect(processedSf.Valid).To(BeTrue()) - }) - It("captures changes for a SnippetsFilter", func() { - processor.CaptureUpsertChange(sfUpdated) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - - processedSf, exists := graph.SnippetsFilters[sfNsName] - Expect(exists).To(BeTrue()) - Expect(processedSf.Source).To(Equal(sfUpdated)) - Expect(processedSf.Valid).To(BeTrue()) - }) - It("handles deletes for a SnippetsFilter", func() { - processor.CaptureDeleteChange(sfUpdated, sfNsName) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.SnippetsFilters).To(BeEmpty()) - }) - }) - }) - Describe("Ensuring non-changing changes don't override previously changing changes", func() { - // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses - // -- this is done in 'Normal cases of processing changes' - - var ( - processor *state.ChangeProcessorImpl - gcNsName, gwNsName, hrNsName, hr2NsName, grNsName, gr2NsName, rgNsName, svcNsName types.NamespacedName - sliceNsName, secretNsName, cmNsName, btlsNsName, npNsName types.NamespacedName - gc, gcUpdated *v1.GatewayClass - gw1, gw1Updated, gw2 *v1.Gateway - hr1, hr1Updated, hr2 *v1.HTTPRoute - gr1, gr1Updated, gr2 *v1.GRPCRoute - rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant - svc, barSvc, unrelatedSvc *apiv1.Service - slice, barSlice, unrelatedSlice *discoveryV1.EndpointSlice - ns, unrelatedNS, testNs, barNs *apiv1.Namespace - secret, secretUpdated, unrelatedSecret, barSecret, barSecretUpdated *apiv1.Secret - cm, cmUpdated, unrelatedCM *apiv1.ConfigMap - btls, btlsUpdated *v1alpha3.BackendTLSPolicy - np, npUpdated *ngfAPIv1alpha2.NginxProxy - ) - - BeforeEach(OncePerOrdered, func() { - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: "test.controller", - GatewayClassName: "test-class", - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }) - - secretNsName = types.NamespacedName{Namespace: "test", Name: "tls-secret"} - secret = &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretNsName.Name, - Namespace: secretNsName.Namespace, - Generation: 1, - }, - Type: apiv1.SecretTypeTLS, - Data: map[string][]byte{ - apiv1.TLSCertKey: cert, - apiv1.TLSPrivateKeyKey: key, - }, - } - secretUpdated = secret.DeepCopy() - secretUpdated.Generation++ - barSecret = &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar-secret", - Namespace: "test", - Generation: 1, - }, - Type: apiv1.SecretTypeTLS, - Data: map[string][]byte{ - apiv1.TLSCertKey: cert, - apiv1.TLSPrivateKeyKey: key, - }, - } - barSecretUpdated = barSecret.DeepCopy() - barSecretUpdated.Generation++ - unrelatedSecret = &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "unrelated-tls-secret", - Namespace: "unrelated-ns", - Generation: 1, - }, - Type: apiv1.SecretTypeTLS, - Data: map[string][]byte{ - apiv1.TLSCertKey: cert, - apiv1.TLSPrivateKeyKey: key, - }, - } - - gcNsName = types.NamespacedName{Name: "test-class"} - - gc = &v1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: gcNsName.Name, - }, - Spec: v1.GatewayClassSpec{ - ControllerName: "test.controller", - }, - } - - gcUpdated = gc.DeepCopy() - gcUpdated.Generation++ - - gwNsName = types.NamespacedName{Namespace: "test", Name: "gw-1"} - - gw1 = &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw-1", - Namespace: "test", - Generation: 1, - }, - Spec: v1.GatewaySpec{ - GatewayClassName: gcName, - Listeners: []v1.Listener{ - { - Name: httpListenerName, - Hostname: nil, - Port: 80, - Protocol: v1.HTTPProtocolType, - AllowedRoutes: &v1.AllowedRoutes{ - Namespaces: &v1.RouteNamespaces{ - From: helpers.GetPointer(v1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "test": "namespace", - }, - }, - }, - }, - }, - { - Name: httpsListenerName, - Hostname: nil, - Port: 443, - Protocol: v1.HTTPSProtocolType, - TLS: &v1.GatewayTLSConfig{ - Mode: helpers.GetPointer(v1.TLSModeTerminate), - CertificateRefs: []v1.SecretObjectReference{ - { - Kind: (*v1.Kind)(helpers.GetPointer("Secret")), - Name: v1.ObjectName(secret.Name), - Namespace: (*v1.Namespace)(&secret.Namespace), - }, - }, - }, - }, - { - Name: "listener-500-1", - Hostname: nil, - Port: 500, - Protocol: v1.HTTPSProtocolType, - TLS: &v1.GatewayTLSConfig{ - Mode: helpers.GetPointer(v1.TLSModeTerminate), - CertificateRefs: []v1.SecretObjectReference{ - { - Kind: (*v1.Kind)(helpers.GetPointer("Secret")), - Name: v1.ObjectName(barSecret.Name), - Namespace: (*v1.Namespace)(&barSecret.Namespace), - }, - }, - }, - }, - }, - }, - } - - gw1Updated = gw1.DeepCopy() - gw1Updated.Generation++ - - gw2 = gw1.DeepCopy() - gw2.Name = "gw-2" - - testNamespace := v1.Namespace("test") - kindService := v1.Kind("Service") - fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) - barRef := createHTTPBackendRef(&kindService, "bar-svc", &testNamespace) - - hrNsName = types.NamespacedName{Namespace: "test", Name: "hr-1"} - hr1 = createHTTPRoute("hr-1", "gw-1", "foo.example.com", fooRef, barRef) - hr1Updated = hr1.DeepCopy() - hr1Updated.Generation++ - hr2NsName = types.NamespacedName{Namespace: "test", Name: "hr-2"} - hr2 = hr1.DeepCopy() - hr2.Name = hr2NsName.Name - - grNsName = types.NamespacedName{Namespace: "test", Name: "gr-1"} - gr1 = createGRPCRoute("gr-1", "gw-1", "foo.grpc.com") - gr1Updated = gr1.DeepCopy() - gr1Updated.Generation++ - gr2NsName = types.NamespacedName{Namespace: "test", Name: "hr-2"} - gr2 = gr1.DeepCopy() - gr2.Name = gr2NsName.Name - - svcNsName = types.NamespacedName{Namespace: "test", Name: "foo-svc"} - svc = &apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: svcNsName.Namespace, - Name: svcNsName.Name, - }, - } - barSvc = &apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "bar-svc", - }, - } - unrelatedSvc = &apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "unrelated-svc", - }, - } - - sliceNsName = types.NamespacedName{Namespace: "test", Name: "slice"} - slice = &discoveryV1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: sliceNsName.Namespace, - Name: sliceNsName.Name, - Labels: map[string]string{index.KubernetesServiceNameLabel: svc.Name}, - }, - } - barSlice = &discoveryV1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "bar-slice", - Labels: map[string]string{index.KubernetesServiceNameLabel: "bar-svc"}, - }, - } - unrelatedSlice = &discoveryV1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "unrelated-slice", - Labels: map[string]string{index.KubernetesServiceNameLabel: "unrelated-svc"}, - }, - } - - testNs = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Labels: map[string]string{ - "test": "namespace", - }, - }, - } - ns = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ns", - Labels: map[string]string{ - "test": "namespace", - }, - }, - } - barNs = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar-ns", - Labels: map[string]string{ - "test": "namespace", - }, - }, - } - unrelatedNS = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "unrelated-ns", - Labels: map[string]string{ - "oranges": "bananas", - }, - }, - } - - rgNsName = types.NamespacedName{Namespace: "test", Name: "rg-1"} - - rg1 = &v1beta1.ReferenceGrant{ - ObjectMeta: metav1.ObjectMeta{ - Name: rgNsName.Name, - Namespace: rgNsName.Namespace, - }, - } - - rg1Updated = rg1.DeepCopy() - rg1Updated.Generation++ - - rg2 = rg1.DeepCopy() - rg2.Name = "rg-2" - - cmNsName = types.NamespacedName{Namespace: "test", Name: "cm-1"} - cm = &apiv1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: cmNsName.Name, - Namespace: cmNsName.Namespace, - }, - Data: map[string]string{ - "ca.crt": "value", - }, - } - cmUpdated = cm.DeepCopy() - cmUpdated.Data["ca.crt"] = "updated-value" - - unrelatedCM = &apiv1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "unrelated-cm", - Namespace: "unrelated-ns", - }, - Data: map[string]string{ - "ca.crt": "value", - }, - } - - btlsNsName = types.NamespacedName{Namespace: "test", Name: "btls-1"} - btls = &v1alpha3.BackendTLSPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: btlsNsName.Name, - Namespace: btlsNsName.Namespace, - Generation: 1, - }, - Spec: v1alpha3.BackendTLSPolicySpec{ - TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ - { - LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ - Kind: "Service", - Name: v1.ObjectName(svc.Name), - }, - }, - }, - Validation: v1alpha3.BackendTLSPolicyValidation{ - CACertificateRefs: []v1.LocalObjectReference{ - { - Name: v1.ObjectName(cm.Name), - }, - }, - }, - }, - } - btlsUpdated = btls.DeepCopy() - - npNsName = types.NamespacedName{Name: "np-1"} - np = &ngfAPIv1alpha2.NginxProxy{ - ObjectMeta: metav1.ObjectMeta{ - Name: npNsName.Name, - }, - Spec: ngfAPIv1alpha2.NginxProxySpec{ - Telemetry: &ngfAPIv1alpha2.Telemetry{ - ServiceName: helpers.GetPointer("my-svc"), - }, - }, - } - npUpdated = np.DeepCopy() - }) - // Changing change - a change that makes processor.Process() report changed - // Non-changing change - a change that doesn't do that - // Related resource - a K8s resource that is related to a configured Gateway API resource - // Unrelated resource - a K8s resource that is not related to a configured Gateway API resource - - // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses - // -- this is done in 'Normal cases of processing changes' - Describe("Multiple Gateway API resource changes", Ordered, func() { - It("should report changed after multiple Upserts", func() { - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(testNs) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(gr1) - processor.CaptureUpsertChange(rg1) - processor.CaptureUpsertChange(btls) - processor.CaptureUpsertChange(cm) - processor.CaptureUpsertChange(np) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - When("a upsert of updated resources is followed by an upsert of the same generation", func() { - It("should report changed", func() { - // these are changing changes - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(gr1Updated) - processor.CaptureUpsertChange(rg1Updated) - processor.CaptureUpsertChange(btlsUpdated) - processor.CaptureUpsertChange(cmUpdated) - processor.CaptureUpsertChange(npUpdated) - - // there are non-changing changes - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(gr1Updated) - processor.CaptureUpsertChange(rg1Updated) - processor.CaptureUpsertChange(btlsUpdated) - processor.CaptureUpsertChange(cmUpdated) - processor.CaptureUpsertChange(npUpdated) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - It("should report changed after upserting new resources", func() { - // we can't have a second GatewayClass, so we don't add it - processor.CaptureUpsertChange(gw2) - processor.CaptureUpsertChange(hr2) - processor.CaptureUpsertChange(gr2) - processor.CaptureUpsertChange(rg2) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - When("resources are deleted followed by upserts with the same generations", func() { - It("should report changed", func() { - // these are changing changes - processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) - processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) - processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) - processor.CaptureDeleteChange(&v1.GRPCRoute{}, grNsName) - processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) - processor.CaptureDeleteChange(&v1alpha3.BackendTLSPolicy{}, btlsNsName) - processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) - processor.CaptureDeleteChange(&ngfAPIv1alpha2.NginxProxy{}, npNsName) - - // these are non-changing changes - processor.CaptureUpsertChange(gw2) - processor.CaptureUpsertChange(hr2) - processor.CaptureUpsertChange(gr2) - processor.CaptureUpsertChange(rg2) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - It("should report changed after deleting resources", func() { - processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) - processor.CaptureDeleteChange(&v1.HTTPRoute{}, gr2NsName) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - Describe("Deleting non-existing Gateway API resource", func() { - It("should not report changed after deleting non-existing", func() { - processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) - processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) - processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) - processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) - processor.CaptureDeleteChange(&v1.HTTPRoute{}, grNsName) - processor.CaptureDeleteChange(&v1.HTTPRoute{}, gr2NsName) - processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - }) - Describe("Multiple Kubernetes API resource changes", Ordered, func() { - BeforeAll(func() { - // Set up graph - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(testNs) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(gr1) - processor.CaptureUpsertChange(secret) - processor.CaptureUpsertChange(barSecret) - processor.CaptureUpsertChange(cm) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - - It("should report changed after multiple Upserts of related resources", func() { - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(ns) - processor.CaptureUpsertChange(secretUpdated) - processor.CaptureUpsertChange(cmUpdated) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - It("should report not changed after multiple Upserts of unrelated resources", func() { - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - When("upserts of related resources are followed by upserts of unrelated resources", func() { - It("should report changed", func() { - // these are changing changes - processor.CaptureUpsertChange(barSvc) - processor.CaptureUpsertChange(barSlice) - processor.CaptureUpsertChange(barNs) - processor.CaptureUpsertChange(barSecretUpdated) - processor.CaptureUpsertChange(cmUpdated) - - // there are non-changing changes - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - When("deletes of related resources are followed by upserts of unrelated resources", func() { - It("should report changed", func() { - // these are changing changes - processor.CaptureDeleteChange(&apiv1.Service{}, svcNsName) - processor.CaptureDeleteChange(&discoveryV1.EndpointSlice{}, sliceNsName) - processor.CaptureDeleteChange(&apiv1.Namespace{}, types.NamespacedName{Name: "ns"}) - processor.CaptureDeleteChange(&apiv1.Secret{}, secretNsName) - processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) - - // these are non-changing changes - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - }) - Describe("Multiple Kubernetes API and Gateway API resource changes", Ordered, func() { - It("should report changed after multiple Upserts of new and related resources", func() { - // new Gateway API resources - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(testNs) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(gr1) - processor.CaptureUpsertChange(rg1) - processor.CaptureUpsertChange(btls) - - // related Kubernetes API resources - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(ns) - processor.CaptureUpsertChange(secret) - processor.CaptureUpsertChange(cm) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - It("should report not changed after multiple Upserts of unrelated resources", func() { - // unrelated Kubernetes API resources - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - It("should report changed after upserting changed resources followed by upserting unrelated resources", - func() { - // these are changing changes - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(gr1Updated) - processor.CaptureUpsertChange(rg1Updated) - processor.CaptureUpsertChange(btlsUpdated) - - // these are non-changing changes - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - }) - }) - Describe("Edge cases with panic", func() { - var processor state.ChangeProcessor - - BeforeEach(func() { - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: "test.controller", - GatewayClassName: "my-class", - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }) - }) - - DescribeTable("CaptureUpsertChange must panic", - func(obj client.Object) { - process := func() { - processor.CaptureUpsertChange(obj) - } - Expect(process).Should(Panic()) - }, - Entry( - "an unsupported resource", - &v1alpha2.TCPRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "tcp"}}, - ), - Entry( - "nil resource", - nil, - ), - ) - - DescribeTable( - "CaptureDeleteChange must panic", - func(resourceType ngftypes.ObjectType, nsname types.NamespacedName) { - process := func() { - processor.CaptureDeleteChange(resourceType, nsname) - } - Expect(process).Should(Panic()) - }, - Entry( - "an unsupported resource", - &v1alpha2.TCPRoute{}, - types.NamespacedName{Namespace: "test", Name: "tcp"}, - ), - Entry( - "nil resource type", - nil, - types.NamespacedName{Namespace: "test", Name: "resource"}, - ), - ) + // Describe("Process services and endpoints", Ordered, func() { + // var ( + // hr1, hr2, hr3, hrInvalidBackendRef, hrMultipleRules *v1.HTTPRoute + // hr1svc, sharedSvc, bazSvc1, bazSvc2, bazSvc3, invalidSvc, notRefSvc *apiv1.Service + // hr1slice1, hr1slice2, noRefSlice, missingSvcNameSlice *discoveryV1.EndpointSlice + // gw *v1.Gateway + // btls *v1alpha3.BackendTLSPolicy + // ) + + // createSvc := func(name string) *apiv1.Service { + // return &apiv1.Service{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: "test", + // Name: name, + // }, + // } + // } + + // createEndpointSlice := func(name string, svcName string) *discoveryV1.EndpointSlice { + // return &discoveryV1.EndpointSlice{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: "test", + // Name: name, + // Labels: map[string]string{index.KubernetesServiceNameLabel: svcName}, + // }, + // } + // } + + // createBackendTLSPolicy := func(name string, svcName string) *v1alpha3.BackendTLSPolicy { + // return &v1alpha3.BackendTLSPolicy{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: "test", + // Name: name, + // }, + // Spec: v1alpha3.BackendTLSPolicySpec{ + // TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + // { + // LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + // Kind: v1.Kind("Service"), + // Name: v1.ObjectName(svcName), + // }, + // }, + // }, + // }, + // } + // } + + // BeforeAll(func() { + // testNamespace := v1.Namespace("test") + // kindService := v1.Kind("Service") + // kindInvalid := v1.Kind("Invalid") + + // // backend Refs + // fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) + // baz1NilNamespace := createHTTPBackendRef(&kindService, "baz-svc-v1", &testNamespace) + // barRef := createHTTPBackendRef(&kindService, "bar-svc", nil) + // baz2Ref := createHTTPBackendRef(&kindService, "baz-svc-v2", &testNamespace) + // baz3Ref := createHTTPBackendRef(&kindService, "baz-svc-v3", &testNamespace) + // invalidKindRef := createHTTPBackendRef(&kindInvalid, "bar-svc", &testNamespace) + + // // httproutes + // hr1 = createHTTPRoute("hr1", "gw", "foo.example.com", fooRef) + // hr2 = createHTTPRoute("hr2", "gw", "bar.example.com", barRef) + // // hr3 shares the same backendRef as hr2 + // hr3 = createHTTPRoute("hr3", "gw", "bar.2.example.com", barRef) + // hrInvalidBackendRef = createHTTPRoute("hr-invalid", "gw", "invalid.com", invalidKindRef) + // hrMultipleRules = createRouteWithMultipleRules( + // "hr-multiple-rules", + // "gw", + // "mutli.example.com", + // []v1.HTTPRouteRule{ + // createHTTPRule("/baz-v1", baz1NilNamespace), + // createHTTPRule("/baz-v2", baz2Ref), + // createHTTPRule("/baz-v3", baz3Ref), + // }, + // ) + + // // services + // hr1svc = createSvc("foo-svc") + // sharedSvc = createSvc("bar-svc") // shared between hr2 and hr3 + // invalidSvc = createSvc("invalid") // nsname matches invalid BackendRef + // notRefSvc = createSvc("not-ref") + // bazSvc1 = createSvc("baz-svc-v1") + // bazSvc2 = createSvc("baz-svc-v2") + // bazSvc3 = createSvc("baz-svc-v3") + + // // endpoint slices + // hr1slice1 = createEndpointSlice("hr1-1", "foo-svc") + // hr1slice2 = createEndpointSlice("hr1-2", "foo-svc") + // noRefSlice = createEndpointSlice("no-ref", "no-ref") + // missingSvcNameSlice = createEndpointSlice("missing-svc-name", "") + + // // backendTLSPolicy + // btls = createBackendTLSPolicy("btls", "foo-svc") + + // gw = createGateway("gw", createHTTPListener()) + // processor.CaptureUpsertChange(gc) + // processor.CaptureUpsertChange(gw) + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + + // testProcessChangedVal := func(expChanged state.ChangeType) { + // changed, _ := processor.Process() + // Expect(changed).To(Equal(expChanged)) + // } + + // testUpsertTriggersChange := func(obj client.Object, expChanged state.ChangeType) { + // processor.CaptureUpsertChange(obj) + // testProcessChangedVal(expChanged) + // } + + // testDeleteTriggersChange := func(obj client.Object, nsname types.NamespacedName, expChanged state.ChangeType) { + // processor.CaptureDeleteChange(obj, nsname) + // testProcessChangedVal(expChanged) + // } + + // When("hr1 is added", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(hr1, state.ClusterStateChange) + // }) + // }) + // When("a hr1 service is added", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(hr1svc, state.ClusterStateChange) + // }) + // }) + // When("a backendTLSPolicy is added for referenced service", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(btls, state.ClusterStateChange) + // }) + // }) + // When("an hr1 endpoint slice is added", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(hr1slice1, state.EndpointsOnlyChange) + // }) + // }) + // When("an hr1 service is updated", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(hr1svc, state.ClusterStateChange) + // }) + // }) + // When("another hr1 endpoint slice is added", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) + // }) + // }) + // When("an endpoint slice with a missing svc name label is added", func() { + // It("should not trigger a change", func() { + // testUpsertTriggersChange(missingSvcNameSlice, state.NoChange) + // }) + // }) + // When("an hr1 endpoint slice is deleted", func() { + // It("should trigger a change", func() { + // testDeleteTriggersChange( + // hr1slice1, + // types.NamespacedName{Namespace: hr1slice1.Namespace, Name: hr1slice1.Name}, + // state.EndpointsOnlyChange, + // ) + // }) + // }) + // When("the second hr1 endpoint slice is deleted", func() { + // It("should trigger a change", func() { + // testDeleteTriggersChange( + // hr1slice2, + // types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, + // state.EndpointsOnlyChange, + // ) + // }) + // }) + // When("the second hr1 endpoint slice is recreated", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) + // }) + // }) + // When("hr1 is deleted", func() { + // It("should trigger a change", func() { + // testDeleteTriggersChange( + // hr1, + // types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name}, + // state.ClusterStateChange, + // ) + // }) + // }) + // When("hr1 service is deleted", func() { + // It("should not trigger a change", func() { + // testDeleteTriggersChange( + // hr1svc, + // types.NamespacedName{Namespace: hr1svc.Namespace, Name: hr1svc.Name}, + // state.NoChange, + // ) + // }) + // }) + // When("the second hr1 endpoint slice is deleted", func() { + // It("should not trigger a change", func() { + // testDeleteTriggersChange( + // hr1slice2, + // types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, + // state.NoChange, + // ) + // }) + // }) + // When("hr2 is added", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(hr2, state.ClusterStateChange) + // }) + // }) + // When("a hr3, that shares a backend service with hr2, is added", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(hr3, state.ClusterStateChange) + // }) + // }) + // When("sharedSvc, a service referenced by both hr2 and hr3, is added", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) + // }) + // }) + // When("hr2 is deleted", func() { + // It("should trigger a change", func() { + // testDeleteTriggersChange( + // hr2, + // types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name}, + // state.ClusterStateChange, + // ) + // }) + // }) + // When("sharedSvc is deleted", func() { + // It("should trigger a change", func() { + // testDeleteTriggersChange( + // sharedSvc, + // types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, + // state.ClusterStateChange, + // ) + // }) + // }) + // When("sharedSvc is recreated", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) + // }) + // }) + // When("hr3 is deleted", func() { + // It("should trigger a change", func() { + // testDeleteTriggersChange( + // hr3, + // types.NamespacedName{Namespace: hr3.Namespace, Name: hr3.Name}, + // state.ClusterStateChange, + // ) + // }) + // }) + // When("sharedSvc is deleted", func() { + // It("should not trigger a change", func() { + // testDeleteTriggersChange( + // sharedSvc, + // types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, + // state.NoChange, + // ) + // }) + // }) + // When("a service that is not referenced by any route is added", func() { + // It("should not trigger a change", func() { + // testUpsertTriggersChange(notRefSvc, state.NoChange) + // }) + // }) + // When("a route with an invalid backend ref type is added", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(hrInvalidBackendRef, state.ClusterStateChange) + // }) + // }) + // When("a service with a namespace name that matches invalid backend ref is added", func() { + // It("should not trigger a change", func() { + // testUpsertTriggersChange(invalidSvc, state.NoChange) + // }) + // }) + // When("an endpoint slice that is not owned by a referenced service is added", func() { + // It("should not trigger a change", func() { + // testUpsertTriggersChange(noRefSlice, state.NoChange) + // }) + // }) + // When("an endpoint slice that is not owned by a referenced service is deleted", func() { + // It("should not trigger a change", func() { + // testDeleteTriggersChange( + // noRefSlice, + // types.NamespacedName{Namespace: noRefSlice.Namespace, Name: noRefSlice.Name}, + // state.NoChange, + // ) + // }) + // }) + // Context("processing a route with multiple rules and three unique backend services", func() { + // When("route is added", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(hrMultipleRules, state.ClusterStateChange) + // }) + // }) + // When("first referenced service is added", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) + // }) + // }) + // When("second referenced service is added", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(bazSvc2, state.ClusterStateChange) + // }) + // }) + // When("first referenced service is deleted", func() { + // It("should trigger a change", func() { + // testDeleteTriggersChange( + // bazSvc1, + // types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, + // state.ClusterStateChange, + // ) + // }) + // }) + // When("first referenced service is recreated", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) + // }) + // }) + // When("third referenced service is added", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) + // }) + // }) + // When("third referenced service is updated", func() { + // It("should trigger a change", func() { + // testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) + // }) + // }) + // When("route is deleted", func() { + // It("should trigger a change", func() { + // testDeleteTriggersChange( + // hrMultipleRules, + // types.NamespacedName{ + // Namespace: hrMultipleRules.Namespace, + // Name: hrMultipleRules.Name, + // }, + // state.ClusterStateChange, + // ) + // }) + // }) + // When("first referenced service is deleted", func() { + // It("should not trigger a change", func() { + // testDeleteTriggersChange( + // bazSvc1, + // types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, + // state.NoChange, + // ) + // }) + // }) + // When("second referenced service is deleted", func() { + // It("should not trigger a change", func() { + // testDeleteTriggersChange( + // bazSvc2, + // types.NamespacedName{Namespace: bazSvc2.Namespace, Name: bazSvc2.Name}, + // state.NoChange, + // ) + // }) + // }) + // When("final referenced service is deleted", func() { + // It("should not trigger a change", func() { + // testDeleteTriggersChange( + // bazSvc3, + // types.NamespacedName{Namespace: bazSvc3.Namespace, Name: bazSvc3.Name}, + // state.NoChange, + // ) + // }) + // }) + // }) + // }) + + // Describe("namespace changes", Ordered, func() { + // var ( + // ns, nsDifferentLabels, nsNoLabels *apiv1.Namespace + // gw *v1.Gateway + // ) + + // BeforeAll(func() { + // ns = &apiv1.Namespace{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "ns", + // Labels: map[string]string{ + // "app": "allowed", + // }, + // }, + // } + // nsDifferentLabels = &apiv1.Namespace{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "ns-different-labels", + // Labels: map[string]string{ + // "oranges": "bananas", + // }, + // }, + // } + // nsNoLabels = &apiv1.Namespace{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "no-labels", + // }, + // } + // gw = &v1.Gateway{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "gw", + // }, + // Spec: v1.GatewaySpec{ + // GatewayClassName: gcName, + // Listeners: []v1.Listener{ + // { + // Port: 80, + // Protocol: v1.HTTPProtocolType, + // AllowedRoutes: &v1.AllowedRoutes{ + // Namespaces: &v1.RouteNamespaces{ + // From: helpers.GetPointer(v1.NamespacesFromSelector), + // Selector: &metav1.LabelSelector{ + // MatchLabels: map[string]string{ + // "app": "allowed", + // }, + // }, + // }, + // }, + // }, + // }, + // }, + // } + // processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + // GatewayCtlrName: controllerName, + // GatewayClassName: gcName, + // Logger: logr.Discard(), + // Validators: createAlwaysValidValidators(), + // MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + // }) + // processor.CaptureUpsertChange(gc) + // processor.CaptureUpsertChange(gw) + // processor.Process() + // }) + + // When("a namespace is created that is not linked to a listener", func() { + // It("does not trigger an update", func() { + // processor.CaptureUpsertChange(nsNoLabels) + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.NoChange)) + // }) + // }) + // When("a namespace is created that is linked to a listener", func() { + // It("triggers an update", func() { + // processor.CaptureUpsertChange(ns) + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // }) + // When("a namespace is deleted that is not linked to a listener", func() { + // It("does not trigger an update", func() { + // processor.CaptureDeleteChange(nsNoLabels, types.NamespacedName{Name: "no-labels"}) + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.NoChange)) + // }) + // }) + // When("a namespace is deleted that is linked to a listener", func() { + // It("triggers an update", func() { + // processor.CaptureDeleteChange(ns, types.NamespacedName{Name: "ns"}) + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // }) + // When("a namespace that is not linked to a listener has its labels changed to match a listener", func() { + // It("triggers an update", func() { + // processor.CaptureUpsertChange(nsDifferentLabels) + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.NoChange)) + + // nsDifferentLabels.Labels = map[string]string{ + // "app": "allowed", + // } + // processor.CaptureUpsertChange(nsDifferentLabels) + // changed, _ = processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // }) + // When( + // "a namespace that is linked to a listener has its labels changed to no longer match a listener", + // func() { + // It("triggers an update", func() { + // nsDifferentLabels.Labels = map[string]string{ + // "oranges": "bananas", + // } + // processor.CaptureUpsertChange(nsDifferentLabels) + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // }, + // ) + // When("a gateway changes its listener's labels", func() { + // It("triggers an update when a namespace that matches the new labels is created", func() { + // gwChangedLabel := gw.DeepCopy() + // gwChangedLabel.Spec.Listeners[0].AllowedRoutes.Namespaces.Selector.MatchLabels = map[string]string{ + // "oranges": "bananas", + // } + // gwChangedLabel.Generation++ + // processor.CaptureUpsertChange(gwChangedLabel) + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + + // // After changing the gateway's labels and generation, the processor should be marked to update + // // the nginx configuration and build a new graph. When processor.Process() gets called, + // // the nginx configuration gets updated and a new graph is built with an updated + // // referencedNamespaces. Thus, when the namespace "ns" is upserted with labels that no longer match + // // the new labels on the gateway, it would not trigger a change as the namespace would no longer + // // be in the updated referencedNamespaces and the labels no longer match the new labels on the + // // gateway. + // processor.CaptureUpsertChange(ns) + // changed, _ = processor.Process() + // Expect(changed).To(Equal(state.NoChange)) + + // processor.CaptureUpsertChange(nsDifferentLabels) + // changed, _ = processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // }) + // When("a namespace that is not linked to a listener has its labels removed", func() { + // It("does not trigger an update", func() { + // ns.Labels = nil + // processor.CaptureUpsertChange(ns) + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.NoChange)) + // }) + // }) + // When("a namespace that is linked to a listener has its labels removed", func() { + // It("triggers an update when labels are removed", func() { + // nsDifferentLabels.Labels = nil + // processor.CaptureUpsertChange(nsDifferentLabels) + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // }) + // }) + + // Describe("NginxProxy resource changes", Ordered, func() { + // Context("referenced by a GatewayClass", func() { + // paramGC := gc.DeepCopy() + // paramGC.Spec.ParametersRef = &v1beta1.ParametersReference{ + // Group: ngfAPIv1alpha1.GroupName, + // Kind: kinds.NginxProxy, + // Name: "np", + // Namespace: helpers.GetPointer[v1.Namespace]("test"), + // } + + // np := &ngfAPIv1alpha2.NginxProxy{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "np", + // Namespace: "test", + // }, + // } + + // npUpdated := &ngfAPIv1alpha2.NginxProxy{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "np", + // Namespace: "test", + // }, + // Spec: ngfAPIv1alpha2.NginxProxySpec{ + // Telemetry: &ngfAPIv1alpha2.Telemetry{ + // Exporter: &ngfAPIv1alpha2.TelemetryExporter{ + // Endpoint: helpers.GetPointer("my-svc:123"), + // BatchSize: helpers.GetPointer(int32(512)), + // BatchCount: helpers.GetPointer(int32(4)), + // Interval: helpers.GetPointer(ngfAPIv1alpha1.Duration("5s")), + // }, + // }, + // }, + // } + // It("handles upserts for an NginxProxy", func() { + // processor.CaptureUpsertChange(np) + // processor.CaptureUpsertChange(paramGC) + + // changed, graph := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // Expect(graph.GatewayClass.NginxProxy.Source).To(Equal(np)) + // }) + // It("captures changes for an NginxProxy", func() { + // processor.CaptureUpsertChange(npUpdated) + // processor.CaptureUpsertChange(paramGC) + + // changed, graph := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // Expect(graph.GatewayClass.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.GatewayClass.NginxProxy).To(BeNil()) + // }) + // }) + // Context("referenced by a Gateway", func() { + // paramGW := &v1.Gateway{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: "test", + // Name: "param-gw", + // Generation: 1, + // }, + // Spec: v1.GatewaySpec{ + // GatewayClassName: gcName, + // Listeners: []v1.Listener{ + // { + // Name: httpListenerName, + // Hostname: nil, + // Port: 80, + // Protocol: v1.HTTPProtocolType, + // }, + // }, + // Infrastructure: &v1.GatewayInfrastructure{ + // ParametersRef: &v1.LocalParametersReference{ + // Group: ngfAPIv1alpha1.GroupName, + // Kind: kinds.NginxProxy, + // Name: "np-gw", + // }, + // }, + // }, + // } + + // np := &ngfAPIv1alpha2.NginxProxy{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "np-gw", + // Namespace: "test", + // }, + // } + + // npUpdated := &ngfAPIv1alpha2.NginxProxy{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "np-gw", + // Namespace: "test", + // }, + // Spec: ngfAPIv1alpha2.NginxProxySpec{ + // Telemetry: &ngfAPIv1alpha2.Telemetry{ + // Exporter: &ngfAPIv1alpha2.TelemetryExporter{ + // Endpoint: helpers.GetPointer("my-svc:123"), + // BatchSize: helpers.GetPointer(int32(512)), + // BatchCount: helpers.GetPointer(int32(4)), + // Interval: helpers.GetPointer(ngfAPIv1alpha1.Duration("5s")), + // }, + // }, + // }, + // } + // It("handles upserts for an NginxProxy", func() { + // processor.CaptureUpsertChange(np) + // processor.CaptureUpsertChange(paramGW) + + // changed, graph := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // Expect(graph.Gateway.NginxProxy.Source).To(Equal(np)) + // }) + // It("captures changes for an NginxProxy", func() { + // processor.CaptureUpsertChange(npUpdated) + // processor.CaptureUpsertChange(paramGW) + + // changed, graph := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // Expect(graph.Gateway.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()) + // }) + // }) + // }) + + // Describe("NGF Policy resource changes", Ordered, func() { + // var ( + // gw *v1.Gateway + // route *v1.HTTPRoute + // svc *apiv1.Service + // csp, cspUpdated *ngfAPIv1alpha1.ClientSettingsPolicy + // obs, obsUpdated *ngfAPIv1alpha2.ObservabilityPolicy + // usp, uspUpdated *ngfAPIv1alpha1.UpstreamSettingsPolicy + // cspKey, obsKey, uspKey graph.PolicyKey + // ) + + // BeforeAll(func() { + // processor.CaptureUpsertChange(gc) + // changed, newGraph := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // Expect(newGraph.GatewayClass.Source).To(Equal(gc)) + // Expect(newGraph.NGFPolicies).To(BeEmpty()) + + // gw = createGateway("gw", createHTTPListener()) + // route = createHTTPRoute( + // "hr-1", + // "gw", + // "foo.example.com", + // v1.HTTPBackendRef{ + // BackendRef: v1.BackendRef{ + // BackendObjectReference: v1.BackendObjectReference{ + // Group: helpers.GetPointer[v1.Group](""), + // Kind: helpers.GetPointer[v1.Kind](kinds.Service), + // Name: "svc", + // Port: helpers.GetPointer[v1.PortNumber](80), + // }, + // }, + // }, + // ) + + // svc = &apiv1.Service{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "svc", + // Namespace: "test", + // }, + // } + + // csp = &ngfAPIv1alpha1.ClientSettingsPolicy{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "csp", + // Namespace: "test", + // }, + // Spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ + // TargetRef: v1alpha2.LocalPolicyTargetReference{ + // Group: v1.GroupName, + // Kind: kinds.Gateway, + // Name: "gw", + // }, + // Body: &ngfAPIv1alpha1.ClientBody{ + // MaxSize: helpers.GetPointer[ngfAPIv1alpha1.Size]("10m"), + // }, + // }, + // } + + // cspUpdated = csp.DeepCopy() + // cspUpdated.Spec.Body.MaxSize = helpers.GetPointer[ngfAPIv1alpha1.Size]("20m") + + // cspKey = graph.PolicyKey{ + // NsName: types.NamespacedName{Name: "csp", Namespace: "test"}, + // GVK: schema.GroupVersionKind{ + // Group: ngfAPIv1alpha1.GroupName, + // Kind: kinds.ClientSettingsPolicy, + // Version: "v1alpha1", + // }, + // } + + // obs = &ngfAPIv1alpha2.ObservabilityPolicy{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "obs", + // Namespace: "test", + // }, + // Spec: ngfAPIv1alpha2.ObservabilityPolicySpec{ + // TargetRefs: []v1alpha2.LocalPolicyTargetReference{ + // { + // Group: v1.GroupName, + // Kind: kinds.HTTPRoute, + // Name: "hr-1", + // }, + // }, + // Tracing: &ngfAPIv1alpha2.Tracing{ + // Strategy: ngfAPIv1alpha2.TraceStrategyRatio, + // }, + // }, + // } + + // obsUpdated = obs.DeepCopy() + // obsUpdated.Spec.Tracing.Strategy = ngfAPIv1alpha2.TraceStrategyParent + + // obsKey = graph.PolicyKey{ + // NsName: types.NamespacedName{Name: "obs", Namespace: "test"}, + // GVK: schema.GroupVersionKind{ + // Group: ngfAPIv1alpha1.GroupName, + // Kind: kinds.ObservabilityPolicy, + // Version: "v1alpha2", + // }, + // } + + // usp = &ngfAPIv1alpha1.UpstreamSettingsPolicy{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "usp", + // Namespace: "test", + // }, + // Spec: ngfAPIv1alpha1.UpstreamSettingsPolicySpec{ + // ZoneSize: helpers.GetPointer[ngfAPIv1alpha1.Size]("10m"), + // TargetRefs: []v1alpha2.LocalPolicyTargetReference{ + // { + // Group: "core", + // Kind: kinds.Service, + // Name: "svc", + // }, + // }, + // }, + // } + + // uspUpdated = usp.DeepCopy() + // uspUpdated.Spec.ZoneSize = helpers.GetPointer[ngfAPIv1alpha1.Size]("20m") + + // uspKey = graph.PolicyKey{ + // NsName: types.NamespacedName{Name: "usp", Namespace: "test"}, + // GVK: schema.GroupVersionKind{ + // Group: ngfAPIv1alpha1.GroupName, + // Kind: kinds.UpstreamSettingsPolicy, + // Version: "v1alpha1", + // }, + // } + // }) + + // /* + // NOTE: When adding a new NGF policy to the change processor, + // update the following tests to make sure that the change processor can track changes for multiple NGF + // policies. + // */ + + // When("a policy is created that references a resource that is not in the last graph", func() { + // It("reports no changes", func() { + // processor.CaptureUpsertChange(csp) + // processor.CaptureUpsertChange(obs) + // processor.CaptureUpsertChange(usp) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.NoChange)) + // }) + // }) + // When("the resource the policy references is created", func() { + // It("populates the graph with the policy", func() { + // processor.CaptureUpsertChange(gw) + + // changed, graph := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // Expect(graph.NGFPolicies).To(HaveKey(cspKey)) + // Expect(graph.NGFPolicies[cspKey].Source).To(Equal(csp)) + // Expect(graph.NGFPolicies).ToNot(HaveKey(obsKey)) + + // processor.CaptureUpsertChange(route) + // changed, graph = processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // Expect(graph.NGFPolicies).To(HaveKey(obsKey)) + // Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obs)) + + // processor.CaptureUpsertChange(svc) + // changed, graph = processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // Expect(graph.NGFPolicies).To(HaveKey(uspKey)) + // Expect(graph.NGFPolicies[uspKey].Source).To(Equal(usp)) + // }) + // }) + // When("the policy is updated", func() { + // It("captures changes for a policy", func() { + // processor.CaptureUpsertChange(cspUpdated) + // processor.CaptureUpsertChange(obsUpdated) + // processor.CaptureUpsertChange(uspUpdated) + + // changed, graph := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // Expect(graph.NGFPolicies).To(HaveKey(cspKey)) + // Expect(graph.NGFPolicies[cspKey].Source).To(Equal(cspUpdated)) + // Expect(graph.NGFPolicies).To(HaveKey(obsKey)) + // Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obsUpdated)) + // Expect(graph.NGFPolicies).To(HaveKey(uspKey)) + // Expect(graph.NGFPolicies[uspKey].Source).To(Equal(uspUpdated)) + // }) + // }) + // When("the policy is deleted", func() { + // It("removes the policy from the graph", func() { + // processor.CaptureDeleteChange(&ngfAPIv1alpha1.ClientSettingsPolicy{}, client.ObjectKeyFromObject(csp)) + // processor.CaptureDeleteChange(&ngfAPIv1alpha2.ObservabilityPolicy{}, client.ObjectKeyFromObject(obs)) + // processor.CaptureDeleteChange(&ngfAPIv1alpha1.UpstreamSettingsPolicy{}, client.ObjectKeyFromObject(usp)) + + // changed, graph := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // Expect(graph.NGFPolicies).To(BeEmpty()) + // }) + // }) + // }) + + // Describe("SnippetsFilter resource changed", Ordered, func() { + // sfNsName := types.NamespacedName{ + // Name: "sf", + // Namespace: "test", + // } + + // sf := &ngfAPIv1alpha1.SnippetsFilter{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: sfNsName.Name, + // Namespace: sfNsName.Namespace, + // }, + // Spec: ngfAPIv1alpha1.SnippetsFilterSpec{ + // Snippets: []ngfAPIv1alpha1.Snippet{ + // { + // Context: ngfAPIv1alpha1.NginxContextMain, + // Value: "main snippet", + // }, + // }, + // }, + // } + + // sfUpdated := &ngfAPIv1alpha1.SnippetsFilter{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: sfNsName.Name, + // Namespace: sfNsName.Namespace, + // }, + // Spec: ngfAPIv1alpha1.SnippetsFilterSpec{ + // Snippets: []ngfAPIv1alpha1.Snippet{ + // { + // Context: ngfAPIv1alpha1.NginxContextMain, + // Value: "main snippet", + // }, + // { + // Context: ngfAPIv1alpha1.NginxContextHTTP, + // Value: "http snippet", + // }, + // }, + // }, + // } + // It("handles upserts for a SnippetsFilter", func() { + // processor.CaptureUpsertChange(sf) + + // changed, graph := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + + // processedSf, exists := graph.SnippetsFilters[sfNsName] + // Expect(exists).To(BeTrue()) + // Expect(processedSf.Source).To(Equal(sf)) + // Expect(processedSf.Valid).To(BeTrue()) + // }) + // It("captures changes for a SnippetsFilter", func() { + // processor.CaptureUpsertChange(sfUpdated) + + // changed, graph := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + + // processedSf, exists := graph.SnippetsFilters[sfNsName] + // Expect(exists).To(BeTrue()) + // Expect(processedSf.Source).To(Equal(sfUpdated)) + // Expect(processedSf.Valid).To(BeTrue()) + // }) + // It("handles deletes for a SnippetsFilter", func() { + // processor.CaptureDeleteChange(sfUpdated, sfNsName) + + // changed, graph := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // Expect(graph.SnippetsFilters).To(BeEmpty()) + // }) + // }) }) + // Describe("Ensuring non-changing changes don't override previously changing changes", func() { + // // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses + // // -- this is done in 'Normal cases of processing changes' + + // var ( + // processor *state.ChangeProcessorImpl + // gcNsName, gwNsName, hrNsName, hr2NsName, grNsName, gr2NsName, rgNsName, svcNsName types.NamespacedName + // sliceNsName, secretNsName, cmNsName, btlsNsName, npNsName types.NamespacedName + // gc, gcUpdated *v1.GatewayClass + // gw1, gw1Updated, gw2 *v1.Gateway + // hr1, hr1Updated, hr2 *v1.HTTPRoute + // gr1, gr1Updated, gr2 *v1.GRPCRoute + // rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant + // svc, barSvc, unrelatedSvc *apiv1.Service + // slice, barSlice, unrelatedSlice *discoveryV1.EndpointSlice + // ns, unrelatedNS, testNs, barNs *apiv1.Namespace + // secret, secretUpdated, unrelatedSecret, barSecret, barSecretUpdated *apiv1.Secret + // cm, cmUpdated, unrelatedCM *apiv1.ConfigMap + // btls, btlsUpdated *v1alpha3.BackendTLSPolicy + // np, npUpdated *ngfAPIv1alpha2.NginxProxy + // ) + + // BeforeEach(OncePerOrdered, func() { + // processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + // GatewayCtlrName: "test.controller", + // GatewayClassName: "test-class", + // Validators: createAlwaysValidValidators(), + // MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + // }) + + // secretNsName = types.NamespacedName{Namespace: "test", Name: "tls-secret"} + // secret = &apiv1.Secret{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: secretNsName.Name, + // Namespace: secretNsName.Namespace, + // Generation: 1, + // }, + // Type: apiv1.SecretTypeTLS, + // Data: map[string][]byte{ + // apiv1.TLSCertKey: cert, + // apiv1.TLSPrivateKeyKey: key, + // }, + // } + // secretUpdated = secret.DeepCopy() + // secretUpdated.Generation++ + // barSecret = &apiv1.Secret{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "bar-secret", + // Namespace: "test", + // Generation: 1, + // }, + // Type: apiv1.SecretTypeTLS, + // Data: map[string][]byte{ + // apiv1.TLSCertKey: cert, + // apiv1.TLSPrivateKeyKey: key, + // }, + // } + // barSecretUpdated = barSecret.DeepCopy() + // barSecretUpdated.Generation++ + // unrelatedSecret = &apiv1.Secret{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "unrelated-tls-secret", + // Namespace: "unrelated-ns", + // Generation: 1, + // }, + // Type: apiv1.SecretTypeTLS, + // Data: map[string][]byte{ + // apiv1.TLSCertKey: cert, + // apiv1.TLSPrivateKeyKey: key, + // }, + // } + + // gcNsName = types.NamespacedName{Name: "test-class"} + + // gc = &v1.GatewayClass{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: gcNsName.Name, + // }, + // Spec: v1.GatewayClassSpec{ + // ControllerName: "test.controller", + // }, + // } + + // gcUpdated = gc.DeepCopy() + // gcUpdated.Generation++ + + // gwNsName = types.NamespacedName{Namespace: "test", Name: "gw-1"} + + // gw1 = &v1.Gateway{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "gw-1", + // Namespace: "test", + // Generation: 1, + // }, + // Spec: v1.GatewaySpec{ + // GatewayClassName: gcName, + // Listeners: []v1.Listener{ + // { + // Name: httpListenerName, + // Hostname: nil, + // Port: 80, + // Protocol: v1.HTTPProtocolType, + // AllowedRoutes: &v1.AllowedRoutes{ + // Namespaces: &v1.RouteNamespaces{ + // From: helpers.GetPointer(v1.NamespacesFromSelector), + // Selector: &metav1.LabelSelector{ + // MatchLabels: map[string]string{ + // "test": "namespace", + // }, + // }, + // }, + // }, + // }, + // { + // Name: httpsListenerName, + // Hostname: nil, + // Port: 443, + // Protocol: v1.HTTPSProtocolType, + // TLS: &v1.GatewayTLSConfig{ + // Mode: helpers.GetPointer(v1.TLSModeTerminate), + // CertificateRefs: []v1.SecretObjectReference{ + // { + // Kind: (*v1.Kind)(helpers.GetPointer("Secret")), + // Name: v1.ObjectName(secret.Name), + // Namespace: (*v1.Namespace)(&secret.Namespace), + // }, + // }, + // }, + // }, + // { + // Name: "listener-500-1", + // Hostname: nil, + // Port: 500, + // Protocol: v1.HTTPSProtocolType, + // TLS: &v1.GatewayTLSConfig{ + // Mode: helpers.GetPointer(v1.TLSModeTerminate), + // CertificateRefs: []v1.SecretObjectReference{ + // { + // Kind: (*v1.Kind)(helpers.GetPointer("Secret")), + // Name: v1.ObjectName(barSecret.Name), + // Namespace: (*v1.Namespace)(&barSecret.Namespace), + // }, + // }, + // }, + // }, + // }, + // }, + // } + + // gw1Updated = gw1.DeepCopy() + // gw1Updated.Generation++ + + // gw2 = gw1.DeepCopy() + // gw2.Name = "gw-2" + + // testNamespace := v1.Namespace("test") + // kindService := v1.Kind("Service") + // fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) + // barRef := createHTTPBackendRef(&kindService, "bar-svc", &testNamespace) + + // hrNsName = types.NamespacedName{Namespace: "test", Name: "hr-1"} + // hr1 = createHTTPRoute("hr-1", "gw-1", "foo.example.com", fooRef, barRef) + // hr1Updated = hr1.DeepCopy() + // hr1Updated.Generation++ + // hr2NsName = types.NamespacedName{Namespace: "test", Name: "hr-2"} + // hr2 = hr1.DeepCopy() + // hr2.Name = hr2NsName.Name + + // grNsName = types.NamespacedName{Namespace: "test", Name: "gr-1"} + // gr1 = createGRPCRoute("gr-1", "gw-1", "foo.grpc.com") + // gr1Updated = gr1.DeepCopy() + // gr1Updated.Generation++ + // gr2NsName = types.NamespacedName{Namespace: "test", Name: "hr-2"} + // gr2 = gr1.DeepCopy() + // gr2.Name = gr2NsName.Name + + // svcNsName = types.NamespacedName{Namespace: "test", Name: "foo-svc"} + // svc = &apiv1.Service{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: svcNsName.Namespace, + // Name: svcNsName.Name, + // }, + // } + // barSvc = &apiv1.Service{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: "test", + // Name: "bar-svc", + // }, + // } + // unrelatedSvc = &apiv1.Service{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: "test", + // Name: "unrelated-svc", + // }, + // } + + // sliceNsName = types.NamespacedName{Namespace: "test", Name: "slice"} + // slice = &discoveryV1.EndpointSlice{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: sliceNsName.Namespace, + // Name: sliceNsName.Name, + // Labels: map[string]string{index.KubernetesServiceNameLabel: svc.Name}, + // }, + // } + // barSlice = &discoveryV1.EndpointSlice{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: "test", + // Name: "bar-slice", + // Labels: map[string]string{index.KubernetesServiceNameLabel: "bar-svc"}, + // }, + // } + // unrelatedSlice = &discoveryV1.EndpointSlice{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: "test", + // Name: "unrelated-slice", + // Labels: map[string]string{index.KubernetesServiceNameLabel: "unrelated-svc"}, + // }, + // } + + // testNs = &apiv1.Namespace{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "test", + // Labels: map[string]string{ + // "test": "namespace", + // }, + // }, + // } + // ns = &apiv1.Namespace{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "ns", + // Labels: map[string]string{ + // "test": "namespace", + // }, + // }, + // } + // barNs = &apiv1.Namespace{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "bar-ns", + // Labels: map[string]string{ + // "test": "namespace", + // }, + // }, + // } + // unrelatedNS = &apiv1.Namespace{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "unrelated-ns", + // Labels: map[string]string{ + // "oranges": "bananas", + // }, + // }, + // } + + // rgNsName = types.NamespacedName{Namespace: "test", Name: "rg-1"} + + // rg1 = &v1beta1.ReferenceGrant{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: rgNsName.Name, + // Namespace: rgNsName.Namespace, + // }, + // } + + // rg1Updated = rg1.DeepCopy() + // rg1Updated.Generation++ + + // rg2 = rg1.DeepCopy() + // rg2.Name = "rg-2" + + // cmNsName = types.NamespacedName{Namespace: "test", Name: "cm-1"} + // cm = &apiv1.ConfigMap{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: cmNsName.Name, + // Namespace: cmNsName.Namespace, + // }, + // Data: map[string]string{ + // "ca.crt": "value", + // }, + // } + // cmUpdated = cm.DeepCopy() + // cmUpdated.Data["ca.crt"] = "updated-value" + + // unrelatedCM = &apiv1.ConfigMap{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: "unrelated-cm", + // Namespace: "unrelated-ns", + // }, + // Data: map[string]string{ + // "ca.crt": "value", + // }, + // } + + // btlsNsName = types.NamespacedName{Namespace: "test", Name: "btls-1"} + // btls = &v1alpha3.BackendTLSPolicy{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: btlsNsName.Name, + // Namespace: btlsNsName.Namespace, + // Generation: 1, + // }, + // Spec: v1alpha3.BackendTLSPolicySpec{ + // TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + // { + // LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + // Kind: "Service", + // Name: v1.ObjectName(svc.Name), + // }, + // }, + // }, + // Validation: v1alpha3.BackendTLSPolicyValidation{ + // CACertificateRefs: []v1.LocalObjectReference{ + // { + // Name: v1.ObjectName(cm.Name), + // }, + // }, + // }, + // }, + // } + // btlsUpdated = btls.DeepCopy() + + // npNsName = types.NamespacedName{Name: "np-1"} + // np = &ngfAPIv1alpha2.NginxProxy{ + // ObjectMeta: metav1.ObjectMeta{ + // Name: npNsName.Name, + // }, + // Spec: ngfAPIv1alpha2.NginxProxySpec{ + // Telemetry: &ngfAPIv1alpha2.Telemetry{ + // ServiceName: helpers.GetPointer("my-svc"), + // }, + // }, + // } + // npUpdated = np.DeepCopy() + // }) + // // Changing change - a change that makes processor.Process() report changed + // // Non-changing change - a change that doesn't do that + // // Related resource - a K8s resource that is related to a configured Gateway API resource + // // Unrelated resource - a K8s resource that is not related to a configured Gateway API resource + + // // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses + // // -- this is done in 'Normal cases of processing changes' + // Describe("Multiple Gateway API resource changes", Ordered, func() { + // It("should report changed after multiple Upserts", func() { + // processor.CaptureUpsertChange(gc) + // processor.CaptureUpsertChange(gw1) + // processor.CaptureUpsertChange(testNs) + // processor.CaptureUpsertChange(hr1) + // processor.CaptureUpsertChange(gr1) + // processor.CaptureUpsertChange(rg1) + // processor.CaptureUpsertChange(btls) + // processor.CaptureUpsertChange(cm) + // processor.CaptureUpsertChange(np) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // When("a upsert of updated resources is followed by an upsert of the same generation", func() { + // It("should report changed", func() { + // // these are changing changes + // processor.CaptureUpsertChange(gcUpdated) + // processor.CaptureUpsertChange(gw1Updated) + // processor.CaptureUpsertChange(hr1Updated) + // processor.CaptureUpsertChange(gr1Updated) + // processor.CaptureUpsertChange(rg1Updated) + // processor.CaptureUpsertChange(btlsUpdated) + // processor.CaptureUpsertChange(cmUpdated) + // processor.CaptureUpsertChange(npUpdated) + + // // there are non-changing changes + // processor.CaptureUpsertChange(gcUpdated) + // processor.CaptureUpsertChange(gw1Updated) + // processor.CaptureUpsertChange(hr1Updated) + // processor.CaptureUpsertChange(gr1Updated) + // processor.CaptureUpsertChange(rg1Updated) + // processor.CaptureUpsertChange(btlsUpdated) + // processor.CaptureUpsertChange(cmUpdated) + // processor.CaptureUpsertChange(npUpdated) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // }) + // It("should report changed after upserting new resources", func() { + // // we can't have a second GatewayClass, so we don't add it + // processor.CaptureUpsertChange(gw2) + // processor.CaptureUpsertChange(hr2) + // processor.CaptureUpsertChange(gr2) + // processor.CaptureUpsertChange(rg2) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // When("resources are deleted followed by upserts with the same generations", func() { + // It("should report changed", func() { + // // these are changing changes + // processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) + // processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) + // processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) + // processor.CaptureDeleteChange(&v1.GRPCRoute{}, grNsName) + // processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) + // processor.CaptureDeleteChange(&v1alpha3.BackendTLSPolicy{}, btlsNsName) + // processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) + // processor.CaptureDeleteChange(&ngfAPIv1alpha2.NginxProxy{}, npNsName) + + // // these are non-changing changes + // processor.CaptureUpsertChange(gw2) + // processor.CaptureUpsertChange(hr2) + // processor.CaptureUpsertChange(gr2) + // processor.CaptureUpsertChange(rg2) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // }) + // It("should report changed after deleting resources", func() { + // processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) + // processor.CaptureDeleteChange(&v1.HTTPRoute{}, gr2NsName) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // }) + // Describe("Deleting non-existing Gateway API resource", func() { + // It("should not report changed after deleting non-existing", func() { + // processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) + // processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) + // processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) + // processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) + // processor.CaptureDeleteChange(&v1.HTTPRoute{}, grNsName) + // processor.CaptureDeleteChange(&v1.HTTPRoute{}, gr2NsName) + // processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.NoChange)) + // }) + // }) + // Describe("Multiple Kubernetes API resource changes", Ordered, func() { + // BeforeAll(func() { + // // Set up graph + // processor.CaptureUpsertChange(gc) + // processor.CaptureUpsertChange(gw1) + // processor.CaptureUpsertChange(testNs) + // processor.CaptureUpsertChange(hr1) + // processor.CaptureUpsertChange(gr1) + // processor.CaptureUpsertChange(secret) + // processor.CaptureUpsertChange(barSecret) + // processor.CaptureUpsertChange(cm) + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + + // It("should report changed after multiple Upserts of related resources", func() { + // processor.CaptureUpsertChange(svc) + // processor.CaptureUpsertChange(slice) + // processor.CaptureUpsertChange(ns) + // processor.CaptureUpsertChange(secretUpdated) + // processor.CaptureUpsertChange(cmUpdated) + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // It("should report not changed after multiple Upserts of unrelated resources", func() { + // processor.CaptureUpsertChange(unrelatedSvc) + // processor.CaptureUpsertChange(unrelatedSlice) + // processor.CaptureUpsertChange(unrelatedNS) + // processor.CaptureUpsertChange(unrelatedSecret) + // processor.CaptureUpsertChange(unrelatedCM) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.NoChange)) + // }) + // When("upserts of related resources are followed by upserts of unrelated resources", func() { + // It("should report changed", func() { + // // these are changing changes + // processor.CaptureUpsertChange(barSvc) + // processor.CaptureUpsertChange(barSlice) + // processor.CaptureUpsertChange(barNs) + // processor.CaptureUpsertChange(barSecretUpdated) + // processor.CaptureUpsertChange(cmUpdated) + + // // there are non-changing changes + // processor.CaptureUpsertChange(unrelatedSvc) + // processor.CaptureUpsertChange(unrelatedSlice) + // processor.CaptureUpsertChange(unrelatedNS) + // processor.CaptureUpsertChange(unrelatedSecret) + // processor.CaptureUpsertChange(unrelatedCM) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // }) + // When("deletes of related resources are followed by upserts of unrelated resources", func() { + // It("should report changed", func() { + // // these are changing changes + // processor.CaptureDeleteChange(&apiv1.Service{}, svcNsName) + // processor.CaptureDeleteChange(&discoveryV1.EndpointSlice{}, sliceNsName) + // processor.CaptureDeleteChange(&apiv1.Namespace{}, types.NamespacedName{Name: "ns"}) + // processor.CaptureDeleteChange(&apiv1.Secret{}, secretNsName) + // processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) + + // // these are non-changing changes + // processor.CaptureUpsertChange(unrelatedSvc) + // processor.CaptureUpsertChange(unrelatedSlice) + // processor.CaptureUpsertChange(unrelatedNS) + // processor.CaptureUpsertChange(unrelatedSecret) + // processor.CaptureUpsertChange(unrelatedCM) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // }) + // }) + // Describe("Multiple Kubernetes API and Gateway API resource changes", Ordered, func() { + // It("should report changed after multiple Upserts of new and related resources", func() { + // // new Gateway API resources + // processor.CaptureUpsertChange(gc) + // processor.CaptureUpsertChange(gw1) + // processor.CaptureUpsertChange(testNs) + // processor.CaptureUpsertChange(hr1) + // processor.CaptureUpsertChange(gr1) + // processor.CaptureUpsertChange(rg1) + // processor.CaptureUpsertChange(btls) + + // // related Kubernetes API resources + // processor.CaptureUpsertChange(svc) + // processor.CaptureUpsertChange(slice) + // processor.CaptureUpsertChange(ns) + // processor.CaptureUpsertChange(secret) + // processor.CaptureUpsertChange(cm) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }) + // It("should report not changed after multiple Upserts of unrelated resources", func() { + // // unrelated Kubernetes API resources + // processor.CaptureUpsertChange(unrelatedSvc) + // processor.CaptureUpsertChange(unrelatedSlice) + // processor.CaptureUpsertChange(unrelatedNS) + // processor.CaptureUpsertChange(unrelatedSecret) + // processor.CaptureUpsertChange(unrelatedCM) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.NoChange)) + // }) + // It("should report changed after upserting changed resources followed by upserting unrelated resources", + // func() { + // // these are changing changes + // processor.CaptureUpsertChange(gcUpdated) + // processor.CaptureUpsertChange(gw1Updated) + // processor.CaptureUpsertChange(hr1Updated) + // processor.CaptureUpsertChange(gr1Updated) + // processor.CaptureUpsertChange(rg1Updated) + // processor.CaptureUpsertChange(btlsUpdated) + + // // these are non-changing changes + // processor.CaptureUpsertChange(unrelatedSvc) + // processor.CaptureUpsertChange(unrelatedSlice) + // processor.CaptureUpsertChange(unrelatedNS) + // processor.CaptureUpsertChange(unrelatedSecret) + // processor.CaptureUpsertChange(unrelatedCM) + + // changed, _ := processor.Process() + // Expect(changed).To(Equal(state.ClusterStateChange)) + // }, + // ) + // }) + // }) + // Describe("Edge cases with panic", func() { + // var processor state.ChangeProcessor + + // BeforeEach(func() { + // processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + // GatewayCtlrName: "test.controller", + // GatewayClassName: "my-class", + // Validators: createAlwaysValidValidators(), + // MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + // }) + // }) + + // DescribeTable("CaptureUpsertChange must panic", + // func(obj client.Object) { + // process := func() { + // processor.CaptureUpsertChange(obj) + // } + // Expect(process).Should(Panic()) + // }, + // Entry( + // "an unsupported resource", + // &v1alpha2.TCPRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "tcp"}}, + // ), + // Entry( + // "nil resource", + // nil, + // ), + // ) + + // DescribeTable( + // "CaptureDeleteChange must panic", + // func(resourceType ngftypes.ObjectType, nsname types.NamespacedName) { + // process := func() { + // processor.CaptureDeleteChange(resourceType, nsname) + // } + // Expect(process).Should(Panic()) + // }, + // Entry( + // "an unsupported resource", + // &v1alpha2.TCPRoute{}, + // types.NamespacedName{Namespace: "test", Name: "tcp"}, + // ), + // Entry( + // "nil resource type", + // nil, + // types.NamespacedName{Namespace: "test", Name: "resource"}, + // ), + // ) + // }) }) diff --git a/internal/mode/static/state/dataplane/configuration.go b/internal/mode/static/state/dataplane/configuration.go index ff53ae3523..9975f98ce8 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.Listeners, 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.Listeners, 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 } @@ -375,13 +376,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 } @@ -401,7 +402,7 @@ func buildServers(g *graph.Graph) (http, ssl []VirtualServer) { httpServers, sslServers := httpRules.buildServers(), sslRules.buildServers() - pols := buildPolicies(g.Gateway.Policies) + pols := buildPolicies(gateway.Policies) for i := range httpServers { httpServers[i].Policies = pols @@ -831,13 +832,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 +901,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 +910,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 } @@ -1016,14 +1017,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 +1048,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 +1070,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..dd32d9fea9 100644 --- a/internal/mode/static/state/dataplane/configuration_test.go +++ b/internal/mode/static/state/dataplane/configuration_test.go @@ -70,15 +70,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{}, @@ -899,7 +911,8 @@ 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, @@ -915,7 +928,8 @@ 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, @@ -954,7 +968,8 @@ 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, // nil hostname @@ -1000,7 +1015,8 @@ 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", Source: invalidListener, Valid: false, @@ -1023,7 +1039,8 @@ 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, @@ -1084,7 +1101,8 @@ 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, @@ -1124,7 +1142,8 @@ 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, @@ -1234,7 +1253,8 @@ 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, @@ -1374,7 +1394,8 @@ 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, @@ -1583,7 +1604,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,7 +1612,8 @@ 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, @@ -1650,7 +1672,8 @@ 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, @@ -1794,7 +1817,8 @@ 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, @@ -1846,7 +1870,8 @@ 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, @@ -1924,7 +1949,8 @@ 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", Source: listener443, Valid: true, @@ -1983,7 +2009,8 @@ 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", Source: listener443, Valid: true, @@ -2042,17 +2069,18 @@ 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{ + gw.Listeners = append(gw.Listeners, &graph.Listener{ Name: "listener-80-1", 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,7 +2102,8 @@ 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, @@ -2093,7 +2122,7 @@ func TestBuildConfiguration(t *testing.T) { 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 +2198,18 @@ 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{ + gw.Listeners = append(gw.Listeners, &graph.Listener{ Name: "listener-80-1", 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 +2222,18 @@ 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{ + gw.Listeners = append(gw.Listeners, &graph.Listener{ Name: "listener-80-1", 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 +2246,18 @@ 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{ + gw.Listeners = append(gw.Listeners, &graph.Listener{ Name: "listener-80-1", 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 +2289,18 @@ 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{ + gw.Listeners = append(gw.Listeners, &graph.Listener{ Name: "listener-80-1", 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 +2353,18 @@ 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{ + gw.Listeners = append(gw.Listeners, &graph.Listener{ Name: "listener-80-1", 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 +2391,7 @@ func TestBuildConfiguration(t *testing.T) { result := BuildConfiguration( context.TODO(), test.graph, + test.graph.Gateways[gatewayNsName], fakeResolver, 1, false, @@ -2409,17 +2444,18 @@ 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{ + gw.Listeners = append(gw.Listeners, &graph.Listener{ Name: "listener-80-1", 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 +2491,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 +2507,7 @@ func TestBuildConfiguration_Plus(t *testing.T) { result := BuildConfiguration( context.TODO(), test.graph, + test.graph.Gateways[gatewayNsName], fakeResolver, 1, true, @@ -3420,8 +3457,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 +3468,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 +3477,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 +3497,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 +3512,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 +3529,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 +3546,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 +3583,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 +3655,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 +3691,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 }) @@ -3782,97 +3835,95 @@ 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, - }, + testGraph := &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, }, }, }, - 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{ + "testingListener": {"app.example.com", "cafe.example.com"}, }, - SectionName: nil, - Port: nil, - Gateway: types.NamespacedName{}, - Idx: 0, }, + SectionName: nil, + Port: nil, + Gateway: types.NamespacedName{}, + 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(testGraph) expectedPassthroughServers := []Layer4VirtualServer{ { @@ -3921,80 +3972,76 @@ 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", + 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, - 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, - }, - }, + 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{}, - }, - }, - 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, - }, - }, + }, + }, + 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, }, }, }, - 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, }, }, }, @@ -4021,7 +4068,7 @@ func TestBuildStreamUpstreams(t *testing.T) { return fakeEndpoints, nil } - streamUpstreams := buildStreamUpstreams(context.Background(), testGraph.Gateway.Listeners, &fakeResolver, Dual) + streamUpstreams := buildStreamUpstreams(context.Background(), listeners, &fakeResolver, Dual) expectedStreamUpstreams := []Upstream{ { @@ -4048,8 +4095,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 +4106,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 +4132,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 +4158,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 +4199,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 +4211,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 +4248,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 +4259,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 +4270,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 +4281,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 +4292,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 +4303,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 +4314,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 +4330,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 +4551,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 +4581,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 +4595,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 +4613,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..4a1b1dc96f 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -46,10 +46,15 @@ func addBackendRefsToRouteRules( refGrantResolver *referenceGrantResolver, services map[types.NamespacedName]*v1.Service, backendTLSPolicies map[types.NamespacedName]*BackendTLSPolicy, - npCfg *EffectiveNginxProxy, + gws map[types.NamespacedName]*Gateway, ) { - for _, r := range routes { - addBackendRefsToRules(r, refGrantResolver, services, backendTLSPolicies, npCfg) + for _, gw := range gws { + if gw == nil { + continue + } + for _, r := range routes { + addBackendRefsToRules(r, refGrantResolver, services, backendTLSPolicies, gw.EffectiveNginxProxy) + } } } diff --git a/internal/mode/static/state/graph/backend_tls_policy.go b/internal/mode/static/state/graph/backend_tls_policy.go index 4e00eecc56..477dc76a47 100644 --- a/internal/mode/static/state/graph/backend_tls_policy.go +++ b/internal/mode/static/state/graph/backend_tls_policy.go @@ -19,7 +19,7 @@ type BackendTLSPolicy struct { // 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 []types.NamespacedName // Conditions include Conditions for the BackendTLSPolicy. Conditions []conditions.Condition // Valid shows whether the BackendTLSPolicy is valid. @@ -35,9 +35,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 || gateways == nil { return nil } @@ -57,12 +57,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 +130,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 +139,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 +157,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 +182,49 @@ func validateBackendTLSWellKnownCACerts(btp *v1alpha3.BackendTLSPolicy) error { } return nil } + +func addGatewaysForBackendTLSPolicies( + backendTLSPolicies map[types.NamespacedName]*BackendTLSPolicy, + services map[types.NamespacedName]*ReferencedService, +) { + for _, backendTLSPolicy := range backendTLSPolicies { + for _, refs := range backendTLSPolicy.Source.Spec.TargetRefs { + for svcNsName, referencedServices := range services { + if refs.Kind != "Service" { + continue + } + if svcNsName.Name != string(refs.Name) { + continue + } + backendTLSPolicy.Gateways = append( + backendTLSPolicy.Gateways, + getUniqueGatewayNsNames(referencedServices.GatewayNsNames)..., + ) + } + } + } + + deduplicateGateways(backendTLSPolicies) +} + +func getUniqueGatewayNsNames(gatewayMap map[types.NamespacedName]struct{}) []types.NamespacedName { + gatewayNsNames := make([]types.NamespacedName, 0, len(gatewayMap)) + for nsname := range gatewayMap { + gatewayNsNames = append(gatewayNsNames, nsname) + } + return gatewayNsNames +} + +func deduplicateGateways(backendTLSPolicies map[types.NamespacedName]*BackendTLSPolicy) { + for _, backendTLSPolicy := range backendTLSPolicies { + gatewayNsNames := make(map[types.NamespacedName]struct{}) + if len(backendTLSPolicy.Gateways) == 0 { + continue + } + for _, gatewayNsName := range backendTLSPolicy.Gateways { + gatewayNsNames[gatewayNsName] = struct{}{} + } + + backendTLSPolicy.Gateways = getUniqueGatewayNsNames(gatewayNsNames) + } +} 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..4c866c68da 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)) }) diff --git a/internal/mode/static/state/graph/gateway.go b/internal/mode/static/state/graph/gateway.go index d41364288d..909fc139d7 100644 --- a/internal/mode/static/state/graph/gateway.go +++ b/internal/mode/static/state/graph/gateway.go @@ -1,64 +1,34 @@ 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. type Gateway struct { - // 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 - // 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. + Source *v1.Gateway + NginxProxy *NginxProxy EffectiveNginxProxy *EffectiveNginxProxy - // Listeners include the listeners of the Gateway. - Listeners []*Listener - // Conditions holds the conditions for the Gateway. - Conditions []conditions.Condition - // Policies holds the policies attached to the Gateway. - Policies []*Policy - // Valid indicates whether the Gateway Spec is valid. - Valid bool -} - -// processedGateways holds the resources that belong to NGF. -type processedGateways struct { - Winner *v1.Gateway - Ignored map[types.NamespacedName]*v1.Gateway + DeploymentName types.NamespacedName + Listeners []*Listener + Conditions []conditions.Condition + Policies []*Policy + Valid bool } // 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) +func GetAllNsNames(gws map[types.NamespacedName]*v1.Gateway) []types.NamespacedName { + allNsNames := make([]types.NamespacedName, 0, len(gws)) - if gws.Winner != nil { - allNsNames = append(allNsNames, client.ObjectKeyFromObject(gws.Winner)) - } - for nsName := range gws.Ignored { + for nsName := range gws { allNsNames = append(allNsNames, nsName) } @@ -69,89 +39,96 @@ func (gws processedGateways) GetAllNsNames() []types.NamespacedName { 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, len(gws)) - 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, + 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 gws == nil { 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 gwNsNames, 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 + if !valid { + builtGateways[gwNsNames] = &Gateway{ + Source: gw, + Valid: false, + NginxProxy: np, + EffectiveNginxProxy: effectiveNginxProxy, + Conditions: conds, + } + } else { + builtGateways[gwNsNames] = &Gateway{ + Source: gw, + Listeners: buildListeners(gw, secretResolver, refGrantResolver, protectedPorts), + NginxProxy: np, + EffectiveNginxProxy: effectiveNginxProxy, + Valid: true, + Conditions: conds, + } } - protectedPorts[metricsPort] = "MetricsPort" } - return &Gateway{ - Source: gw, - Listeners: buildListeners(gw, secretResolver, refGrantResolver, protectedPorts), - NginxProxy: np, - EffectiveNginxProxy: effectiveNginxProxy, - Valid: true, - Conditions: conds, + return builtGateways +} + +func addDeploymentNameToGateway(gws map[types.NamespacedName]*Gateway) { + for _, gw := range gws { + if gw == nil { + continue + } + + gw.DeploymentName = types.NamespacedName{ + Namespace: gw.Source.Namespace, + Name: controller.CreateNginxResourceName(gw.Source.Name, string(gw.Source.Spec.GatewayClassName)), + } } } diff --git a/internal/mode/static/state/graph/gateway_test.go b/internal/mode/static/state/graph/gateway_test.go index 0e50d14380..1442ddc955 100644 --- a/internal/mode/static/state/graph/gateway_test.go +++ b/internal/mode/static/state/graph/gateway_test.go @@ -22,13 +22,13 @@ import ( func TestProcessedGatewaysGetAllNsNames(t *testing.T) { t.Parallel() - winner := &v1.Gateway{ + gateway1 := &v1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", Name: "gateway-1", }, } - loser := &v1.Gateway{ + gateway2 := &v1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", Name: "gateway-2", @@ -36,27 +36,25 @@ func TestProcessedGatewaysGetAllNsNames(t *testing.T) { } tests := []struct { - gws processedGateways + gws map[types.NamespacedName]*v1.Gateway name string expected []types.NamespacedName }{ { - gws: processedGateways{}, - expected: nil, + gws: nil, + expected: []types.NamespacedName{}, name: "no gateways", }, { - gws: processedGateways{ - Winner: winner, - Ignored: map[types.NamespacedName]*v1.Gateway{ - client.ObjectKeyFromObject(loser): loser, - }, + gws: map[types.NamespacedName]*v1.Gateway{ + client.ObjectKeyFromObject(gateway1): gateway1, + client.ObjectKeyFromObject(gateway2): gateway2, }, expected: []types.NamespacedName{ - client.ObjectKeyFromObject(winner), - client.ObjectKeyFromObject(loser), + client.ObjectKeyFromObject(gateway1), + client.ObjectKeyFromObject(gateway2), }, - name: "winner and ignored", + name: "all gateways", }, } @@ -64,8 +62,8 @@ func TestProcessedGatewaysGetAllNsNames(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() g := NewWithT(t) - result := test.gws.GetAllNsNames() - g.Expect(result).To(Equal(test.expected)) + result := GetAllNsNames(test.gws) + g.Expect(result).To(ConsistOf(test.expected)) }) } } @@ -74,7 +72,7 @@ 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 +81,7 @@ func TestProcessGateways(t *testing.T) { GatewayClassName: gcName, }, } - loser := &v1.Gateway{ + gw2 := &v1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test", Name: "gateway-2", @@ -95,12 +93,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 +107,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", }, @@ -273,7 +268,7 @@ func TestBuildGateway(t *testing.T) { foo8081Listener := createHTTPListener("foo-8081", "foo.example.com", 8081) foo443HTTPListener := createHTTPListener("foo-443-http", "foo.example.com", 443) - // foo https listeners + // // foo https listeners foo80HTTPSListener := createHTTPSListener("foo-80-https", "foo.example.com", 80, gatewayTLSConfigSameNs) foo443HTTPSListener1 := createHTTPSListener("foo-443-https-1", "foo.example.com", 443, gatewayTLSConfigSameNs) foo8443HTTPSListener := createHTTPSListener("foo-8443-https", "foo.example.com", 8443, gatewayTLSConfigSameNs) @@ -338,15 +333,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 +359,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 } @@ -434,99 +438,105 @@ 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, - }, - { - Name: "foo-8080", - Source: foo8080Listener, - 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", + 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, + }, }, + 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, - }, - { - 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, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway-https"}: { + 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, + }, + { + 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, + }, }, + 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", + 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)}, + }, }, }, + 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,104 +562,120 @@ 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", + Source: crossNamespaceSecretListener, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secretDiffNamespace)), + SupportedKinds: supportedKindsForListeners, + }, }, + 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", + 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), + Valid: true, + NginxProxy: &NginxProxy{ + Source: validGwNp, + Valid: true, + }, + EffectiveNginxProxy: &EffectiveNginxProxy{ + Logging: &ngfAPIv1alpha2.NginxLogging{ + ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError), + }, }, + 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", + 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), + 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), + }, + 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}}), 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"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + Source: foo80Listener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + }, + 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", @@ -657,70 +683,76 @@ func TestBuildGateway(t *testing.T) { { gateway: createGateway(gatewayCfg{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"}: { + 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, + }, }, + Valid: true, }, - Valid: true, }, name: "invalid attachable https listener with cross-namespace secret; no reference grant", }, { gateway: createGateway(gatewayCfg{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"}: { + 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)}, + }, }, }, + Valid: true, }, - Valid: true, }, name: "attachable http listener with invalid label selector", }, { gateway: createGateway(gatewayCfg{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"}: { + 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{}, + }, }, + Valid: true, }, - Valid: true, }, name: "invalid listener protocol", }, @@ -735,47 +767,49 @@ 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, - }, - { - 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{}, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test"}: { + 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, + }, + { + 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{}, + }, }, + Valid: true, }, - Valid: true, }, name: "invalid ports", }, @@ -784,52 +818,56 @@ func TestBuildGateway(t *testing.T) { gatewayCfg{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, - }, - { - Name: "invalid-https-hostname", - Source: invalidHTTPSHostnameListener, - Valid: false, - Conditions: staticConds.NewListenerUnsupportedValue(invalidHostnameMsg), - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - SupportedKinds: supportedKindsForListeners, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test"}: { + 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, + }, + { + Name: "invalid-https-hostname", + Source: invalidHTTPSHostnameListener, + Valid: false, + Conditions: staticConds.NewListenerUnsupportedValue(invalidHostnameMsg), + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, }, + Valid: true, }, - Valid: true, }, name: "invalid hostnames", }, { gateway: createGateway(gatewayCfg{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"}: { + 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, + }, }, + Valid: true, }, - Valid: true, }, name: "invalid https listener (secret does not exist)", }, @@ -849,87 +887,89 @@ 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, - }, - { - 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, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test"}: { + 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, + }, + { + 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, + }, }, + Valid: true, }, - Valid: true, }, name: "multiple valid http/https listeners", }, @@ -947,74 +987,76 @@ 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, - }, - { - 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, + expected: map[types.NamespacedName]*Gateway{ + {Namespace: "test"}: { + 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, + }, + { + 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, + }, }, + Valid: true, }, - Valid: true, }, name: "port/protocol collisions", }, @@ -1026,12 +1068,14 @@ func TestBuildGateway(t *testing.T) { }, ), 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"}: { + Source: getLastCreatedGateway(), + Valid: false, + Conditions: staticConds.NewGatewayUnsupportedValue("spec." + + "addresses: Forbidden: addresses are not supported", + ), + }, }, name: "gateway addresses are not supported", }, @@ -1045,10 +1089,12 @@ func TestBuildGateway(t *testing.T) { gatewayCfg{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"}: { + Source: getLastCreatedGateway(), + Valid: false, + Conditions: staticConds.NewGatewayInvalid("GatewayClass is invalid"), + }, }, name: "invalid gatewayclass", }, @@ -1057,10 +1103,12 @@ func TestBuildGateway(t *testing.T) { gatewayCfg{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"}: { + Source: getLastCreatedGateway(), + Valid: false, + Conditions: staticConds.NewGatewayInvalid("GatewayClass doesn't exist"), + }, }, name: "nil gatewayclass", }, @@ -1069,31 +1117,33 @@ func TestBuildGateway(t *testing.T) { gatewayCfg{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"}: { + 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)}, + }, + }, + { + 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-443-http", - Source: foo443HTTPListener, - Valid: false, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, - Conditions: staticConds.NewListenerProtocolConflict(conflict443PortMsg), - SupportedKinds: supportedKindsForListeners, }, }, }, @@ -1104,32 +1154,34 @@ func TestBuildGateway(t *testing.T) { gatewayCfg{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"}: { + 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)}, + }, + }, + { + 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, }, - }, - { - 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, }, }, }, @@ -1140,30 +1192,32 @@ func TestBuildGateway(t *testing.T) { gatewayCfg{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"}: { + 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)}, + }, + }, + { + 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, }, - }, - { - 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, }, }, }, @@ -1172,29 +1226,31 @@ func TestBuildGateway(t *testing.T) { { gateway: createGateway(gatewayCfg{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"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + Source: foo80Listener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + }, + 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", @@ -1202,25 +1258,27 @@ func TestBuildGateway(t *testing.T) { { gateway: createGateway(gatewayCfg{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"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + Source: foo80Listener1, + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + SupportedKinds: supportedKindsForListeners, + }, + }, + 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", @@ -1228,30 +1286,32 @@ func TestBuildGateway(t *testing.T) { { gateway: createGateway(gatewayCfg{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"}: { + Source: getLastCreatedGateway(), + Listeners: []*Listener{ + { + Name: "foo-80-1", + 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 + 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", @@ -1261,21 +1321,23 @@ func TestBuildGateway(t *testing.T) { gatewayCfg{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"}: { + Source: getLastCreatedGateway(), + Valid: false, + NginxProxy: &NginxProxy{ + Source: invalidGwNp, + ErrMsgs: field.ErrorList{ + field.Required(field.NewPath("somePath"), "someField"), // fake error + }, + Valid: false, }, - 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", }, diff --git a/internal/mode/static/state/graph/graph.go b/internal/mode/static/state/graph/graph.go index 88d6b4abe8..3749369674 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 + // Gateway 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. @@ -78,17 +72,12 @@ type Graph struct { 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 +114,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: @@ -190,11 +179,11 @@ func (g *Graph) gatewayAPIResourceExist(ref v1alpha2.LocalPolicyTargetReference, switch kind := ref.Kind; kind { case kinds.Gateway: - if g.Gateway == nil { + if g.Gateways == nil { 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 +212,7 @@ func BuildGraph( state.NginxProxies, validators.GenericValidator, processedGwClasses.Winner, - processedGws.Winner, + processedGws, ) gc := buildGatewayClass( @@ -237,65 +226,59 @@ func BuildGraph( refGrantResolver := newReferenceGrantResolver(state.ReferenceGrants) - gw := buildGateway( - processedGws.Winner, + gws := buildGateway( + processedGws, secretResolver, gc, refGrantResolver, processedNginxProxies, ) + addDeploymentNameToGateway(gws) + processedBackendTLSPolicies := processBackendTLSPolicies( state.BackendTLSPolicies, configMapResolver, secretResolver, controllerName, - gw, + gws, ) processedSnippetsFilters := processSnippetsFilters(state.SnippetsFilters) - var effectiveNginxProxy *EffectiveNginxProxy - if gw != nil { - effectiveNginxProxy = gw.EffectiveNginxProxy - } + nsNamesForProcessedGateways := GetAllNsNames(processedGws) routes := buildRoutesForGateways( validators.HTTPFieldsValidator, state.HTTPRoutes, state.GRPCRoutes, - processedGws.GetAllNsNames(), - effectiveNginxProxy, + nsNamesForProcessedGateways, + gws, processedSnippetsFilters, ) l4routes := buildL4RoutesForGateways( state.TLSRoutes, - processedGws.GetAllNsNames(), + nsNamesForProcessedGateways, state.Services, - effectiveNginxProxy, + gws, refGrantResolver, ) - bindRoutesToListeners(routes, l4routes, gw, state.Namespaces) + bindRoutesToListeners(routes, l4routes, gws, state.Namespaces) addBackendRefsToRouteRules( routes, refGrantResolver, state.Services, processedBackendTLSPolicies, - effectiveNginxProxy, + gws, ) - 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, @@ -303,27 +286,17 @@ func BuildGraph( 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,7 +304,6 @@ func BuildGraph( ReferencedNginxProxies: processedNginxProxies, BackendTLSPolicies: processedBackendTLSPolicies, NGFPolicies: processedPolicies, - GlobalSettings: globalSettings, SnippetsFilters: processedSnippetsFilters, PlusSecrets: plusSecrets, } @@ -341,21 +313,12 @@ func BuildGraph( return g } -func gatewayExists( - gwNsName types.NamespacedName, - winner *gatewayv1.Gateway, - ignored map[types.NamespacedName]*gatewayv1.Gateway, -) bool { - if winner == nil { +func gatewayExists[T any](gwNsName types.NamespacedName, gateways map[types.NamespacedName]*T) bool { + if gateways == nil { 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..7e6dd18ed0 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -53,6 +53,9 @@ func TestBuildGraph(t *testing.T) { staticConds.NewPolicyAccepted(), staticConds.NewPolicyAccepted(), staticConds.NewPolicyAccepted(), + staticConds.NewPolicyAccepted(), + staticConds.NewPolicyAccepted(), + staticConds.NewPolicyAccepted(), } btp := BackendTLSPolicy{ @@ -85,7 +88,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"}, } @@ -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", @@ -865,82 +865,157 @@ 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, + 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, + }, + 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, + }, + { + 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)}, + }, + }, + { + 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)}, + }, }, - 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, + Listeners: []*Listener{ + { + Name: "listener-80-1", + Source: gw2.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", + Source: gw2.Spec.Listeners[1], + Valid: true, + Attachable: true, + Routes: map[RouteKey]*L7Route{}, + L4Routes: map[L4RouteKey]*L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(secret)), + SupportedKinds: supportedKindsForListeners, }, - ServiceName: helpers.GetPointer("my-svc"), - SpanAttributes: []ngfAPIv1alpha1.SpanAttribute{ - {Key: "key", Value: "value"}, + { + Name: "listener-443-2", + Source: gw2.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", + Source: gw2.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)}, + }, + }, + }, + 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 +1039,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 +1066,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 +1088,6 @@ func TestBuildGraph(t *testing.T) { }, }, }, - DeploymentName: types.NamespacedName{ - Namespace: "test", - Name: "gateway-1-my-class", - }, } } @@ -1165,15 +1240,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 +1294,7 @@ func TestIsReferenced(t *testing.T) { } graph := &Graph{ - Gateway: gw, + Gateways: gw, ReferencedSecrets: map[types.NamespacedName]*Secret{ client.ObjectKeyFromObject(baseSecret): { Source: baseSecret, @@ -1413,17 +1490,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 +1558,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 +1596,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 +1606,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")), diff --git a/internal/mode/static/state/graph/grpcroute_test.go b/internal/mode/static/state/graph/grpcroute_test.go index a5b0c5a605..7cf9e5aa40 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{ @@ -194,10 +209,6 @@ func TestBuildGRPCRoutes(t *testing.T) { validator := &validationfakes.FakeHTTPFieldsValidator{} - npCfg := &EffectiveNginxProxy{ - DisableHTTP2: helpers.GetPointer(false), - } - for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() @@ -218,7 +229,7 @@ func TestBuildGRPCRoutes(t *testing.T) { map[types.NamespacedName]*v1.HTTPRoute{}, grRoutes, test.gwNsNames, - npCfg, + gateways, snippetsFilters, ) g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) diff --git a/internal/mode/static/state/graph/httproute_test.go b/internal/mode/static/state/graph/httproute_test.go index d08f00b9ed..89659b3cc9 100644 --- a/internal/mode/static/state/graph/httproute_test.go +++ b/internal/mode/static/state/graph/httproute_test.go @@ -131,6 +131,18 @@ func TestBuildHTTPRoutes(t *testing.T) { }, } + gateways := map[types.NamespacedName]*Gateway{ + {}: { + Source: &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway", + }, + }, + Valid: true, + }, + } + tests := []struct { expected map[RouteKey]*L7Route name string @@ -220,7 +232,7 @@ func TestBuildHTTPRoutes(t *testing.T) { hrRoutes, map[types.NamespacedName]*gatewayv1.GRPCRoute{}, test.gwNsNames, - nil, + gateways, snippetsFilters, ) g.Expect(helpers.Diff(test.expected, routes)).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..df33a6f23e --- /dev/null +++ b/internal/mode/static/state/graph/multiple_gateways_test.go @@ -0,0 +1,4 @@ +package graph + +// func Test_Gateways(t *testing.T) { +// } diff --git a/internal/mode/static/state/graph/namespace.go b/internal/mode/static/state/graph/namespace.go index 481e4d749b..27cda2acc9 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 gws == nil || ns == nil { 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..baa9bebfd2 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,16 @@ func processNginxProxies( } } - if gwReferencesAnyNginxProxy(winningGateway) { - refNp := types.NamespacedName{ - Name: winningGateway.Spec.Infrastructure.ParametersRef.Name, - Namespace: winningGateway.Namespace, - } + 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) + if np, ok := nps[refNp]; ok { + referencedNginxProxies[refNp] = buildNginxProxy(np, validator) + } } } diff --git a/internal/mode/static/state/graph/nginxproxy_test.go b/internal/mode/static/state/graph/nginxproxy_test.go index bf074ab562..7b5eba3902 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,7 +561,7 @@ func TestProcessNginxProxies(t *testing.T) { name: "no nginx proxies", nps: nil, gc: gatewayClass, - gw: gateway, + gws: gateway, validator: createValidValidator(), expResult: nil, }, @@ -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..74bf9ffffd 100644 --- a/internal/mode/static/state/graph/policies.go +++ b/internal/mode/static/state/graph/policies.go @@ -68,7 +68,7 @@ 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 { + if len(g.Gateways) == 0 { return } @@ -76,7 +76,7 @@ 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 { @@ -90,7 +90,7 @@ func (g *Graph) attachPolicies(ctlrName string) { continue } - attachPolicyToService(policy, svc, g.Gateway, ctlrName) + attachPolicyToService(policy, svc, g.Gateways, ctlrName) } } } @@ -99,32 +99,38 @@ 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 - } + for gwNsNames, gw := range gws { + if ngfPolicyAncestorsFull(policy, ctlrName) { + return + } - ancestor := PolicyAncestor{ - Ancestor: createParentReference(v1.GroupName, kinds.Gateway, client.ObjectKeyFromObject(gw.Source)), - } + if _, belongsToGw := svc.GatewayNsNames[gwNsNames]; !belongsToGw { + continue + } - if !gw.Valid { - ancestor.Conditions = []conditions.Condition{staticConds.NewPolicyTargetNotFound("Parent Gateway is invalid")} - if ancestorsContainsAncestorRef(policy.Ancestors, ancestor.Ancestor) { + ancestor := PolicyAncestor{ + Ancestor: createParentReference(v1.GroupName, kinds.Gateway, client.ObjectKeyFromObject(gw.Source)), + } + + if !gw.Valid { + ancestor.Conditions = []conditions.Condition{staticConds.NewPolicyTargetNotFound("Parent Gateway is invalid")} + if ancestorsContainsAncestorRef(policy.Ancestors, ancestor.Ancestor) { + return + } + + policy.Ancestors = append(policy.Ancestors, ancestor) return } - policy.Ancestors = append(policy.Ancestors, ancestor) - return - } + if !ancestorsContainsAncestorRef(policy.Ancestors, ancestor.Ancestor) { + policy.Ancestors = append(policy.Ancestors, ancestor) + } - if !ancestorsContainsAncestorRef(policy.Ancestors, ancestor.Ancestor) { - policy.Ancestors = append(policy.Ancestors, ancestor) + svc.Policies = append(svc.Policies, policy) } - - svc.Policies = append(svc.Policies, policy) } func attachPolicyToRoute(policy *Policy, route *L7Route, ctlrName string) { @@ -157,13 +163,12 @@ func attachPolicyToRoute(policy *Policy, route *L7Route, ctlrName string) { 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] + gw, exists := gateways[ref.Nsname] - if !ignored && ref.Nsname != client.ObjectKeyFromObject(gw.Source) { + if !exists && gw != nil && gw.Source == nil { return } @@ -176,7 +181,7 @@ func attachPolicyToGateway( return } - if ignored { + if !exists { ancestor.Conditions = []conditions.Condition{staticConds.NewPolicyTargetNotFound("TargetRef is ignored")} policy.Ancestors = append(policy.Ancestors, ancestor) return @@ -195,72 +200,82 @@ func attachPolicyToGateway( func processPolicies( pols map[PolicyKey]policies.Policy, validator validation.PolicyValidator, - gateways processedGateways, + processedGateways map[types.NamespacedName]*v1.Gateway, 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(processedGateways) == 0 { return nil } processedPolicies := make(map[PolicyKey]*Policy) - for key, policy := range pols { - var conds []conditions.Condition + for _, gw := range gws { + for key, policy := range pols { + var conds []conditions.Condition - targetRefs := make([]PolicyTargetRef, 0, len(policy.GetTargetRefs())) - targetedRoutes := make(map[types.NamespacedName]*L7Route) + 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), + } + } - for _, ref := range policy.GetTargetRefs() { - refNsName := types.NamespacedName{Name: string(ref.Name), Namespace: policy.GetNamespace()} + targetRefs := make([]PolicyTargetRef, 0, len(policy.GetTargetRefs())) + targetedRoutes := make(map[types.NamespacedName]*L7Route) - switch refGroupKind(ref.Group, ref.Kind) { - case gatewayGroupKind: - if !gatewayExists(refNsName, gateways.Winner, gateways.Ignored) { - continue - } - case hrGroupKind, grpcGroupKind: - if route, exists := routes[routeKeyForKind(ref.Kind, refNsName)]; exists { - targetedRoutes[client.ObjectKeyFromObject(route.Source)] = route - } else { - continue - } - case serviceGroupKind: - if _, exists := services[refNsName]; !exists { + for _, ref := range policy.GetTargetRefs() { + refNsName := types.NamespacedName{Name: string(ref.Name), Namespace: policy.GetNamespace()} + + switch refGroupKind(ref.Group, ref.Kind) { + case gatewayGroupKind: + if !gatewayExists(refNsName, processedGateways) { + continue + } + case hrGroupKind, grpcGroupKind: + if route, exists := routes[routeKeyForKind(ref.Kind, refNsName)]; exists { + targetedRoutes[client.ObjectKeyFromObject(route.Source)] = route + } else { + continue + } + case serviceGroupKind: + if _, exists := services[refNsName]; !exists { + continue + } + default: continue } - default: - continue - } - targetRefs = append(targetRefs, - PolicyTargetRef{ - Kind: ref.Kind, - Group: ref.Group, - Nsname: refNsName, - }) - } + targetRefs = append(targetRefs, + PolicyTargetRef{ + Kind: ref.Kind, + Group: ref.Group, + Nsname: refNsName, + }) + } - if len(targetRefs) == 0 { - continue - } + if len(targetRefs) == 0 { + continue + } - overlapConds := checkTargetRoutesForOverlap(targetedRoutes, routes) - conds = append(conds, overlapConds...) + overlapConds := checkTargetRoutesForOverlap(targetedRoutes, routes) + conds = append(conds, overlapConds...) - conds = append(conds, validator.Validate(policy, globalSettings)...) + conds = append(conds, validator.Validate(policy, globalSettings)...) - processedPolicies[key] = &Policy{ - Source: policy, - Valid: len(conds) == 0, - Conditions: conds, - TargetRefs: targetRefs, - Ancestors: make([]PolicyAncestor, 0, len(targetRefs)), + processedPolicies[key] = &Policy{ + Source: policy, + Valid: len(conds) == 0, + Conditions: conds, + TargetRefs: targetRefs, + Ancestors: make([]PolicyAncestor, 0, len(targetRefs)), + } } - } - markConflictedPolicies(processedPolicies, validator) + markConflictedPolicies(processedPolicies, validator) + } return processedPolicies } diff --git a/internal/mode/static/state/graph/policies_test.go b/internal/mode/static/state/graph/policies_test.go index a7a8b71dc2..479fe1e575 100644 --- a/internal/mode/static/state/graph/policies_test.go +++ b/internal/mode/static/state/graph/policies_test.go @@ -75,8 +75,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 +95,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)) + } } } @@ -106,7 +110,7 @@ func TestAttachPolicies(t *testing.T) { expectSvcPolicyAttachment := func(g *WithT, graph *Graph) { for _, r := range graph.ReferencedServices { - g.Expect(r.Policies).To(HaveLen(1)) + g.Expect(r.Policies).To(HaveLen(2)) } } @@ -144,26 +148,43 @@ func TestAttachPolicies(t *testing.T) { ) } - getGateway := func() *Gateway { - return &Gateway{ - Source: &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Namespace: testNs, + getGateway := 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 @@ -214,7 +235,7 @@ 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, @@ -382,23 +403,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 @@ -414,7 +438,7 @@ func TestAttachPolicyToGateway(t *testing.T) { }, }, }, - gw: newGateway(true, gatewayNsName), + gws: newGatewayMap(true, []types.NamespacedName{gatewayNsName}), expAncestors: []PolicyAncestor{ {Ancestor: getGatewayParentRef(gatewayNsName)}, }, @@ -434,7 +458,7 @@ func TestAttachPolicyToGateway(t *testing.T) { {Ancestor: getGatewayParentRef(gatewayNsName)}, }, }, - gw: newGateway(true, gatewayNsName), + gws: newGatewayMap(true, []types.NamespacedName{gatewayNsName}), expAncestors: []PolicyAncestor{ {Ancestor: getGatewayParentRef(gatewayNsName)}, {Ancestor: getGatewayParentRef(gatewayNsName)}, @@ -442,20 +466,20 @@ func TestAttachPolicyToGateway(t *testing.T) { expAttached: true, }, { - name: "not attached; gateway ignored", + name: "not attached; gateway is invalid", policy: &Policy{ Source: &policiesfakes.FakePolicy{}, TargetRefs: []PolicyTargetRef{ { - Nsname: ignoredGatewayNsName, + Nsname: gateway2NsName, Kind: "Gateway", }, }, }, - gw: newGateway(true, gatewayNsName), + gws: newGatewayMap(true, []types.NamespacedName{gatewayNsName}), expAncestors: []PolicyAncestor{ { - Ancestor: getGatewayParentRef(ignoredGatewayNsName), + Ancestor: getGatewayParentRef(gateway2NsName), Conditions: []conditions.Condition{staticConds.NewPolicyTargetNotFound("TargetRef is ignored")}, }, }, @@ -472,7 +496,7 @@ func TestAttachPolicyToGateway(t *testing.T) { }, }, }, - gw: newGateway(false, gatewayNsName), + gws: newGatewayMap(false, []types.NamespacedName{gatewayNsName}), expAncestors: []PolicyAncestor{ { Ancestor: getGatewayParentRef(gatewayNsName), @@ -492,9 +516,14 @@ func TestAttachPolicyToGateway(t *testing.T) { }, }, }, - gw: newGateway(true, gatewayNsName), - expAncestors: nil, - expAttached: false, + gws: newGatewayMap(true, []types.NamespacedName{gatewayNsName}), + expAncestors: []PolicyAncestor{ + { + Ancestor: getGatewayParentRef(gateway2NsName), + Conditions: []conditions.Condition{staticConds.NewPolicyTargetNotFound("TargetRef is ignored")}, + }, + }, + expAttached: false, }, { name: "not attached; max ancestors", @@ -507,27 +536,27 @@ func TestAttachPolicyToGateway(t *testing.T) { }, }, }, - 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 +570,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{}}, + svc: &ReferencedService{ + GatewayNsNames: map[types.NamespacedName]struct{}{ + gwNsname: {}, + }, + }, + gws: getGateway(true /*valid*/), expAttached: true, expAncestors: []PolicyAncestor{ { @@ -583,8 +618,12 @@ func TestAttachPolicyToService(t *testing.T) { }, }, }, - svc: &ReferencedService{}, - gw: getGateway(true /*valid*/), + svc: &ReferencedService{ + GatewayNsNames: map[types.NamespacedName]struct{}{ + gwNsname: {}, + }, + }, + gws: getGateway(true /*valid*/), expAttached: true, expAncestors: []PolicyAncestor{ { @@ -602,8 +641,13 @@ func TestAttachPolicyToService(t *testing.T) { }, }, }, - svc: &ReferencedService{}, - gw: getGateway(true /*valid*/), + svc: &ReferencedService{ + GatewayNsNames: map[types.NamespacedName]struct{}{ + gw2Nsname: {}, + gwNsname: {}, + }, + }, + gws: getGateway(true /*valid*/), expAttached: true, expAncestors: []PolicyAncestor{ { @@ -615,10 +659,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{}}, + svc: &ReferencedService{ + GatewayNsNames: map[types.NamespacedName]struct{}{ + gwNsname: {}, + }, + }, + gws: getGateway(false /*invalid*/), expAttached: false, expAncestors: []PolicyAncestor{ { @@ -628,10 +676,14 @@ 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)}, + svc: &ReferencedService{ + GatewayNsNames: map[types.NamespacedName]struct{}{ + gwNsname: {}, + }, + }, + gws: getGateway(true /*valid*/), expAttached: false, expAncestors: nil, }, @@ -642,7 +694,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 +715,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 +729,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) @@ -755,7 +807,7 @@ func TestProcessPolicies(t *testing.T) { Source: pol4, TargetRefs: []PolicyTargetRef{ { - Nsname: types.NamespacedName{Namespace: testNs, Name: "ignored"}, + Nsname: types.NamespacedName{Namespace: testNs, Name: "gw2"}, Kind: kinds.Gateway, Group: v1.GroupName, }, @@ -868,20 +920,40 @@ func TestProcessPolicies(t *testing.T) { }, } - gateways := processedGateways{ - Winner: &v1.Gateway{ + processedGateways := map[types.NamespacedName]*v1.Gateway{ + {Namespace: testNs, Name: "gw"}: { ObjectMeta: metav1.ObjectMeta{ Name: "gw", Namespace: testNs, }, }, - Ignored: map[types.NamespacedName]*v1.Gateway{ - {Namespace: testNs, Name: "ignored"}: { + + {Namespace: testNs, Name: "gw2"}: { + ObjectMeta: metav1.ObjectMeta{ + Name: "gw2", + Namespace: testNs, + }, + }, + } + + gateways := map[types.NamespacedName]*Gateway{ + {Namespace: testNs, Name: "gw"}: { + Source: &v1.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gw", Namespace: testNs, }, }, + Valid: true, + }, + {Namespace: testNs, Name: "gw2"}: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw2", + Namespace: testNs, + }, + }, + Valid: true, }, } @@ -913,7 +985,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, processedGateways, routes, services, gateways) g.Expect(processed).To(BeEquivalentTo(test.expProcessedPolicies)) }) } @@ -1039,8 +1111,8 @@ func TestProcessPolicies_RouteOverlap(t *testing.T) { }, } - gateways := processedGateways{ - Winner: &v1.Gateway{ + processedGateways := map[types.NamespacedName]*v1.Gateway{ + {Namespace: testNs, Name: "gw"}: { ObjectMeta: metav1.ObjectMeta{ Name: "gw", Namespace: testNs, @@ -1048,12 +1120,24 @@ func TestProcessPolicies_RouteOverlap(t *testing.T) { }, } + gateways := map[types.NamespacedName]*Gateway{ + {Namespace: testNs, Name: "gw"}: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + Namespace: testNs, + }, + }, + Valid: true, + }, + } + for _, test := range tests { t.Run(test.name, func(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, processedGateways, 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..2c037db7f9 100644 --- a/internal/mode/static/state/graph/route_common.go +++ b/internal/mode/static/state/graph/route_common.go @@ -187,26 +187,35 @@ 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(gatewayNsNames) == 0 || len(gws) == 0 { return nil } routes := make(map[L4RouteKey]*L4Route) - for _, route := range tlsRoutes { - r := buildTLSRoute( - route, - gatewayNsNames, - services, - npCfg, - resolver.refAllowedFrom(fromTLSRoute(route.Namespace)), - ) - if r != nil { - routes[CreateRouteKeyL4(route)] = r + + for _, gw := range gws { + if gw == nil { + continue + } + + npCfg := gw.EffectiveNginxProxy + for _, route := range tlsRoutes { + r := buildTLSRoute( + route, + gatewayNsNames, + services, + npCfg, + resolver.refAllowedFrom(fromTLSRoute(route.Namespace)), + ) + if r != nil { + routes[CreateRouteKeyL4(route)] = r + } } } + return routes } @@ -215,29 +224,34 @@ func buildRoutesForGateways( validator validation.HTTPFieldsValidator, httpRoutes map[types.NamespacedName]*v1.HTTPRoute, grpcRoutes map[types.NamespacedName]*v1.GRPCRoute, - gatewayNsNames []types.NamespacedName, - effectiveNginxProxy *EffectiveNginxProxy, + processedGwsNsNames []types.NamespacedName, + gateways map[types.NamespacedName]*Gateway, snippetsFilters map[types.NamespacedName]*SnippetsFilter, ) map[RouteKey]*L7Route { - if len(gatewayNsNames) == 0 { + if len(processedGwsNsNames) == 0 || len(gateways) == 0 { return nil } routes := make(map[RouteKey]*L7Route) + for _, gw := range gateways { + if gw == nil { + continue + } - http2disabled := isHTTP2Disabled(effectiveNginxProxy) + http2disabled := isHTTP2Disabled(gw.EffectiveNginxProxy) - for _, route := range httpRoutes { - r := buildHTTPRoute(validator, route, gatewayNsNames, snippetsFilters) - if r != nil { - routes[CreateRouteKey(route)] = r + for _, route := range httpRoutes { + r := buildHTTPRoute(validator, route, processedGwsNsNames, snippetsFilters) + if r != nil { + routes[CreateRouteKey(route)] = r + } } - } - for _, route := range grpcRoutes { - r := buildGRPCRoute(validator, route, gatewayNsNames, http2disabled, snippetsFilters) - if r != nil { - routes[CreateRouteKey(route)] = r + for _, route := range grpcRoutes { + r := buildGRPCRoute(validator, route, processedGwsNsNames, http2disabled, snippetsFilters) + if r != nil { + routes[CreateRouteKey(route)] = r + } } } @@ -331,43 +345,45 @@ func findGatewayForParentRef( 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 { @@ -375,14 +391,16 @@ type hostPort struct { 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)) for _, l := range listeners { - listenerHostPortMap[l.Name] = hostPort{ + listenerKey := fmt.Sprintf("%s-%s-%s", gw.Source.Namespace, gw.Source.Name, l.Name) + listenerHostPortMap[listenerKey] = hostPort{ hostname: getHostname(l.Source.Hostname), port: l.Source.Port, } } + return listenerHostPortMap } @@ -412,6 +430,10 @@ 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 { @@ -425,7 +447,7 @@ func isolateHostnamesForParentRefs(parentRef []ParentRef, listenerHostnameMap ma continue } - // for L7Routes, we compare the hostname, port and listener name combination + // for L7Routes, we compare the hostname, port and gatewayNamespace-gatewayName-listenerName combination // to identify if hostname needs to be isolated. if h == lHostPort.hostname && listenerName != lName { // for L4Routes, we only compare the hostname and listener name combination @@ -487,16 +509,7 @@ func validateParentRef( 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() - 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() @@ -524,7 +537,14 @@ func bindL4RouteToListeners( continue } - // Winning Gateway + gwNsName := types.NamespacedName{ + Name: gw.Source.Name, + Namespace: gw.Source.Namespace, + } + if ref.Gateway != gwNsName { + continue + } + // Try to attach Route to all matching listeners cond, attached := tryToAttachL4RouteToListeners( @@ -671,13 +691,20 @@ 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 != gwNsName { + continue + } + attachment, attachableListeners := validateParentRef(ref, gw) if attachment.FailedCondition != (conditions.Condition{}) { continue } - // Winning Gateway // Try to attach Route to all matching listeners cond, attached := tryToAttachL7RouteToListeners( diff --git a/internal/mode/static/state/graph/route_common_test.go b/internal/mode/static/state/graph/route_common_test.go index 8268af036e..179ad3860b 100644 --- a/internal/mode/static/state/graph/route_common_test.go +++ b/internal/mode/static/state/graph/route_common_test.go @@ -408,20 +408,6 @@ func TestBindRouteToListeners(t *testing.T) { }, }, } - 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, @@ -740,32 +726,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{ @@ -1620,21 +1580,6 @@ func TestBindL4RouteToListeners(t *testing.T) { }, 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, - }, - }, - Attachable: true, - } tests := []struct { route *L4Route @@ -1649,6 +1594,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-443"), }, @@ -1680,6 +1629,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-443"), }, @@ -1701,6 +1654,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-444"), }, @@ -1723,6 +1680,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-443"), }, @@ -1750,42 +1711,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"), }, @@ -1817,6 +1751,10 @@ 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.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ @@ -1859,6 +1797,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 @@ -1908,7 +1850,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")) @@ -1940,6 +1886,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-443"), }, @@ -1972,6 +1922,10 @@ func TestBindL4RouteToListeners(t *testing.T) { gateway: &Gateway{ Source: gw, Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Listeners: []*Listener{ createListener("listener-443"), }, @@ -2001,7 +1955,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{}, }, @@ -2022,7 +1980,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"), }, @@ -2054,6 +2016,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 diff --git a/internal/mode/static/state/graph/service.go b/internal/mode/static/state/graph/service.go index ad6fb817ef..aceae55bf8 100644 --- a/internal/mode/static/state/graph/service.go +++ b/internal/mode/static/state/graph/service.go @@ -5,83 +5,58 @@ 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 { - // Policies is a list of NGF Policies that target this Service. - Policies []*Policy + GatewayNsNames map[types.NamespacedName]struct{} + Policies []*Policy } 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 gwNsNames, 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 == 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 + // Processes both valid and invalid BackendRefs as invalid ones still have referenced services + // we may want to track. + addServicesAndGatewayForL7Routes(route.Spec.Rules, gwNsNames, referencedServices) } - if !belongsToWinningGw(route.ParentRefs) { - continue - } - - addServicesForL7Routes(route.Spec.Rules) - } + for _, route := range l4Routes { + if !route.Valid || !belongsToGw(route.ParentRefs) { + continue + } - for _, route := range l4Routes { - if !route.Valid { - continue + addServicesAndGatewayForL4Routes(route, gwNsNames, referencedServices) } - if !belongsToWinningGw(route.ParentRefs) { + if len(referencedServices) == 0 { continue } - - addServicesForL4Routes(route) } if len(referencedServices) == 0 { @@ -90,3 +65,41 @@ func buildReferencedServices( return referencedServices } + +func addServicesAndGatewayForL4Routes( + route *L4Route, + gwNsNames 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[gwNsNames] = struct{}{} + } +} + +func addServicesAndGatewayForL7Routes( + routeRules []RouteRule, + gwNsNames 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[gwNsNames] = struct{}{} + } + } + } +} diff --git a/internal/mode/static/state/graph/service_test.go b/internal/mode/static/state/graph/service_test.go index 0fa316e73f..b8db40e66c 100644 --- a/internal/mode/static/state/graph/service_test.go +++ b/internal/mode/static/state/graph/service_test.go @@ -13,24 +13,48 @@ 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, + gw2NsNames := types.NamespacedName{Namespace: "test", Name: "gw2Nsname"} + gw3NsNames := types.NamespacedName{Namespace: "test", Name: "gw3Nsname"} + gw := map[types.NamespacedName]*Gateway{ + gwNsname: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: gwNsname.Namespace, + Name: gwNsname.Name, + }, + }, + }, + gw2NsNames: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: gw2NsNames.Namespace, + Name: gw2NsNames.Name, + }, + }, + }, + gw3NsNames: { + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: gw3NsNames.Namespace, + Name: gw3NsNames.Name, + }, }, }, } - ignoredGw := types.NamespacedName{Namespace: "test", Name: "ignoredGw"} + + parentRefs := []ParentRef{ + { + Gateway: gwNsname, + }, + { + Gateway: gw2NsNames, + }, + } 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,7 @@ func TestBuildReferencedServices(t *testing.T) { }, { name: "nil gateway", - gw: nil, + gws: nil, l7Routes: map[RouteKey]*L7Route{ {NamespacedName: types.NamespacedName{Name: "no-service-nsname"}}: validRouteNoServiceNsName, }, @@ -337,7 +335,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/status/prepare_requests.go b/internal/mode/static/status/prepare_requests.go index f3bd39c2a3..a895cd3fc6 100644 --- a/internal/mode/static/status/prepare_requests.go +++ b/internal/mode/static/status/prepare_requests.go @@ -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 _, gwNsNames := range pol.Gateways { + policyAncestorStatus := v1alpha2.PolicyAncestorStatus{ + AncestorRef: v1.ParentReference{ + Namespace: helpers.GetPointer(v1.Namespace(gwNsNames.Namespace)), + Name: v1.ObjectName(gwNsNames.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..2368c63764 100644 --- a/internal/mode/static/status/prepare_requests_test.go +++ b/internal/mode/static/status/prepare_requests_test.go @@ -741,77 +741,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 +1202,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, @@ -1295,217 +1226,217 @@ func TestBuildGatewayStatuses(t *testing.T) { } } -func TestBuildBackendTLSPolicyStatuses(t *testing.T) { - t.Parallel() - const gatewayCtlrName = "controller" - - transitionTime := helpers.PrepareTimeForFakeClient(metav1.Now()) - - type policyCfg struct { - Name string - Conditions []conditions.Condition - Valid bool - Ignored bool - IsReferenced bool - } - - getBackendTLSPolicy := func(policyCfg policyCfg) *graph.BackendTLSPolicy { - return &graph.BackendTLSPolicy{ - Source: &v1alpha3.BackendTLSPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: policyCfg.Name, - Generation: 1, - }, - }, - Valid: policyCfg.Valid, - Ignored: policyCfg.Ignored, - IsReferenced: policyCfg.IsReferenced, - Conditions: policyCfg.Conditions, - Gateway: types.NamespacedName{Name: "gateway", Namespace: "test"}, - } - } - - attachedConds := []conditions.Condition{staticConds.NewPolicyAccepted()} - invalidConds := []conditions.Condition{staticConds.NewPolicyInvalid("invalid backendTLSPolicy")} - - validPolicyCfg := policyCfg{ - Name: "valid-bt", - Valid: true, - IsReferenced: true, - Conditions: attachedConds, - } - - invalidPolicyCfg := policyCfg{ - Name: "invalid-bt", - IsReferenced: true, - Conditions: invalidConds, - } - - ignoredPolicyCfg := policyCfg{ - Name: "ignored-bt", - Ignored: true, - IsReferenced: true, - } - - notReferencedPolicyCfg := policyCfg{ - Name: "not-referenced", - Valid: true, - } - - tests := []struct { - backendTLSPolicies map[types.NamespacedName]*graph.BackendTLSPolicy - expected map[types.NamespacedName]v1alpha2.PolicyStatus - name string - expectedReqs int - }{ - { - name: "nil backendTLSPolicies", - expectedReqs: 0, - expected: map[types.NamespacedName]v1alpha2.PolicyStatus{}, - }, - { - name: "valid backendTLSPolicy", - backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ - {Namespace: "test", Name: "valid-bt"}: getBackendTLSPolicy(validPolicyCfg), - }, - expectedReqs: 1, - expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ - {Name: "valid-bt", Namespace: "test"}: { - Ancestors: []v1alpha2.PolicyAncestorStatus{ - { - AncestorRef: v1.ParentReference{ - Namespace: helpers.GetPointer[v1.Namespace]("test"), - Name: "gateway", - 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", - }, - }, - }, - }, - }, - }, - }, - { - name: "invalid backendTLSPolicy", - backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ - {Namespace: "test", Name: "invalid-bt"}: getBackendTLSPolicy(invalidPolicyCfg), - }, - expectedReqs: 1, - expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ - {Name: "invalid-bt", Namespace: "test"}: { - Ancestors: []v1alpha2.PolicyAncestorStatus{ - { - AncestorRef: v1.ParentReference{ - Namespace: helpers.GetPointer[v1.Namespace]("test"), - Name: "gateway", - 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.ConditionFalse, - ObservedGeneration: 1, - LastTransitionTime: transitionTime, - Reason: string(v1alpha2.PolicyReasonInvalid), - Message: "invalid backendTLSPolicy", - }, - }, - }, - }, - }, - }, - }, - { - name: "ignored or not referenced backendTLSPolicies", - backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ - {Namespace: "test", Name: "ignored-bt"}: getBackendTLSPolicy(ignoredPolicyCfg), - {Namespace: "test", Name: "not-referenced"}: getBackendTLSPolicy(notReferencedPolicyCfg), - }, - expectedReqs: 0, - expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ - {Name: "ignored-bt", Namespace: "test"}: {}, - {Name: "not-referenced", Namespace: "test"}: {}, - }, - }, - { - name: "mix valid and ignored backendTLSPolicies", - backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ - {Namespace: "test", Name: "ignored-bt"}: getBackendTLSPolicy(ignoredPolicyCfg), - {Namespace: "test", Name: "valid-bt"}: getBackendTLSPolicy(validPolicyCfg), - }, - expectedReqs: 1, - expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ - {Name: "ignored-bt", Namespace: "test"}: {}, - {Name: "valid-bt", Namespace: "test"}: { - Ancestors: []v1alpha2.PolicyAncestorStatus{ - { - AncestorRef: v1.ParentReference{ - Namespace: helpers.GetPointer[v1.Namespace]("test"), - Name: "gateway", - 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", - }, - }, - }, - }, - }, - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - k8sClient := createK8sClientFor(&v1alpha3.BackendTLSPolicy{}) - - for _, pol := range test.backendTLSPolicies { - err := k8sClient.Create(context.Background(), pol.Source) - g.Expect(err).ToNot(HaveOccurred()) - } - - updater := statusFramework.NewUpdater(k8sClient, logr.Discard()) - - reqs := PrepareBackendTLSPolicyRequests(test.backendTLSPolicies, transitionTime, gatewayCtlrName) - - g.Expect(reqs).To(HaveLen(test.expectedReqs)) - - updater.Update(context.Background(), reqs...) - - for nsname, expected := range test.expected { - var pol v1alpha3.BackendTLSPolicy - - err := k8sClient.Get(context.Background(), nsname, &pol) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(helpers.Diff(expected, pol.Status)).To(BeEmpty()) - } - }) - } -} +// func TestBuildBackendTLSPolicyStatuses(t *testing.T) { +// t.Parallel() +// const gatewayCtlrName = "controller" + +// transitionTime := helpers.PrepareTimeForFakeClient(metav1.Now()) + +// type policyCfg struct { +// Name string +// Conditions []conditions.Condition +// Valid bool +// Ignored bool +// IsReferenced bool +// } + +// getBackendTLSPolicy := func(policyCfg policyCfg) *graph.BackendTLSPolicy { +// return &graph.BackendTLSPolicy{ +// Source: &v1alpha3.BackendTLSPolicy{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: "test", +// Name: policyCfg.Name, +// Generation: 1, +// }, +// }, +// Valid: policyCfg.Valid, +// Ignored: policyCfg.Ignored, +// IsReferenced: policyCfg.IsReferenced, +// Conditions: policyCfg.Conditions, +// Gateway: types.NamespacedName{Name: "gateway", Namespace: "test"}, +// } +// } + +// attachedConds := []conditions.Condition{staticConds.NewPolicyAccepted()} +// invalidConds := []conditions.Condition{staticConds.NewPolicyInvalid("invalid backendTLSPolicy")} + +// validPolicyCfg := policyCfg{ +// Name: "valid-bt", +// Valid: true, +// IsReferenced: true, +// Conditions: attachedConds, +// } + +// invalidPolicyCfg := policyCfg{ +// Name: "invalid-bt", +// IsReferenced: true, +// Conditions: invalidConds, +// } + +// ignoredPolicyCfg := policyCfg{ +// Name: "ignored-bt", +// Ignored: true, +// IsReferenced: true, +// } + +// notReferencedPolicyCfg := policyCfg{ +// Name: "not-referenced", +// Valid: true, +// } + +// tests := []struct { +// backendTLSPolicies map[types.NamespacedName]*graph.BackendTLSPolicy +// expected map[types.NamespacedName]v1alpha2.PolicyStatus +// name string +// expectedReqs int +// }{ +// { +// name: "nil backendTLSPolicies", +// expectedReqs: 0, +// expected: map[types.NamespacedName]v1alpha2.PolicyStatus{}, +// }, +// { +// name: "valid backendTLSPolicy", +// backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ +// {Namespace: "test", Name: "valid-bt"}: getBackendTLSPolicy(validPolicyCfg), +// }, +// expectedReqs: 1, +// expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ +// {Name: "valid-bt", Namespace: "test"}: { +// Ancestors: []v1alpha2.PolicyAncestorStatus{ +// { +// AncestorRef: v1.ParentReference{ +// Namespace: helpers.GetPointer[v1.Namespace]("test"), +// Name: "gateway", +// 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", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// { +// name: "invalid backendTLSPolicy", +// backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ +// {Namespace: "test", Name: "invalid-bt"}: getBackendTLSPolicy(invalidPolicyCfg), +// }, +// expectedReqs: 1, +// expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ +// {Name: "invalid-bt", Namespace: "test"}: { +// Ancestors: []v1alpha2.PolicyAncestorStatus{ +// { +// AncestorRef: v1.ParentReference{ +// Namespace: helpers.GetPointer[v1.Namespace]("test"), +// Name: "gateway", +// 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.ConditionFalse, +// ObservedGeneration: 1, +// LastTransitionTime: transitionTime, +// Reason: string(v1alpha2.PolicyReasonInvalid), +// Message: "invalid backendTLSPolicy", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// { +// name: "ignored or not referenced backendTLSPolicies", +// backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ +// {Namespace: "test", Name: "ignored-bt"}: getBackendTLSPolicy(ignoredPolicyCfg), +// {Namespace: "test", Name: "not-referenced"}: getBackendTLSPolicy(notReferencedPolicyCfg), +// }, +// expectedReqs: 0, +// expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ +// {Name: "ignored-bt", Namespace: "test"}: {}, +// {Name: "not-referenced", Namespace: "test"}: {}, +// }, +// }, +// { +// name: "mix valid and ignored backendTLSPolicies", +// backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ +// {Namespace: "test", Name: "ignored-bt"}: getBackendTLSPolicy(ignoredPolicyCfg), +// {Namespace: "test", Name: "valid-bt"}: getBackendTLSPolicy(validPolicyCfg), +// }, +// expectedReqs: 1, +// expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ +// {Name: "ignored-bt", Namespace: "test"}: {}, +// {Name: "valid-bt", Namespace: "test"}: { +// Ancestors: []v1alpha2.PolicyAncestorStatus{ +// { +// AncestorRef: v1.ParentReference{ +// Namespace: helpers.GetPointer[v1.Namespace]("test"), +// Name: "gateway", +// 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", +// }, +// }, +// }, +// }, +// }, +// }, +// }, +// } + +// for _, test := range tests { +// t.Run(test.name, func(t *testing.T) { +// t.Parallel() +// g := NewWithT(t) + +// k8sClient := createK8sClientFor(&v1alpha3.BackendTLSPolicy{}) + +// for _, pol := range test.backendTLSPolicies { +// err := k8sClient.Create(context.Background(), pol.Source) +// g.Expect(err).ToNot(HaveOccurred()) +// } + +// updater := statusFramework.NewUpdater(k8sClient, logr.Discard()) + +// reqs := PrepareBackendTLSPolicyRequests(test.backendTLSPolicies, transitionTime, gatewayCtlrName) + +// g.Expect(reqs).To(HaveLen(test.expectedReqs)) + +// updater.Update(context.Background(), reqs...) + +// for nsname, expected := range test.expected { +// var pol v1alpha3.BackendTLSPolicy + +// err := k8sClient.Get(context.Background(), nsname, &pol) +// g.Expect(err).ToNot(HaveOccurred()) +// g.Expect(helpers.Diff(expected, pol.Status)).To(BeEmpty()) +// } +// }) +// } +// } func TestBuildNginxGatewayStatus(t *testing.T) { t.Parallel() 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/suite/client_settings_test.go b/tests/suite/client_settings_test.go index 835f3a9896..a7d24ab13d 100644 --- a/tests/suite/client_settings_test.go +++ b/tests/suite/client_settings_test.go @@ -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/manifests/clientsettings/invalid-route-csp.yaml b/tests/suite/manifests/clientsettings/invalid-route-csp.yaml new file mode 100644 index 0000000000..2eb77c980e --- /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: /coffee + 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 \ No newline at end of file From b57371c9c02d1c98b1b5ef9f1a49036ca8f74b5c Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Fri, 28 Mar 2025 12:10:32 -0600 Subject: [PATCH 02/19] Fix failing tests; remove flag --- cmd/gateway/commands.go | 28 ++--- cmd/gateway/commands_test.go | 17 --- internal/mode/static/config/config.go | 2 - internal/mode/static/handler.go | 16 ++- internal/mode/static/handler_test.go | 66 +---------- internal/mode/static/manager.go | 29 +++-- .../static/state/change_processor_test.go | 2 +- .../state/graph/multiple_gateways_test.go | 111 ++++++++++++++++++ .../mode/static/state/graph/route_common.go | 20 ++-- 9 files changed, 157 insertions(+), 134 deletions(-) diff --git a/cmd/gateway/commands.go b/cmd/gateway/commands.go index 853ddd0f72..d73ae49a31 100644 --- a/cmd/gateway/commands.go +++ b/cmd/gateway/commands.go @@ -63,7 +63,6 @@ func createControllerCommand() *cobra.Command { configFlag = "config" serviceFlag = "service" agentTLSSecretFlag = "agent-tls-secret" - updateGCStatusFlag = "update-gatewayclass-status" metricsDisableFlag = "metrics-disable" metricsSecureFlag = "metrics-secure-serving" metricsPortFlag = "metrics-port" @@ -94,9 +93,8 @@ func createControllerCommand() *cobra.Command { validator: validateResourceName, } - updateGCStatus bool - gateway = namespacedNameValue{} - configName = stringValidatingValue{ + gateway = namespacedNameValue{} + configName = stringValidatingValue{ validator: validateResourceName, } serviceName = stringValidatingValue{ @@ -229,14 +227,13 @@ 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, + GatewayNsName: gwNsName, + GatewayPodConfig: podConfig, HealthConfig: config.HealthConfig{ Enabled: !disableHealth, Port: healthListenPort.value, @@ -326,13 +323,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..3235e767a7 100644 --- a/cmd/gateway/commands_test.go +++ b/cmd/gateway/commands_test.go @@ -141,7 +141,6 @@ func TestControllerCmdFlagValidation(t *testing.T) { "--config=nginx-gateway-config", "--service=nginx-gateway", "--agent-tls-secret=agent-tls", - "--update-gatewayclass-status=true", "--metrics-port=9114", "--metrics-disable", "--metrics-secure-serving", @@ -235,22 +234,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{ diff --git a/internal/mode/static/config/config.go b/internal/mode/static/config/config.go index 9248070e03..53ef2e1781 100644 --- a/internal/mode/static/config/config.go +++ b/internal/mode/static/config/config.go @@ -46,8 +46,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 06707007a3..e66eea318e 100644 --- a/internal/mode/static/handler.go +++ b/internal/mode/static/handler.go @@ -79,8 +79,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,6 +183,15 @@ func (h *eventHandlerImpl) sendNginxConfig( return } + if len(gr.Gateways) == 0 { + // still need to update GatewayClass status + obj := &status.QueueObject{ + UpdateType: status.UpdateAll, + } + h.cfg.statusQueue.Enqueue(obj) + return + } + for _, gw := range gr.Gateways { if gw == nil { // still need to update GatewayClass status @@ -361,10 +368,7 @@ 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) - } + gcReqs := status.PrepareGatewayClassRequests(gr.GatewayClass, gr.IgnoredGatewayClasses, transitionTime) routeReqs := status.PrepareRouteRequests( gr.L4Routes, gr.Routes, diff --git a/internal/mode/static/handler_test.go b/internal/mode/static/handler_test.go index 9e9e25c0c0..8ba179a1d3 100644 --- a/internal/mode/static/handler_test.go +++ b/internal/mode/static/handler_test.go @@ -140,8 +140,7 @@ var _ = Describe("eventHandler", func() { ServiceName: "nginx-gateway", Namespace: "nginx-gateway", }, - metricsCollector: collectors.NewControllerNoopCollector(), - updateGatewayClassStatus: true, + metricsCollector: collectors.NewControllerNoopCollector(), }) Expect(handler.cfg.graphBuiltHealthChecker.ready).To(BeFalse()) }) @@ -246,69 +245,6 @@ var _ = Describe("eventHandler", func() { }) }) - 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, - }, - Gateways: map[types.NamespacedName]*graph.Gateway{}, - } - - 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{ diff --git a/internal/mode/static/manager.go b/internal/mode/static/manager.go index 7b3d28f140..104d6e3196 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) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index e3c4fca5f4..21323d66a8 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -1478,7 +1478,7 @@ 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) grpcRoute := expGraph.Routes[grpcRouteKey1] diff --git a/internal/mode/static/state/graph/multiple_gateways_test.go b/internal/mode/static/state/graph/multiple_gateways_test.go index df33a6f23e..9f0b5c608b 100644 --- a/internal/mode/static/state/graph/multiple_gateways_test.go +++ b/internal/mode/static/state/graph/multiple_gateways_test.go @@ -1,4 +1,115 @@ package graph // func Test_Gateways(t *testing.T) { +// const gcName = "my-gateway-class" +// createListener := func( +// name string, +// hostname string, +// port int, +// protocol v1.ProtocolType, +// tls *v1.GatewayTLSConfig, +// ) v1.Listener { +// return v1.Listener{ +// Name: v1.SectionName(name), +// Hostname: (*v1.Hostname)(helpers.GetPointer(hostname)), +// Port: v1.PortNumber(port), //nolint:gosec // port number will not overflow int32 +// Protocol: protocol, +// TLS: tls, +// } +// } + +// createHTTPListener := func(name, hostname string, port int) v1.Listener { +// return createListener(name, hostname, port, v1.HTTPProtocolType, nil) +// } +// createTCPListener := func(name, hostname string, port int) v1.Listener { +// return createListener(name, hostname, port, v1.TCPProtocolType, nil) +// } +// createTLSListener := func(name, hostname string, port int) v1.Listener { +// return createListener( +// name, +// hostname, +// port, +// v1.TLSProtocolType, +// &v1.GatewayTLSConfig{Mode: helpers.GetPointer(v1.TLSModePassthrough)}, +// ) +// } + +// createHTTPSListener := func(name, hostname string, port int, tls *v1.GatewayTLSConfig) v1.Listener { +// return createListener(name, hostname, port, v1.HTTPSProtocolType, tls) +// } + +// var lastCreatedGateway *v1.Gateway +// type gatewayCfg struct { +// name string +// ref *v1.LocalParametersReference +// listeners []v1.Listener +// addresses []v1.GatewayAddress +// } +// 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{ +// GatewayClassName: gcName, +// Listeners: cfg.listeners, +// Addresses: cfg.addresses, +// }, +// } + +// if cfg.ref != nil { +// lastCreatedGateway.Spec.Infrastructure = &v1.GatewayInfrastructure{ +// ParametersRef: cfg.ref, +// } +// } + +// gatewayMap[types.NamespacedName{ +// Namespace: lastCreatedGateway.Namespace, +// Name: lastCreatedGateway.Name, +// }] = lastCreatedGateway +// return gatewayMap +// } + +// getLastCreatedGateway := func() *v1.Gateway { +// return lastCreatedGateway +// } + +// validGwNp := &ngfAPIv1alpha2.NginxProxy{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: "test", +// Name: "valid-gw-np", +// }, +// Spec: ngfAPIv1alpha2.NginxProxySpec{ +// Logging: &ngfAPIv1alpha2.NginxLogging{ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError)}, +// }, +// } + +// validGC := &GatewayClass{ +// Valid: true, +// } + +// gatewayTLSConfigDiffNs := &v1.GatewayTLSConfig{ +// Mode: helpers.GetPointer(v1.TLSModeTerminate), +// CertificateRefs: []v1.SecretObjectReference{ +// { +// Kind: helpers.GetPointer[v1.Kind]("Secret"), +// Name: v1.ObjectName(secretDiffNamespace.Name), +// Namespace: (*v1.Namespace)(&secretDiffNamespace.Namespace), +// }, +// }, +// } + +// secretDiffNamespace := &apiv1.Secret{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: "diff-ns", +// Name: "secret", +// }, +// Data: map[string][]byte{ +// apiv1.TLSCertKey: cert, +// apiv1.TLSPrivateKeyKey: key, +// }, +// Type: apiv1.SecretTypeTLS, +// } // } diff --git a/internal/mode/static/state/graph/route_common.go b/internal/mode/static/state/graph/route_common.go index 2c037db7f9..ad647092d8 100644 --- a/internal/mode/static/state/graph/route_common.go +++ b/internal/mode/static/state/graph/route_common.go @@ -362,8 +362,8 @@ func bindRoutesToListeners( routes = append(routes, r) } - listenerMap := getListenerHostPortMap(gw.Listeners, gw) - isolateL7RouteListeners(routes, listenerMap) + // listenerMap := getListenerHostPortMap(gw.Listeners, gw) + // isolateL7RouteListeners(routes, listenerMap) l4RouteSlice := make([]*L4Route, 0, len(l4Routes)) for _, r := range l4Routes { @@ -382,7 +382,7 @@ func bindRoutesToListeners( bindL4RouteToListeners(r, gw, namespaces, portHostnamesMap) } - isolateL4RouteListeners(l4RouteSlice, listenerMap) + // isolateL4RouteListeners(l4RouteSlice, listenerMap) } } @@ -531,20 +531,21 @@ func bindL4RouteToListeners( for i := range route.ParentRefs { ref := &(route.ParentRefs)[i] - attachment, attachableListeners := validateParentRef(ref, gw) - - if attachment.FailedCondition != (conditions.Condition{}) { - continue - } - gwNsName := types.NamespacedName{ Name: gw.Source.Name, Namespace: gw.Source.Namespace, } + if ref.Gateway != gwNsName { continue } + attachment, attachableListeners := validateParentRef(ref, gw) + + if attachment.FailedCondition != (conditions.Condition{}) { + continue + } + // Try to attach Route to all matching listeners cond, attached := tryToAttachL4RouteToListeners( @@ -695,6 +696,7 @@ func bindL7RouteToListeners( Name: gw.Source.Name, Namespace: gw.Source.Namespace, } + if ref.Gateway != gwNsName { continue } From f4a5ae30973b1064c5645aa3c328b0f3159797fa Mon Sep 17 00:00:00 2001 From: salonichf5 <146118978+salonichf5@users.noreply.github.com> Date: Sun, 30 Mar 2025 12:28:38 +0530 Subject: [PATCH 03/19] fix functional tests and add unit tests --- .../state/graph/multiple_gateways_test.go | 982 ++++++++++++++++-- .../mode/static/state/graph/route_common.go | 27 +- .../static/state/graph/route_common_test.go | 20 +- .../clientsettings/ignored-gateway.yaml | 11 - .../manifests/clientsettings/invalid-csp.yaml | 18 - .../clientsettings/invalid-route-csp.yaml | 2 +- 6 files changed, 898 insertions(+), 162 deletions(-) delete mode 100644 tests/suite/manifests/clientsettings/ignored-gateway.yaml delete mode 100644 tests/suite/manifests/clientsettings/invalid-csp.yaml diff --git a/internal/mode/static/state/graph/multiple_gateways_test.go b/internal/mode/static/state/graph/multiple_gateways_test.go index 9f0b5c608b..b30beffa0b 100644 --- a/internal/mode/static/state/graph/multiple_gateways_test.go +++ b/internal/mode/static/state/graph/multiple_gateways_test.go @@ -1,115 +1,871 @@ package graph -// func Test_Gateways(t *testing.T) { -// const gcName = "my-gateway-class" -// createListener := func( -// name string, -// hostname string, -// port int, -// protocol v1.ProtocolType, -// tls *v1.GatewayTLSConfig, -// ) v1.Listener { -// return v1.Listener{ -// Name: v1.SectionName(name), -// Hostname: (*v1.Hostname)(helpers.GetPointer(hostname)), -// Port: v1.PortNumber(port), //nolint:gosec // port number will not overflow int32 -// Protocol: protocol, -// TLS: tls, -// } -// } - -// createHTTPListener := func(name, hostname string, port int) v1.Listener { -// return createListener(name, hostname, port, v1.HTTPProtocolType, nil) -// } -// createTCPListener := func(name, hostname string, port int) v1.Listener { -// return createListener(name, hostname, port, v1.TCPProtocolType, nil) -// } -// createTLSListener := func(name, hostname string, port int) v1.Listener { -// return createListener( -// name, -// hostname, -// port, -// v1.TLSProtocolType, -// &v1.GatewayTLSConfig{Mode: helpers.GetPointer(v1.TLSModePassthrough)}, -// ) -// } - -// createHTTPSListener := func(name, hostname string, port int, tls *v1.GatewayTLSConfig) v1.Listener { -// return createListener(name, hostname, port, v1.HTTPSProtocolType, tls) -// } - -// var lastCreatedGateway *v1.Gateway -// type gatewayCfg struct { -// name string -// ref *v1.LocalParametersReference -// listeners []v1.Listener -// addresses []v1.GatewayAddress -// } -// 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{ -// GatewayClassName: gcName, -// Listeners: cfg.listeners, -// Addresses: cfg.addresses, -// }, -// } - -// if cfg.ref != nil { -// lastCreatedGateway.Spec.Infrastructure = &v1.GatewayInfrastructure{ -// ParametersRef: cfg.ref, -// } -// } - -// gatewayMap[types.NamespacedName{ -// Namespace: lastCreatedGateway.Namespace, -// Name: lastCreatedGateway.Name, -// }] = lastCreatedGateway -// return gatewayMap -// } - -// getLastCreatedGateway := func() *v1.Gateway { -// return lastCreatedGateway -// } - -// validGwNp := &ngfAPIv1alpha2.NginxProxy{ -// ObjectMeta: metav1.ObjectMeta{ -// Namespace: "test", -// Name: "valid-gw-np", -// }, -// Spec: ngfAPIv1alpha2.NginxProxySpec{ -// Logging: &ngfAPIv1alpha2.NginxLogging{ErrorLevel: helpers.GetPointer(ngfAPIv1alpha2.NginxLogLevelError)}, -// }, -// } - -// validGC := &GatewayClass{ -// Valid: true, -// } - -// gatewayTLSConfigDiffNs := &v1.GatewayTLSConfig{ -// Mode: helpers.GetPointer(v1.TLSModeTerminate), -// CertificateRefs: []v1.SecretObjectReference{ -// { -// Kind: helpers.GetPointer[v1.Kind]("Secret"), -// Name: v1.ObjectName(secretDiffNamespace.Name), -// Namespace: (*v1.Namespace)(&secretDiffNamespace.Namespace), -// }, -// }, -// } - -// secretDiffNamespace := &apiv1.Secret{ -// ObjectMeta: metav1.ObjectMeta{ -// Namespace: "diff-ns", -// Name: "secret", -// }, -// Data: map[string][]byte{ -// apiv1.TLSCertKey: cert, -// apiv1.TLSPrivateKeyKey: key, -// }, -// Type: apiv1.SecretTypeTLS, -// } -// } +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, nginxProxyName string, listeners []gatewayv1.Listener) *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, + }, + }, + Listeners: listeners, + }, + } +} + +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, + secret *v1.Secret, + supportedKinds []gatewayv1.RouteGroupKind, + l7Route map[RouteKey]*L7Route, + l4Route map[L4RouteKey]*L4Route, +) *Listener { + l := &Listener{ + Name: string(listener.Name), + 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", testNs, 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", "nginx-proxy", []gatewayv1.Listener{}) + gateway2 := createGateway("gateway-2", "nginx-proxy", []gatewayv1.Listener{}) + gateway3 := createGateway("gateway-3", "nginx-proxy", []gatewayv1.Listener{}) + + gateway1withNP := createGateway("gateway-1", "nginx-proxy-gateway-1", []gatewayv1.Listener{}) + gateway3withNP := createGateway("gateway-3", "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, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{}, + gcConditions..., + ), + client.ObjectKeyFromObject(gateway2): convertedGateway( + gateway2, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{}, + gcConditions..., + ), + client.ObjectKeyFromObject(gateway3): convertedGateway( + gateway3, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{}, + gcConditions..., + ), + }, + 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(gateway1): gateway1withNP, + client.ObjectKeyFromObject(gateway2): gateway2, + client.ObjectKeyFromObject(gateway3): 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, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{}, + gcConditions..., + ), + 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", "nginx-proxy", []gatewayv1.Listener{ + createListener( + "listener-tls-mode-terminate", + "*.example.com", + 443, + gatewayv1.HTTPSProtocolType, + tlsConfigDiffNsSecret, + allowedRoutesHTTPGRPC, + ), + }) + gateway2 := createGateway("gateway-2", "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", "nginx-proxy", listeners) + gatewayMultipleListeners2 := createGateway("gateway-multiple-listeners-2", "nginx-proxy", listeners) + gatewayMultipleListeners3 := createGateway("gateway-multiple-listeners-3", "nginx-proxy", listeners) + + // valid TLS and https listener same port and hostname + gatewayTLSSamePortHostname := createGateway("gateway-tls-foo", + "nginx-proxy", + []gatewayv1.Listener{ + createListener( + "foo-listener-tls", + "foo.example.com", + 443, + gatewayv1.TLSProtocolType, + tlsConfigPassthrough, + allowedRoutesTLS, + ), + }, + ) + + gatewayHTTPSSamePortHostname := createGateway("gateway-http-foo", + "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], + secretDiffNs, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + staticConds.NewGatewayClassResolvedRefs(), + ), + client.ObjectKeyFromObject(gateway2): convertedGateway( + gateway2, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{ + convertListener( + gateway2.Spec.Listeners[0], + secretDiffNs, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + 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], + nil, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + convertListener( + gatewayMultipleListeners1.Spec.Listeners[1], + secretSameNs, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + convertListener( + gatewayMultipleListeners1.Spec.Listeners[2], + nil, + supportedTLS, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + staticConds.NewGatewayClassResolvedRefs(), + ), + client.ObjectKeyFromObject(gatewayMultipleListeners2): convertedGateway( + gatewayMultipleListeners2, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{ + convertListener( + gatewayMultipleListeners2.Spec.Listeners[0], + nil, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + convertListener( + gatewayMultipleListeners2.Spec.Listeners[1], + secretSameNs, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + convertListener( + gatewayMultipleListeners2.Spec.Listeners[2], + nil, + supportedTLS, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + staticConds.NewGatewayClassResolvedRefs(), + ), + client.ObjectKeyFromObject(gatewayMultipleListeners3): convertedGateway( + gatewayMultipleListeners3, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{ + convertListener( + gatewayMultipleListeners3.Spec.Listeners[0], + nil, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + convertListener( + gatewayMultipleListeners3.Spec.Listeners[1], + secretSameNs, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + convertListener( + gatewayMultipleListeners3.Spec.Listeners[2], + nil, + supportedTLS, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + 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], + nil, + supportedTLS, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + staticConds.NewGatewayClassResolvedRefs(), + ), + client.ObjectKeyFromObject(gatewayHTTPSSamePortHostname): convertedGateway( + gatewayHTTPSSamePortHostname, + &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, + []*Listener{ + convertListener( + gatewayHTTPSSamePortHostname.Spec.Listeners[0], + secretSameNs, + supportedHTTPGRPC, + map[RouteKey]*L7Route{}, + map[L4RouteKey]*L4Route{}, + ), + }, + 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/route_common.go b/internal/mode/static/state/graph/route_common.go index ad647092d8..6503dfabca 100644 --- a/internal/mode/static/state/graph/route_common.go +++ b/internal/mode/static/state/graph/route_common.go @@ -362,8 +362,8 @@ func bindRoutesToListeners( routes = append(routes, r) } - // listenerMap := getListenerHostPortMap(gw.Listeners, gw) - // isolateL7RouteListeners(routes, listenerMap) + listenerMap := getListenerHostPortMap(gw.Listeners, gw) + isolateL7RouteListeners(routes, listenerMap) l4RouteSlice := make([]*L4Route, 0, len(l4Routes)) for _, r := range l4Routes { @@ -382,22 +382,27 @@ func bindRoutesToListeners( bindL4RouteToListeners(r, gw, namespaces, portHostnamesMap) } - // isolateL4RouteListeners(l4RouteSlice, listenerMap) + isolateL4RouteListeners(l4RouteSlice, listenerMap) } } type hostPort struct { - hostname string - port v1.PortNumber + gwNsNames types.NamespacedName + hostname string + port v1.PortNumber } func getListenerHostPortMap(listeners []*Listener, gw *Gateway) map[string]hostPort { listenerHostPortMap := make(map[string]hostPort, len(listeners)) + gwNsNames := types.NamespacedName{ + Name: gw.Source.Name, + Namespace: gw.Source.Namespace, + } for _, l := range listeners { - listenerKey := fmt.Sprintf("%s-%s-%s", gw.Source.Namespace, gw.Source.Name, l.Name) - listenerHostPortMap[listenerKey] = hostPort{ - hostname: getHostname(l.Source.Hostname), - port: l.Source.Port, + listenerHostPortMap[l.Name] = hostPort{ + hostname: getHostname(l.Source.Hostname), + port: l.Source.Port, + gwNsNames: gwNsNames, } } @@ -442,6 +447,10 @@ func isolateHostnamesForParentRefs(parentRef []ParentRef, listenerHostnameMap ma } for _, h := range hostnames { for lName, lHostPort := range listenerHostnameMap { + if lHostPort.gwNsNames != ref.Gateway { + continue + } + // skip comparison if it is a catch all listener block if lHostPort.hostname == "" { continue diff --git a/internal/mode/static/state/graph/route_common_test.go b/internal/mode/static/state/graph/route_common_test.go index 179ad3860b..18e04e6c5b 100644 --- a/internal/mode/static/state/graph/route_common_test.go +++ b/internal/mode/static/state/graph/route_common_test.go @@ -2302,11 +2302,11 @@ 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}, + "empty-hostname": {hostname: "", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, + "wildcard-example-com": {hostname: "*.example.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, + "foo-wildcard-example-com": {hostname: "*.foo.example.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, + "abc-com": {hostname: "abc.foo.example.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, + "no-match": {hostname: "no-match.cafe.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, } expectedResultHostnameIntersection := map[string][]ParentRef{ @@ -2678,11 +2678,11 @@ 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}, + "empty-hostname": {hostname: "", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, + "wildcard-example-com": {hostname: "*.example.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, + "foo-wildcard-example-com": {hostname: "*.foo.example.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, + "abc-com": {hostname: "abc.foo.example.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, + "no-match": {hostname: "no-match.cafe.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, } expectedResultHostnameIntersection := map[string][]ParentRef{ 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 index 2eb77c980e..894b8b4775 100644 --- a/tests/suite/manifests/clientsettings/invalid-route-csp.yaml +++ b/tests/suite/manifests/clientsettings/invalid-route-csp.yaml @@ -12,7 +12,7 @@ spec: - matches: - path: type: PathPrefix - value: /coffee + value: /invalid headers: - name: host_name value: v2 From 7840a5c06aaec2f0a2c60c1a9319be0d4579c4d0 Mon Sep 17 00:00:00 2001 From: salonichf5 <146118978+salonichf5@users.noreply.github.com> Date: Wed, 2 Apr 2025 08:53:47 +0530 Subject: [PATCH 04/19] update handler to recieve status update for gateways --- cmd/gateway/commands_test.go | 2 + examples/cafe-example/cafe-routes.yaml | 4 + examples/cafe-example/gateway.yaml | 24 + internal/mode/static/handler.go | 104 +- internal/mode/static/handler_test.go | 2 +- .../static/state/change_processor_test.go | 4764 +++++++++-------- .../mode/static/state/graph/backend_refs.go | 2 +- .../static/state/graph/backend_refs_test.go | 258 + .../state/graph/backend_tls_policy_test.go | 181 +- internal/mode/static/state/graph/graph.go | 10 +- .../static/status/prepare_requests_test.go | 468 +- 11 files changed, 3385 insertions(+), 2434 deletions(-) diff --git a/cmd/gateway/commands_test.go b/cmd/gateway/commands_test.go index 3235e767a7..a6a22b1c96 100644 --- a/cmd/gateway/commands_test.go +++ b/cmd/gateway/commands_test.go @@ -63,6 +63,8 @@ func TestCommonFlagsValidation(t *testing.T) { args: []string{ "--gateway-ctlr-name=gateway.nginx.org/nginx-gateway", "--gatewayclass=nginx", + "--gateway=nginx-gateway/nginx", + "--config=nginx-gateway-config", }, wantErr: false, }, diff --git a/examples/cafe-example/cafe-routes.yaml b/examples/cafe-example/cafe-routes.yaml index 67927335cb..3af38ca944 100644 --- a/examples/cafe-example/cafe-routes.yaml +++ b/examples/cafe-example/cafe-routes.yaml @@ -6,6 +6,8 @@ spec: parentRefs: - name: gateway sectionName: http + - name: gateway3 + sectionName: http hostnames: - "cafe.example.com" rules: @@ -25,6 +27,8 @@ spec: parentRefs: - name: gateway sectionName: http + - name: gateway3 + sectionName: http hostnames: - "cafe.example.com" rules: diff --git a/examples/cafe-example/gateway.yaml b/examples/cafe-example/gateway.yaml index e6507f613b..88fd062463 100644 --- a/examples/cafe-example/gateway.yaml +++ b/examples/cafe-example/gateway.yaml @@ -9,3 +9,27 @@ spec: port: 80 protocol: HTTP hostname: "*.example.com" +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: gateway2 +spec: + gatewayClassName: nginx + listeners: + - name: http + port: 80 + protocol: HTTP + hostname: "*.example.com" +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: gateway3 +spec: + gatewayClassName: nginx + listeners: + - name: http + port: 80 + protocol: HTTP + hostname: "*.example.com" diff --git a/internal/mode/static/handler.go b/internal/mode/static/handler.go index e66eea318e..6abd25227b 100644 --- a/internal/mode/static/handler.go +++ b/internal/mode/static/handler.go @@ -294,65 +294,72 @@ 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 } + deploymentName := item.Deployment + gw := gr.Gateways[deploymentName] + var nginxReloadRes graph.NginxReloadResult - for _, gw := range gr.Gateways { - switch { - case item.Error != nil: - h.cfg.logger.Error(item.Error, "Failed to update NGINX configuration") - nginxReloadRes.Error = item.Error - case gw != nil: - h.cfg.logger.Info("NGINX configuration was successfully updated") - } - gr.LatestReloadResult = nginxReloadRes - - switch item.UpdateType { - case status.UpdateAll: - h.updateStatuses(ctx, gr, gw) - case status.UpdateGateway: - gwAddresses, err := getGatewayAddresses( - ctx, - h.cfg.k8sClient, + switch { + case item.Error != nil: + h.cfg.logger.Error(item.Error, "Failed to update NGINX configuration") + nginxReloadRes.Error = item.Error + case gw != nil: + h.cfg.logger.Info("NGINX configuration was successfully updated") + } + gr.LatestReloadResult[deploymentName] = nginxReloadRes + + switch item.UpdateType { + case status.UpdateAll: + h.updateStatuses(ctx, gr, gw) + case status.UpdateGateway: + gwAddresses, err := getGatewayAddresses( + ctx, + h.cfg.k8sClient, + item.GatewayService, + gw, + h.cfg.gatewayClassName, + ) + if err != nil { + msg := "error getting Gateway Service IP address" + h.cfg.logger.Error(err, msg) + h.cfg.eventRecorder.Eventf( item.GatewayService, - gw, - h.cfg.gatewayClassName, - ) - if err != nil { - msg := "error getting Gateway Service IP address" - h.cfg.logger.Error(err, msg) - h.cfg.eventRecorder.Eventf( - item.GatewayService, - v1.EventTypeWarning, - "GetServiceIPFailed", - msg+": %s", - err.Error(), - ) - continue - } - - transitionTime := metav1.Now() - - gatewayStatuses := status.PrepareGatewayRequests( - gw, - transitionTime, - gwAddresses, - gr.LatestReloadResult, + v1.EventTypeWarning, + "GetServiceIPFailed", + msg+": %s", + err.Error(), ) - h.cfg.statusUpdater.UpdateGroup(ctx, groupGateways, gatewayStatuses...) - default: - panic(fmt.Sprintf("unknown event type %T", item.UpdateType)) + continue } + + transitionTime := metav1.Now() + + gatewayStatuses := status.PrepareGatewayRequests( + gw, + transitionTime, + gwAddresses, + gr.LatestReloadResult[deploymentName], + ) + h.cfg.statusUpdater.UpdateGroup(ctx, groupGateways, gatewayStatuses...) + default: + panic(fmt.Sprintf("unknown event type %T", item.UpdateType)) } } } 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 || gw.DeploymentName == (types.NamespacedName{}) { + 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" @@ -366,14 +373,11 @@ func (h *eventHandlerImpl) updateStatuses(ctx context.Context, gr *graph.Graph, ) } - transitionTime := metav1.Now() - - gcReqs := status.PrepareGatewayClassRequests(gr.GatewayClass, gr.IgnoredGatewayClasses, transitionTime) routeReqs := status.PrepareRouteRequests( gr.L4Routes, gr.Routes, transitionTime, - gr.LatestReloadResult, + gr.LatestReloadResult[gw.DeploymentName], h.cfg.gatewayCtlrName, ) @@ -404,7 +408,7 @@ func (h *eventHandlerImpl) updateStatuses(ctx context.Context, gr *graph.Graph, gw, transitionTime, gwAddresses, - gr.LatestReloadResult, + gr.LatestReloadResult[gw.DeploymentName], ) h.cfg.statusUpdater.UpdateGroup(ctx, groupGateways, gwReqs...) } diff --git a/internal/mode/static/handler_test.go b/internal/mode/static/handler_test.go index 8ba179a1d3..bfb4a0e71e 100644 --- a/internal/mode/static/handler_test.go +++ b/internal/mode/static/handler_test.go @@ -381,7 +381,7 @@ var _ = Describe("eventHandler", func() { }).Should(Equal(2)) gr := handler.cfg.processor.GetLatestGraph() - Expect(gr.LatestReloadResult.Error.Error()).To(Equal("status error")) + Expect(gr.LatestReloadResult[types.NamespacedName{}].Error.Error()).To(Equal("status error")) }) It("should update Gateway status when receiving a queue event", func() { diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 21323d66a8..ee184c65cc 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -10,6 +10,7 @@ import ( apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -21,9 +22,11 @@ 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/controller/index" "github.com/nginx/nginx-gateway-fabric/internal/framework/gatewayclass" "github.com/nginx/nginx-gateway-fabric/internal/framework/helpers" "github.com/nginx/nginx-gateway-fabric/internal/framework/kinds" + ngftypes "github.com/nginx/nginx-gateway-fabric/internal/framework/types" "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state" staticConds "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/conditions" "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/graph" @@ -227,41 +230,41 @@ func createGateway(name string, listeners ...v1.Listener) *v1.Gateway { } } -// func createRouteWithMultipleRules( -// name, gateway, hostname string, -// rules []v1.HTTPRouteRule, -// ) *v1.HTTPRoute { -// hr := createHTTPRoute(name, gateway, hostname) -// hr.Spec.Rules = rules - -// return hr -// } - -// func createHTTPRule(path string, backendRefs ...v1.HTTPBackendRef) v1.HTTPRouteRule { -// return v1.HTTPRouteRule{ -// Matches: []v1.HTTPRouteMatch{ -// { -// Path: &v1.HTTPPathMatch{ -// Type: helpers.GetPointer(v1.PathMatchPathPrefix), -// Value: &path, -// }, -// }, -// }, -// BackendRefs: backendRefs, -// } -// } - -// func createHTTPBackendRef( -// kind *v1.Kind, -// name v1.ObjectName, -// namespace *v1.Namespace, -// ) v1.HTTPBackendRef { -// return v1.HTTPBackendRef{ -// BackendRef: v1.BackendRef{ -// BackendObjectReference: createBackendRefObj(kind, name, namespace), -// }, -// } -// } +func createRouteWithMultipleRules( + name, gateway, hostname string, + rules []v1.HTTPRouteRule, +) *v1.HTTPRoute { + hr := createHTTPRoute(name, gateway, hostname) + hr.Spec.Rules = rules + + return hr +} + +func createHTTPRule(path string, backendRefs ...v1.HTTPBackendRef) v1.HTTPRouteRule { + return v1.HTTPRouteRule{ + Matches: []v1.HTTPRouteMatch{ + { + Path: &v1.HTTPPathMatch{ + Type: helpers.GetPointer(v1.PathMatchPathPrefix), + Value: &path, + }, + }, + }, + BackendRefs: backendRefs, + } +} + +func createHTTPBackendRef( + kind *v1.Kind, + name v1.ObjectName, + namespace *v1.Namespace, +) v1.HTTPBackendRef { + return v1.HTTPBackendRef{ + BackendRef: v1.BackendRef{ + BackendObjectReference: createBackendRefObj(kind, name, namespace), + }, + } +} func createTLSBackendRef(name, namespace string) v1.BackendRef { kindSvc := v1.Kind("Service") @@ -422,21 +425,22 @@ var _ = Describe("ChangeProcessor", func() { gcUpdated *v1.GatewayClass diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret diffNsTLSCert, sameNsTLSCert *graph.CertificateBundle - hr1, hr1Updated *v1.HTTPRoute - gr1, gr1Updated *v1.GRPCRoute - tr1, tr1Updated *v1alpha2.TLSRoute + hr1, hr1Updated, hr2 *v1.HTTPRoute + gr1, gr1Updated, gr2 *v1.GRPCRoute + tr1, tr1Updated, tr2 *v1alpha2.TLSRoute gw1, gw1Updated, gw2, gw2Updated *v1.Gateway secretRefGrant, hrServiceRefGrant *v1beta1.ReferenceGrant grServiceRefGrant, trServiceRefGrant *v1beta1.ReferenceGrant - expGraph *graph.Graph - expRouteHR1 *graph.L7Route - expRouteGR1 *graph.L7Route - expRouteTR1 *graph.L4Route + expGraph, expGraph2 *graph.Graph + expRouteHR1, expRouteHR2 *graph.L7Route + expRouteGR1, expRouteGR2 *graph.L7Route + expRouteTR1, expRouteTR2 *graph.L4Route gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata - httpRouteKey1, grpcRouteKey1 graph.RouteKey - // httpRouteKey2 graph.RouteKey - trKey1 graph.L4RouteKey - refSvc, refGRPCSvc, refTLSSvc types.NamespacedName + httpRoute1 graph.RouteKey + grpcRoute1 graph.RouteKey + httpRoute2, grpcRoute2 graph.RouteKey + trKey1, trKey2 graph.L4RouteKey + refSvc, refGRPCSvc, refTLSSvc types.NamespacedName ) processAndValidateGraph := func(expGraph *graph.Graph) { @@ -477,26 +481,29 @@ var _ = Describe("ChangeProcessor", func() { } hr1 = createHTTPRoute("hr-1", "gateway-1", "foo.example.com", crossNsHTTPBackendRef) - httpRouteKey1 = graph.CreateRouteKey(hr1) + httpRoute1 = graph.CreateRouteKey(hr1) hr1Updated = hr1.DeepCopy() hr1Updated.Generation++ - // hr2 = createHTTPRoute("hr-2", "gateway-2", "bar.example.com") - // httpRouteKey2 = graph.CreateRouteKey(hr2) + + hr2 = createHTTPRoute("hr-2", "gateway-2", "bar.example.com", crossNsHTTPBackendRef) + httpRoute2 = graph.CreateRouteKey(hr2) gr1 = createGRPCRoute("gr-1", "gateway-1", "foo.example.com", grpcBackendRef) - grpcRouteKey1 = graph.CreateRouteKey(gr1) + grpcRoute1 = graph.CreateRouteKey(gr1) gr1Updated = gr1.DeepCopy() gr1Updated.Generation++ - // gr2 = createGRPCRoute("gr-2", "gateway-2", "bar.example.com") - // grpcRouteKey2 = graph.CreateRouteKey(gr2) + + gr2 = createGRPCRoute("gr-2", "gateway-2", "bar.example.com", grpcBackendRef) + grpcRoute2 = graph.CreateRouteKey(gr2) tlsBackendRef := createTLSBackendRef(refTLSSvc.Name, refTLSSvc.Namespace) tr1 = createTLSRoute("tr-1", "gateway-1", "foo.tls.com", tlsBackendRef) trKey1 = graph.CreateRouteKeyL4(tr1) tr1Updated = tr1.DeepCopy() tr1Updated.Generation++ - // tr2 = createTLSRoute("tr-2", "gateway-2", "bar.tls.com", tlsBackendRef) - // trKey2 = graph.CreateRouteKeyL4(tr2) + + tr2 = createTLSRoute("tr-2", "gateway-2", "bar.tls.com", tlsBackendRef) + trKey2 = graph.CreateRouteKeyL4(tr2) secretRefGrant = &v1beta1.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ @@ -716,47 +723,58 @@ var _ = Describe("ChangeProcessor", func() { }, } - // expRouteHR2 = &graph.L7Route{ - // Source: hr2, - // RouteType: graph.RouteTypeHTTP, - // ParentRefs: []graph.ParentRef{ - // { - // Attachment: &graph.ParentRefAttachmentStatus{ - // AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, - // Attached: true, - // ListenerPort: 80, - // }, - // 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, - // }, - // Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - // Idx: 1, - // SectionName: hr2.Spec.ParentRefs[1].SectionName, - // }, - // }, - // Spec: graph.L7RouteSpec{ - // Hostnames: hr2.Spec.Hostnames, - // Rules: []graph.RouteRule{ - // { - // ValidMatches: true, - // Filters: graph.RouteRuleFilters{ - // Valid: true, - // Filters: []graph.Filter{}, - // }, - // Matches: hr2.Spec.Rules[0].Matches, - // RouteBackendRefs: []graph.RouteBackendRef{}, - // }, - // }, - // }, - // Valid: true, - // Attachable: true, - // } + expRouteHR2 = &graph.L7Route{ + Source: hr2, + RouteType: graph.RouteTypeHTTP, + ParentRefs: []graph.ParentRef{ + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, + Attached: true, + ListenerPort: 80, + }, + 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, + }, + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + Idx: 1, + SectionName: hr2.Spec.ParentRefs[1].SectionName, + }, + }, + Spec: graph.L7RouteSpec{ + Hostnames: hr2.Spec.Hostnames, + Rules: []graph.RouteRule{ + { + BackendRefs: []graph.BackendRef{ + { + SvcNsName: refSvc, + Weight: 1, + }, + }, + ValidMatches: true, + Filters: graph.RouteRuleFilters{ + Valid: true, + Filters: []graph.Filter{}, + }, + Matches: hr2.Spec.Rules[0].Matches, + 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{ Source: gr1, @@ -811,47 +829,58 @@ var _ = Describe("ChangeProcessor", func() { }, } - // expRouteGR2 = &graph.L7Route{ - // Source: gr2, - // RouteType: graph.RouteTypeGRPC, - // ParentRefs: []graph.ParentRef{ - // { - // Attachment: &graph.ParentRefAttachmentStatus{ - // AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, - // Attached: true, - // ListenerPort: 80, - // }, - // 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, - // }, - // Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - // Idx: 1, - // SectionName: gr2.Spec.ParentRefs[1].SectionName, - // }, - // }, - // Spec: graph.L7RouteSpec{ - // Hostnames: gr2.Spec.Hostnames, - // Rules: []graph.RouteRule{ - // { - // ValidMatches: true, - // Filters: graph.RouteRuleFilters{ - // Valid: true, - // Filters: []graph.Filter{}, - // }, - // Matches: graph.ConvertGRPCMatches(gr2.Spec.Rules[0].Matches), - // RouteBackendRefs: []graph.RouteBackendRef{}, - // }, - // }, - // }, - // Valid: true, - // Attachable: true, - // } + expRouteGR2 = &graph.L7Route{ + Source: gr2, + RouteType: graph.RouteTypeGRPC, + ParentRefs: []graph.ParentRef{ + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, + Attached: true, + ListenerPort: 80, + }, + 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, + }, + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + Idx: 1, + SectionName: gr2.Spec.ParentRefs[1].SectionName, + }, + }, + Spec: graph.L7RouteSpec{ + Hostnames: gr2.Spec.Hostnames, + Rules: []graph.RouteRule{ + { + BackendRefs: []graph.BackendRef{ + { + SvcNsName: refGRPCSvc, + Weight: 1, + }, + }, + ValidMatches: true, + Filters: graph.RouteRuleFilters{ + Valid: true, + Filters: []graph.Filter{}, + }, + Matches: graph.ConvertGRPCMatches(gr2.Spec.Rules[0].Matches), + 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{ Source: tr1, @@ -881,33 +910,33 @@ var _ = Describe("ChangeProcessor", func() { }, } - // expRouteTR2 = &graph.L4Route{ - // Source: tr2, - // ParentRefs: []graph.ParentRef{ - // { - // Attachment: &graph.ParentRefAttachmentStatus{ - // AcceptedHostnames: map[string][]string{tlsListenerName: {"bar.tls.com"}}, - // Attached: true, - // }, - // 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, - // }, - // }, - // Valid: true, - // Attachable: true, - // Conditions: []conditions.Condition{ - // staticConds.NewRouteBackendRefRefBackendNotFound( - // "spec.rules[0].backendRefs[0].name: Not found: \"tls-service\"", - // ), - // }, - // } + expRouteTR2 = &graph.L4Route{ + Source: tr2, + ParentRefs: []graph.ParentRef{ + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{tlsListenerName: {"bar.tls.com"}}, + Attached: true, + }, + 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, + }, + }, + Valid: true, + Attachable: true, + Conditions: []conditions.Condition{ + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"tls-service\"", + ), + }, + } // This is the base case expected graph. Tests will manipulate this to add or remove elements // to fit the expected output of the input under test. @@ -925,7 +954,7 @@ var _ = Describe("ChangeProcessor", func() { Source: gw1.Spec.Listeners[0], Valid: true, Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, + Routes: map[graph.RouteKey]*graph.L7Route{httpRoute1: expRouteHR1, grpcRoute1: expRouteGR1}, L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, SupportedKinds: []v1.RouteGroupKind{ {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, @@ -937,7 +966,7 @@ var _ = Describe("ChangeProcessor", func() { Source: gw1.Spec.Listeners[1], Valid: true, Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, + Routes: map[graph.RouteKey]*graph.L7Route{httpRoute1: expRouteHR1, grpcRoute1: expRouteGR1}, L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(diffNsTLSSecret)), SupportedKinds: []v1.RouteGroupKind{ @@ -965,7 +994,7 @@ var _ = Describe("ChangeProcessor", func() { }, }, L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, - Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, + Routes: map[graph.RouteKey]*graph.L7Route{httpRoute1: expRouteHR1, grpcRoute1: expRouteGR1}, ReferencedSecrets: map[types.NamespacedName]*graph.Secret{}, ReferencedServices: map[types.NamespacedName]*graph.ReferencedService{ refSvc: { @@ -978,6 +1007,134 @@ var _ = Describe("ChangeProcessor", func() { GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, }, }, + LatestReloadResult: map[types.NamespacedName]graph.NginxReloadResult{}, + } + + 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, + Source: gw1.Spec.Listeners[0], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{httpRoute1: expRouteHR1, grpcRoute1: 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{httpRoute1: expRouteHR1, grpcRoute1: 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)}, + }, + }, + }, + Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: "gateway-1-test-class", + }, + }, + {Namespace: "test", Name: "gateway-2"}: { + Source: gw2, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + 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, + 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, + 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{httpRoute1: expRouteHR1, grpcRoute1: 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"}: {}}, + }, + }, + LatestReloadResult: map[types.NamespacedName]graph.NginxReloadResult{}, } }) When("no upsert has occurred", func() { @@ -1040,13 +1197,13 @@ var _ = Describe("ChangeProcessor", func() { gw.Listeners = nil // no ref grant exists yet for the routes - expGraph.Routes[httpRouteKey1].Conditions = []conditions.Condition{ + expGraph.Routes[httpRoute1].Conditions = []conditions.Condition{ staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", ), } - expGraph.Routes[grpcRouteKey1].Conditions = []conditions.Condition{ + expGraph.Routes[grpcRoute1].Conditions = []conditions.Condition{ staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service grpc-service-ns/grpc-service not permitted by any ReferenceGrant", ), @@ -1059,19 +1216,19 @@ var _ = Describe("ChangeProcessor", func() { } // gateway class does not exist so routes cannot attach - expGraph.Routes[httpRouteKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + expGraph.Routes[httpRoute1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, FailedCondition: staticConds.NewRouteNoMatchingParent(), } - expGraph.Routes[httpRouteKey1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + expGraph.Routes[httpRoute1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, FailedCondition: staticConds.NewRouteNoMatchingParent(), } - expGraph.Routes[grpcRouteKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + expGraph.Routes[grpcRoute1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, FailedCondition: staticConds.NewRouteNoMatchingParent(), } - expGraph.Routes[grpcRouteKey1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + expGraph.Routes[grpcRoute1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, FailedCondition: staticConds.NewRouteNoMatchingParent(), } @@ -1123,30 +1280,30 @@ var _ = Describe("ChangeProcessor", func() { } 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 - listener443.Routes[grpcRouteKey1].ParentRefs[1].Attachment = expAttachment443 + listener80.Routes[httpRoute1].ParentRefs[0].Attachment = expAttachment80 + listener443.Routes[httpRoute1].ParentRefs[1].Attachment = expAttachment443 + listener80.Routes[grpcRoute1].ParentRefs[0].Attachment = expAttachment80 + listener443.Routes[grpcRoute1].ParentRefs[1].Attachment = expAttachment443 // no ref grant exists yet for hr1 - expGraph.Routes[httpRouteKey1].Conditions = []conditions.Condition{ + expGraph.Routes[httpRoute1].Conditions = []conditions.Condition{ staticConds.NewRouteInvalidListener(), staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", ), } - expGraph.Routes[httpRouteKey1].ParentRefs[0].Attachment = expAttachment80 - expGraph.Routes[httpRouteKey1].ParentRefs[1].Attachment = expAttachment443 + expGraph.Routes[httpRoute1].ParentRefs[0].Attachment = expAttachment80 + expGraph.Routes[httpRoute1].ParentRefs[1].Attachment = expAttachment443 // no ref grant exists yet for gr1 - expGraph.Routes[grpcRouteKey1].Conditions = []conditions.Condition{ + expGraph.Routes[grpcRoute1].Conditions = []conditions.Condition{ staticConds.NewRouteInvalidListener(), staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service grpc-service-ns/grpc-service not permitted by any ReferenceGrant", ), } - expGraph.Routes[grpcRouteKey1].ParentRefs[0].Attachment = expAttachment80 - expGraph.Routes[grpcRouteKey1].ParentRefs[1].Attachment = expAttachment443 + expGraph.Routes[grpcRoute1].ParentRefs[0].Attachment = expAttachment80 + expGraph.Routes[grpcRoute1].ParentRefs[1].Attachment = expAttachment443 // no ref grant exists yet for tr1 expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ @@ -1170,14 +1327,14 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(secretRefGrant) // no ref grant exists yet for hr1 - expGraph.Routes[httpRouteKey1].Conditions = []conditions.Condition{ + expGraph.Routes[httpRoute1].Conditions = []conditions.Condition{ staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", ), } // no ref grant exists yet for gr1 - expGraph.Routes[grpcRouteKey1].Conditions = []conditions.Condition{ + expGraph.Routes[grpcRoute1].Conditions = []conditions.Condition{ staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service grpc-service-ns/grpc-service not permitted by any ReferenceGrant", ), @@ -1215,7 +1372,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(hrServiceRefGrant) // no ref grant exists yet for gr1 - expGraph.Routes[grpcRouteKey1].Conditions = []conditions.Condition{ + expGraph.Routes[grpcRoute1].Conditions = []conditions.Condition{ staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service grpc-service-ns/grpc-service not permitted by any ReferenceGrant", ), @@ -1363,10 +1520,10 @@ var _ = Describe("ChangeProcessor", func() { gw := expGraph.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-1"}] listener443 := getListenerByName(gw, httpsListenerName) - listener443.Routes[httpRouteKey1].Source.SetGeneration(hr1Updated.Generation) + listener443.Routes[httpRoute1].Source.SetGeneration(hr1Updated.Generation) listener80 := getListenerByName(gw, httpListenerName) - listener80.Routes[httpRouteKey1].Source.SetGeneration(hr1Updated.Generation) + listener80.Routes[httpRoute1].Source.SetGeneration(hr1Updated.Generation) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ Source: diffNsTLSSecret, CertBundle: diffNsTLSCert, @@ -1382,10 +1539,10 @@ var _ = Describe("ChangeProcessor", func() { gw := expGraph.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-1"}] listener443 := getListenerByName(gw, httpsListenerName) - listener443.Routes[grpcRouteKey1].Source.SetGeneration(gr1Updated.Generation) + listener443.Routes[grpcRoute1].Source.SetGeneration(gr1Updated.Generation) listener80 := getListenerByName(gw, httpListenerName) - listener80.Routes[grpcRouteKey1].Source.SetGeneration(gr1Updated.Generation) + listener80.Routes[grpcRoute1].Source.SetGeneration(gr1Updated.Generation) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ Source: diffNsTLSSecret, CertBundle: diffNsTLSCert, @@ -1481,2003 +1638,2274 @@ var _ = Describe("ChangeProcessor", func() { It("returns populated graph with second gateway", func() { processor.CaptureUpsertChange(gw2) - grpcRoute := expGraph.Routes[grpcRouteKey1] + grpcRoute := expGraph2.Routes[grpcRoute1] grpcRoute.Conditions = append(grpcRoute.Conditions, staticConds.NewRouteBackendRefRefBackendNotFound( "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", )) - httpRoute := expGraph.Routes[httpRouteKey1] + httpRoute := expGraph2.Routes[httpRoute1] httpRoute.Conditions = append(httpRoute.Conditions, staticConds.NewRouteBackendRefRefBackendNotFound( "spec.rules[0].backendRefs[0].name: Not found: \"service\"", )) - expGraph.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] = &graph.Gateway{ - Source: gw2, - Listeners: []*graph.Listener{ - { - Name: httpListenerName, - 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, - 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, - 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(expGraph2) + }) + }) + When("the second HTTPRoute is upserted", func() { + It("returns populated graph", func() { + processor.CaptureUpsertChange(hr2) - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, - CertBundle: sameNsTLSCert, - } + grpcRoute := expGraph2.Routes[grpcRoute1] + grpcRoute.Conditions = append(grpcRoute.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", + )) - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + httpRoute := expGraph2.Routes[httpRoute1] + httpRoute.Conditions = append(httpRoute.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"service\"", + )) + + expGraph2.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ Source: diffNsTLSSecret, CertBundle: diffNsTLSCert, } - processAndValidateGraph(expGraph) - }) + gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] + + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRoute2: expRouteHR2, + } + + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRoute2: expRouteHR2, + } + + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRoute2: expRouteHR2, + httpRoute1: expRouteHR1, + grpcRoute1: expRouteGR1, + } + + httpRoute2 := expGraph2.Routes[httpRoute2] + httpRoute2.Conditions = append(httpRoute2.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"service\"", + )) + + expGraph2.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ + refSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-1"}: {}, + {Namespace: "test", Name: "gateway-2"}: {}, + }, + }, + refTLSSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-1"}: {}, + }, + }, + refGRPCSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, + }, + } + + processAndValidateGraph(expGraph2) + }) + }) + When("the second GRPCRoute is upserted", func() { + It("returns populated graph", func() { + processor.CaptureUpsertChange(gr2) + + grpcRoute := expGraph2.Routes[grpcRoute1] + grpcRoute.Conditions = append(grpcRoute.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", + )) + + httpRoute := expGraph2.Routes[httpRoute1] + httpRoute.Conditions = append(httpRoute.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"service\"", + )) + + gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] + + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRoute2: expRouteHR2, + grpcRoute2: expRouteGR2, + } + + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRoute2: expRouteHR2, + grpcRoute2: expRouteGR2, + } + + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRoute2: expRouteHR2, + httpRoute1: expRouteHR1, + grpcRoute1: expRouteGR1, + grpcRoute2: expRouteGR2, + } + + httpRoute2 := expGraph2.Routes[httpRoute2] + httpRoute2.Conditions = append(httpRoute2.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"service\"", + )) + + grpcRoute2 := expGraph2.Routes[grpcRoute2] + grpcRoute2.Conditions = append(grpcRoute2.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", + )) + + expGraph2.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ + refSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-1"}: {}, + {Namespace: "test", Name: "gateway-2"}: {}, + }, + }, + refTLSSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-1"}: {}, + }, + }, + refGRPCSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-1"}: {}, + {Namespace: "test", Name: "gateway-2"}: {}, + }, + }, + } + + processAndValidateGraph(expGraph2) + }) + }) + When("the second TLSRoute is upserted", func() { + It("returns populated graph", func() { + processor.CaptureUpsertChange(tr2) + + grpcRoute := expGraph2.Routes[grpcRoute1] + grpcRoute.Conditions = append(grpcRoute.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", + )) + + httpRoute := expGraph2.Routes[httpRoute1] + httpRoute.Conditions = append(httpRoute.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"service\"", + )) + + gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] + + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRoute2: expRouteHR2, + grpcRoute2: expRouteGR2, + } + + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRoute2: expRouteHR2, + grpcRoute2: expRouteGR2, + } + + tlsListener := getListenerByName(gw, tlsListenerName) + tlsListener.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey2: expRouteTR2, + } + + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRoute2: expRouteHR2, + httpRoute1: expRouteHR1, + grpcRoute1: expRouteGR1, + grpcRoute2: expRouteGR2, + } + + expGraph2.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey1: expRouteTR1, + trKey2: expRouteTR2, + } + + httpRoute2 := expGraph2.Routes[httpRoute2] + httpRoute2.Conditions = append(httpRoute2.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"service\"", + )) + + grpcRoute2 := expGraph2.Routes[grpcRoute2] + grpcRoute2.Conditions = append(grpcRoute2.Conditions, + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", + )) + + expGraph2.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ + refSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-1"}: {}, + {Namespace: "test", Name: "gateway-2"}: {}, + }, + }, + refTLSSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-1"}: {}, + {Namespace: "test", Name: "gateway-2"}: {}, + }, + }, + refGRPCSvc: { + GatewayNsNames: map[types.NamespacedName]struct{}{ + {Namespace: "test", Name: "gateway-1"}: {}, + {Namespace: "test", Name: "gateway-2"}: {}, + }, + }, + } + + processAndValidateGraph(expGraph2) + }) + }) + When("the first Gateway is deleted", func() { + It("returns updated graph", func() { + processor.CaptureDeleteChange( + &v1.Gateway{}, + types.NamespacedName{Namespace: "test", Name: "gateway-1"}, + ) + + // gateway 2 only remains; + expGraph2.Gateways = map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway-2"}: { + Source: gw2, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + 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, + 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, + 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", + }, + }, + } + + gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] + + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRoute2: expRouteHR2, + grpcRoute2: expRouteGR2, + } + + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRoute2: expRouteHR2, + grpcRoute2: expRouteGR2, + } + + tlsListener := getListenerByName(gw, tlsListenerName) + tlsListener.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey2: expRouteTR2, + } + + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ + httpRoute2: expRouteHR2, + grpcRoute2: 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() { + It("returns updated graph", func() { + processor.CaptureDeleteChange( + &v1.HTTPRoute{}, + types.NamespacedName{Namespace: "test", Name: "hr-2"}, + ) + + // gateway 2 only remains; + expGraph2.Gateways = map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway-2"}: { + Source: gw2, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + 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, + 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, + 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", + }, + }, + } + + gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] + + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{ + grpcRoute2: expRouteGR2, + } + + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{ + grpcRoute2: expRouteGR2, + } + + tlsListener := getListenerByName(gw, tlsListenerName) + tlsListener.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey2: expRouteTR2, + } + + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ + grpcRoute2: 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() { + It("returns updated graph", func() { + processor.CaptureDeleteChange( + &v1.GRPCRoute{}, + types.NamespacedName{Namespace: "test", Name: "gr-2"}, + ) + + // gateway 2 only remains; + expGraph2.Gateways = map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway-2"}: { + Source: gw2, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + 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, + 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, + 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", + }, + }, + } + + gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] + + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{} + + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{} + + tlsListener := getListenerByName(gw, tlsListenerName) + tlsListener.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + trKey2: expRouteTR2, + } + + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{} + + 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() { + It("returns updated graph", func() { + processor.CaptureDeleteChange( + &v1alpha2.TLSRoute{}, + types.NamespacedName{Namespace: "test", Name: "tr-2"}, + ) + + // gateway 2 only remains; + expGraph2.Gateways = map[types.NamespacedName]*graph.Gateway{ + {Namespace: "test", Name: "gateway-2"}: { + Source: gw2, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + 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, + 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, + 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", + }, + }, + } + + gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] + + listener80 := getListenerByName(gw, httpListenerName) + listener80.Routes = map[graph.RouteKey]*graph.L7Route{} + + listener443 := getListenerByName(gw, httpsListenerName) + listener443.Routes = map[graph.RouteKey]*graph.L7Route{} + + tlsListener := getListenerByName(gw, tlsListenerName) + tlsListener.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} + + expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{} + expGraph2.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} + + 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() { + It("returns updated graph", func() { + processor.CaptureDeleteChange( + &v1.GatewayClass{}, + types.NamespacedName{Name: gcName}, + ) + + 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", + }, + }, + } + 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{} + expGraph2.ReferencedServices = nil + + processAndValidateGraph(expGraph2) + }) + }) + When("the second Gateway is deleted", func() { + It("returns empty graph", func() { + processor.CaptureDeleteChange( + &v1.Gateway{}, + types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + ) + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServices = nil + + processAndValidateGraph(&graph.Graph{}) + }) + }) + When("the first HTTPRoute is deleted", func() { + It("returns empty graph", func() { + processor.CaptureDeleteChange( + &v1.HTTPRoute{}, + types.NamespacedName{Namespace: "test", Name: "hr-1"}, + ) + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServices = nil + + processAndValidateGraph(&graph.Graph{}) + }) + }) + When("the first GRPCRoute is deleted", func() { + It("returns empty graph", func() { + processor.CaptureDeleteChange( + &v1.GRPCRoute{}, + types.NamespacedName{Namespace: "test", Name: "gr-1"}, + ) + + expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServices = nil + + processAndValidateGraph(&graph.Graph{}) + }) + }) + When("the first TLSRoute is deleted", func() { + It("returns empty graph", func() { + processor.CaptureDeleteChange( + &v1alpha2.TLSRoute{}, + types.NamespacedName{Namespace: "test", Name: "tr-1"}, + ) + + expGraph.ReferencedServices = nil + + processAndValidateGraph(&graph.Graph{}) + }) + }) + }) + + Describe("Process services and endpoints", Ordered, func() { + var ( + hr1, hr2, hr3, hrInvalidBackendRef, hrMultipleRules *v1.HTTPRoute + hr1svc, sharedSvc, bazSvc1, bazSvc2, bazSvc3, invalidSvc, notRefSvc *apiv1.Service + hr1slice1, hr1slice2, noRefSlice, missingSvcNameSlice *discoveryV1.EndpointSlice + gw *v1.Gateway + btls *v1alpha3.BackendTLSPolicy + ) + + createSvc := func(name string) *apiv1.Service { + return &apiv1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: name, + }, + } + } + + createEndpointSlice := func(name string, svcName string) *discoveryV1.EndpointSlice { + return &discoveryV1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: name, + Labels: map[string]string{index.KubernetesServiceNameLabel: svcName}, + }, + } + } + + createBackendTLSPolicy := func(name string, svcName string) *v1alpha3.BackendTLSPolicy { + return &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: name, + }, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: v1.Kind("Service"), + Name: v1.ObjectName(svcName), + }, + }, + }, + }, + } + } + + BeforeAll(func() { + testNamespace := v1.Namespace("test") + kindService := v1.Kind("Service") + kindInvalid := v1.Kind("Invalid") + + // backend Refs + fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) + baz1NilNamespace := createHTTPBackendRef(&kindService, "baz-svc-v1", &testNamespace) + barRef := createHTTPBackendRef(&kindService, "bar-svc", nil) + baz2Ref := createHTTPBackendRef(&kindService, "baz-svc-v2", &testNamespace) + baz3Ref := createHTTPBackendRef(&kindService, "baz-svc-v3", &testNamespace) + invalidKindRef := createHTTPBackendRef(&kindInvalid, "bar-svc", &testNamespace) + + // httproutes + hr1 = createHTTPRoute("hr1", "gw", "foo.example.com", fooRef) + hr2 = createHTTPRoute("hr2", "gw", "bar.example.com", barRef) + // hr3 shares the same backendRef as hr2 + hr3 = createHTTPRoute("hr3", "gw", "bar.2.example.com", barRef) + hrInvalidBackendRef = createHTTPRoute("hr-invalid", "gw", "invalid.com", invalidKindRef) + hrMultipleRules = createRouteWithMultipleRules( + "hr-multiple-rules", + "gw", + "mutli.example.com", + []v1.HTTPRouteRule{ + createHTTPRule("/baz-v1", baz1NilNamespace), + createHTTPRule("/baz-v2", baz2Ref), + createHTTPRule("/baz-v3", baz3Ref), + }, + ) + + // services + hr1svc = createSvc("foo-svc") + sharedSvc = createSvc("bar-svc") // shared between hr2 and hr3 + invalidSvc = createSvc("invalid") // nsname matches invalid BackendRef + notRefSvc = createSvc("not-ref") + bazSvc1 = createSvc("baz-svc-v1") + bazSvc2 = createSvc("baz-svc-v2") + bazSvc3 = createSvc("baz-svc-v3") + + // endpoint slices + hr1slice1 = createEndpointSlice("hr1-1", "foo-svc") + hr1slice2 = createEndpointSlice("hr1-2", "foo-svc") + noRefSlice = createEndpointSlice("no-ref", "no-ref") + missingSvcNameSlice = createEndpointSlice("missing-svc-name", "") + + // backendTLSPolicy + btls = createBackendTLSPolicy("btls", "foo-svc") + + gw = createGateway("gw", createHTTPListener()) + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + + testProcessChangedVal := func(expChanged state.ChangeType) { + changed, _ := processor.Process() + Expect(changed).To(Equal(expChanged)) + } + + testUpsertTriggersChange := func(obj client.Object, expChanged state.ChangeType) { + processor.CaptureUpsertChange(obj) + testProcessChangedVal(expChanged) + } + + testDeleteTriggersChange := func(obj client.Object, nsname types.NamespacedName, expChanged state.ChangeType) { + processor.CaptureDeleteChange(obj, nsname) + testProcessChangedVal(expChanged) + } + + When("hr1 is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1, state.ClusterStateChange) + }) + }) + When("a hr1 service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1svc, state.ClusterStateChange) + }) + }) + When("a backendTLSPolicy is added for referenced service", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(btls, state.ClusterStateChange) + }) + }) + When("an hr1 endpoint slice is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1slice1, state.EndpointsOnlyChange) + }) + }) + When("an hr1 service is updated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1svc, state.ClusterStateChange) + }) + }) + When("another hr1 endpoint slice is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) + }) + }) + When("an endpoint slice with a missing svc name label is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(missingSvcNameSlice, state.NoChange) + }) + }) + When("an hr1 endpoint slice is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr1slice1, + types.NamespacedName{Namespace: hr1slice1.Namespace, Name: hr1slice1.Name}, + state.EndpointsOnlyChange, + ) + }) + }) + When("the second hr1 endpoint slice is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr1slice2, + types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, + state.EndpointsOnlyChange, + ) + }) + }) + When("the second hr1 endpoint slice is recreated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) + }) + }) + When("hr1 is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr1, + types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name}, + state.ClusterStateChange, + ) + }) + }) + When("hr1 service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + hr1svc, + types.NamespacedName{Namespace: hr1svc.Namespace, Name: hr1svc.Name}, + state.NoChange, + ) + }) + }) + When("the second hr1 endpoint slice is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + hr1slice2, + types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, + state.NoChange, + ) + }) + }) + When("hr2 is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr2, state.ClusterStateChange) + }) + }) + When("a hr3, that shares a backend service with hr2, is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr3, state.ClusterStateChange) + }) + }) + When("sharedSvc, a service referenced by both hr2 and hr3, is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) + }) + }) + When("hr2 is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr2, + types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name}, + state.ClusterStateChange, + ) + }) + }) + When("sharedSvc is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + sharedSvc, + types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, + state.ClusterStateChange, + ) + }) + }) + When("sharedSvc is recreated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) + }) + }) + When("hr3 is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr3, + types.NamespacedName{Namespace: hr3.Namespace, Name: hr3.Name}, + state.ClusterStateChange, + ) + }) + }) + When("sharedSvc is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + sharedSvc, + types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, + state.NoChange, + ) + }) + }) + When("a service that is not referenced by any route is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(notRefSvc, state.NoChange) + }) + }) + When("a route with an invalid backend ref type is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hrInvalidBackendRef, state.ClusterStateChange) + }) + }) + When("a service with a namespace name that matches invalid backend ref is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(invalidSvc, state.NoChange) + }) + }) + When("an endpoint slice that is not owned by a referenced service is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(noRefSlice, state.NoChange) + }) + }) + When("an endpoint slice that is not owned by a referenced service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + noRefSlice, + types.NamespacedName{Namespace: noRefSlice.Namespace, Name: noRefSlice.Name}, + state.NoChange, + ) + }) + }) + Context("processing a route with multiple rules and three unique backend services", func() { + When("route is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hrMultipleRules, state.ClusterStateChange) + }) + }) + When("first referenced service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) + }) + }) + When("second referenced service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc2, state.ClusterStateChange) + }) + }) + When("first referenced service is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + bazSvc1, + types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, + state.ClusterStateChange, + ) + }) + }) + When("first referenced service is recreated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) + }) + }) + When("third referenced service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) + }) + }) + When("third referenced service is updated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) + }) + }) + When("route is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hrMultipleRules, + types.NamespacedName{ + Namespace: hrMultipleRules.Namespace, + Name: hrMultipleRules.Name, + }, + state.ClusterStateChange, + ) + }) + }) + When("first referenced service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + bazSvc1, + types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, + state.NoChange, + ) + }) + }) + When("second referenced service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + bazSvc2, + types.NamespacedName{Namespace: bazSvc2.Namespace, Name: bazSvc2.Name}, + state.NoChange, + ) + }) + }) + When("final referenced service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + bazSvc3, + types.NamespacedName{Namespace: bazSvc3.Namespace, Name: bazSvc3.Name}, + state.NoChange, + ) + }) + }) + }) + }) + + Describe("namespace changes", Ordered, func() { + var ( + ns, nsDifferentLabels, nsNoLabels *apiv1.Namespace + gw *v1.Gateway + ) + + BeforeAll(func() { + ns = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns", + Labels: map[string]string{ + "app": "allowed", + }, + }, + } + nsDifferentLabels = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns-different-labels", + Labels: map[string]string{ + "oranges": "bananas", + }, + }, + } + nsNoLabels = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "no-labels", + }, + } + gw = &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + }, + Spec: v1.GatewaySpec{ + GatewayClassName: gcName, + Listeners: []v1.Listener{ + { + Port: 80, + Protocol: v1.HTTPProtocolType, + AllowedRoutes: &v1.AllowedRoutes{ + Namespaces: &v1.RouteNamespaces{ + From: helpers.GetPointer(v1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "allowed", + }, + }, + }, + }, + }, + }, + }, + } + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: logr.Discard(), + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }) + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw) + processor.Process() + }) + + When("a namespace is created that is not linked to a listener", func() { + It("does not trigger an update", func() { + processor.CaptureUpsertChange(nsNoLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + }) + When("a namespace is created that is linked to a listener", func() { + It("triggers an update", func() { + processor.CaptureUpsertChange(ns) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + When("a namespace is deleted that is not linked to a listener", func() { + It("does not trigger an update", func() { + processor.CaptureDeleteChange(nsNoLabels, types.NamespacedName{Name: "no-labels"}) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + }) + When("a namespace is deleted that is linked to a listener", func() { + It("triggers an update", func() { + processor.CaptureDeleteChange(ns, types.NamespacedName{Name: "ns"}) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + When("a namespace that is not linked to a listener has its labels changed to match a listener", func() { + It("triggers an update", func() { + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + + nsDifferentLabels.Labels = map[string]string{ + "app": "allowed", + } + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ = processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + When( + "a namespace that is linked to a listener has its labels changed to no longer match a listener", + func() { + It("triggers an update", func() { + nsDifferentLabels.Labels = map[string]string{ + "oranges": "bananas", + } + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }, + ) + When("a gateway changes its listener's labels", func() { + It("triggers an update when a namespace that matches the new labels is created", func() { + gwChangedLabel := gw.DeepCopy() + gwChangedLabel.Spec.Listeners[0].AllowedRoutes.Namespaces.Selector.MatchLabels = map[string]string{ + "oranges": "bananas", + } + gwChangedLabel.Generation++ + processor.CaptureUpsertChange(gwChangedLabel) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + + // After changing the gateway's labels and generation, the processor should be marked to update + // the nginx configuration and build a new graph. When processor.Process() gets called, + // the nginx configuration gets updated and a new graph is built with an updated + // referencedNamespaces. Thus, when the namespace "ns" is upserted with labels that no longer match + // the new labels on the gateway, it would not trigger a change as the namespace would no longer + // be in the updated referencedNamespaces and the labels no longer match the new labels on the + // gateway. + processor.CaptureUpsertChange(ns) + changed, _ = processor.Process() + Expect(changed).To(Equal(state.NoChange)) + + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ = processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + When("a namespace that is not linked to a listener has its labels removed", func() { + It("does not trigger an update", func() { + ns.Labels = nil + processor.CaptureUpsertChange(ns) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + }) + When("a namespace that is linked to a listener has its labels removed", func() { + It("triggers an update when labels are removed", func() { + nsDifferentLabels.Labels = nil + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + }) + + Describe("NginxProxy resource changes", Ordered, func() { + Context("referenced by a GatewayClass", func() { + paramGC := gc.DeepCopy() + paramGC.Spec.ParametersRef = &v1beta1.ParametersReference{ + Group: ngfAPIv1alpha1.GroupName, + Kind: kinds.NginxProxy, + Name: "np", + Namespace: helpers.GetPointer[v1.Namespace]("test"), + } + + np := &ngfAPIv1alpha2.NginxProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "np", + Namespace: "test", + }, + } + + npUpdated := &ngfAPIv1alpha2.NginxProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "np", + Namespace: "test", + }, + Spec: ngfAPIv1alpha2.NginxProxySpec{ + Telemetry: &ngfAPIv1alpha2.Telemetry{ + Exporter: &ngfAPIv1alpha2.TelemetryExporter{ + Endpoint: helpers.GetPointer("my-svc:123"), + BatchSize: helpers.GetPointer(int32(512)), + BatchCount: helpers.GetPointer(int32(4)), + Interval: helpers.GetPointer(ngfAPIv1alpha1.Duration("5s")), + }, + }, + }, + } + It("handles upserts for an NginxProxy", func() { + processor.CaptureUpsertChange(np) + processor.CaptureUpsertChange(paramGC) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.GatewayClass.NginxProxy.Source).To(Equal(np)) + }) + It("captures changes for an NginxProxy", func() { + processor.CaptureUpsertChange(npUpdated) + processor.CaptureUpsertChange(paramGC) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.GatewayClass.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.GatewayClass.NginxProxy).To(BeNil()) + }) + }) + Context("referenced by a Gateway", func() { + paramGW := &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "param-gw", + Generation: 1, + }, + Spec: v1.GatewaySpec{ + GatewayClassName: gcName, + Listeners: []v1.Listener{ + { + Name: httpListenerName, + Hostname: nil, + Port: 80, + Protocol: v1.HTTPProtocolType, + }, + }, + Infrastructure: &v1.GatewayInfrastructure{ + ParametersRef: &v1.LocalParametersReference{ + Group: ngfAPIv1alpha1.GroupName, + Kind: kinds.NginxProxy, + Name: "np-gw", + }, + }, + }, + } + + np := &ngfAPIv1alpha2.NginxProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "np-gw", + Namespace: "test", + }, + } + + npUpdated := &ngfAPIv1alpha2.NginxProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "np-gw", + Namespace: "test", + }, + Spec: ngfAPIv1alpha2.NginxProxySpec{ + Telemetry: &ngfAPIv1alpha2.Telemetry{ + Exporter: &ngfAPIv1alpha2.TelemetryExporter{ + Endpoint: helpers.GetPointer("my-svc:123"), + BatchSize: helpers.GetPointer(int32(512)), + BatchCount: helpers.GetPointer(int32(4)), + Interval: helpers.GetPointer(ngfAPIv1alpha1.Duration("5s")), + }, + }, + }, + } + It("handles upserts for an NginxProxy", func() { + processor.CaptureUpsertChange(np) + processor.CaptureUpsertChange(paramGW) + + changed, graph := processor.Process() + gw := graph.Gateways[types.NamespacedName{Namespace: "test", Name: "param-gw"}] + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(gw.NginxProxy.Source).To(Equal(np)) + }) + It("captures changes for an NginxProxy", func() { + processor.CaptureUpsertChange(npUpdated) + processor.CaptureUpsertChange(paramGW) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + 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)) + gw := graph.Gateways[types.NamespacedName{Namespace: "test", Name: "param-gw"}] + Expect(gw.NginxProxy).To(BeNil()) + }) + }) + }) + + Describe("NGF Policy resource changes", Ordered, func() { + var ( + gw *v1.Gateway + route *v1.HTTPRoute + svc *apiv1.Service + csp, cspUpdated *ngfAPIv1alpha1.ClientSettingsPolicy + obs, obsUpdated *ngfAPIv1alpha2.ObservabilityPolicy + usp, uspUpdated *ngfAPIv1alpha1.UpstreamSettingsPolicy + cspKey, obsKey, uspKey graph.PolicyKey + ) + + BeforeAll(func() { + processor.CaptureUpsertChange(gc) + changed, newGraph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(newGraph.GatewayClass.Source).To(Equal(gc)) + Expect(newGraph.NGFPolicies).To(BeEmpty()) + + gw = createGateway("gw", createHTTPListener()) + route = createHTTPRoute( + "hr-1", + "gw", + "foo.example.com", + v1.HTTPBackendRef{ + BackendRef: v1.BackendRef{ + BackendObjectReference: v1.BackendObjectReference{ + Group: helpers.GetPointer[v1.Group](""), + Kind: helpers.GetPointer[v1.Kind](kinds.Service), + Name: "svc", + Port: helpers.GetPointer[v1.PortNumber](80), + }, + }, + }, + ) + + svc = &apiv1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "svc", + Namespace: "test", + }, + } + + csp = &ngfAPIv1alpha1.ClientSettingsPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csp", + Namespace: "test", + }, + Spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ + TargetRef: v1alpha2.LocalPolicyTargetReference{ + Group: v1.GroupName, + Kind: kinds.Gateway, + Name: "gw", + }, + Body: &ngfAPIv1alpha1.ClientBody{ + MaxSize: helpers.GetPointer[ngfAPIv1alpha1.Size]("10m"), + }, + }, + } + + cspUpdated = csp.DeepCopy() + cspUpdated.Spec.Body.MaxSize = helpers.GetPointer[ngfAPIv1alpha1.Size]("20m") + + cspKey = graph.PolicyKey{ + NsName: types.NamespacedName{Name: "csp", Namespace: "test"}, + GVK: schema.GroupVersionKind{ + Group: ngfAPIv1alpha1.GroupName, + Kind: kinds.ClientSettingsPolicy, + Version: "v1alpha1", + }, + } + + obs = &ngfAPIv1alpha2.ObservabilityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "obs", + Namespace: "test", + }, + Spec: ngfAPIv1alpha2.ObservabilityPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReference{ + { + Group: v1.GroupName, + Kind: kinds.HTTPRoute, + Name: "hr-1", + }, + }, + Tracing: &ngfAPIv1alpha2.Tracing{ + Strategy: ngfAPIv1alpha2.TraceStrategyRatio, + }, + }, + } + + obsUpdated = obs.DeepCopy() + obsUpdated.Spec.Tracing.Strategy = ngfAPIv1alpha2.TraceStrategyParent + + obsKey = graph.PolicyKey{ + NsName: types.NamespacedName{Name: "obs", Namespace: "test"}, + GVK: schema.GroupVersionKind{ + Group: ngfAPIv1alpha1.GroupName, + Kind: kinds.ObservabilityPolicy, + Version: "v1alpha2", + }, + } + + usp = &ngfAPIv1alpha1.UpstreamSettingsPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "usp", + Namespace: "test", + }, + Spec: ngfAPIv1alpha1.UpstreamSettingsPolicySpec{ + ZoneSize: helpers.GetPointer[ngfAPIv1alpha1.Size]("10m"), + TargetRefs: []v1alpha2.LocalPolicyTargetReference{ + { + Group: "core", + Kind: kinds.Service, + Name: "svc", + }, + }, + }, + } + + uspUpdated = usp.DeepCopy() + uspUpdated.Spec.ZoneSize = helpers.GetPointer[ngfAPIv1alpha1.Size]("20m") + + uspKey = graph.PolicyKey{ + NsName: types.NamespacedName{Name: "usp", Namespace: "test"}, + GVK: schema.GroupVersionKind{ + Group: ngfAPIv1alpha1.GroupName, + Kind: kinds.UpstreamSettingsPolicy, + Version: "v1alpha1", + }, + } + }) + + /* + NOTE: When adding a new NGF policy to the change processor, + update the following tests to make sure that the change processor can track changes for multiple NGF + policies. + */ + + When("a policy is created that references a resource that is not in the last graph", func() { + It("reports no changes", func() { + processor.CaptureUpsertChange(csp) + processor.CaptureUpsertChange(obs) + processor.CaptureUpsertChange(usp) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + }) + When("the resource the policy references is created", func() { + It("populates the graph with the policy", func() { + processor.CaptureUpsertChange(gw) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(HaveKey(cspKey)) + Expect(graph.NGFPolicies[cspKey].Source).To(Equal(csp)) + Expect(graph.NGFPolicies).ToNot(HaveKey(obsKey)) + + processor.CaptureUpsertChange(route) + changed, graph = processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(HaveKey(obsKey)) + Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obs)) + + processor.CaptureUpsertChange(svc) + changed, graph = processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(HaveKey(uspKey)) + Expect(graph.NGFPolicies[uspKey].Source).To(Equal(usp)) + }) + }) + When("the policy is updated", func() { + It("captures changes for a policy", func() { + processor.CaptureUpsertChange(cspUpdated) + processor.CaptureUpsertChange(obsUpdated) + processor.CaptureUpsertChange(uspUpdated) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(HaveKey(cspKey)) + Expect(graph.NGFPolicies[cspKey].Source).To(Equal(cspUpdated)) + Expect(graph.NGFPolicies).To(HaveKey(obsKey)) + Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obsUpdated)) + Expect(graph.NGFPolicies).To(HaveKey(uspKey)) + Expect(graph.NGFPolicies[uspKey].Source).To(Equal(uspUpdated)) + }) + }) + When("the policy is deleted", func() { + It("removes the policy from the graph", func() { + processor.CaptureDeleteChange(&ngfAPIv1alpha1.ClientSettingsPolicy{}, client.ObjectKeyFromObject(csp)) + processor.CaptureDeleteChange(&ngfAPIv1alpha2.ObservabilityPolicy{}, client.ObjectKeyFromObject(obs)) + processor.CaptureDeleteChange(&ngfAPIv1alpha1.UpstreamSettingsPolicy{}, client.ObjectKeyFromObject(usp)) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(BeEmpty()) + }) + }) + }) + + Describe("SnippetsFilter resource changed", Ordered, func() { + sfNsName := types.NamespacedName{ + Name: "sf", + Namespace: "test", + } + + sf := &ngfAPIv1alpha1.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: sfNsName.Name, + Namespace: sfNsName.Namespace, + }, + Spec: ngfAPIv1alpha1.SnippetsFilterSpec{ + Snippets: []ngfAPIv1alpha1.Snippet{ + { + Context: ngfAPIv1alpha1.NginxContextMain, + Value: "main snippet", + }, + }, + }, + } + + sfUpdated := &ngfAPIv1alpha1.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: sfNsName.Name, + Namespace: sfNsName.Namespace, + }, + Spec: ngfAPIv1alpha1.SnippetsFilterSpec{ + Snippets: []ngfAPIv1alpha1.Snippet{ + { + Context: ngfAPIv1alpha1.NginxContextMain, + Value: "main snippet", + }, + { + Context: ngfAPIv1alpha1.NginxContextHTTP, + Value: "http snippet", + }, + }, + }, + } + It("handles upserts for a SnippetsFilter", func() { + processor.CaptureUpsertChange(sf) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + + processedSf, exists := graph.SnippetsFilters[sfNsName] + Expect(exists).To(BeTrue()) + Expect(processedSf.Source).To(Equal(sf)) + Expect(processedSf.Valid).To(BeTrue()) + }) + It("captures changes for a SnippetsFilter", func() { + processor.CaptureUpsertChange(sfUpdated) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + + processedSf, exists := graph.SnippetsFilters[sfNsName] + Expect(exists).To(BeTrue()) + Expect(processedSf.Source).To(Equal(sfUpdated)) + Expect(processedSf.Valid).To(BeTrue()) + }) + It("handles deletes for a SnippetsFilter", func() { + processor.CaptureDeleteChange(sfUpdated, sfNsName) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.SnippetsFilters).To(BeEmpty()) + }) + }) + }) + Describe("Ensuring non-changing changes don't override previously changing changes", func() { + // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses + // -- this is done in 'Normal cases of processing changes' + + var ( + processor *state.ChangeProcessorImpl + gcNsName, gwNsName, hrNsName, hr2NsName, grNsName, gr2NsName, rgNsName, svcNsName types.NamespacedName + sliceNsName, secretNsName, cmNsName, btlsNsName, npNsName types.NamespacedName + gc, gcUpdated *v1.GatewayClass + gw1, gw1Updated, gw2 *v1.Gateway + hr1, hr1Updated, hr2 *v1.HTTPRoute + gr1, gr1Updated, gr2 *v1.GRPCRoute + rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant + svc, barSvc, unrelatedSvc *apiv1.Service + slice, barSlice, unrelatedSlice *discoveryV1.EndpointSlice + ns, unrelatedNS, testNs, barNs *apiv1.Namespace + secret, secretUpdated, unrelatedSecret, barSecret, barSecretUpdated *apiv1.Secret + cm, cmUpdated, unrelatedCM *apiv1.ConfigMap + btls, btlsUpdated *v1alpha3.BackendTLSPolicy + np, npUpdated *ngfAPIv1alpha2.NginxProxy + ) + + BeforeEach(OncePerOrdered, func() { + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: "test.controller", + GatewayClassName: "test-class", + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }) + + secretNsName = types.NamespacedName{Namespace: "test", Name: "tls-secret"} + secret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretNsName.Name, + Namespace: secretNsName.Namespace, + Generation: 1, + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } + secretUpdated = secret.DeepCopy() + secretUpdated.Generation++ + barSecret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar-secret", + Namespace: "test", + Generation: 1, + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } + barSecretUpdated = barSecret.DeepCopy() + barSecretUpdated.Generation++ + unrelatedSecret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unrelated-tls-secret", + Namespace: "unrelated-ns", + Generation: 1, + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } + + gcNsName = types.NamespacedName{Name: "test-class"} + + gc = &v1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: gcNsName.Name, + }, + Spec: v1.GatewayClassSpec{ + ControllerName: "test.controller", + }, + } + + gcUpdated = gc.DeepCopy() + gcUpdated.Generation++ + + gwNsName = types.NamespacedName{Namespace: "test", Name: "gw-1"} + + gw1 = &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-1", + Namespace: "test", + Generation: 1, + }, + Spec: v1.GatewaySpec{ + GatewayClassName: gcName, + Listeners: []v1.Listener{ + { + Name: httpListenerName, + Hostname: nil, + Port: 80, + Protocol: v1.HTTPProtocolType, + AllowedRoutes: &v1.AllowedRoutes{ + Namespaces: &v1.RouteNamespaces{ + From: helpers.GetPointer(v1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "namespace", + }, + }, + }, + }, + }, + { + Name: httpsListenerName, + Hostname: nil, + Port: 443, + Protocol: v1.HTTPSProtocolType, + TLS: &v1.GatewayTLSConfig{ + Mode: helpers.GetPointer(v1.TLSModeTerminate), + CertificateRefs: []v1.SecretObjectReference{ + { + Kind: (*v1.Kind)(helpers.GetPointer("Secret")), + Name: v1.ObjectName(secret.Name), + Namespace: (*v1.Namespace)(&secret.Namespace), + }, + }, + }, + }, + { + Name: "listener-500-1", + Hostname: nil, + Port: 500, + Protocol: v1.HTTPSProtocolType, + TLS: &v1.GatewayTLSConfig{ + Mode: helpers.GetPointer(v1.TLSModeTerminate), + CertificateRefs: []v1.SecretObjectReference{ + { + Kind: (*v1.Kind)(helpers.GetPointer("Secret")), + Name: v1.ObjectName(barSecret.Name), + Namespace: (*v1.Namespace)(&barSecret.Namespace), + }, + }, + }, + }, + }, + }, + } + + gw1Updated = gw1.DeepCopy() + gw1Updated.Generation++ + + gw2 = gw1.DeepCopy() + gw2.Name = "gw-2" + + testNamespace := v1.Namespace("test") + kindService := v1.Kind("Service") + fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) + barRef := createHTTPBackendRef(&kindService, "bar-svc", &testNamespace) + + hrNsName = types.NamespacedName{Namespace: "test", Name: "hr-1"} + hr1 = createHTTPRoute("hr-1", "gw-1", "foo.example.com", fooRef, barRef) + hr1Updated = hr1.DeepCopy() + hr1Updated.Generation++ + hr2NsName = types.NamespacedName{Namespace: "test", Name: "hr-2"} + hr2 = hr1.DeepCopy() + hr2.Name = hr2NsName.Name + + grNsName = types.NamespacedName{Namespace: "test", Name: "gr-1"} + gr1 = createGRPCRoute("gr-1", "gw-1", "foo.grpc.com") + gr1Updated = gr1.DeepCopy() + gr1Updated.Generation++ + gr2NsName = types.NamespacedName{Namespace: "test", Name: "hr-2"} + gr2 = gr1.DeepCopy() + gr2.Name = gr2NsName.Name + + svcNsName = types.NamespacedName{Namespace: "test", Name: "foo-svc"} + svc = &apiv1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: svcNsName.Namespace, + Name: svcNsName.Name, + }, + } + barSvc = &apiv1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "bar-svc", + }, + } + unrelatedSvc = &apiv1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "unrelated-svc", + }, + } + + sliceNsName = types.NamespacedName{Namespace: "test", Name: "slice"} + slice = &discoveryV1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: sliceNsName.Namespace, + Name: sliceNsName.Name, + Labels: map[string]string{index.KubernetesServiceNameLabel: svc.Name}, + }, + } + barSlice = &discoveryV1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "bar-slice", + Labels: map[string]string{index.KubernetesServiceNameLabel: "bar-svc"}, + }, + } + unrelatedSlice = &discoveryV1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "unrelated-slice", + Labels: map[string]string{index.KubernetesServiceNameLabel: "unrelated-svc"}, + }, + } + + testNs = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Labels: map[string]string{ + "test": "namespace", + }, + }, + } + ns = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns", + Labels: map[string]string{ + "test": "namespace", + }, + }, + } + barNs = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar-ns", + Labels: map[string]string{ + "test": "namespace", + }, + }, + } + unrelatedNS = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unrelated-ns", + Labels: map[string]string{ + "oranges": "bananas", + }, + }, + } + + rgNsName = types.NamespacedName{Namespace: "test", Name: "rg-1"} + + rg1 = &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Name: rgNsName.Name, + Namespace: rgNsName.Namespace, + }, + } + + rg1Updated = rg1.DeepCopy() + rg1Updated.Generation++ + + rg2 = rg1.DeepCopy() + rg2.Name = "rg-2" + + cmNsName = types.NamespacedName{Namespace: "test", Name: "cm-1"} + cm = &apiv1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: cmNsName.Name, + Namespace: cmNsName.Namespace, + }, + Data: map[string]string{ + "ca.crt": "value", + }, + } + cmUpdated = cm.DeepCopy() + cmUpdated.Data["ca.crt"] = "updated-value" + + unrelatedCM = &apiv1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unrelated-cm", + Namespace: "unrelated-ns", + }, + Data: map[string]string{ + "ca.crt": "value", + }, + } + + btlsNsName = types.NamespacedName{Namespace: "test", Name: "btls-1"} + btls = &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: btlsNsName.Name, + Namespace: btlsNsName.Namespace, + Generation: 1, + }, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: "Service", + Name: v1.ObjectName(svc.Name), + }, + }, + }, + Validation: v1alpha3.BackendTLSPolicyValidation{ + CACertificateRefs: []v1.LocalObjectReference{ + { + Name: v1.ObjectName(cm.Name), + }, + }, + }, + }, + } + btlsUpdated = btls.DeepCopy() + + npNsName = types.NamespacedName{Name: "np-1"} + np = &ngfAPIv1alpha2.NginxProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: npNsName.Name, + }, + Spec: ngfAPIv1alpha2.NginxProxySpec{ + Telemetry: &ngfAPIv1alpha2.Telemetry{ + ServiceName: helpers.GetPointer("my-svc"), + }, + }, + } + npUpdated = np.DeepCopy() + }) + // Changing change - a change that makes processor.Process() report changed + // Non-changing change - a change that doesn't do that + // Related resource - a K8s resource that is related to a configured Gateway API resource + // Unrelated resource - a K8s resource that is not related to a configured Gateway API resource + + // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses + // -- this is done in 'Normal cases of processing changes' + Describe("Multiple Gateway API resource changes", Ordered, func() { + It("should report changed after multiple Upserts", func() { + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(testNs) + processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(gr1) + processor.CaptureUpsertChange(rg1) + processor.CaptureUpsertChange(btls) + processor.CaptureUpsertChange(cm) + processor.CaptureUpsertChange(np) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + When("a upsert of updated resources is followed by an upsert of the same generation", func() { + It("should report changed", func() { + // these are changing changes + processor.CaptureUpsertChange(gcUpdated) + processor.CaptureUpsertChange(gw1Updated) + processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(gr1Updated) + processor.CaptureUpsertChange(rg1Updated) + processor.CaptureUpsertChange(btlsUpdated) + processor.CaptureUpsertChange(cmUpdated) + processor.CaptureUpsertChange(npUpdated) + + // there are non-changing changes + processor.CaptureUpsertChange(gcUpdated) + processor.CaptureUpsertChange(gw1Updated) + processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(gr1Updated) + processor.CaptureUpsertChange(rg1Updated) + processor.CaptureUpsertChange(btlsUpdated) + processor.CaptureUpsertChange(cmUpdated) + processor.CaptureUpsertChange(npUpdated) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + It("should report changed after upserting new resources", func() { + // we can't have a second GatewayClass, so we don't add it + processor.CaptureUpsertChange(gw2) + processor.CaptureUpsertChange(hr2) + processor.CaptureUpsertChange(gr2) + processor.CaptureUpsertChange(rg2) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + When("resources are deleted followed by upserts with the same generations", func() { + It("should report changed", func() { + // these are changing changes + processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) + processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) + processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) + processor.CaptureDeleteChange(&v1.GRPCRoute{}, grNsName) + processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) + processor.CaptureDeleteChange(&v1alpha3.BackendTLSPolicy{}, btlsNsName) + processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) + processor.CaptureDeleteChange(&ngfAPIv1alpha2.NginxProxy{}, npNsName) + + // these are non-changing changes + processor.CaptureUpsertChange(gw2) + processor.CaptureUpsertChange(hr2) + processor.CaptureUpsertChange(gr2) + processor.CaptureUpsertChange(rg2) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + It("should report changed after deleting resources", func() { + processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) + processor.CaptureDeleteChange(&v1.HTTPRoute{}, gr2NsName) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + Describe("Deleting non-existing Gateway API resource", func() { + It("should not report changed after deleting non-existing", func() { + processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) + processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) + processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) + processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) + processor.CaptureDeleteChange(&v1.HTTPRoute{}, grNsName) + processor.CaptureDeleteChange(&v1.HTTPRoute{}, gr2NsName) + processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + }) + Describe("Multiple Kubernetes API resource changes", Ordered, func() { + BeforeAll(func() { + // Set up graph + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(testNs) + processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(gr1) + processor.CaptureUpsertChange(secret) + processor.CaptureUpsertChange(barSecret) + processor.CaptureUpsertChange(cm) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + + It("should report changed after multiple Upserts of related resources", func() { + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(ns) + processor.CaptureUpsertChange(secretUpdated) + processor.CaptureUpsertChange(cmUpdated) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + It("should report not changed after multiple Upserts of unrelated resources", func() { + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + When("upserts of related resources are followed by upserts of unrelated resources", func() { + It("should report changed", func() { + // these are changing changes + processor.CaptureUpsertChange(barSvc) + processor.CaptureUpsertChange(barSlice) + processor.CaptureUpsertChange(barNs) + processor.CaptureUpsertChange(barSecretUpdated) + processor.CaptureUpsertChange(cmUpdated) + + // there are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + When("deletes of related resources are followed by upserts of unrelated resources", func() { + It("should report changed", func() { + // these are changing changes + processor.CaptureDeleteChange(&apiv1.Service{}, svcNsName) + processor.CaptureDeleteChange(&discoveryV1.EndpointSlice{}, sliceNsName) + processor.CaptureDeleteChange(&apiv1.Namespace{}, types.NamespacedName{Name: "ns"}) + processor.CaptureDeleteChange(&apiv1.Secret{}, secretNsName) + processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) + + // these are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + }) + Describe("Multiple Kubernetes API and Gateway API resource changes", Ordered, func() { + It("should report changed after multiple Upserts of new and related resources", func() { + // new Gateway API resources + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(testNs) + processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(gr1) + processor.CaptureUpsertChange(rg1) + processor.CaptureUpsertChange(btls) + + // related Kubernetes API resources + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(ns) + processor.CaptureUpsertChange(secret) + processor.CaptureUpsertChange(cm) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + It("should report not changed after multiple Upserts of unrelated resources", func() { + // unrelated Kubernetes API resources + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + It("should report changed after upserting changed resources followed by upserting unrelated resources", + func() { + // these are changing changes + processor.CaptureUpsertChange(gcUpdated) + processor.CaptureUpsertChange(gw1Updated) + processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(gr1Updated) + processor.CaptureUpsertChange(rg1Updated) + processor.CaptureUpsertChange(btlsUpdated) + + // these are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + }) + }) + Describe("Edge cases with panic", func() { + var processor state.ChangeProcessor + + BeforeEach(func() { + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: "test.controller", + GatewayClassName: "my-class", + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), }) - // When("the second HTTPRoute is upserted", func() { - // It("returns populated graph", func() { - // processor.CaptureUpsertChange(hr2) - - // grpcRoute := expGraph.Routes[grpcRouteKey1] - // grpcRoute.Conditions = append(grpcRoute.Conditions, - // staticConds.NewRouteBackendRefRefBackendNotFound( - // "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", - // )) - - // httpRoute := expGraph.Routes[httpRouteKey1] - // httpRoute.Conditions = append(httpRoute.Conditions, - // staticConds.NewRouteBackendRefRefBackendNotFound( - // "spec.rules[0].backendRefs[0].name: Not found: \"service\"", - // )) - - // expGraph.Routes[httpRouteKey2] = expRouteHR2 - // expGraph.Routes[httpRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - // AcceptedHostnames: map[string][]string{}, - // } - // expGraph.Routes[httpRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - // AcceptedHostnames: map[string][]string{}, - // } - // expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - // Source: diffNsTLSSecret, - // CertBundle: diffNsTLSCert, - // } - - // processAndValidateGraph(expGraph) - // }) - // }) - // 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(), - // } - - // expGraph.Routes[grpcRouteKey2] = expRouteGR2 - // expGraph.Routes[grpcRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - // AcceptedHostnames: map[string][]string{}, - // FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - // } - // expGraph.Routes[grpcRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - // AcceptedHostnames: map[string][]string{}, - // FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - // } - - // expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - // Source: diffNsTLSSecret, - // CertBundle: diffNsTLSCert, - // } - - // processAndValidateGraph(expGraph) - // }) - // }) - // 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(), - // } - - // expGraph.Routes[grpcRouteKey2] = expRouteGR2 - // expGraph.Routes[grpcRouteKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - // AcceptedHostnames: map[string][]string{}, - // FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - // } - // expGraph.Routes[grpcRouteKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - // AcceptedHostnames: map[string][]string{}, - // FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - // } - - // expGraph.L4Routes[trKey2] = expRouteTR2 - // expGraph.L4Routes[trKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - // AcceptedHostnames: map[string][]string{}, - // FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - // } - - // expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - // Source: diffNsTLSSecret, - // CertBundle: diffNsTLSCert, - // } - - // processAndValidateGraph(expGraph) - // }) - // }) - // When("the first Gateway is deleted", func() { - // It("returns updated graph", func() { - // processor.CaptureDeleteChange( - // &v1.Gateway{}, - // 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{} - - // processAndValidateGraph(expGraph) - // }) - // }) - // When("the second HTTPRoute is deleted", func() { - // It("returns updated graph", func() { - // processor.CaptureDeleteChange( - // &v1.HTTPRoute{}, - // 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{} - - // processAndValidateGraph(expGraph) - // }) - // }) - // When("the second GRPCRoute is deleted", func() { - // It("returns updated graph", func() { - // processor.CaptureDeleteChange( - // &v1.GRPCRoute{}, - // 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] - - // delete(listener80.Routes, httpRouteKey1) - // delete(listener443.Routes, httpRouteKey1) - // delete(listener80.Routes, grpcRouteKey1) - // delete(listener443.Routes, grpcRouteKey1) - // delete(tlsListener.L4Routes, trKey1) - - // tlsListener.L4Routes[trKey2] = expRouteTR2 - // expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - - // 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{} - - // processAndValidateGraph(expGraph) - // }) - // }) - // When("the second TLSRoute is deleted", func() { - // It("returns updated graph", func() { - // processor.CaptureDeleteChange( - // &v1alpha2.TLSRoute{}, - // 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) - - // 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) - - // expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - // expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} - - // sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) - // listener443.ResolvedSecret = sameNsTLSSecretRef - // expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - // Source: sameNsTLSSecret, - // CertBundle: sameNsTLSCert, - // } - - // expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - // expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - // expGraph.ReferencedServices = nil - - // processAndValidateGraph(expGraph) - // }) - // }) - // When("the GatewayClass is deleted", func() { - // It("returns updated graph", func() { - // processor.CaptureDeleteChange( - // &v1.GatewayClass{}, - // types.NamespacedName{Name: gcName}, - // ) - - // expGraph.GatewayClass = nil - // expGraph.Gateway = &graph.Gateway{ - // Source: gw2, - // Conditions: staticConds.NewGatewayInvalid("GatewayClass doesn't exist"), - // } - // expGraph.DeploymentName.Name = "gateway-2-test-class" - // expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - // expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} - // expGraph.ReferencedSecrets = nil - - // expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - // expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - // expGraph.ReferencedServices = nil - - // processAndValidateGraph(expGraph) - // }) - // }) - // When("the second Gateway is deleted", func() { - // It("returns empty graph", func() { - // processor.CaptureDeleteChange( - // &v1.Gateway{}, - // types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - // ) - - // expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - // expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - // expGraph.ReferencedServices = nil - - // processAndValidateGraph(&graph.Graph{}) - // }) - // }) - // When("the first HTTPRoute is deleted", func() { - // It("returns empty graph", func() { - // processor.CaptureDeleteChange( - // &v1.HTTPRoute{}, - // types.NamespacedName{Namespace: "test", Name: "hr-1"}, - // ) - - // expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - // expGraph.ReferencedServices = nil - - // processAndValidateGraph(&graph.Graph{}) - // }) - // }) - // When("the first GRPCRoute is deleted", func() { - // It("returns empty graph", func() { - // processor.CaptureDeleteChange( - // &v1.GRPCRoute{}, - // types.NamespacedName{Namespace: "test", Name: "gr-1"}, - // ) - - // expRouteGR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - // expGraph.ReferencedServices = nil - - // processAndValidateGraph(&graph.Graph{}) - // }) - // }) - // When("the first TLSRoute is deleted", func() { - // It("returns empty graph", func() { - // processor.CaptureDeleteChange( - // &v1alpha2.TLSRoute{}, - // types.NamespacedName{Namespace: "test", Name: "tr-1"}, - // ) - - // expGraph.ReferencedServices = nil - - // processAndValidateGraph(&graph.Graph{}) - // }) - // }) }) - // Describe("Process services and endpoints", Ordered, func() { - // var ( - // hr1, hr2, hr3, hrInvalidBackendRef, hrMultipleRules *v1.HTTPRoute - // hr1svc, sharedSvc, bazSvc1, bazSvc2, bazSvc3, invalidSvc, notRefSvc *apiv1.Service - // hr1slice1, hr1slice2, noRefSlice, missingSvcNameSlice *discoveryV1.EndpointSlice - // gw *v1.Gateway - // btls *v1alpha3.BackendTLSPolicy - // ) - - // createSvc := func(name string) *apiv1.Service { - // return &apiv1.Service{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: "test", - // Name: name, - // }, - // } - // } - - // createEndpointSlice := func(name string, svcName string) *discoveryV1.EndpointSlice { - // return &discoveryV1.EndpointSlice{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: "test", - // Name: name, - // Labels: map[string]string{index.KubernetesServiceNameLabel: svcName}, - // }, - // } - // } - - // createBackendTLSPolicy := func(name string, svcName string) *v1alpha3.BackendTLSPolicy { - // return &v1alpha3.BackendTLSPolicy{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: "test", - // Name: name, - // }, - // Spec: v1alpha3.BackendTLSPolicySpec{ - // TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ - // { - // LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ - // Kind: v1.Kind("Service"), - // Name: v1.ObjectName(svcName), - // }, - // }, - // }, - // }, - // } - // } - - // BeforeAll(func() { - // testNamespace := v1.Namespace("test") - // kindService := v1.Kind("Service") - // kindInvalid := v1.Kind("Invalid") - - // // backend Refs - // fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) - // baz1NilNamespace := createHTTPBackendRef(&kindService, "baz-svc-v1", &testNamespace) - // barRef := createHTTPBackendRef(&kindService, "bar-svc", nil) - // baz2Ref := createHTTPBackendRef(&kindService, "baz-svc-v2", &testNamespace) - // baz3Ref := createHTTPBackendRef(&kindService, "baz-svc-v3", &testNamespace) - // invalidKindRef := createHTTPBackendRef(&kindInvalid, "bar-svc", &testNamespace) - - // // httproutes - // hr1 = createHTTPRoute("hr1", "gw", "foo.example.com", fooRef) - // hr2 = createHTTPRoute("hr2", "gw", "bar.example.com", barRef) - // // hr3 shares the same backendRef as hr2 - // hr3 = createHTTPRoute("hr3", "gw", "bar.2.example.com", barRef) - // hrInvalidBackendRef = createHTTPRoute("hr-invalid", "gw", "invalid.com", invalidKindRef) - // hrMultipleRules = createRouteWithMultipleRules( - // "hr-multiple-rules", - // "gw", - // "mutli.example.com", - // []v1.HTTPRouteRule{ - // createHTTPRule("/baz-v1", baz1NilNamespace), - // createHTTPRule("/baz-v2", baz2Ref), - // createHTTPRule("/baz-v3", baz3Ref), - // }, - // ) - - // // services - // hr1svc = createSvc("foo-svc") - // sharedSvc = createSvc("bar-svc") // shared between hr2 and hr3 - // invalidSvc = createSvc("invalid") // nsname matches invalid BackendRef - // notRefSvc = createSvc("not-ref") - // bazSvc1 = createSvc("baz-svc-v1") - // bazSvc2 = createSvc("baz-svc-v2") - // bazSvc3 = createSvc("baz-svc-v3") - - // // endpoint slices - // hr1slice1 = createEndpointSlice("hr1-1", "foo-svc") - // hr1slice2 = createEndpointSlice("hr1-2", "foo-svc") - // noRefSlice = createEndpointSlice("no-ref", "no-ref") - // missingSvcNameSlice = createEndpointSlice("missing-svc-name", "") - - // // backendTLSPolicy - // btls = createBackendTLSPolicy("btls", "foo-svc") - - // gw = createGateway("gw", createHTTPListener()) - // processor.CaptureUpsertChange(gc) - // processor.CaptureUpsertChange(gw) - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - - // testProcessChangedVal := func(expChanged state.ChangeType) { - // changed, _ := processor.Process() - // Expect(changed).To(Equal(expChanged)) - // } - - // testUpsertTriggersChange := func(obj client.Object, expChanged state.ChangeType) { - // processor.CaptureUpsertChange(obj) - // testProcessChangedVal(expChanged) - // } - - // testDeleteTriggersChange := func(obj client.Object, nsname types.NamespacedName, expChanged state.ChangeType) { - // processor.CaptureDeleteChange(obj, nsname) - // testProcessChangedVal(expChanged) - // } - - // When("hr1 is added", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(hr1, state.ClusterStateChange) - // }) - // }) - // When("a hr1 service is added", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(hr1svc, state.ClusterStateChange) - // }) - // }) - // When("a backendTLSPolicy is added for referenced service", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(btls, state.ClusterStateChange) - // }) - // }) - // When("an hr1 endpoint slice is added", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(hr1slice1, state.EndpointsOnlyChange) - // }) - // }) - // When("an hr1 service is updated", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(hr1svc, state.ClusterStateChange) - // }) - // }) - // When("another hr1 endpoint slice is added", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) - // }) - // }) - // When("an endpoint slice with a missing svc name label is added", func() { - // It("should not trigger a change", func() { - // testUpsertTriggersChange(missingSvcNameSlice, state.NoChange) - // }) - // }) - // When("an hr1 endpoint slice is deleted", func() { - // It("should trigger a change", func() { - // testDeleteTriggersChange( - // hr1slice1, - // types.NamespacedName{Namespace: hr1slice1.Namespace, Name: hr1slice1.Name}, - // state.EndpointsOnlyChange, - // ) - // }) - // }) - // When("the second hr1 endpoint slice is deleted", func() { - // It("should trigger a change", func() { - // testDeleteTriggersChange( - // hr1slice2, - // types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, - // state.EndpointsOnlyChange, - // ) - // }) - // }) - // When("the second hr1 endpoint slice is recreated", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) - // }) - // }) - // When("hr1 is deleted", func() { - // It("should trigger a change", func() { - // testDeleteTriggersChange( - // hr1, - // types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name}, - // state.ClusterStateChange, - // ) - // }) - // }) - // When("hr1 service is deleted", func() { - // It("should not trigger a change", func() { - // testDeleteTriggersChange( - // hr1svc, - // types.NamespacedName{Namespace: hr1svc.Namespace, Name: hr1svc.Name}, - // state.NoChange, - // ) - // }) - // }) - // When("the second hr1 endpoint slice is deleted", func() { - // It("should not trigger a change", func() { - // testDeleteTriggersChange( - // hr1slice2, - // types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, - // state.NoChange, - // ) - // }) - // }) - // When("hr2 is added", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(hr2, state.ClusterStateChange) - // }) - // }) - // When("a hr3, that shares a backend service with hr2, is added", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(hr3, state.ClusterStateChange) - // }) - // }) - // When("sharedSvc, a service referenced by both hr2 and hr3, is added", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) - // }) - // }) - // When("hr2 is deleted", func() { - // It("should trigger a change", func() { - // testDeleteTriggersChange( - // hr2, - // types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name}, - // state.ClusterStateChange, - // ) - // }) - // }) - // When("sharedSvc is deleted", func() { - // It("should trigger a change", func() { - // testDeleteTriggersChange( - // sharedSvc, - // types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, - // state.ClusterStateChange, - // ) - // }) - // }) - // When("sharedSvc is recreated", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) - // }) - // }) - // When("hr3 is deleted", func() { - // It("should trigger a change", func() { - // testDeleteTriggersChange( - // hr3, - // types.NamespacedName{Namespace: hr3.Namespace, Name: hr3.Name}, - // state.ClusterStateChange, - // ) - // }) - // }) - // When("sharedSvc is deleted", func() { - // It("should not trigger a change", func() { - // testDeleteTriggersChange( - // sharedSvc, - // types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, - // state.NoChange, - // ) - // }) - // }) - // When("a service that is not referenced by any route is added", func() { - // It("should not trigger a change", func() { - // testUpsertTriggersChange(notRefSvc, state.NoChange) - // }) - // }) - // When("a route with an invalid backend ref type is added", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(hrInvalidBackendRef, state.ClusterStateChange) - // }) - // }) - // When("a service with a namespace name that matches invalid backend ref is added", func() { - // It("should not trigger a change", func() { - // testUpsertTriggersChange(invalidSvc, state.NoChange) - // }) - // }) - // When("an endpoint slice that is not owned by a referenced service is added", func() { - // It("should not trigger a change", func() { - // testUpsertTriggersChange(noRefSlice, state.NoChange) - // }) - // }) - // When("an endpoint slice that is not owned by a referenced service is deleted", func() { - // It("should not trigger a change", func() { - // testDeleteTriggersChange( - // noRefSlice, - // types.NamespacedName{Namespace: noRefSlice.Namespace, Name: noRefSlice.Name}, - // state.NoChange, - // ) - // }) - // }) - // Context("processing a route with multiple rules and three unique backend services", func() { - // When("route is added", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(hrMultipleRules, state.ClusterStateChange) - // }) - // }) - // When("first referenced service is added", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) - // }) - // }) - // When("second referenced service is added", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(bazSvc2, state.ClusterStateChange) - // }) - // }) - // When("first referenced service is deleted", func() { - // It("should trigger a change", func() { - // testDeleteTriggersChange( - // bazSvc1, - // types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, - // state.ClusterStateChange, - // ) - // }) - // }) - // When("first referenced service is recreated", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) - // }) - // }) - // When("third referenced service is added", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) - // }) - // }) - // When("third referenced service is updated", func() { - // It("should trigger a change", func() { - // testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) - // }) - // }) - // When("route is deleted", func() { - // It("should trigger a change", func() { - // testDeleteTriggersChange( - // hrMultipleRules, - // types.NamespacedName{ - // Namespace: hrMultipleRules.Namespace, - // Name: hrMultipleRules.Name, - // }, - // state.ClusterStateChange, - // ) - // }) - // }) - // When("first referenced service is deleted", func() { - // It("should not trigger a change", func() { - // testDeleteTriggersChange( - // bazSvc1, - // types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, - // state.NoChange, - // ) - // }) - // }) - // When("second referenced service is deleted", func() { - // It("should not trigger a change", func() { - // testDeleteTriggersChange( - // bazSvc2, - // types.NamespacedName{Namespace: bazSvc2.Namespace, Name: bazSvc2.Name}, - // state.NoChange, - // ) - // }) - // }) - // When("final referenced service is deleted", func() { - // It("should not trigger a change", func() { - // testDeleteTriggersChange( - // bazSvc3, - // types.NamespacedName{Namespace: bazSvc3.Namespace, Name: bazSvc3.Name}, - // state.NoChange, - // ) - // }) - // }) - // }) - // }) - - // Describe("namespace changes", Ordered, func() { - // var ( - // ns, nsDifferentLabels, nsNoLabels *apiv1.Namespace - // gw *v1.Gateway - // ) - - // BeforeAll(func() { - // ns = &apiv1.Namespace{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "ns", - // Labels: map[string]string{ - // "app": "allowed", - // }, - // }, - // } - // nsDifferentLabels = &apiv1.Namespace{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "ns-different-labels", - // Labels: map[string]string{ - // "oranges": "bananas", - // }, - // }, - // } - // nsNoLabels = &apiv1.Namespace{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "no-labels", - // }, - // } - // gw = &v1.Gateway{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "gw", - // }, - // Spec: v1.GatewaySpec{ - // GatewayClassName: gcName, - // Listeners: []v1.Listener{ - // { - // Port: 80, - // Protocol: v1.HTTPProtocolType, - // AllowedRoutes: &v1.AllowedRoutes{ - // Namespaces: &v1.RouteNamespaces{ - // From: helpers.GetPointer(v1.NamespacesFromSelector), - // Selector: &metav1.LabelSelector{ - // MatchLabels: map[string]string{ - // "app": "allowed", - // }, - // }, - // }, - // }, - // }, - // }, - // }, - // } - // processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - // GatewayCtlrName: controllerName, - // GatewayClassName: gcName, - // Logger: logr.Discard(), - // Validators: createAlwaysValidValidators(), - // MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - // }) - // processor.CaptureUpsertChange(gc) - // processor.CaptureUpsertChange(gw) - // processor.Process() - // }) - - // When("a namespace is created that is not linked to a listener", func() { - // It("does not trigger an update", func() { - // processor.CaptureUpsertChange(nsNoLabels) - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.NoChange)) - // }) - // }) - // When("a namespace is created that is linked to a listener", func() { - // It("triggers an update", func() { - // processor.CaptureUpsertChange(ns) - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // }) - // When("a namespace is deleted that is not linked to a listener", func() { - // It("does not trigger an update", func() { - // processor.CaptureDeleteChange(nsNoLabels, types.NamespacedName{Name: "no-labels"}) - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.NoChange)) - // }) - // }) - // When("a namespace is deleted that is linked to a listener", func() { - // It("triggers an update", func() { - // processor.CaptureDeleteChange(ns, types.NamespacedName{Name: "ns"}) - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // }) - // When("a namespace that is not linked to a listener has its labels changed to match a listener", func() { - // It("triggers an update", func() { - // processor.CaptureUpsertChange(nsDifferentLabels) - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.NoChange)) - - // nsDifferentLabels.Labels = map[string]string{ - // "app": "allowed", - // } - // processor.CaptureUpsertChange(nsDifferentLabels) - // changed, _ = processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // }) - // When( - // "a namespace that is linked to a listener has its labels changed to no longer match a listener", - // func() { - // It("triggers an update", func() { - // nsDifferentLabels.Labels = map[string]string{ - // "oranges": "bananas", - // } - // processor.CaptureUpsertChange(nsDifferentLabels) - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // }, - // ) - // When("a gateway changes its listener's labels", func() { - // It("triggers an update when a namespace that matches the new labels is created", func() { - // gwChangedLabel := gw.DeepCopy() - // gwChangedLabel.Spec.Listeners[0].AllowedRoutes.Namespaces.Selector.MatchLabels = map[string]string{ - // "oranges": "bananas", - // } - // gwChangedLabel.Generation++ - // processor.CaptureUpsertChange(gwChangedLabel) - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - - // // After changing the gateway's labels and generation, the processor should be marked to update - // // the nginx configuration and build a new graph. When processor.Process() gets called, - // // the nginx configuration gets updated and a new graph is built with an updated - // // referencedNamespaces. Thus, when the namespace "ns" is upserted with labels that no longer match - // // the new labels on the gateway, it would not trigger a change as the namespace would no longer - // // be in the updated referencedNamespaces and the labels no longer match the new labels on the - // // gateway. - // processor.CaptureUpsertChange(ns) - // changed, _ = processor.Process() - // Expect(changed).To(Equal(state.NoChange)) - - // processor.CaptureUpsertChange(nsDifferentLabels) - // changed, _ = processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // }) - // When("a namespace that is not linked to a listener has its labels removed", func() { - // It("does not trigger an update", func() { - // ns.Labels = nil - // processor.CaptureUpsertChange(ns) - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.NoChange)) - // }) - // }) - // When("a namespace that is linked to a listener has its labels removed", func() { - // It("triggers an update when labels are removed", func() { - // nsDifferentLabels.Labels = nil - // processor.CaptureUpsertChange(nsDifferentLabels) - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // }) - // }) - - // Describe("NginxProxy resource changes", Ordered, func() { - // Context("referenced by a GatewayClass", func() { - // paramGC := gc.DeepCopy() - // paramGC.Spec.ParametersRef = &v1beta1.ParametersReference{ - // Group: ngfAPIv1alpha1.GroupName, - // Kind: kinds.NginxProxy, - // Name: "np", - // Namespace: helpers.GetPointer[v1.Namespace]("test"), - // } - - // np := &ngfAPIv1alpha2.NginxProxy{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "np", - // Namespace: "test", - // }, - // } - - // npUpdated := &ngfAPIv1alpha2.NginxProxy{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "np", - // Namespace: "test", - // }, - // Spec: ngfAPIv1alpha2.NginxProxySpec{ - // Telemetry: &ngfAPIv1alpha2.Telemetry{ - // Exporter: &ngfAPIv1alpha2.TelemetryExporter{ - // Endpoint: helpers.GetPointer("my-svc:123"), - // BatchSize: helpers.GetPointer(int32(512)), - // BatchCount: helpers.GetPointer(int32(4)), - // Interval: helpers.GetPointer(ngfAPIv1alpha1.Duration("5s")), - // }, - // }, - // }, - // } - // It("handles upserts for an NginxProxy", func() { - // processor.CaptureUpsertChange(np) - // processor.CaptureUpsertChange(paramGC) - - // changed, graph := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // Expect(graph.GatewayClass.NginxProxy.Source).To(Equal(np)) - // }) - // It("captures changes for an NginxProxy", func() { - // processor.CaptureUpsertChange(npUpdated) - // processor.CaptureUpsertChange(paramGC) - - // changed, graph := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // Expect(graph.GatewayClass.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.GatewayClass.NginxProxy).To(BeNil()) - // }) - // }) - // Context("referenced by a Gateway", func() { - // paramGW := &v1.Gateway{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: "test", - // Name: "param-gw", - // Generation: 1, - // }, - // Spec: v1.GatewaySpec{ - // GatewayClassName: gcName, - // Listeners: []v1.Listener{ - // { - // Name: httpListenerName, - // Hostname: nil, - // Port: 80, - // Protocol: v1.HTTPProtocolType, - // }, - // }, - // Infrastructure: &v1.GatewayInfrastructure{ - // ParametersRef: &v1.LocalParametersReference{ - // Group: ngfAPIv1alpha1.GroupName, - // Kind: kinds.NginxProxy, - // Name: "np-gw", - // }, - // }, - // }, - // } - - // np := &ngfAPIv1alpha2.NginxProxy{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "np-gw", - // Namespace: "test", - // }, - // } - - // npUpdated := &ngfAPIv1alpha2.NginxProxy{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "np-gw", - // Namespace: "test", - // }, - // Spec: ngfAPIv1alpha2.NginxProxySpec{ - // Telemetry: &ngfAPIv1alpha2.Telemetry{ - // Exporter: &ngfAPIv1alpha2.TelemetryExporter{ - // Endpoint: helpers.GetPointer("my-svc:123"), - // BatchSize: helpers.GetPointer(int32(512)), - // BatchCount: helpers.GetPointer(int32(4)), - // Interval: helpers.GetPointer(ngfAPIv1alpha1.Duration("5s")), - // }, - // }, - // }, - // } - // It("handles upserts for an NginxProxy", func() { - // processor.CaptureUpsertChange(np) - // processor.CaptureUpsertChange(paramGW) - - // changed, graph := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // Expect(graph.Gateway.NginxProxy.Source).To(Equal(np)) - // }) - // It("captures changes for an NginxProxy", func() { - // processor.CaptureUpsertChange(npUpdated) - // processor.CaptureUpsertChange(paramGW) - - // changed, graph := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // Expect(graph.Gateway.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()) - // }) - // }) - // }) - - // Describe("NGF Policy resource changes", Ordered, func() { - // var ( - // gw *v1.Gateway - // route *v1.HTTPRoute - // svc *apiv1.Service - // csp, cspUpdated *ngfAPIv1alpha1.ClientSettingsPolicy - // obs, obsUpdated *ngfAPIv1alpha2.ObservabilityPolicy - // usp, uspUpdated *ngfAPIv1alpha1.UpstreamSettingsPolicy - // cspKey, obsKey, uspKey graph.PolicyKey - // ) - - // BeforeAll(func() { - // processor.CaptureUpsertChange(gc) - // changed, newGraph := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // Expect(newGraph.GatewayClass.Source).To(Equal(gc)) - // Expect(newGraph.NGFPolicies).To(BeEmpty()) - - // gw = createGateway("gw", createHTTPListener()) - // route = createHTTPRoute( - // "hr-1", - // "gw", - // "foo.example.com", - // v1.HTTPBackendRef{ - // BackendRef: v1.BackendRef{ - // BackendObjectReference: v1.BackendObjectReference{ - // Group: helpers.GetPointer[v1.Group](""), - // Kind: helpers.GetPointer[v1.Kind](kinds.Service), - // Name: "svc", - // Port: helpers.GetPointer[v1.PortNumber](80), - // }, - // }, - // }, - // ) - - // svc = &apiv1.Service{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "svc", - // Namespace: "test", - // }, - // } - - // csp = &ngfAPIv1alpha1.ClientSettingsPolicy{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "csp", - // Namespace: "test", - // }, - // Spec: ngfAPIv1alpha1.ClientSettingsPolicySpec{ - // TargetRef: v1alpha2.LocalPolicyTargetReference{ - // Group: v1.GroupName, - // Kind: kinds.Gateway, - // Name: "gw", - // }, - // Body: &ngfAPIv1alpha1.ClientBody{ - // MaxSize: helpers.GetPointer[ngfAPIv1alpha1.Size]("10m"), - // }, - // }, - // } - - // cspUpdated = csp.DeepCopy() - // cspUpdated.Spec.Body.MaxSize = helpers.GetPointer[ngfAPIv1alpha1.Size]("20m") - - // cspKey = graph.PolicyKey{ - // NsName: types.NamespacedName{Name: "csp", Namespace: "test"}, - // GVK: schema.GroupVersionKind{ - // Group: ngfAPIv1alpha1.GroupName, - // Kind: kinds.ClientSettingsPolicy, - // Version: "v1alpha1", - // }, - // } - - // obs = &ngfAPIv1alpha2.ObservabilityPolicy{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "obs", - // Namespace: "test", - // }, - // Spec: ngfAPIv1alpha2.ObservabilityPolicySpec{ - // TargetRefs: []v1alpha2.LocalPolicyTargetReference{ - // { - // Group: v1.GroupName, - // Kind: kinds.HTTPRoute, - // Name: "hr-1", - // }, - // }, - // Tracing: &ngfAPIv1alpha2.Tracing{ - // Strategy: ngfAPIv1alpha2.TraceStrategyRatio, - // }, - // }, - // } - - // obsUpdated = obs.DeepCopy() - // obsUpdated.Spec.Tracing.Strategy = ngfAPIv1alpha2.TraceStrategyParent - - // obsKey = graph.PolicyKey{ - // NsName: types.NamespacedName{Name: "obs", Namespace: "test"}, - // GVK: schema.GroupVersionKind{ - // Group: ngfAPIv1alpha1.GroupName, - // Kind: kinds.ObservabilityPolicy, - // Version: "v1alpha2", - // }, - // } - - // usp = &ngfAPIv1alpha1.UpstreamSettingsPolicy{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "usp", - // Namespace: "test", - // }, - // Spec: ngfAPIv1alpha1.UpstreamSettingsPolicySpec{ - // ZoneSize: helpers.GetPointer[ngfAPIv1alpha1.Size]("10m"), - // TargetRefs: []v1alpha2.LocalPolicyTargetReference{ - // { - // Group: "core", - // Kind: kinds.Service, - // Name: "svc", - // }, - // }, - // }, - // } - - // uspUpdated = usp.DeepCopy() - // uspUpdated.Spec.ZoneSize = helpers.GetPointer[ngfAPIv1alpha1.Size]("20m") - - // uspKey = graph.PolicyKey{ - // NsName: types.NamespacedName{Name: "usp", Namespace: "test"}, - // GVK: schema.GroupVersionKind{ - // Group: ngfAPIv1alpha1.GroupName, - // Kind: kinds.UpstreamSettingsPolicy, - // Version: "v1alpha1", - // }, - // } - // }) - - // /* - // NOTE: When adding a new NGF policy to the change processor, - // update the following tests to make sure that the change processor can track changes for multiple NGF - // policies. - // */ - - // When("a policy is created that references a resource that is not in the last graph", func() { - // It("reports no changes", func() { - // processor.CaptureUpsertChange(csp) - // processor.CaptureUpsertChange(obs) - // processor.CaptureUpsertChange(usp) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.NoChange)) - // }) - // }) - // When("the resource the policy references is created", func() { - // It("populates the graph with the policy", func() { - // processor.CaptureUpsertChange(gw) - - // changed, graph := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // Expect(graph.NGFPolicies).To(HaveKey(cspKey)) - // Expect(graph.NGFPolicies[cspKey].Source).To(Equal(csp)) - // Expect(graph.NGFPolicies).ToNot(HaveKey(obsKey)) - - // processor.CaptureUpsertChange(route) - // changed, graph = processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // Expect(graph.NGFPolicies).To(HaveKey(obsKey)) - // Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obs)) - - // processor.CaptureUpsertChange(svc) - // changed, graph = processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // Expect(graph.NGFPolicies).To(HaveKey(uspKey)) - // Expect(graph.NGFPolicies[uspKey].Source).To(Equal(usp)) - // }) - // }) - // When("the policy is updated", func() { - // It("captures changes for a policy", func() { - // processor.CaptureUpsertChange(cspUpdated) - // processor.CaptureUpsertChange(obsUpdated) - // processor.CaptureUpsertChange(uspUpdated) - - // changed, graph := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // Expect(graph.NGFPolicies).To(HaveKey(cspKey)) - // Expect(graph.NGFPolicies[cspKey].Source).To(Equal(cspUpdated)) - // Expect(graph.NGFPolicies).To(HaveKey(obsKey)) - // Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obsUpdated)) - // Expect(graph.NGFPolicies).To(HaveKey(uspKey)) - // Expect(graph.NGFPolicies[uspKey].Source).To(Equal(uspUpdated)) - // }) - // }) - // When("the policy is deleted", func() { - // It("removes the policy from the graph", func() { - // processor.CaptureDeleteChange(&ngfAPIv1alpha1.ClientSettingsPolicy{}, client.ObjectKeyFromObject(csp)) - // processor.CaptureDeleteChange(&ngfAPIv1alpha2.ObservabilityPolicy{}, client.ObjectKeyFromObject(obs)) - // processor.CaptureDeleteChange(&ngfAPIv1alpha1.UpstreamSettingsPolicy{}, client.ObjectKeyFromObject(usp)) - - // changed, graph := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // Expect(graph.NGFPolicies).To(BeEmpty()) - // }) - // }) - // }) - - // Describe("SnippetsFilter resource changed", Ordered, func() { - // sfNsName := types.NamespacedName{ - // Name: "sf", - // Namespace: "test", - // } - - // sf := &ngfAPIv1alpha1.SnippetsFilter{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: sfNsName.Name, - // Namespace: sfNsName.Namespace, - // }, - // Spec: ngfAPIv1alpha1.SnippetsFilterSpec{ - // Snippets: []ngfAPIv1alpha1.Snippet{ - // { - // Context: ngfAPIv1alpha1.NginxContextMain, - // Value: "main snippet", - // }, - // }, - // }, - // } - - // sfUpdated := &ngfAPIv1alpha1.SnippetsFilter{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: sfNsName.Name, - // Namespace: sfNsName.Namespace, - // }, - // Spec: ngfAPIv1alpha1.SnippetsFilterSpec{ - // Snippets: []ngfAPIv1alpha1.Snippet{ - // { - // Context: ngfAPIv1alpha1.NginxContextMain, - // Value: "main snippet", - // }, - // { - // Context: ngfAPIv1alpha1.NginxContextHTTP, - // Value: "http snippet", - // }, - // }, - // }, - // } - // It("handles upserts for a SnippetsFilter", func() { - // processor.CaptureUpsertChange(sf) - - // changed, graph := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - - // processedSf, exists := graph.SnippetsFilters[sfNsName] - // Expect(exists).To(BeTrue()) - // Expect(processedSf.Source).To(Equal(sf)) - // Expect(processedSf.Valid).To(BeTrue()) - // }) - // It("captures changes for a SnippetsFilter", func() { - // processor.CaptureUpsertChange(sfUpdated) - - // changed, graph := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - - // processedSf, exists := graph.SnippetsFilters[sfNsName] - // Expect(exists).To(BeTrue()) - // Expect(processedSf.Source).To(Equal(sfUpdated)) - // Expect(processedSf.Valid).To(BeTrue()) - // }) - // It("handles deletes for a SnippetsFilter", func() { - // processor.CaptureDeleteChange(sfUpdated, sfNsName) - - // changed, graph := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // Expect(graph.SnippetsFilters).To(BeEmpty()) - // }) - // }) + DescribeTable("CaptureUpsertChange must panic", + func(obj client.Object) { + process := func() { + processor.CaptureUpsertChange(obj) + } + Expect(process).Should(Panic()) + }, + Entry( + "an unsupported resource", + &v1alpha2.TCPRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "tcp"}}, + ), + Entry( + "nil resource", + nil, + ), + ) + + DescribeTable( + "CaptureDeleteChange must panic", + func(resourceType ngftypes.ObjectType, nsname types.NamespacedName) { + process := func() { + processor.CaptureDeleteChange(resourceType, nsname) + } + Expect(process).Should(Panic()) + }, + Entry( + "an unsupported resource", + &v1alpha2.TCPRoute{}, + types.NamespacedName{Namespace: "test", Name: "tcp"}, + ), + Entry( + "nil resource type", + nil, + types.NamespacedName{Namespace: "test", Name: "resource"}, + ), + ) }) - // Describe("Ensuring non-changing changes don't override previously changing changes", func() { - // // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses - // // -- this is done in 'Normal cases of processing changes' - - // var ( - // processor *state.ChangeProcessorImpl - // gcNsName, gwNsName, hrNsName, hr2NsName, grNsName, gr2NsName, rgNsName, svcNsName types.NamespacedName - // sliceNsName, secretNsName, cmNsName, btlsNsName, npNsName types.NamespacedName - // gc, gcUpdated *v1.GatewayClass - // gw1, gw1Updated, gw2 *v1.Gateway - // hr1, hr1Updated, hr2 *v1.HTTPRoute - // gr1, gr1Updated, gr2 *v1.GRPCRoute - // rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant - // svc, barSvc, unrelatedSvc *apiv1.Service - // slice, barSlice, unrelatedSlice *discoveryV1.EndpointSlice - // ns, unrelatedNS, testNs, barNs *apiv1.Namespace - // secret, secretUpdated, unrelatedSecret, barSecret, barSecretUpdated *apiv1.Secret - // cm, cmUpdated, unrelatedCM *apiv1.ConfigMap - // btls, btlsUpdated *v1alpha3.BackendTLSPolicy - // np, npUpdated *ngfAPIv1alpha2.NginxProxy - // ) - - // BeforeEach(OncePerOrdered, func() { - // processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - // GatewayCtlrName: "test.controller", - // GatewayClassName: "test-class", - // Validators: createAlwaysValidValidators(), - // MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - // }) - - // secretNsName = types.NamespacedName{Namespace: "test", Name: "tls-secret"} - // secret = &apiv1.Secret{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: secretNsName.Name, - // Namespace: secretNsName.Namespace, - // Generation: 1, - // }, - // Type: apiv1.SecretTypeTLS, - // Data: map[string][]byte{ - // apiv1.TLSCertKey: cert, - // apiv1.TLSPrivateKeyKey: key, - // }, - // } - // secretUpdated = secret.DeepCopy() - // secretUpdated.Generation++ - // barSecret = &apiv1.Secret{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "bar-secret", - // Namespace: "test", - // Generation: 1, - // }, - // Type: apiv1.SecretTypeTLS, - // Data: map[string][]byte{ - // apiv1.TLSCertKey: cert, - // apiv1.TLSPrivateKeyKey: key, - // }, - // } - // barSecretUpdated = barSecret.DeepCopy() - // barSecretUpdated.Generation++ - // unrelatedSecret = &apiv1.Secret{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "unrelated-tls-secret", - // Namespace: "unrelated-ns", - // Generation: 1, - // }, - // Type: apiv1.SecretTypeTLS, - // Data: map[string][]byte{ - // apiv1.TLSCertKey: cert, - // apiv1.TLSPrivateKeyKey: key, - // }, - // } - - // gcNsName = types.NamespacedName{Name: "test-class"} - - // gc = &v1.GatewayClass{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: gcNsName.Name, - // }, - // Spec: v1.GatewayClassSpec{ - // ControllerName: "test.controller", - // }, - // } - - // gcUpdated = gc.DeepCopy() - // gcUpdated.Generation++ - - // gwNsName = types.NamespacedName{Namespace: "test", Name: "gw-1"} - - // gw1 = &v1.Gateway{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "gw-1", - // Namespace: "test", - // Generation: 1, - // }, - // Spec: v1.GatewaySpec{ - // GatewayClassName: gcName, - // Listeners: []v1.Listener{ - // { - // Name: httpListenerName, - // Hostname: nil, - // Port: 80, - // Protocol: v1.HTTPProtocolType, - // AllowedRoutes: &v1.AllowedRoutes{ - // Namespaces: &v1.RouteNamespaces{ - // From: helpers.GetPointer(v1.NamespacesFromSelector), - // Selector: &metav1.LabelSelector{ - // MatchLabels: map[string]string{ - // "test": "namespace", - // }, - // }, - // }, - // }, - // }, - // { - // Name: httpsListenerName, - // Hostname: nil, - // Port: 443, - // Protocol: v1.HTTPSProtocolType, - // TLS: &v1.GatewayTLSConfig{ - // Mode: helpers.GetPointer(v1.TLSModeTerminate), - // CertificateRefs: []v1.SecretObjectReference{ - // { - // Kind: (*v1.Kind)(helpers.GetPointer("Secret")), - // Name: v1.ObjectName(secret.Name), - // Namespace: (*v1.Namespace)(&secret.Namespace), - // }, - // }, - // }, - // }, - // { - // Name: "listener-500-1", - // Hostname: nil, - // Port: 500, - // Protocol: v1.HTTPSProtocolType, - // TLS: &v1.GatewayTLSConfig{ - // Mode: helpers.GetPointer(v1.TLSModeTerminate), - // CertificateRefs: []v1.SecretObjectReference{ - // { - // Kind: (*v1.Kind)(helpers.GetPointer("Secret")), - // Name: v1.ObjectName(barSecret.Name), - // Namespace: (*v1.Namespace)(&barSecret.Namespace), - // }, - // }, - // }, - // }, - // }, - // }, - // } - - // gw1Updated = gw1.DeepCopy() - // gw1Updated.Generation++ - - // gw2 = gw1.DeepCopy() - // gw2.Name = "gw-2" - - // testNamespace := v1.Namespace("test") - // kindService := v1.Kind("Service") - // fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) - // barRef := createHTTPBackendRef(&kindService, "bar-svc", &testNamespace) - - // hrNsName = types.NamespacedName{Namespace: "test", Name: "hr-1"} - // hr1 = createHTTPRoute("hr-1", "gw-1", "foo.example.com", fooRef, barRef) - // hr1Updated = hr1.DeepCopy() - // hr1Updated.Generation++ - // hr2NsName = types.NamespacedName{Namespace: "test", Name: "hr-2"} - // hr2 = hr1.DeepCopy() - // hr2.Name = hr2NsName.Name - - // grNsName = types.NamespacedName{Namespace: "test", Name: "gr-1"} - // gr1 = createGRPCRoute("gr-1", "gw-1", "foo.grpc.com") - // gr1Updated = gr1.DeepCopy() - // gr1Updated.Generation++ - // gr2NsName = types.NamespacedName{Namespace: "test", Name: "hr-2"} - // gr2 = gr1.DeepCopy() - // gr2.Name = gr2NsName.Name - - // svcNsName = types.NamespacedName{Namespace: "test", Name: "foo-svc"} - // svc = &apiv1.Service{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: svcNsName.Namespace, - // Name: svcNsName.Name, - // }, - // } - // barSvc = &apiv1.Service{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: "test", - // Name: "bar-svc", - // }, - // } - // unrelatedSvc = &apiv1.Service{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: "test", - // Name: "unrelated-svc", - // }, - // } - - // sliceNsName = types.NamespacedName{Namespace: "test", Name: "slice"} - // slice = &discoveryV1.EndpointSlice{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: sliceNsName.Namespace, - // Name: sliceNsName.Name, - // Labels: map[string]string{index.KubernetesServiceNameLabel: svc.Name}, - // }, - // } - // barSlice = &discoveryV1.EndpointSlice{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: "test", - // Name: "bar-slice", - // Labels: map[string]string{index.KubernetesServiceNameLabel: "bar-svc"}, - // }, - // } - // unrelatedSlice = &discoveryV1.EndpointSlice{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: "test", - // Name: "unrelated-slice", - // Labels: map[string]string{index.KubernetesServiceNameLabel: "unrelated-svc"}, - // }, - // } - - // testNs = &apiv1.Namespace{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "test", - // Labels: map[string]string{ - // "test": "namespace", - // }, - // }, - // } - // ns = &apiv1.Namespace{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "ns", - // Labels: map[string]string{ - // "test": "namespace", - // }, - // }, - // } - // barNs = &apiv1.Namespace{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "bar-ns", - // Labels: map[string]string{ - // "test": "namespace", - // }, - // }, - // } - // unrelatedNS = &apiv1.Namespace{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "unrelated-ns", - // Labels: map[string]string{ - // "oranges": "bananas", - // }, - // }, - // } - - // rgNsName = types.NamespacedName{Namespace: "test", Name: "rg-1"} - - // rg1 = &v1beta1.ReferenceGrant{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: rgNsName.Name, - // Namespace: rgNsName.Namespace, - // }, - // } - - // rg1Updated = rg1.DeepCopy() - // rg1Updated.Generation++ - - // rg2 = rg1.DeepCopy() - // rg2.Name = "rg-2" - - // cmNsName = types.NamespacedName{Namespace: "test", Name: "cm-1"} - // cm = &apiv1.ConfigMap{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: cmNsName.Name, - // Namespace: cmNsName.Namespace, - // }, - // Data: map[string]string{ - // "ca.crt": "value", - // }, - // } - // cmUpdated = cm.DeepCopy() - // cmUpdated.Data["ca.crt"] = "updated-value" - - // unrelatedCM = &apiv1.ConfigMap{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: "unrelated-cm", - // Namespace: "unrelated-ns", - // }, - // Data: map[string]string{ - // "ca.crt": "value", - // }, - // } - - // btlsNsName = types.NamespacedName{Namespace: "test", Name: "btls-1"} - // btls = &v1alpha3.BackendTLSPolicy{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: btlsNsName.Name, - // Namespace: btlsNsName.Namespace, - // Generation: 1, - // }, - // Spec: v1alpha3.BackendTLSPolicySpec{ - // TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ - // { - // LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ - // Kind: "Service", - // Name: v1.ObjectName(svc.Name), - // }, - // }, - // }, - // Validation: v1alpha3.BackendTLSPolicyValidation{ - // CACertificateRefs: []v1.LocalObjectReference{ - // { - // Name: v1.ObjectName(cm.Name), - // }, - // }, - // }, - // }, - // } - // btlsUpdated = btls.DeepCopy() - - // npNsName = types.NamespacedName{Name: "np-1"} - // np = &ngfAPIv1alpha2.NginxProxy{ - // ObjectMeta: metav1.ObjectMeta{ - // Name: npNsName.Name, - // }, - // Spec: ngfAPIv1alpha2.NginxProxySpec{ - // Telemetry: &ngfAPIv1alpha2.Telemetry{ - // ServiceName: helpers.GetPointer("my-svc"), - // }, - // }, - // } - // npUpdated = np.DeepCopy() - // }) - // // Changing change - a change that makes processor.Process() report changed - // // Non-changing change - a change that doesn't do that - // // Related resource - a K8s resource that is related to a configured Gateway API resource - // // Unrelated resource - a K8s resource that is not related to a configured Gateway API resource - - // // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses - // // -- this is done in 'Normal cases of processing changes' - // Describe("Multiple Gateway API resource changes", Ordered, func() { - // It("should report changed after multiple Upserts", func() { - // processor.CaptureUpsertChange(gc) - // processor.CaptureUpsertChange(gw1) - // processor.CaptureUpsertChange(testNs) - // processor.CaptureUpsertChange(hr1) - // processor.CaptureUpsertChange(gr1) - // processor.CaptureUpsertChange(rg1) - // processor.CaptureUpsertChange(btls) - // processor.CaptureUpsertChange(cm) - // processor.CaptureUpsertChange(np) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // When("a upsert of updated resources is followed by an upsert of the same generation", func() { - // It("should report changed", func() { - // // these are changing changes - // processor.CaptureUpsertChange(gcUpdated) - // processor.CaptureUpsertChange(gw1Updated) - // processor.CaptureUpsertChange(hr1Updated) - // processor.CaptureUpsertChange(gr1Updated) - // processor.CaptureUpsertChange(rg1Updated) - // processor.CaptureUpsertChange(btlsUpdated) - // processor.CaptureUpsertChange(cmUpdated) - // processor.CaptureUpsertChange(npUpdated) - - // // there are non-changing changes - // processor.CaptureUpsertChange(gcUpdated) - // processor.CaptureUpsertChange(gw1Updated) - // processor.CaptureUpsertChange(hr1Updated) - // processor.CaptureUpsertChange(gr1Updated) - // processor.CaptureUpsertChange(rg1Updated) - // processor.CaptureUpsertChange(btlsUpdated) - // processor.CaptureUpsertChange(cmUpdated) - // processor.CaptureUpsertChange(npUpdated) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // }) - // It("should report changed after upserting new resources", func() { - // // we can't have a second GatewayClass, so we don't add it - // processor.CaptureUpsertChange(gw2) - // processor.CaptureUpsertChange(hr2) - // processor.CaptureUpsertChange(gr2) - // processor.CaptureUpsertChange(rg2) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // When("resources are deleted followed by upserts with the same generations", func() { - // It("should report changed", func() { - // // these are changing changes - // processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) - // processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) - // processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) - // processor.CaptureDeleteChange(&v1.GRPCRoute{}, grNsName) - // processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) - // processor.CaptureDeleteChange(&v1alpha3.BackendTLSPolicy{}, btlsNsName) - // processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) - // processor.CaptureDeleteChange(&ngfAPIv1alpha2.NginxProxy{}, npNsName) - - // // these are non-changing changes - // processor.CaptureUpsertChange(gw2) - // processor.CaptureUpsertChange(hr2) - // processor.CaptureUpsertChange(gr2) - // processor.CaptureUpsertChange(rg2) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // }) - // It("should report changed after deleting resources", func() { - // processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) - // processor.CaptureDeleteChange(&v1.HTTPRoute{}, gr2NsName) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // }) - // Describe("Deleting non-existing Gateway API resource", func() { - // It("should not report changed after deleting non-existing", func() { - // processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) - // processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) - // processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) - // processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) - // processor.CaptureDeleteChange(&v1.HTTPRoute{}, grNsName) - // processor.CaptureDeleteChange(&v1.HTTPRoute{}, gr2NsName) - // processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.NoChange)) - // }) - // }) - // Describe("Multiple Kubernetes API resource changes", Ordered, func() { - // BeforeAll(func() { - // // Set up graph - // processor.CaptureUpsertChange(gc) - // processor.CaptureUpsertChange(gw1) - // processor.CaptureUpsertChange(testNs) - // processor.CaptureUpsertChange(hr1) - // processor.CaptureUpsertChange(gr1) - // processor.CaptureUpsertChange(secret) - // processor.CaptureUpsertChange(barSecret) - // processor.CaptureUpsertChange(cm) - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - - // It("should report changed after multiple Upserts of related resources", func() { - // processor.CaptureUpsertChange(svc) - // processor.CaptureUpsertChange(slice) - // processor.CaptureUpsertChange(ns) - // processor.CaptureUpsertChange(secretUpdated) - // processor.CaptureUpsertChange(cmUpdated) - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // It("should report not changed after multiple Upserts of unrelated resources", func() { - // processor.CaptureUpsertChange(unrelatedSvc) - // processor.CaptureUpsertChange(unrelatedSlice) - // processor.CaptureUpsertChange(unrelatedNS) - // processor.CaptureUpsertChange(unrelatedSecret) - // processor.CaptureUpsertChange(unrelatedCM) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.NoChange)) - // }) - // When("upserts of related resources are followed by upserts of unrelated resources", func() { - // It("should report changed", func() { - // // these are changing changes - // processor.CaptureUpsertChange(barSvc) - // processor.CaptureUpsertChange(barSlice) - // processor.CaptureUpsertChange(barNs) - // processor.CaptureUpsertChange(barSecretUpdated) - // processor.CaptureUpsertChange(cmUpdated) - - // // there are non-changing changes - // processor.CaptureUpsertChange(unrelatedSvc) - // processor.CaptureUpsertChange(unrelatedSlice) - // processor.CaptureUpsertChange(unrelatedNS) - // processor.CaptureUpsertChange(unrelatedSecret) - // processor.CaptureUpsertChange(unrelatedCM) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // }) - // When("deletes of related resources are followed by upserts of unrelated resources", func() { - // It("should report changed", func() { - // // these are changing changes - // processor.CaptureDeleteChange(&apiv1.Service{}, svcNsName) - // processor.CaptureDeleteChange(&discoveryV1.EndpointSlice{}, sliceNsName) - // processor.CaptureDeleteChange(&apiv1.Namespace{}, types.NamespacedName{Name: "ns"}) - // processor.CaptureDeleteChange(&apiv1.Secret{}, secretNsName) - // processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) - - // // these are non-changing changes - // processor.CaptureUpsertChange(unrelatedSvc) - // processor.CaptureUpsertChange(unrelatedSlice) - // processor.CaptureUpsertChange(unrelatedNS) - // processor.CaptureUpsertChange(unrelatedSecret) - // processor.CaptureUpsertChange(unrelatedCM) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // }) - // }) - // Describe("Multiple Kubernetes API and Gateway API resource changes", Ordered, func() { - // It("should report changed after multiple Upserts of new and related resources", func() { - // // new Gateway API resources - // processor.CaptureUpsertChange(gc) - // processor.CaptureUpsertChange(gw1) - // processor.CaptureUpsertChange(testNs) - // processor.CaptureUpsertChange(hr1) - // processor.CaptureUpsertChange(gr1) - // processor.CaptureUpsertChange(rg1) - // processor.CaptureUpsertChange(btls) - - // // related Kubernetes API resources - // processor.CaptureUpsertChange(svc) - // processor.CaptureUpsertChange(slice) - // processor.CaptureUpsertChange(ns) - // processor.CaptureUpsertChange(secret) - // processor.CaptureUpsertChange(cm) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }) - // It("should report not changed after multiple Upserts of unrelated resources", func() { - // // unrelated Kubernetes API resources - // processor.CaptureUpsertChange(unrelatedSvc) - // processor.CaptureUpsertChange(unrelatedSlice) - // processor.CaptureUpsertChange(unrelatedNS) - // processor.CaptureUpsertChange(unrelatedSecret) - // processor.CaptureUpsertChange(unrelatedCM) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.NoChange)) - // }) - // It("should report changed after upserting changed resources followed by upserting unrelated resources", - // func() { - // // these are changing changes - // processor.CaptureUpsertChange(gcUpdated) - // processor.CaptureUpsertChange(gw1Updated) - // processor.CaptureUpsertChange(hr1Updated) - // processor.CaptureUpsertChange(gr1Updated) - // processor.CaptureUpsertChange(rg1Updated) - // processor.CaptureUpsertChange(btlsUpdated) - - // // these are non-changing changes - // processor.CaptureUpsertChange(unrelatedSvc) - // processor.CaptureUpsertChange(unrelatedSlice) - // processor.CaptureUpsertChange(unrelatedNS) - // processor.CaptureUpsertChange(unrelatedSecret) - // processor.CaptureUpsertChange(unrelatedCM) - - // changed, _ := processor.Process() - // Expect(changed).To(Equal(state.ClusterStateChange)) - // }, - // ) - // }) - // }) - // Describe("Edge cases with panic", func() { - // var processor state.ChangeProcessor - - // BeforeEach(func() { - // processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - // GatewayCtlrName: "test.controller", - // GatewayClassName: "my-class", - // Validators: createAlwaysValidValidators(), - // MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - // }) - // }) - - // DescribeTable("CaptureUpsertChange must panic", - // func(obj client.Object) { - // process := func() { - // processor.CaptureUpsertChange(obj) - // } - // Expect(process).Should(Panic()) - // }, - // Entry( - // "an unsupported resource", - // &v1alpha2.TCPRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "tcp"}}, - // ), - // Entry( - // "nil resource", - // nil, - // ), - // ) - - // DescribeTable( - // "CaptureDeleteChange must panic", - // func(resourceType ngftypes.ObjectType, nsname types.NamespacedName) { - // process := func() { - // processor.CaptureDeleteChange(resourceType, nsname) - // } - // Expect(process).Should(Panic()) - // }, - // Entry( - // "an unsupported resource", - // &v1alpha2.TCPRoute{}, - // types.NamespacedName{Namespace: "test", Name: "tcp"}, - // ), - // Entry( - // "nil resource type", - // nil, - // types.NamespacedName{Namespace: "test", Name: "resource"}, - // ), - // ) - // }) }) diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index 4a1b1dc96f..2e36ff8fd3 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -49,7 +49,7 @@ func addBackendRefsToRouteRules( gws map[types.NamespacedName]*Gateway, ) { for _, gw := range gws { - if gw == nil { + if gw == nil || gw == (&Gateway{}) { continue } for _, r := range routes { diff --git a/internal/mode/static/state/graph/backend_refs_test.go b/internal/mode/static/state/graph/backend_refs_test.go index bb6ebf0b0f..af7d99e20d 100644 --- a/internal/mode/static/state/graph/backend_refs_test.go +++ b/internal/mode/static/state/graph/backend_refs_test.go @@ -745,6 +745,264 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { } } +func TestAddBackendRefsToRouteRulesTest(t *testing.T) { + t.Parallel() + sectionNameRefs := []ParentRef{ + { + Idx: 0, + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway"}, + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + { + Idx: 0, + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + } + createRoute := func( + name string, + kind gatewayv1.Kind, + refsPerBackend int, + serviceNames ...string, + ) *L7Route { + hr := &L7Route{ + Source: &gatewayv1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: name, + }, + }, + RouteType: RouteTypeHTTP, + ParentRefs: sectionNameRefs, + Valid: true, + } + + createRouteBackendRef := func(svcName string, port gatewayv1.PortNumber, weight *int32) RouteBackendRef { + return RouteBackendRef{ + BackendRef: gatewayv1.BackendRef{ + BackendObjectReference: gatewayv1.BackendObjectReference{ + Kind: helpers.GetPointer(kind), + Name: gatewayv1.ObjectName(svcName), + Namespace: helpers.GetPointer[gatewayv1.Namespace]("test"), + Port: helpers.GetPointer(port), + }, + Weight: weight, + }, + } + } + + hr.Spec.Rules = make([]RouteRule, len(serviceNames)) + + for idx, svcName := range serviceNames { + refs := []RouteBackendRef{ + createRouteBackendRef(svcName, 80, nil), + } + if refsPerBackend == 2 { + refs = append(refs, createRouteBackendRef(svcName, 81, helpers.GetPointer[int32](5))) + } + if refsPerBackend != 1 && refsPerBackend != 2 { + panic("invalid refsPerBackend") + } + + hr.Spec.Rules[idx] = RouteRule{ + RouteBackendRefs: refs, + ValidMatches: true, + Filters: RouteRuleFilters{ + Filters: []Filter{}, + Valid: true, + }, + } + } + return hr + } + + getSvc := func(name string) *v1.Service { + return &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: name, + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + { + Port: 80, + }, + { + Port: 81, + }, + }, + }, + } + } + + getPolicy := func(name, svcName, cmName string, gateway []types.NamespacedName) *BackendTLSPolicy { + return &BackendTLSPolicy{ + Valid: true, + Source: &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "test", + }, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Group: "", + Kind: "Service", + Name: gatewayv1.ObjectName(svcName), + }, + }, + }, + Validation: v1alpha3.BackendTLSPolicyValidation{ + Hostname: "foo.example.com", + CACertificateRefs: []gatewayv1.LocalObjectReference{ + { + Group: "", + Kind: "ConfigMap", + Name: gatewayv1.ObjectName(cmName), + }, + }, + }, + }, + }, + Gateways: gateway, + } + } + + getBtp := func(name string, svcName string, cmName string) *BackendTLSPolicy { + return &BackendTLSPolicy{ + Source: &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"}, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Group: "", + Kind: "Service", + Name: gatewayv1.ObjectName(svcName), + }, + }, + }, + Validation: v1alpha3.BackendTLSPolicyValidation{ + Hostname: "foo.example.com", + CACertificateRefs: []gatewayv1.LocalObjectReference{ + { + Group: "", + Kind: "ConfigMap", + Name: gatewayv1.ObjectName(cmName), + }, + }, + }, + }, + }, + Conditions: []conditions.Condition{ + { + Type: "Accepted", + Status: "True", + Reason: "Accepted", + Message: "Policy is accepted", + }, + }, + Valid: true, + IsReferenced: true, + } + } + + gatewayNsNames := []types.NamespacedName{ + {Namespace: "test", Name: "gateway"}, + {Namespace: "test", Name: "gateway-2"}, + } + btp1 := getBtp("btp1", "svc1", "test") + conds := []conditions.Condition{ + { + Type: "Accepted", + Status: "True", + Reason: "Accepted", + Message: "Policy is accepted", + }, + { + Type: "Accepted", + Status: "True", + Reason: "Accepted", + Message: "Policy is accepted", + }, + } + btp1.Conditions = append(btp1.Conditions, conds...) + btp1.Gateways = gatewayNsNames + + policies1 := map[types.NamespacedName]*BackendTLSPolicy{ + {Namespace: "test", Name: "btp1"}: getPolicy("btp1", "svc1", "test", gatewayNsNames), + } + + svc1 := getSvc("svc1") + svc2 := getSvc("svc2") + + gateways := map[types.NamespacedName]*Gateway{ + {Namespace: "test", Name: "gateway"}: { + Source: &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Namespace: "test", + }, + }, + Valid: true, + EffectiveNginxProxy: &EffectiveNginxProxy{}, + }, + {Namespace: "test", Name: "gateway-2"}: { + Source: &gatewayv1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-2", + Namespace: "test", + }, + }, + Valid: true, + EffectiveNginxProxy: &EffectiveNginxProxy{}, + }, + {Namespace: "test", Name: "gateway-3"}: {}, + {Namespace: "test", Name: "gateway-4"}: nil, + } + + services := map[types.NamespacedName]*v1.Service{ + {Namespace: "test", Name: "svc1"}: svc1, + {Namespace: "test", Name: "svc2"}: svc2, + } + + route := map[RouteKey]*L7Route{ + {}: createRoute("hr1", "Service", 1, "svc1"), + } + + expectedBackendRefs := []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "test", Name: "svc1"}, + ServicePort: svc1.Spec.Ports[0], + Valid: true, + Weight: 1, + BackendTLSPolicy: btp1, + }, + } + + t.Run("routes with multiple gateways and backend TLS Policy", func(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + resolver := newReferenceGrantResolver(nil) + addBackendRefsToRouteRules(route, resolver, services, policies1, gateways) + + var actual []BackendRef + route := route[RouteKey{}] + if route.Spec.Rules != nil { + actual = route.Spec.Rules[0].BackendRefs + } + + g.Expect(helpers.Diff(expectedBackendRefs, actual)).To(BeEmpty()) + g.Expect(route.Conditions).To(BeNil()) + }) +} + func TestCreateBackend(t *testing.T) { t.Parallel() createService := func(name string) *v1.Service { 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 4c866c68da..0dd08a640d 100644 --- a/internal/mode/static/state/graph/backend_tls_policy_test.go +++ b/internal/mode/static/state/graph/backend_tls_policy_test.go @@ -121,7 +121,7 @@ func TestValidateBackendTLSPolicy(t *testing.T) { localObjectRefInvalidKind := []gatewayv1.LocalObjectReference{ { - Kind: "Secret", + Kind: "Invalid", Name: "secret", Group: "", }, @@ -477,3 +477,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/graph.go b/internal/mode/static/state/graph/graph.go index 3749369674..d510f83de6 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -76,8 +76,8 @@ type Graph struct { 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 + // LatestReloadResult is holds latest result of applying config to nginx for all gateways. + LatestReloadResult map[types.NamespacedName]NginxReloadResult } // NginxReloadResult describes the result of an NGINX reload. @@ -291,6 +291,11 @@ func BuildGraph( setPlusSecretContent(state.Secrets, plusSecrets) + var latestReloadResult map[types.NamespacedName]NginxReloadResult + if gws != nil { + latestReloadResult = make(map[types.NamespacedName]NginxReloadResult, len(gws)) + } + g := &Graph{ GatewayClass: gc, Gateways: gws, @@ -306,6 +311,7 @@ func BuildGraph( NGFPolicies: processedPolicies, SnippetsFilters: processedSnippetsFilters, PlusSecrets: plusSecrets, + LatestReloadResult: latestReloadResult, } g.attachPolicies(controllerName) diff --git a/internal/mode/static/status/prepare_requests_test.go b/internal/mode/static/status/prepare_requests_test.go index 2368c63764..604429992f 100644 --- a/internal/mode/static/status/prepare_requests_test.go +++ b/internal/mode/static/status/prepare_requests_test.go @@ -1226,217 +1226,263 @@ func TestBuildGatewayStatuses(t *testing.T) { } } -// func TestBuildBackendTLSPolicyStatuses(t *testing.T) { -// t.Parallel() -// const gatewayCtlrName = "controller" - -// transitionTime := helpers.PrepareTimeForFakeClient(metav1.Now()) - -// type policyCfg struct { -// Name string -// Conditions []conditions.Condition -// Valid bool -// Ignored bool -// IsReferenced bool -// } - -// getBackendTLSPolicy := func(policyCfg policyCfg) *graph.BackendTLSPolicy { -// return &graph.BackendTLSPolicy{ -// Source: &v1alpha3.BackendTLSPolicy{ -// ObjectMeta: metav1.ObjectMeta{ -// Namespace: "test", -// Name: policyCfg.Name, -// Generation: 1, -// }, -// }, -// Valid: policyCfg.Valid, -// Ignored: policyCfg.Ignored, -// IsReferenced: policyCfg.IsReferenced, -// Conditions: policyCfg.Conditions, -// Gateway: types.NamespacedName{Name: "gateway", Namespace: "test"}, -// } -// } - -// attachedConds := []conditions.Condition{staticConds.NewPolicyAccepted()} -// invalidConds := []conditions.Condition{staticConds.NewPolicyInvalid("invalid backendTLSPolicy")} - -// validPolicyCfg := policyCfg{ -// Name: "valid-bt", -// Valid: true, -// IsReferenced: true, -// Conditions: attachedConds, -// } - -// invalidPolicyCfg := policyCfg{ -// Name: "invalid-bt", -// IsReferenced: true, -// Conditions: invalidConds, -// } - -// ignoredPolicyCfg := policyCfg{ -// Name: "ignored-bt", -// Ignored: true, -// IsReferenced: true, -// } - -// notReferencedPolicyCfg := policyCfg{ -// Name: "not-referenced", -// Valid: true, -// } - -// tests := []struct { -// backendTLSPolicies map[types.NamespacedName]*graph.BackendTLSPolicy -// expected map[types.NamespacedName]v1alpha2.PolicyStatus -// name string -// expectedReqs int -// }{ -// { -// name: "nil backendTLSPolicies", -// expectedReqs: 0, -// expected: map[types.NamespacedName]v1alpha2.PolicyStatus{}, -// }, -// { -// name: "valid backendTLSPolicy", -// backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ -// {Namespace: "test", Name: "valid-bt"}: getBackendTLSPolicy(validPolicyCfg), -// }, -// expectedReqs: 1, -// expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ -// {Name: "valid-bt", Namespace: "test"}: { -// Ancestors: []v1alpha2.PolicyAncestorStatus{ -// { -// AncestorRef: v1.ParentReference{ -// Namespace: helpers.GetPointer[v1.Namespace]("test"), -// Name: "gateway", -// 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", -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// { -// name: "invalid backendTLSPolicy", -// backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ -// {Namespace: "test", Name: "invalid-bt"}: getBackendTLSPolicy(invalidPolicyCfg), -// }, -// expectedReqs: 1, -// expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ -// {Name: "invalid-bt", Namespace: "test"}: { -// Ancestors: []v1alpha2.PolicyAncestorStatus{ -// { -// AncestorRef: v1.ParentReference{ -// Namespace: helpers.GetPointer[v1.Namespace]("test"), -// Name: "gateway", -// 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.ConditionFalse, -// ObservedGeneration: 1, -// LastTransitionTime: transitionTime, -// Reason: string(v1alpha2.PolicyReasonInvalid), -// Message: "invalid backendTLSPolicy", -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// { -// name: "ignored or not referenced backendTLSPolicies", -// backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ -// {Namespace: "test", Name: "ignored-bt"}: getBackendTLSPolicy(ignoredPolicyCfg), -// {Namespace: "test", Name: "not-referenced"}: getBackendTLSPolicy(notReferencedPolicyCfg), -// }, -// expectedReqs: 0, -// expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ -// {Name: "ignored-bt", Namespace: "test"}: {}, -// {Name: "not-referenced", Namespace: "test"}: {}, -// }, -// }, -// { -// name: "mix valid and ignored backendTLSPolicies", -// backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ -// {Namespace: "test", Name: "ignored-bt"}: getBackendTLSPolicy(ignoredPolicyCfg), -// {Namespace: "test", Name: "valid-bt"}: getBackendTLSPolicy(validPolicyCfg), -// }, -// expectedReqs: 1, -// expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ -// {Name: "ignored-bt", Namespace: "test"}: {}, -// {Name: "valid-bt", Namespace: "test"}: { -// Ancestors: []v1alpha2.PolicyAncestorStatus{ -// { -// AncestorRef: v1.ParentReference{ -// Namespace: helpers.GetPointer[v1.Namespace]("test"), -// Name: "gateway", -// 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", -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// } - -// for _, test := range tests { -// t.Run(test.name, func(t *testing.T) { -// t.Parallel() -// g := NewWithT(t) - -// k8sClient := createK8sClientFor(&v1alpha3.BackendTLSPolicy{}) - -// for _, pol := range test.backendTLSPolicies { -// err := k8sClient.Create(context.Background(), pol.Source) -// g.Expect(err).ToNot(HaveOccurred()) -// } - -// updater := statusFramework.NewUpdater(k8sClient, logr.Discard()) - -// reqs := PrepareBackendTLSPolicyRequests(test.backendTLSPolicies, transitionTime, gatewayCtlrName) - -// g.Expect(reqs).To(HaveLen(test.expectedReqs)) - -// updater.Update(context.Background(), reqs...) - -// for nsname, expected := range test.expected { -// var pol v1alpha3.BackendTLSPolicy - -// err := k8sClient.Get(context.Background(), nsname, &pol) -// g.Expect(err).ToNot(HaveOccurred()) -// g.Expect(helpers.Diff(expected, pol.Status)).To(BeEmpty()) -// } -// }) -// } -// } +func TestBuildBackendTLSPolicyStatuses(t *testing.T) { + t.Parallel() + const gatewayCtlrName = "controller" + + transitionTime := helpers.PrepareTimeForFakeClient(metav1.Now()) + + type policyCfg struct { + Name string + Conditions []conditions.Condition + Gateways []types.NamespacedName + Valid bool + Ignored bool + IsReferenced bool + } + + getBackendTLSPolicy := func(policyCfg policyCfg) *graph.BackendTLSPolicy { + return &graph.BackendTLSPolicy{ + Source: &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: policyCfg.Name, + Generation: 1, + }, + }, + Valid: policyCfg.Valid, + Ignored: policyCfg.Ignored, + IsReferenced: policyCfg.IsReferenced, + Conditions: policyCfg.Conditions, + Gateways: policyCfg.Gateways, + } + } + + attachedConds := []conditions.Condition{staticConds.NewPolicyAccepted()} + invalidConds := []conditions.Condition{staticConds.NewPolicyInvalid("invalid backendTLSPolicy")} + + validPolicyCfg := policyCfg{ + Name: "valid-bt", + 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{ + Name: "ignored-bt", + Ignored: true, + IsReferenced: true, + } + + notReferencedPolicyCfg := policyCfg{ + Name: "not-referenced", + Valid: true, + } + + tests := []struct { + backendTLSPolicies map[types.NamespacedName]*graph.BackendTLSPolicy + expected map[types.NamespacedName]v1alpha2.PolicyStatus + name string + expectedReqs int + }{ + { + name: "nil backendTLSPolicies", + expectedReqs: 0, + expected: map[types.NamespacedName]v1alpha2.PolicyStatus{}, + }, + { + name: "valid backendTLSPolicy", + backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ + {Namespace: "test", Name: "valid-bt"}: getBackendTLSPolicy(validPolicyCfg), + }, + expectedReqs: 1, + expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ + {Name: "valid-bt", Namespace: "test"}: { + Ancestors: []v1alpha2.PolicyAncestorStatus{ + { + AncestorRef: v1.ParentReference{ + Namespace: helpers.GetPointer[v1.Namespace]("test"), + Name: "gateway", + 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", + }, + }, + }, + { + 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", + }, + }, + }, + }, + }, + }, + }, + { + name: "invalid backendTLSPolicy", + backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ + {Namespace: "test", Name: "invalid-bt"}: getBackendTLSPolicy(invalidPolicyCfg), + }, + expectedReqs: 1, + expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ + {Name: "invalid-bt", Namespace: "test"}: { + Ancestors: []v1alpha2.PolicyAncestorStatus{ + { + AncestorRef: v1.ParentReference{ + Namespace: helpers.GetPointer[v1.Namespace]("test"), + Name: "gateway", + 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.ConditionFalse, + ObservedGeneration: 1, + LastTransitionTime: transitionTime, + Reason: string(v1alpha2.PolicyReasonInvalid), + Message: "invalid backendTLSPolicy", + }, + }, + }, + }, + }, + }, + }, + { + name: "ignored or not referenced backendTLSPolicies", + backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ + {Namespace: "test", Name: "ignored-bt"}: getBackendTLSPolicy(ignoredPolicyCfg), + {Namespace: "test", Name: "not-referenced"}: getBackendTLSPolicy(notReferencedPolicyCfg), + }, + expectedReqs: 0, + expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ + {Name: "ignored-bt", Namespace: "test"}: {}, + {Name: "not-referenced", Namespace: "test"}: {}, + }, + }, + { + name: "mix valid and ignored backendTLSPolicies", + backendTLSPolicies: map[types.NamespacedName]*graph.BackendTLSPolicy{ + {Namespace: "test", Name: "ignored-bt"}: getBackendTLSPolicy(ignoredPolicyCfg), + {Namespace: "test", Name: "valid-bt"}: getBackendTLSPolicy(validPolicyCfg), + }, + expectedReqs: 1, + expected: map[types.NamespacedName]v1alpha2.PolicyStatus{ + {Name: "ignored-bt", Namespace: "test"}: {}, + {Name: "valid-bt", Namespace: "test"}: { + Ancestors: []v1alpha2.PolicyAncestorStatus{ + { + AncestorRef: v1.ParentReference{ + Namespace: helpers.GetPointer[v1.Namespace]("test"), + Name: "gateway", + 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", + }, + }, + }, + { + 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", + }, + }, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + k8sClient := createK8sClientFor(&v1alpha3.BackendTLSPolicy{}) + + for _, pol := range test.backendTLSPolicies { + err := k8sClient.Create(context.Background(), pol.Source) + g.Expect(err).ToNot(HaveOccurred()) + } + + updater := statusFramework.NewUpdater(k8sClient, logr.Discard()) + + reqs := PrepareBackendTLSPolicyRequests(test.backendTLSPolicies, transitionTime, gatewayCtlrName) + + g.Expect(reqs).To(HaveLen(test.expectedReqs)) + + updater.Update(context.Background(), reqs...) + + for nsname, expected := range test.expected { + var pol v1alpha3.BackendTLSPolicy + + err := k8sClient.Get(context.Background(), nsname, &pol) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(helpers.Diff(expected, pol.Status)).To(BeEmpty()) + } + }) + } +} func TestBuildNginxGatewayStatus(t *testing.T) { t.Parallel() From 46705fe4b06ce138f348e5f416855bcd0c8511f2 Mon Sep 17 00:00:00 2001 From: salonichf5 <146118978+salonichf5@users.noreply.github.com> Date: Fri, 4 Apr 2025 22:58:36 +0530 Subject: [PATCH 05/19] add more unit tests, update handler --- examples/cafe-example/cafe-routes.yaml | 4 - examples/cafe-example/gateway.yaml | 24 - internal/mode/static/handler.go | 93 +-- internal/mode/static/handler_test.go | 3 +- .../static/state/change_processor_test.go | 2 - internal/mode/static/state/graph/gateway.go | 1 + internal/mode/static/state/graph/graph.go | 8 - .../mode/static/state/graph/route_common.go | 10 +- .../static/state/graph/route_common_test.go | 558 ++++++++++++++++-- 9 files changed, 573 insertions(+), 130 deletions(-) diff --git a/examples/cafe-example/cafe-routes.yaml b/examples/cafe-example/cafe-routes.yaml index 3af38ca944..67927335cb 100644 --- a/examples/cafe-example/cafe-routes.yaml +++ b/examples/cafe-example/cafe-routes.yaml @@ -6,8 +6,6 @@ spec: parentRefs: - name: gateway sectionName: http - - name: gateway3 - sectionName: http hostnames: - "cafe.example.com" rules: @@ -27,8 +25,6 @@ spec: parentRefs: - name: gateway sectionName: http - - name: gateway3 - sectionName: http hostnames: - "cafe.example.com" rules: diff --git a/examples/cafe-example/gateway.yaml b/examples/cafe-example/gateway.yaml index 88fd062463..e6507f613b 100644 --- a/examples/cafe-example/gateway.yaml +++ b/examples/cafe-example/gateway.yaml @@ -9,27 +9,3 @@ spec: port: 80 protocol: HTTP hostname: "*.example.com" ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: Gateway -metadata: - name: gateway2 -spec: - gatewayClassName: nginx - listeners: - - name: http - port: 80 - protocol: HTTP - hostname: "*.example.com" ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: Gateway -metadata: - name: gateway3 -spec: - gatewayClassName: nginx - listeners: - - name: http - port: 80 - protocol: HTTP - hostname: "*.example.com" diff --git a/internal/mode/static/handler.go b/internal/mode/static/handler.go index 6abd25227b..648d3acd45 100644 --- a/internal/mode/static/handler.go +++ b/internal/mode/static/handler.go @@ -294,59 +294,60 @@ 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 } - deploymentName := item.Deployment - gw := gr.Gateways[deploymentName] - var nginxReloadRes graph.NginxReloadResult - switch { - case item.Error != nil: - h.cfg.logger.Error(item.Error, "Failed to update NGINX configuration") - nginxReloadRes.Error = item.Error - case gw != nil: - h.cfg.logger.Info("NGINX configuration was successfully updated") - } - gr.LatestReloadResult[deploymentName] = nginxReloadRes - - switch item.UpdateType { - case status.UpdateAll: - h.updateStatuses(ctx, gr, gw) - case status.UpdateGateway: - gwAddresses, err := getGatewayAddresses( - ctx, - h.cfg.k8sClient, - item.GatewayService, - gw, - h.cfg.gatewayClassName, - ) - if err != nil { - msg := "error getting Gateway Service IP address" - h.cfg.logger.Error(err, msg) - h.cfg.eventRecorder.Eventf( + for _, gw := range gr.Gateways { + switch { + case item.Error != nil: + h.cfg.logger.Error(item.Error, "Failed to update NGINX configuration") + nginxReloadRes.Error = item.Error + case gw != nil: + h.cfg.logger.Info("NGINX configuration was successfully updated") + } + gw.LatestReloadResult = nginxReloadRes + + switch item.UpdateType { + case status.UpdateAll: + h.updateStatuses(ctx, gr, gw) + case status.UpdateGateway: + gwAddresses, err := getGatewayAddresses( + ctx, + h.cfg.k8sClient, item.GatewayService, - v1.EventTypeWarning, - "GetServiceIPFailed", - msg+": %s", - err.Error(), + gw, + h.cfg.gatewayClassName, ) - continue + if err != nil { + msg := "error getting Gateway Service IP address" + h.cfg.logger.Error(err, msg) + h.cfg.eventRecorder.Eventf( + item.GatewayService, + v1.EventTypeWarning, + "GetServiceIPFailed", + msg+": %s", + err.Error(), + ) + continue + } + + transitionTime := metav1.Now() + + gatewayStatuses := status.PrepareGatewayRequests( + gw, + transitionTime, + gwAddresses, + gw.LatestReloadResult, + ) + h.cfg.statusUpdater.UpdateGroup(ctx, groupGateways, gatewayStatuses...) + default: + panic(fmt.Sprintf("unknown event type %T", item.UpdateType)) } - - transitionTime := metav1.Now() - - gatewayStatuses := status.PrepareGatewayRequests( - gw, - transitionTime, - gwAddresses, - gr.LatestReloadResult[deploymentName], - ) - h.cfg.statusUpdater.UpdateGroup(ctx, groupGateways, gatewayStatuses...) - default: - panic(fmt.Sprintf("unknown event type %T", item.UpdateType)) } } } @@ -377,7 +378,7 @@ func (h *eventHandlerImpl) updateStatuses(ctx context.Context, gr *graph.Graph, gr.L4Routes, gr.Routes, transitionTime, - gr.LatestReloadResult[gw.DeploymentName], + gw.LatestReloadResult, h.cfg.gatewayCtlrName, ) @@ -408,7 +409,7 @@ func (h *eventHandlerImpl) updateStatuses(ctx context.Context, gr *graph.Graph, gw, transitionTime, gwAddresses, - gr.LatestReloadResult[gw.DeploymentName], + 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 bfb4a0e71e..8da457acfc 100644 --- a/internal/mode/static/handler_test.go +++ b/internal/mode/static/handler_test.go @@ -381,7 +381,8 @@ var _ = Describe("eventHandler", func() { }).Should(Equal(2)) gr := handler.cfg.processor.GetLatestGraph() - Expect(gr.LatestReloadResult[types.NamespacedName{}].Error.Error()).To(Equal("status error")) + gw := gr.Gateways[types.NamespacedName{}] + Expect(gw.LatestReloadResult.Error.Error()).To(Equal("status error")) }) It("should update Gateway status when receiving a queue event", func() { diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index ee184c65cc..e6fa1ada95 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -1007,7 +1007,6 @@ var _ = Describe("ChangeProcessor", func() { GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, }, }, - LatestReloadResult: map[types.NamespacedName]graph.NginxReloadResult{}, } expGraph2 = &graph.Graph{ @@ -1134,7 +1133,6 @@ var _ = Describe("ChangeProcessor", func() { GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, }, }, - LatestReloadResult: map[types.NamespacedName]graph.NginxReloadResult{}, } }) When("no upsert has occurred", func() { diff --git a/internal/mode/static/state/graph/gateway.go b/internal/mode/static/state/graph/gateway.go index 909fc139d7..0b6b4370f0 100644 --- a/internal/mode/static/state/graph/gateway.go +++ b/internal/mode/static/state/graph/gateway.go @@ -14,6 +14,7 @@ import ( // Gateway represents the winning Gateway resource. type Gateway struct { + LatestReloadResult NginxReloadResult Source *v1.Gateway NginxProxy *NginxProxy EffectiveNginxProxy *EffectiveNginxProxy diff --git a/internal/mode/static/state/graph/graph.go b/internal/mode/static/state/graph/graph.go index d510f83de6..e3095085c2 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -76,8 +76,6 @@ type Graph struct { SnippetsFilters map[types.NamespacedName]*SnippetsFilter // PlusSecrets holds the secrets related to NGINX Plus licensing. PlusSecrets map[types.NamespacedName][]PlusSecretFile - // LatestReloadResult is holds latest result of applying config to nginx for all gateways. - LatestReloadResult map[types.NamespacedName]NginxReloadResult } // NginxReloadResult describes the result of an NGINX reload. @@ -291,11 +289,6 @@ func BuildGraph( setPlusSecretContent(state.Secrets, plusSecrets) - var latestReloadResult map[types.NamespacedName]NginxReloadResult - if gws != nil { - latestReloadResult = make(map[types.NamespacedName]NginxReloadResult, len(gws)) - } - g := &Graph{ GatewayClass: gc, Gateways: gws, @@ -311,7 +304,6 @@ func BuildGraph( NGFPolicies: processedPolicies, SnippetsFilters: processedSnippetsFilters, PlusSecrets: plusSecrets, - LatestReloadResult: latestReloadResult, } g.attachPolicies(controllerName) diff --git a/internal/mode/static/state/graph/route_common.go b/internal/mode/static/state/graph/route_common.go index 6503dfabca..e109164309 100644 --- a/internal/mode/static/state/graph/route_common.go +++ b/internal/mode/static/state/graph/route_common.go @@ -399,7 +399,8 @@ func getListenerHostPortMap(listeners []*Listener, gw *Gateway) map[string]hostP Namespace: gw.Source.Namespace, } for _, l := range listeners { - listenerHostPortMap[l.Name] = hostPort{ + key := fmt.Sprintf("%s,%s,%s", l.Name, gw.Source.Name, gw.Source.Namespace) + listenerHostPortMap[key] = hostPort{ hostname: getHostname(l.Source.Hostname), port: l.Source.Port, gwNsNames: gwNsNames, @@ -447,6 +448,7 @@ func isolateHostnamesForParentRefs(parentRef []ParentRef, listenerHostnameMap ma } for _, h := range hostnames { for lName, lHostPort := range listenerHostnameMap { + // skip comparison if not part of the same gateway if lHostPort.gwNsNames != ref.Gateway { continue } @@ -456,8 +458,12 @@ func isolateHostnamesForParentRefs(parentRef []ParentRef, listenerHostnameMap ma continue } - // for L7Routes, we compare the hostname, port and gatewayNamespace-gatewayName-listenerName combination + // for L7Routes, we compare the hostname, port and listenerName combination // to identify if hostname needs to be isolated. + splitLName := strings.Split(lName, ",") + if len(splitLName) > 1 { + lName = splitLName[0] + } if h == lHostPort.hostname && listenerName != lName { // for L4Routes, we only compare the hostname and listener name combination // because we do not allow l4Routes to attach to the same listener diff --git a/internal/mode/static/state/graph/route_common_test.go b/internal/mode/static/state/graph/route_common_test.go index 18e04e6c5b..61001cf0a4 100644 --- a/internal/mode/static/state/graph/route_common_test.go +++ b/internal/mode/static/state/graph/route_common_test.go @@ -2142,6 +2142,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{ @@ -2151,12 +2156,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, @@ -2164,12 +2183,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, }, @@ -2179,31 +2193,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..., ) @@ -2302,11 +2341,27 @@ func TestIsolateL4Listeners(t *testing.T) { } listenerMapHostnameIntersection := map[string]hostPort{ - "empty-hostname": {hostname: "", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, - "wildcard-example-com": {hostname: "*.example.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, - "foo-wildcard-example-com": {hostname: "*.foo.example.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, - "abc-com": {hostname: "abc.foo.example.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, - "no-match": {hostname: "no-match.cafe.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, + "empty-hostname,test,gateway": {hostname: "", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, + "wildcard-example-com,test,gateway": { + hostname: "*.example.com", + port: 80, + gwNsNames: client.ObjectKeyFromObject(gw), + }, + "foo-wildcard-example-com,test,gateway": { + hostname: "*.foo.example.com", + port: 80, + gwNsNames: client.ObjectKeyFromObject(gw), + }, + "abc-com,test,gateway": { + hostname: "abc.foo.example.com", + port: 80, + gwNsNames: client.ObjectKeyFromObject(gw), + }, + "no-match,test,gateway": { + hostname: "no-match.cafe.com", + port: 80, + gwNsNames: client.ObjectKeyFromObject(gw), + }, } expectedResultHostnameIntersection := map[string][]ParentRef{ @@ -2410,6 +2465,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 @@ -2448,9 +2540,9 @@ 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": { @@ -2500,6 +2592,149 @@ 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: client.ObjectKey{ + Namespace: gw.Namespace, + Name: gw.Name, + }, + SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: acceptedHostanamesMultipleGateways, + Attached: true, + ListenerPort: gatewayv1.PortNumber(443), + }, + }, + { + Idx: 0, + Gateway: client.ObjectKey{ + Namespace: gw1.Namespace, + Name: gw1.Name, + }, + 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: client.ObjectKey{ + Namespace: gw.Namespace, + Name: gw.Name, + }, + SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: acceptedHostanamesMultipleGateways, + Attached: true, + ListenerPort: gatewayv1.PortNumber(443), + }, + }, + { + Idx: 0, + Gateway: client.ObjectKey{ + Namespace: gw1.Namespace, + Name: gw1.Name, + }, + 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, + gwNsNames: client.ObjectKeyFromObject(gw), + }, + "wildcard-example-com,test,gateway1": { + hostname: "*.example.com", + port: 443, + gwNsNames: client.ObjectKeyFromObject(gw), + }, + }, + expectedResult: map[string][]ParentRef{ + "tls_coffee": { + { + Idx: 0, + Gateway: 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: client.ObjectKeyFromObject(gw1), + 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: 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: client.ObjectKeyFromObject(gw1), + 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 { @@ -2526,12 +2761,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, @@ -2539,12 +2788,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, }, @@ -2584,31 +2828,56 @@ 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 ) @@ -2678,11 +2947,27 @@ func TestIsolateL7Listeners(t *testing.T) { } listenerMapHostnameIntersection := map[string]hostPort{ - "empty-hostname": {hostname: "", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, - "wildcard-example-com": {hostname: "*.example.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, - "foo-wildcard-example-com": {hostname: "*.foo.example.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, - "abc-com": {hostname: "abc.foo.example.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, - "no-match": {hostname: "no-match.cafe.com", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, + "empty-hostname,test,gateway": {hostname: "", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, + "wildcard-example-com,test,gateway": { + hostname: "*.example.com", + port: 80, + gwNsNames: client.ObjectKeyFromObject(gw), + }, + "foo-wildcard-example-com,test,gateway": { + hostname: "*.foo.example.com", + port: 80, + gwNsNames: client.ObjectKeyFromObject(gw), + }, + "abc-com,test,gateway": { + hostname: "abc.foo.example.com", + port: 80, + gwNsNames: client.ObjectKeyFromObject(gw), + }, + "no-match,test,gateway": { + hostname: "no-match.cafe.com", + port: 80, + gwNsNames: client.ObjectKeyFromObject(gw), + }, } expectedResultHostnameIntersection := map[string][]ParentRef{ @@ -2761,7 +3046,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..., ) @@ -2800,6 +3090,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 @@ -2824,8 +3153,8 @@ 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": { @@ -2870,9 +3199,9 @@ 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": { @@ -2922,6 +3251,149 @@ 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: client.ObjectKey{ + Namespace: gw.Namespace, + Name: gw.Name, + }, + SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: acceptedHostNamesMultipleGateway, + Attached: true, + ListenerPort: gatewayv1.PortNumber(80), + }, + }, + { + Idx: 0, + Gateway: client.ObjectKey{ + Namespace: gw1.Namespace, + Name: gw1.Name, + }, + 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: client.ObjectKey{ + Namespace: gw.Namespace, + Name: gw.Name, + }, + SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: acceptedHostNamesMultipleGateway, + Attached: true, + ListenerPort: gatewayv1.PortNumber(80), + }, + }, + { + Idx: 0, + Gateway: client.ObjectKey{ + Namespace: gw1.Namespace, + Name: gw1.Name, + }, + 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, + gwNsNames: client.ObjectKeyFromObject(gw), + }, + "wildcard-example-com,test,gateway1": { + hostname: "*.example.com", + port: 80, + gwNsNames: client.ObjectKeyFromObject(gw1), + }, + }, + expectedResult: map[string][]ParentRef{ + "hr_coffee": { + { + Idx: 0, + Gateway: 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: client.ObjectKeyFromObject(gw1), + 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: 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: client.ObjectKeyFromObject(gw1), + 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 { From 0ff8cfa570b313d5d5a2bc6055967b800978d7f1 Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Mon, 14 Apr 2025 13:40:51 -0600 Subject: [PATCH 06/19] Fix logic relating to backends/policies/nginxproxy/etc --- cmd/gateway/commands.go | 19 - cmd/gateway/commands_test.go | 51 -- cmd/gateway/validating_types.go | 30 - cmd/gateway/validation.go | 35 - cmd/gateway/validation_test.go | 132 --- internal/mode/static/config/config.go | 4 - internal/mode/static/handler.go | 95 ++- internal/mode/static/handler_test.go | 38 +- internal/mode/static/manager.go | 17 +- internal/mode/static/manager_test.go | 63 +- .../policies/clientsettings/validator.go | 10 +- .../policies/clientsettings/validator_test.go | 13 +- .../policies/observability/validator.go | 37 +- .../policies/observability/validator_test.go | 95 ++- .../policies/policiesfakes/fake_validator.go | 94 ++- .../static/nginx/config/policies/policy.go | 2 - .../policies/upstreamsettings/validator.go | 10 +- .../upstreamsettings/validator_test.go | 13 +- .../static/nginx/config/policies/validator.go | 23 +- .../nginx/config/policies/validator_test.go | 26 +- internal/mode/static/provisioner/objects.go | 2 + .../static/state/change_processor_test.go | 761 ++++++++---------- .../static/state/conditions/conditions.go | 15 - .../static/state/dataplane/configuration.go | 140 +++- .../state/dataplane/configuration_test.go | 593 ++++++++------ .../mode/static/state/graph/backend_refs.go | 103 ++- .../static/state/graph/backend_refs_test.go | 471 +++-------- .../static/state/graph/backend_tls_policy.go | 46 +- internal/mode/static/state/graph/gateway.go | 72 +- .../static/state/graph/gateway_listener.go | 8 +- .../mode/static/state/graph/gateway_test.go | 399 +++++---- internal/mode/static/state/graph/graph.go | 23 +- .../mode/static/state/graph/graph_test.go | 317 +++++--- internal/mode/static/state/graph/grpcroute.go | 13 +- .../mode/static/state/graph/grpcroute_test.go | 99 ++- internal/mode/static/state/graph/httproute.go | 4 +- .../mode/static/state/graph/httproute_test.go | 87 +- .../state/graph/multiple_gateways_test.go | 15 + internal/mode/static/state/graph/namespace.go | 2 +- .../mode/static/state/graph/nginxproxy.go | 3 +- .../static/state/graph/nginxproxy_test.go | 2 +- internal/mode/static/state/graph/policies.go | 173 ++-- .../mode/static/state/graph/policies_test.go | 277 ++++--- .../mode/static/state/graph/route_common.go | 221 ++--- .../static/state/graph/route_common_test.go | 639 +++++++++------ internal/mode/static/state/graph/service.go | 24 +- .../mode/static/state/graph/service_test.go | 28 +- internal/mode/static/state/graph/tlsroute.go | 42 +- .../mode/static/state/graph/tlsroute_test.go | 158 ++-- .../validationfakes/fake_policy_validator.go | 94 ++- .../mode/static/state/validation/validator.go | 4 +- .../mode/static/status/prepare_requests.go | 12 +- .../static/status/prepare_requests_test.go | 12 +- .../clientsettings/invalid-route-csp.yaml | 2 +- 54 files changed, 2959 insertions(+), 2709 deletions(-) diff --git a/cmd/gateway/commands.go b/cmd/gateway/commands.go index d73ae49a31..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,7 +58,6 @@ func createRootCommand() *cobra.Command { func createControllerCommand() *cobra.Command { // flag names const ( - gatewayFlag = "gateway" configFlag = "config" serviceFlag = "service" agentTLSSecretFlag = "agent-tls-secret" @@ -93,7 +91,6 @@ func createControllerCommand() *cobra.Command { validator: validateResourceName, } - gateway = namespacedNameValue{} configName = stringValidatingValue{ validator: validateResourceName, } @@ -198,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") @@ -232,7 +224,6 @@ func createControllerCommand() *cobra.Command { Logger: logger, AtomicLevel: atom, GatewayClassName: gatewayClassName.value, - GatewayNsName: gwNsName, GatewayPodConfig: podConfig, HealthConfig: config.HealthConfig{ Enabled: !disableHealth, @@ -290,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, diff --git a/cmd/gateway/commands_test.go b/cmd/gateway/commands_test.go index a6a22b1c96..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" ) @@ -63,8 +62,6 @@ func TestCommonFlagsValidation(t *testing.T) { args: []string{ "--gateway-ctlr-name=gateway.nginx.org/nginx-gateway", "--gatewayclass=nginx", - "--gateway=nginx-gateway/nginx", - "--config=nginx-gateway-config", }, wantErr: false, }, @@ -139,7 +136,6 @@ 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", @@ -171,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{ @@ -712,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", @@ -745,9 +700,6 @@ func TestParseFlags(t *testing.T) { "customStringFlagDefault", "customStringFlagUserDefined", - - "customStringFlagNoDefaultValueUnset", - "customStringFlagNoDefaultValueUserDefined", } expectedValues := []string{ "true", @@ -758,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 53ef2e1781..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. diff --git a/internal/mode/static/handler.go b/internal/mode/static/handler.go index 648d3acd45..df027fb716 100644 --- a/internal/mode/static/handler.go +++ b/internal/mode/static/handler.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "sync" "time" @@ -294,60 +295,68 @@ 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 - for _, gw := range gr.Gateways { - switch { - case item.Error != nil: - h.cfg.logger.Error(item.Error, "Failed to update NGINX configuration") - nginxReloadRes.Error = item.Error - case gw != nil: - h.cfg.logger.Info("NGINX configuration was successfully updated") + 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 gw != nil: + h.cfg.logger.Info("NGINX configuration was successfully updated") + } + if gw != nil { gw.LatestReloadResult = nginxReloadRes + } - switch item.UpdateType { - case status.UpdateAll: - h.updateStatuses(ctx, gr, gw) - case status.UpdateGateway: - gwAddresses, err := getGatewayAddresses( - ctx, - h.cfg.k8sClient, + switch item.UpdateType { + case status.UpdateAll: + h.updateStatuses(ctx, gr, gw) + case status.UpdateGateway: + gwAddresses, err := getGatewayAddresses( + ctx, + h.cfg.k8sClient, + item.GatewayService, + gw, + h.cfg.gatewayClassName, + ) + if err != nil { + msg := "error getting Gateway Service IP address" + h.cfg.logger.Error(err, msg) + h.cfg.eventRecorder.Eventf( item.GatewayService, - gw, - h.cfg.gatewayClassName, - ) - if err != nil { - msg := "error getting Gateway Service IP address" - h.cfg.logger.Error(err, msg) - h.cfg.eventRecorder.Eventf( - item.GatewayService, - v1.EventTypeWarning, - "GetServiceIPFailed", - msg+": %s", - err.Error(), - ) - continue - } - - transitionTime := metav1.Now() - - gatewayStatuses := status.PrepareGatewayRequests( - gw, - transitionTime, - gwAddresses, - gw.LatestReloadResult, + v1.EventTypeWarning, + "GetServiceIPFailed", + msg+": %s", + err.Error(), ) - h.cfg.statusUpdater.UpdateGroup(ctx, groupGateways, gatewayStatuses...) - default: - panic(fmt.Sprintf("unknown event type %T", item.UpdateType)) + continue } + + transitionTime := metav1.Now() + + gatewayStatuses := status.PrepareGatewayRequests( + gw, + transitionTime, + gwAddresses, + gw.LatestReloadResult, + ) + h.cfg.statusUpdater.UpdateGroup(ctx, groupGateways, gatewayStatuses...) + default: + panic(fmt.Sprintf("unknown event type %T", item.UpdateType)) } } } @@ -356,7 +365,7 @@ func (h *eventHandlerImpl) updateStatuses(ctx context.Context, gr *graph.Graph, transitionTime := metav1.Now() gcReqs := status.PrepareGatewayClassRequests(gr.GatewayClass, gr.IgnoredGatewayClasses, transitionTime) - if gw == nil || gw.DeploymentName == (types.NamespacedName{}) { + if gw == nil { h.cfg.statusUpdater.UpdateGroup(ctx, groupAllExceptGateways, gcReqs...) return } diff --git a/internal/mode/static/handler_test.go b/internal/mode/static/handler_test.go index 8da457acfc..da939e932c 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)) @@ -97,9 +87,13 @@ var _ = Describe("eventHandler", func() { baseGraph = &graph.Graph{ 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"), + }, }, }, } @@ -118,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, @@ -140,6 +131,7 @@ var _ = Describe("eventHandler", func() { ServiceName: "nginx-gateway", Namespace: "nginx-gateway", }, + gatewayClassName: "nginx", metricsCollector: collectors.NewControllerNoopCollector(), }) Expect(handler.cfg.graphBuiltHealthChecker.ready).To(BeFalse()) @@ -370,8 +362,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) @@ -381,14 +376,17 @@ var _ = Describe("eventHandler", func() { }).Should(Equal(2)) gr := handler.cfg.processor.GetLatestGraph() - gw := gr.Gateways[types.NamespacedName{}] + 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) diff --git a/internal/mode/static/manager.go b/internal/mode/static/manager.go index 104d6e3196..0c78a8f10d 100644 --- a/internal/mode/static/manager.go +++ b/internal/mode/static/manager.go @@ -434,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 }(), }, @@ -777,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..b2659fe223 100644 --- a/internal/mode/static/nginx/config/policies/validator_test.go +++ b/internal/mode/static/nginx/config/policies/validator_test.go @@ -42,18 +42,24 @@ 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, @@ -63,13 +69,23 @@ var _ = Describe("Policy CompositeValidator", func() { 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 +95,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()) diff --git a/internal/mode/static/provisioner/objects.go b/internal/mode/static/provisioner/objects.go index 7c058bf784..56883e447e 100644 --- a/internal/mode/static/provisioner/objects.go +++ b/internal/mode/static/provisioner/objects.go @@ -593,6 +593,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 +688,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 e6fa1ada95..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, @@ -422,25 +412,23 @@ var _ = Describe("ChangeProcessor", func() { Describe("Process gateway resources", Ordered, func() { var ( - gcUpdated *v1.GatewayClass - diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret - diffNsTLSCert, sameNsTLSCert *graph.CertificateBundle - hr1, hr1Updated, hr2 *v1.HTTPRoute - gr1, gr1Updated, gr2 *v1.GRPCRoute - tr1, tr1Updated, tr2 *v1alpha2.TLSRoute - gw1, gw1Updated, gw2, gw2Updated *v1.Gateway - secretRefGrant, hrServiceRefGrant *v1beta1.ReferenceGrant - grServiceRefGrant, trServiceRefGrant *v1beta1.ReferenceGrant - expGraph, expGraph2 *graph.Graph - expRouteHR1, expRouteHR2 *graph.L7Route - expRouteGR1, expRouteGR2 *graph.L7Route - expRouteTR1, expRouteTR2 *graph.L4Route - gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata - httpRoute1 graph.RouteKey - grpcRoute1 graph.RouteKey - httpRoute2, grpcRoute2 graph.RouteKey - trKey1, trKey2 graph.L4RouteKey - refSvc, refGRPCSvc, refTLSSvc types.NamespacedName + gcUpdated *v1.GatewayClass + diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret + diffNsTLSCert, sameNsTLSCert *graph.CertificateBundle + hr1, hr1Updated, hr2 *v1.HTTPRoute + gr1, gr1Updated, gr2 *v1.GRPCRoute + tr1, tr1Updated, tr2 *v1alpha2.TLSRoute + gw1, gw1Updated, gw2, gw2Updated *v1.Gateway + secretRefGrant, hrServiceRefGrant *v1beta1.ReferenceGrant + grServiceRefGrant, trServiceRefGrant *v1beta1.ReferenceGrant + 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 // gitleaks:allow not a secret + trKey1, trKey2 graph.L4RouteKey + refSvc, refGRPCSvc, refTLSSvc types.NamespacedName ) processAndValidateGraph := func(expGraph *graph.Graph) { @@ -481,20 +469,20 @@ var _ = Describe("ChangeProcessor", func() { } hr1 = createHTTPRoute("hr-1", "gateway-1", "foo.example.com", crossNsHTTPBackendRef) - httpRoute1 = graph.CreateRouteKey(hr1) + httpRouteKey1 = graph.CreateRouteKey(hr1) hr1Updated = hr1.DeepCopy() hr1Updated.Generation++ hr2 = createHTTPRoute("hr-2", "gateway-2", "bar.example.com", crossNsHTTPBackendRef) - httpRoute2 = graph.CreateRouteKey(hr2) + httpRouteKey2 = graph.CreateRouteKey(hr2) gr1 = createGRPCRoute("gr-1", "gateway-1", "foo.example.com", grpcBackendRef) - grpcRoute1 = graph.CreateRouteKey(gr1) + grpcRouteKey1 = graph.CreateRouteKey(gr1) gr1Updated = gr1.DeepCopy() gr1Updated.Generation++ gr2 = createGRPCRoute("gr-2", "gateway-2", "bar.example.com", grpcBackendRef) - grpcRoute2 = graph.CreateRouteKey(gr2) + grpcRouteKey2 = graph.CreateRouteKey(gr2) tlsBackendRef := createTLSBackendRef(refTLSSvc.Name, refTLSSvc.Namespace) tr1 = createTLSRoute("tr-1", "gateway-1", "foo.tls.com", tlsBackendRef) @@ -676,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, }, @@ -700,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, @@ -729,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, }, @@ -753,8 +770,9 @@ var _ = Describe("ChangeProcessor", func() { { BackendRefs: []graph.BackendRef{ { - SvcNsName: refSvc, - Weight: 1, + SvcNsName: refSvc, + Weight: 1, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, ValidMatches: true, @@ -782,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, }, @@ -806,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, @@ -835,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, }, @@ -859,8 +906,9 @@ var _ = Describe("ChangeProcessor", func() { { BackendRefs: []graph.BackendRef{ { - SvcNsName: refGRPCSvc, - Weight: 1, + SvcNsName: refGRPCSvc, + Weight: 1, + InvalidForGateways: map[types.NamespacedName]conditions.Condition{}, }, }, ValidMatches: true, @@ -887,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, @@ -915,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, @@ -950,12 +1014,13 @@ var _ = Describe("ChangeProcessor", func() { Source: gw1, Listeners: []*graph.Listener{ { - Name: httpListenerName, - Source: gw1.Spec.Listeners[0], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{httpRoute1: expRouteHR1, grpcRoute1: expRouteGR1}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + 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)}, @@ -963,10 +1028,11 @@ var _ = Describe("ChangeProcessor", func() { }, { Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw1), Source: gw1.Spec.Listeners[1], Valid: true, Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{httpRoute1: expRouteHR1, grpcRoute1: expRouteGR1}, + 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{ @@ -975,12 +1041,13 @@ var _ = Describe("ChangeProcessor", func() { }, }, { - 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}, + 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)}, }, @@ -994,7 +1061,7 @@ var _ = Describe("ChangeProcessor", func() { }, }, L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, - Routes: map[graph.RouteKey]*graph.L7Route{httpRoute1: expRouteHR1, grpcRoute1: expRouteGR1}, + Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, ReferencedSecrets: map[types.NamespacedName]*graph.Secret{}, ReferencedServices: map[types.NamespacedName]*graph.ReferencedService{ refSvc: { @@ -1019,12 +1086,13 @@ var _ = Describe("ChangeProcessor", func() { Source: gw1, Listeners: []*graph.Listener{ { - Name: httpListenerName, - Source: gw1.Spec.Listeners[0], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{httpRoute1: expRouteHR1, grpcRoute1: expRouteGR1}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + 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)}, @@ -1032,10 +1100,11 @@ var _ = Describe("ChangeProcessor", func() { }, { Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw1), Source: gw1.Spec.Listeners[1], Valid: true, Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{httpRoute1: expRouteHR1, grpcRoute1: expRouteGR1}, + 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{ @@ -1044,12 +1113,13 @@ var _ = Describe("ChangeProcessor", func() { }, }, { - 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}, + 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)}, }, @@ -1065,12 +1135,13 @@ var _ = Describe("ChangeProcessor", func() { Source: gw2, Listeners: []*graph.Listener{ { - Name: httpListenerName, - Source: gw2.Spec.Listeners[0], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + 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)}, @@ -1078,6 +1149,7 @@ var _ = Describe("ChangeProcessor", func() { }, { Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), Source: gw2.Spec.Listeners[1], Valid: true, Attachable: true, @@ -1090,12 +1162,13 @@ var _ = Describe("ChangeProcessor", func() { }, }, { - Name: tlsListenerName, - Source: gw2.Spec.Listeners[2], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + 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)}, }, @@ -1109,7 +1182,7 @@ var _ = Describe("ChangeProcessor", func() { }, }, L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, - Routes: map[graph.RouteKey]*graph.L7Route{httpRoute1: expRouteHR1, grpcRoute1: expRouteGR1}, + Routes: map[graph.RouteKey]*graph.L7Route{httpRouteKey1: expRouteHR1, grpcRouteKey1: expRouteGR1}, ReferencedSecrets: map[types.NamespacedName]*graph.Secret{ client.ObjectKeyFromObject(sameNsTLSSecret): { Source: sameNsTLSSecret, @@ -1125,9 +1198,7 @@ var _ = Describe("ChangeProcessor", func() { GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, }, refTLSSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "gateway-1"}: {}, - }, + GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, }, refGRPCSvc: { GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, @@ -1195,13 +1266,13 @@ var _ = Describe("ChangeProcessor", func() { gw.Listeners = nil // no ref grant exists yet for the routes - expGraph.Routes[httpRoute1].Conditions = []conditions.Condition{ + expGraph.Routes[httpRouteKey1].Conditions = []conditions.Condition{ staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", ), } - expGraph.Routes[grpcRoute1].Conditions = []conditions.Condition{ + expGraph.Routes[grpcRouteKey1].Conditions = []conditions.Condition{ staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service grpc-service-ns/grpc-service not permitted by any ReferenceGrant", ), @@ -1214,25 +1285,25 @@ var _ = Describe("ChangeProcessor", func() { } // gateway class does not exist so routes cannot attach - expGraph.Routes[httpRoute1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + expGraph.Routes[httpRouteKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingParent()}, } - expGraph.Routes[httpRoute1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + expGraph.Routes[httpRouteKey1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingParent()}, } - expGraph.Routes[grpcRoute1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + expGraph.Routes[grpcRouteKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingParent()}, } - expGraph.Routes[grpcRoute1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + 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 @@ -1263,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, @@ -1271,37 +1345,40 @@ 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(gw, httpListenerName) - listener80.Routes[httpRoute1].ParentRefs[0].Attachment = expAttachment80 - listener443.Routes[httpRoute1].ParentRefs[1].Attachment = expAttachment443 - listener80.Routes[grpcRoute1].ParentRefs[0].Attachment = expAttachment80 - listener443.Routes[grpcRoute1].ParentRefs[1].Attachment = expAttachment443 + listener80.Routes[httpRouteKey1].ParentRefs[0].Attachment = expAttachment80 + listener443.Routes[httpRouteKey1].ParentRefs[1].Attachment = expAttachment443 + listener80.Routes[grpcRouteKey1].ParentRefs[0].Attachment = expAttachment80 + listener443.Routes[grpcRouteKey1].ParentRefs[1].Attachment = expAttachment443 // no ref grant exists yet for hr1 - expGraph.Routes[httpRoute1].Conditions = []conditions.Condition{ - staticConds.NewRouteInvalidListener(), + expGraph.Routes[httpRouteKey1].Conditions = []conditions.Condition{ staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", ), + staticConds.NewRouteInvalidListener(), } - expGraph.Routes[httpRoute1].ParentRefs[0].Attachment = expAttachment80 - expGraph.Routes[httpRoute1].ParentRefs[1].Attachment = expAttachment443 + expGraph.Routes[httpRouteKey1].ParentRefs[0].Attachment = expAttachment80 + expGraph.Routes[httpRouteKey1].ParentRefs[1].Attachment = expAttachment443 // no ref grant exists yet for gr1 - expGraph.Routes[grpcRoute1].Conditions = []conditions.Condition{ - staticConds.NewRouteInvalidListener(), + expGraph.Routes[grpcRouteKey1].Conditions = []conditions.Condition{ staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service grpc-service-ns/grpc-service not permitted by any ReferenceGrant", ), + staticConds.NewRouteInvalidListener(), } - expGraph.Routes[grpcRoute1].ParentRefs[0].Attachment = expAttachment80 - expGraph.Routes[grpcRoute1].ParentRefs[1].Attachment = expAttachment443 + expGraph.Routes[grpcRouteKey1].ParentRefs[0].Attachment = expAttachment80 + expGraph.Routes[grpcRouteKey1].ParentRefs[1].Attachment = expAttachment443 // no ref grant exists yet for tr1 expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ @@ -1325,14 +1402,14 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(secretRefGrant) // no ref grant exists yet for hr1 - expGraph.Routes[httpRoute1].Conditions = []conditions.Condition{ + expGraph.Routes[httpRouteKey1].Conditions = []conditions.Condition{ staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", ), } // no ref grant exists yet for gr1 - expGraph.Routes[grpcRoute1].Conditions = []conditions.Condition{ + expGraph.Routes[grpcRouteKey1].Conditions = []conditions.Condition{ staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service grpc-service-ns/grpc-service not permitted by any ReferenceGrant", ), @@ -1370,7 +1447,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(hrServiceRefGrant) // no ref grant exists yet for gr1 - expGraph.Routes[grpcRoute1].Conditions = []conditions.Condition{ + expGraph.Routes[grpcRouteKey1].Conditions = []conditions.Condition{ staticConds.NewRouteBackendRefRefNotPermitted( "Backend ref to Service grpc-service-ns/grpc-service not permitted by any ReferenceGrant", ), @@ -1399,13 +1476,6 @@ var _ = Describe("ChangeProcessor", func() { ), } - expGraph.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ - refSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "gateway-1"}: {}, - }, - }, - } processAndValidateGraph(expGraph) }) }) @@ -1434,19 +1504,6 @@ var _ = Describe("ChangeProcessor", func() { ), } - expGraph.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ - refGRPCSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "gateway-1"}: {}, - }, - }, - refSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "gateway-1"}: {}, - }, - }, - } - processAndValidateGraph(expGraph) }) }) @@ -1518,10 +1575,10 @@ var _ = Describe("ChangeProcessor", func() { gw := expGraph.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-1"}] listener443 := getListenerByName(gw, httpsListenerName) - listener443.Routes[httpRoute1].Source.SetGeneration(hr1Updated.Generation) + listener443.Routes[httpRouteKey1].Source.SetGeneration(hr1Updated.Generation) listener80 := getListenerByName(gw, httpListenerName) - listener80.Routes[httpRoute1].Source.SetGeneration(hr1Updated.Generation) + listener80.Routes[httpRouteKey1].Source.SetGeneration(hr1Updated.Generation) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ Source: diffNsTLSSecret, CertBundle: diffNsTLSCert, @@ -1537,10 +1594,10 @@ var _ = Describe("ChangeProcessor", func() { gw := expGraph.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-1"}] listener443 := getListenerByName(gw, httpsListenerName) - listener443.Routes[grpcRoute1].Source.SetGeneration(gr1Updated.Generation) + listener443.Routes[grpcRouteKey1].Source.SetGeneration(gr1Updated.Generation) listener80 := getListenerByName(gw, httpListenerName) - listener80.Routes[grpcRoute1].Source.SetGeneration(gr1Updated.Generation) + listener80.Routes[grpcRouteKey1].Source.SetGeneration(gr1Updated.Generation) expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ Source: diffNsTLSSecret, CertBundle: diffNsTLSCert, @@ -1636,18 +1693,6 @@ var _ = Describe("ChangeProcessor", func() { It("returns populated graph with second gateway", func() { processor.CaptureUpsertChange(gw2) - grpcRoute := expGraph2.Routes[grpcRoute1] - grpcRoute.Conditions = append(grpcRoute.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", - )) - - httpRoute := expGraph2.Routes[httpRoute1] - httpRoute.Conditions = append(httpRoute.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"service\"", - )) - processAndValidateGraph(expGraph2) }) }) @@ -1655,63 +1700,31 @@ var _ = Describe("ChangeProcessor", func() { It("returns populated graph", func() { processor.CaptureUpsertChange(hr2) - grpcRoute := expGraph2.Routes[grpcRoute1] - grpcRoute.Conditions = append(grpcRoute.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", - )) - - httpRoute := expGraph2.Routes[httpRoute1] - httpRoute.Conditions = append(httpRoute.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"service\"", - )) - expGraph2.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ Source: diffNsTLSSecret, CertBundle: diffNsTLSCert, } - gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] + gw2NSName := client.ObjectKeyFromObject(gw2) + gw := expGraph2.Gateways[gw2NSName] listener80 := getListenerByName(gw, httpListenerName) listener80.Routes = map[graph.RouteKey]*graph.L7Route{ - httpRoute2: expRouteHR2, + httpRouteKey2: expRouteHR2, } listener443 := getListenerByName(gw, httpsListenerName) listener443.Routes = map[graph.RouteKey]*graph.L7Route{ - httpRoute2: expRouteHR2, + httpRouteKey2: expRouteHR2, } expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ - httpRoute2: expRouteHR2, - httpRoute1: expRouteHR1, - grpcRoute1: expRouteGR1, + httpRouteKey2: expRouteHR2, + httpRouteKey1: expRouteHR1, + grpcRouteKey1: expRouteGR1, } - httpRoute2 := expGraph2.Routes[httpRoute2] - httpRoute2.Conditions = append(httpRoute2.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"service\"", - )) - - expGraph2.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ - refSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "gateway-1"}: {}, - {Namespace: "test", Name: "gateway-2"}: {}, - }, - }, - refTLSSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "gateway-1"}: {}, - }, - }, - refGRPCSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{{Namespace: "test", Name: "gateway-1"}: {}}, - }, - } + expGraph2.ReferencedServices[refSvc].GatewayNsNames[gw2NSName] = struct{}{} processAndValidateGraph(expGraph2) }) @@ -1720,70 +1733,30 @@ var _ = Describe("ChangeProcessor", func() { It("returns populated graph", func() { processor.CaptureUpsertChange(gr2) - grpcRoute := expGraph2.Routes[grpcRoute1] - grpcRoute.Conditions = append(grpcRoute.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", - )) - - httpRoute := expGraph2.Routes[httpRoute1] - httpRoute.Conditions = append(httpRoute.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"service\"", - )) - - gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] + gw2NSName := client.ObjectKeyFromObject(gw2) + gw := expGraph2.Gateways[gw2NSName] listener80 := getListenerByName(gw, httpListenerName) listener80.Routes = map[graph.RouteKey]*graph.L7Route{ - httpRoute2: expRouteHR2, - grpcRoute2: expRouteGR2, + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, } listener443 := getListenerByName(gw, httpsListenerName) listener443.Routes = map[graph.RouteKey]*graph.L7Route{ - httpRoute2: expRouteHR2, - grpcRoute2: expRouteGR2, + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, } expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ - httpRoute2: expRouteHR2, - httpRoute1: expRouteHR1, - grpcRoute1: expRouteGR1, - grpcRoute2: expRouteGR2, + httpRouteKey2: expRouteHR2, + httpRouteKey1: expRouteHR1, + grpcRouteKey1: expRouteGR1, + grpcRouteKey2: expRouteGR2, } - httpRoute2 := expGraph2.Routes[httpRoute2] - httpRoute2.Conditions = append(httpRoute2.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"service\"", - )) - - grpcRoute2 := expGraph2.Routes[grpcRoute2] - grpcRoute2.Conditions = append(grpcRoute2.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", - )) - - expGraph2.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ - refSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "gateway-1"}: {}, - {Namespace: "test", Name: "gateway-2"}: {}, - }, - }, - refTLSSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "gateway-1"}: {}, - }, - }, - refGRPCSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "gateway-1"}: {}, - {Namespace: "test", Name: "gateway-2"}: {}, - }, - }, - } + expGraph2.ReferencedServices[refSvc].GatewayNsNames[gw2NSName] = struct{}{} + expGraph2.ReferencedServices[refGRPCSvc].GatewayNsNames[gw2NSName] = struct{}{} processAndValidateGraph(expGraph2) }) @@ -1792,30 +1765,19 @@ var _ = Describe("ChangeProcessor", func() { It("returns populated graph", func() { processor.CaptureUpsertChange(tr2) - grpcRoute := expGraph2.Routes[grpcRoute1] - grpcRoute.Conditions = append(grpcRoute.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", - )) - - httpRoute := expGraph2.Routes[httpRoute1] - httpRoute.Conditions = append(httpRoute.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"service\"", - )) - - gw := expGraph2.Gateways[types.NamespacedName{Namespace: "test", Name: "gateway-2"}] + gw2NSName := client.ObjectKeyFromObject(gw2) + gw := expGraph2.Gateways[gw2NSName] listener80 := getListenerByName(gw, httpListenerName) listener80.Routes = map[graph.RouteKey]*graph.L7Route{ - httpRoute2: expRouteHR2, - grpcRoute2: expRouteGR2, + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, } listener443 := getListenerByName(gw, httpsListenerName) listener443.Routes = map[graph.RouteKey]*graph.L7Route{ - httpRoute2: expRouteHR2, - grpcRoute2: expRouteGR2, + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, } tlsListener := getListenerByName(gw, tlsListenerName) @@ -1824,10 +1786,10 @@ var _ = Describe("ChangeProcessor", func() { } expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ - httpRoute2: expRouteHR2, - httpRoute1: expRouteHR1, - grpcRoute1: expRouteGR1, - grpcRoute2: expRouteGR2, + httpRouteKey2: expRouteHR2, + httpRouteKey1: expRouteHR1, + grpcRouteKey1: expRouteGR1, + grpcRouteKey2: expRouteGR2, } expGraph2.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ @@ -1835,38 +1797,9 @@ var _ = Describe("ChangeProcessor", func() { trKey2: expRouteTR2, } - httpRoute2 := expGraph2.Routes[httpRoute2] - httpRoute2.Conditions = append(httpRoute2.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"service\"", - )) - - grpcRoute2 := expGraph2.Routes[grpcRoute2] - grpcRoute2.Conditions = append(grpcRoute2.Conditions, - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"grpc-service\"", - )) - - expGraph2.ReferencedServices = map[types.NamespacedName]*graph.ReferencedService{ - refSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "gateway-1"}: {}, - {Namespace: "test", Name: "gateway-2"}: {}, - }, - }, - refTLSSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "gateway-1"}: {}, - {Namespace: "test", Name: "gateway-2"}: {}, - }, - }, - refGRPCSvc: { - GatewayNsNames: map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "gateway-1"}: {}, - {Namespace: "test", Name: "gateway-2"}: {}, - }, - }, - } + expGraph2.ReferencedServices[refSvc].GatewayNsNames[gw2NSName] = struct{}{} + expGraph2.ReferencedServices[refGRPCSvc].GatewayNsNames[gw2NSName] = struct{}{} + expGraph2.ReferencedServices[refTLSSvc].GatewayNsNames[gw2NSName] = struct{}{} processAndValidateGraph(expGraph2) }) @@ -1884,12 +1817,13 @@ var _ = Describe("ChangeProcessor", func() { Source: gw2, Listeners: []*graph.Listener{ { - Name: httpListenerName, - Source: gw2.Spec.Listeners[0], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + 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)}, @@ -1897,6 +1831,7 @@ var _ = Describe("ChangeProcessor", func() { }, { Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), Source: gw2.Spec.Listeners[1], Valid: true, Attachable: true, @@ -1909,12 +1844,13 @@ var _ = Describe("ChangeProcessor", func() { }, }, { - Name: tlsListenerName, - Source: gw2.Spec.Listeners[2], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + 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)}, }, @@ -1932,14 +1868,14 @@ var _ = Describe("ChangeProcessor", func() { listener80 := getListenerByName(gw, httpListenerName) listener80.Routes = map[graph.RouteKey]*graph.L7Route{ - httpRoute2: expRouteHR2, - grpcRoute2: expRouteGR2, + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, } listener443 := getListenerByName(gw, httpsListenerName) listener443.Routes = map[graph.RouteKey]*graph.L7Route{ - httpRoute2: expRouteHR2, - grpcRoute2: expRouteGR2, + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, } tlsListener := getListenerByName(gw, tlsListenerName) @@ -1948,8 +1884,8 @@ var _ = Describe("ChangeProcessor", func() { } expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ - httpRoute2: expRouteHR2, - grpcRoute2: expRouteGR2, + httpRouteKey2: expRouteHR2, + grpcRouteKey2: expRouteGR2, } expGraph2.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ @@ -1996,12 +1932,13 @@ var _ = Describe("ChangeProcessor", func() { Source: gw2, Listeners: []*graph.Listener{ { - Name: httpListenerName, - Source: gw2.Spec.Listeners[0], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + 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)}, @@ -2009,6 +1946,7 @@ var _ = Describe("ChangeProcessor", func() { }, { Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), Source: gw2.Spec.Listeners[1], Valid: true, Attachable: true, @@ -2021,12 +1959,13 @@ var _ = Describe("ChangeProcessor", func() { }, }, { - Name: tlsListenerName, - Source: gw2.Spec.Listeners[2], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + 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)}, }, @@ -2044,12 +1983,12 @@ var _ = Describe("ChangeProcessor", func() { listener80 := getListenerByName(gw, httpListenerName) listener80.Routes = map[graph.RouteKey]*graph.L7Route{ - grpcRoute2: expRouteGR2, + grpcRouteKey2: expRouteGR2, } listener443 := getListenerByName(gw, httpsListenerName) listener443.Routes = map[graph.RouteKey]*graph.L7Route{ - grpcRoute2: expRouteGR2, + grpcRouteKey2: expRouteGR2, } tlsListener := getListenerByName(gw, tlsListenerName) @@ -2058,7 +1997,7 @@ var _ = Describe("ChangeProcessor", func() { } expGraph2.Routes = map[graph.RouteKey]*graph.L7Route{ - grpcRoute2: expRouteGR2, + grpcRouteKey2: expRouteGR2, } expGraph2.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ @@ -2099,12 +2038,13 @@ var _ = Describe("ChangeProcessor", func() { Source: gw2, Listeners: []*graph.Listener{ { - Name: httpListenerName, - Source: gw2.Spec.Listeners[0], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + 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)}, @@ -2112,6 +2052,7 @@ var _ = Describe("ChangeProcessor", func() { }, { Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), Source: gw2.Spec.Listeners[1], Valid: true, Attachable: true, @@ -2124,12 +2065,13 @@ var _ = Describe("ChangeProcessor", func() { }, }, { - Name: tlsListenerName, - Source: gw2.Spec.Listeners[2], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + 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)}, }, @@ -2191,12 +2133,13 @@ var _ = Describe("ChangeProcessor", func() { Source: gw2, Listeners: []*graph.Listener{ { - Name: httpListenerName, - Source: gw2.Spec.Listeners[0], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + 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)}, @@ -2204,6 +2147,7 @@ var _ = Describe("ChangeProcessor", func() { }, { Name: httpsListenerName, + GatewayName: client.ObjectKeyFromObject(gw2), Source: gw2.Spec.Listeners[1], Valid: true, Attachable: true, @@ -2216,12 +2160,13 @@ var _ = Describe("ChangeProcessor", func() { }, }, { - Name: tlsListenerName, - Source: gw2.Spec.Listeners[2], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + 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)}, }, 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 9975f98ce8..0119476856 100644 --- a/internal/mode/static/state/dataplane/configuration.go +++ b/internal/mode/static/state/dataplane/configuration.go @@ -51,7 +51,7 @@ func BuildConfiguration( backendGroups := buildBackendGroups(append(httpServers, sslServers...)) upstreams := buildUpstreams( ctx, - gateway.Listeners, + gateway, serviceResolver, g.ReferencedServices, baseHTTPConfig.IPFamily, @@ -67,7 +67,7 @@ func BuildConfiguration( SSLServers: sslServers, TLSPassthroughServers: buildPassthroughServers(gateway), Upstreams: upstreams, - StreamUpstreams: buildStreamUpstreams(ctx, gateway.Listeners, serviceResolver, baseHTTPConfig.IPFamily), + StreamUpstreams: buildStreamUpstreams(ctx, gateway, serviceResolver, baseHTTPConfig.IPFamily), BackendGroups: backendGroups, SSLKeyPairs: buildSSLKeyPairs(g.ReferencedSecrets, gateway.Listeners), Version: configVersion, @@ -106,7 +106,8 @@ func buildPassthroughServers(gateway *graph.Gateway) []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 } @@ -158,7 +159,7 @@ func buildPassthroughServers(gateway *graph.Gateway) []Layer4VirtualServer { // buildStreamUpstreams builds all stream upstreams. func buildStreamUpstreams( ctx context.Context, - listeners []*graph.Listener, + gateway *graph.Gateway, serviceResolver resolver.ServiceResolver, ipFamily IPFamilyType, ) []Upstream { @@ -166,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 } @@ -182,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 { @@ -339,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 { @@ -347,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), }) } @@ -393,7 +409,7 @@ func buildServers(gateway *graph.Gateway) (http, ssl []VirtualServer) { rulesForProtocol[l.Source.Protocol][l.Source.Port] = rules } - rules.upsertListener(l) + rules.upsertListener(l, gateway) } } @@ -402,7 +418,7 @@ func buildServers(gateway *graph.Gateway) (http, ssl []VirtualServer) { httpServers, sslServers := httpRules.buildServers(), sslRules.buildServers() - pols := buildPolicies(gateway.Policies) + pols := buildPolicies(gateway, gateway.Policies) for i := range httpServers { httpServers[i].Policies = pols @@ -454,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) @@ -467,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 @@ -487,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 } @@ -522,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 { @@ -546,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), }) @@ -643,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, @@ -655,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 } @@ -670,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 } } } @@ -715,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: @@ -991,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 } @@ -1002,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) } diff --git a/internal/mode/static/state/dataplane/configuration_test.go b/internal/mode/static/state/dataplane/configuration_test.go index dd32d9fea9..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" @@ -267,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, }, }, }, @@ -485,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", @@ -518,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"}, }, }, }, @@ -913,9 +918,10 @@ func TestBuildConfiguration(t *testing.T) { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { 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, }) return g }), @@ -931,17 +937,19 @@ func TestBuildConfiguration(t *testing.T) { 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, }, @@ -972,6 +980,7 @@ func TestBuildConfiguration(t *testing.T) { 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{}, @@ -979,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{}, @@ -1018,6 +1028,7 @@ func TestBuildConfiguration(t *testing.T) { gw := g.Gateways[gatewayNsName] gw.Listeners = append(gw.Listeners, &graph.Listener{ Name: "invalid-listener", + GatewayName: gatewayNsName, Source: invalidListener, Valid: false, ResolvedSecret: &secret1NsName, @@ -1041,9 +1052,10 @@ func TestBuildConfiguration(t *testing.T) { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { 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(hr1): routeHR1, graph.CreateRouteKey(hr2): routeHR2, @@ -1103,9 +1115,10 @@ func TestBuildConfiguration(t *testing.T) { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { 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(gr): routeGR, }, @@ -1145,9 +1158,10 @@ func TestBuildConfiguration(t *testing.T) { 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, @@ -1155,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, }, @@ -1256,18 +1271,20 @@ func TestBuildConfiguration(t *testing.T) { 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, @@ -1397,34 +1414,38 @@ func TestBuildConfiguration(t *testing.T) { 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, }, @@ -1614,9 +1635,10 @@ func TestBuildConfiguration(t *testing.T) { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { 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(hr5): routeHR5, }, @@ -1675,27 +1697,30 @@ func TestBuildConfiguration(t *testing.T) { 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, @@ -1703,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, @@ -1715,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{}, @@ -1819,9 +1846,10 @@ func TestBuildConfiguration(t *testing.T) { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { 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(hr7): routeHR7, }, @@ -1873,18 +1901,20 @@ func TestBuildConfiguration(t *testing.T) { 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, }, @@ -1951,9 +1981,10 @@ func TestBuildConfiguration(t *testing.T) { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { gw := g.Gateways[gatewayNsName] gw.Listeners = append(gw.Listeners, &graph.Listener{ - 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(httpsHR8): httpsRouteHR8, }, @@ -2011,9 +2042,10 @@ func TestBuildConfiguration(t *testing.T) { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { gw := g.Gateways[gatewayNsName] gw.Listeners = append(gw.Listeners, &graph.Listener{ - 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(httpsHR9): httpsRouteHR9, }, @@ -2075,10 +2107,11 @@ func TestBuildConfiguration(t *testing.T) { Namespace: "ns", } gw.Listeners = append(gw.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) gw.EffectiveNginxProxy = nginxProxy return g @@ -2105,17 +2138,19 @@ func TestBuildConfiguration(t *testing.T) { 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, }, @@ -2204,10 +2239,11 @@ func TestBuildConfiguration(t *testing.T) { Namespace: "ns", } gw.Listeners = append(gw.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) gw.EffectiveNginxProxy = nginxProxyIPv4 return g @@ -2228,10 +2264,11 @@ func TestBuildConfiguration(t *testing.T) { Namespace: "ns", } gw.Listeners = append(gw.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) gw.EffectiveNginxProxy = nginxProxyIPv6 return g @@ -2252,10 +2289,11 @@ func TestBuildConfiguration(t *testing.T) { Namespace: "ns", } gw.Listeners = append(gw.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) gw.EffectiveNginxProxy = &graph.EffectiveNginxProxy{ RewriteClientIP: &ngfAPIv1alpha2.RewriteClientIP{ @@ -2295,10 +2333,11 @@ func TestBuildConfiguration(t *testing.T) { Namespace: "ns", } gw.Listeners = append(gw.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) gw.EffectiveNginxProxy = &graph.EffectiveNginxProxy{ Logging: &ngfAPIv1alpha2.NginxLogging{ @@ -2359,10 +2398,11 @@ func TestBuildConfiguration(t *testing.T) { Namespace: "ns", } gw.Listeners = append(gw.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) gw.EffectiveNginxProxy = &graph.EffectiveNginxProxy{ NginxPlus: &ngfAPIv1alpha2.NginxPlus{ @@ -2450,10 +2490,11 @@ func TestBuildConfiguration_Plus(t *testing.T) { Namespace: "ns", } gw.Listeners = append(gw.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + Name: "listener-80-1", + GatewayName: gatewayNsName, + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, }) gw.EffectiveNginxProxy = &graph.EffectiveNginxProxy{ NginxPlus: &ngfAPIv1alpha2.NginxPlus{ @@ -2900,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", @@ -2963,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") @@ -2985,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"}}: { @@ -3047,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, + }, }, } @@ -3085,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{ { @@ -3161,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": @@ -3184,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)) } @@ -3724,6 +3788,7 @@ func TestBuildPolicies(t *testing.T) { tests := []struct { name string + gateway *graph.Gateway policies []*graph.Policy expPolicies []string }{ @@ -3736,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{ @@ -3762,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 { @@ -3769,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())) @@ -3835,10 +3934,14 @@ func TestCreatePassthroughServers(t *testing.T) { secureAppKey := getL4RouteKey("secure-app") secureApp2Key := getL4RouteKey("secure-app2") secureApp3Key := getL4RouteKey("secure-app3") - testGraph := &graph.Gateway{ + gateway := &graph.Gateway{ Listeners: []*graph.Listener{ { - Name: "testingListener", + Name: "testingListener", + GatewayName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, Valid: true, Source: v1.Listener{ Protocol: v1.TLSProtocolType, @@ -3869,13 +3972,21 @@ func TestCreatePassthroughServers(t *testing.T) { { Attachment: &graph.ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ - "testingListener": {"app.example.com", "cafe.example.com"}, + graph.CreateGatewayListenerKey( + gatewayNsName, + "testingListener", + ): {"app.example.com", "cafe.example.com"}, }, }, SectionName: nil, Port: nil, - Gateway: types.NamespacedName{}, - Idx: 0, + Gateway: &graph.ParentRefGateway{ + NamespacedName: types.NamespacedName{ + Namespace: "test", + Name: "gateway", + }, + }, + Idx: 0, }, }, }, @@ -3923,7 +4034,7 @@ func TestCreatePassthroughServers(t *testing.T) { }, } - passthroughServers := buildPassthroughServers(testGraph) + passthroughServers := buildPassthroughServers(gateway) expectedPassthroughServers := []Layer4VirtualServer{ { @@ -3972,76 +4083,108 @@ func TestBuildStreamUpstreams(t *testing.T) { secureApp3Key := getL4RouteKey("secure-app3") secureApp4Key := getL4RouteKey("secure-app4") secureApp5Key := getL4RouteKey("secure-app5") - listeners := []*graph.Listener{ - { - Name: "testingListener", - Valid: true, - Source: v1.Listener{ - Protocol: v1.TLSProtocolType, - Port: 443, + secureApp6Key := getL4RouteKey("secure-app6") + + gateway := &graph.Gateway{ + Source: &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "gateway", }, - 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, + }, + 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, + }, }, }, }, @@ -4068,7 +4211,7 @@ func TestBuildStreamUpstreams(t *testing.T) { return fakeEndpoints, nil } - streamUpstreams := buildStreamUpstreams(context.Background(), listeners, &fakeResolver, Dual) + streamUpstreams := buildStreamUpstreams(context.Background(), gateway, &fakeResolver, Dual) expectedStreamUpstreams := []Upstream{ { diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index 2e36ff8fd3..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,15 +50,9 @@ func addBackendRefsToRouteRules( refGrantResolver *referenceGrantResolver, services map[types.NamespacedName]*v1.Service, backendTLSPolicies map[types.NamespacedName]*BackendTLSPolicy, - gws map[types.NamespacedName]*Gateway, ) { - for _, gw := range gws { - if gw == nil || gw == (&Gateway{}) { - continue - } - for _, r := range routes { - addBackendRefsToRules(r, refGrantResolver, services, backendTLSPolicies, gw.EffectiveNginxProxy) - } + for _, r := range routes { + addBackendRefsToRules(r, refGrantResolver, services, backendTLSPolicies) } } @@ -65,7 +63,6 @@ func addBackendRefsToRules( refGrantResolver *referenceGrantResolver, services map[types.NamespacedName]*v1.Service, backendTLSPolicies map[types.NamespacedName]*BackendTLSPolicy, - npCfg *EffectiveNginxProxy, ) { if !route.Valid { return @@ -90,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...) } } @@ -122,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. @@ -142,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 af7d99e20d..236bb70e38 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 { @@ -745,264 +753,6 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { } } -func TestAddBackendRefsToRouteRulesTest(t *testing.T) { - t.Parallel() - sectionNameRefs := []ParentRef{ - { - Idx: 0, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway"}, - Attachment: &ParentRefAttachmentStatus{ - Attached: true, - }, - }, - { - Idx: 0, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - Attachment: &ParentRefAttachmentStatus{ - Attached: true, - }, - }, - } - createRoute := func( - name string, - kind gatewayv1.Kind, - refsPerBackend int, - serviceNames ...string, - ) *L7Route { - hr := &L7Route{ - Source: &gatewayv1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: name, - }, - }, - RouteType: RouteTypeHTTP, - ParentRefs: sectionNameRefs, - Valid: true, - } - - createRouteBackendRef := func(svcName string, port gatewayv1.PortNumber, weight *int32) RouteBackendRef { - return RouteBackendRef{ - BackendRef: gatewayv1.BackendRef{ - BackendObjectReference: gatewayv1.BackendObjectReference{ - Kind: helpers.GetPointer(kind), - Name: gatewayv1.ObjectName(svcName), - Namespace: helpers.GetPointer[gatewayv1.Namespace]("test"), - Port: helpers.GetPointer(port), - }, - Weight: weight, - }, - } - } - - hr.Spec.Rules = make([]RouteRule, len(serviceNames)) - - for idx, svcName := range serviceNames { - refs := []RouteBackendRef{ - createRouteBackendRef(svcName, 80, nil), - } - if refsPerBackend == 2 { - refs = append(refs, createRouteBackendRef(svcName, 81, helpers.GetPointer[int32](5))) - } - if refsPerBackend != 1 && refsPerBackend != 2 { - panic("invalid refsPerBackend") - } - - hr.Spec.Rules[idx] = RouteRule{ - RouteBackendRefs: refs, - ValidMatches: true, - Filters: RouteRuleFilters{ - Filters: []Filter{}, - Valid: true, - }, - } - } - return hr - } - - getSvc := func(name string) *v1.Service { - return &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: name, - }, - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - { - Port: 80, - }, - { - Port: 81, - }, - }, - }, - } - } - - getPolicy := func(name, svcName, cmName string, gateway []types.NamespacedName) *BackendTLSPolicy { - return &BackendTLSPolicy{ - Valid: true, - Source: &v1alpha3.BackendTLSPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: "test", - }, - Spec: v1alpha3.BackendTLSPolicySpec{ - TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ - { - LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ - Group: "", - Kind: "Service", - Name: gatewayv1.ObjectName(svcName), - }, - }, - }, - Validation: v1alpha3.BackendTLSPolicyValidation{ - Hostname: "foo.example.com", - CACertificateRefs: []gatewayv1.LocalObjectReference{ - { - Group: "", - Kind: "ConfigMap", - Name: gatewayv1.ObjectName(cmName), - }, - }, - }, - }, - }, - Gateways: gateway, - } - } - - getBtp := func(name string, svcName string, cmName string) *BackendTLSPolicy { - return &BackendTLSPolicy{ - Source: &v1alpha3.BackendTLSPolicy{ - ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"}, - Spec: v1alpha3.BackendTLSPolicySpec{ - TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ - { - LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ - Group: "", - Kind: "Service", - Name: gatewayv1.ObjectName(svcName), - }, - }, - }, - Validation: v1alpha3.BackendTLSPolicyValidation{ - Hostname: "foo.example.com", - CACertificateRefs: []gatewayv1.LocalObjectReference{ - { - Group: "", - Kind: "ConfigMap", - Name: gatewayv1.ObjectName(cmName), - }, - }, - }, - }, - }, - Conditions: []conditions.Condition{ - { - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "Policy is accepted", - }, - }, - Valid: true, - IsReferenced: true, - } - } - - gatewayNsNames := []types.NamespacedName{ - {Namespace: "test", Name: "gateway"}, - {Namespace: "test", Name: "gateway-2"}, - } - btp1 := getBtp("btp1", "svc1", "test") - conds := []conditions.Condition{ - { - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "Policy is accepted", - }, - { - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "Policy is accepted", - }, - } - btp1.Conditions = append(btp1.Conditions, conds...) - btp1.Gateways = gatewayNsNames - - policies1 := map[types.NamespacedName]*BackendTLSPolicy{ - {Namespace: "test", Name: "btp1"}: getPolicy("btp1", "svc1", "test", gatewayNsNames), - } - - svc1 := getSvc("svc1") - svc2 := getSvc("svc2") - - gateways := map[types.NamespacedName]*Gateway{ - {Namespace: "test", Name: "gateway"}: { - Source: &gatewayv1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway", - Namespace: "test", - }, - }, - Valid: true, - EffectiveNginxProxy: &EffectiveNginxProxy{}, - }, - {Namespace: "test", Name: "gateway-2"}: { - Source: &gatewayv1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-2", - Namespace: "test", - }, - }, - Valid: true, - EffectiveNginxProxy: &EffectiveNginxProxy{}, - }, - {Namespace: "test", Name: "gateway-3"}: {}, - {Namespace: "test", Name: "gateway-4"}: nil, - } - - services := map[types.NamespacedName]*v1.Service{ - {Namespace: "test", Name: "svc1"}: svc1, - {Namespace: "test", Name: "svc2"}: svc2, - } - - route := map[RouteKey]*L7Route{ - {}: createRoute("hr1", "Service", 1, "svc1"), - } - - expectedBackendRefs := []BackendRef{ - { - SvcNsName: types.NamespacedName{Namespace: "test", Name: "svc1"}, - ServicePort: svc1.Spec.Ports[0], - Valid: true, - Weight: 1, - BackendTLSPolicy: btp1, - }, - } - - t.Run("routes with multiple gateways and backend TLS Policy", func(t *testing.T) { - t.Parallel() - - g := NewWithT(t) - resolver := newReferenceGrantResolver(nil) - addBackendRefsToRouteRules(route, resolver, services, policies1, gateways) - - var actual []BackendRef - route := route[RouteKey{}] - if route.Spec.Rules != nil { - actual = route.Spec.Rules[0].BackendRefs - } - - g.Expect(helpers.Diff(expectedBackendRefs, actual)).To(BeEmpty()) - g.Expect(route.Conditions).To(BeNil()) - }) -} - func TestCreateBackend(t *testing.T) { t.Parallel() createService := func(name string) *v1.Service { @@ -1082,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 }{ { @@ -1094,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", }, { @@ -1111,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", }, { @@ -1128,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", }, { @@ -1149,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", }, { @@ -1176,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{ @@ -1210,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", }, { @@ -1228,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", }, } @@ -1251,10 +1009,9 @@ func TestCreateBackend(t *testing.T) { policies := map[types.NamespacedName]*BackendTLSPolicy{ client.ObjectKeyFromObject(btp.Source): &btp, client.ObjectKeyFromObject(btp2.Source): &btp2, + // client.ObjectKeyFromObject(btpDupe.Source): &btpDupe, } - sourceNamespace := "test" - refPath := field.NewPath("test") for _, test := range tests { @@ -1268,18 +1025,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 477dc76a47..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,7 +19,7 @@ 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. + // 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 @@ -37,7 +38,7 @@ func processBackendTLSPolicies( ctlrName string, gateways map[types.NamespacedName]*Gateway, ) map[types.NamespacedName]*BackendTLSPolicy { - if len(backendTLSPolicies) == 0 || gateways == nil { + if len(backendTLSPolicies) == 0 || len(gateways) == 0 { return nil } @@ -188,43 +189,26 @@ func addGatewaysForBackendTLSPolicies( 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 refs.Kind != "Service" { - continue - } if svcNsName.Name != string(refs.Name) { continue } - backendTLSPolicy.Gateways = append( - backendTLSPolicy.Gateways, - getUniqueGatewayNsNames(referencedServices.GatewayNsNames)..., - ) + + for gateway := range referencedServices.GatewayNsNames { + gateways[gateway] = struct{}{} + } } } - } - deduplicateGateways(backendTLSPolicies) -} - -func getUniqueGatewayNsNames(gatewayMap map[types.NamespacedName]struct{}) []types.NamespacedName { - gatewayNsNames := make([]types.NamespacedName, 0, len(gatewayMap)) - for nsname := range gatewayMap { - gatewayNsNames = append(gatewayNsNames, nsname) - } - return gatewayNsNames -} - -func deduplicateGateways(backendTLSPolicies map[types.NamespacedName]*BackendTLSPolicy) { - for _, backendTLSPolicy := range backendTLSPolicies { - gatewayNsNames := make(map[types.NamespacedName]struct{}) - if len(backendTLSPolicy.Gateways) == 0 { - continue + for gateway := range gateways { + backendTLSPolicy.Gateways = append(backendTLSPolicy.Gateways, gateway) } - for _, gatewayNsName := range backendTLSPolicy.Gateways { - gatewayNsNames[gatewayNsName] = struct{}{} - } - - backendTLSPolicy.Gateways = getUniqueGatewayNsNames(gatewayNsNames) } } diff --git a/internal/mode/static/state/graph/gateway.go b/internal/mode/static/state/graph/gateway.go index 0b6b4370f0..2b31464f89 100644 --- a/internal/mode/static/state/graph/gateway.go +++ b/internal/mode/static/state/graph/gateway.go @@ -12,28 +12,28 @@ import ( staticConds "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/conditions" ) -// Gateway represents the winning Gateway resource. +// Gateway represents the a Gateway resource. type Gateway struct { - LatestReloadResult NginxReloadResult - Source *v1.Gateway - NginxProxy *NginxProxy + // 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 + // 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 types.NamespacedName - Listeners []*Listener - Conditions []conditions.Condition - Policies []*Policy - Valid bool -} - -// GetAllNsNames returns all the NamespacedNames of the Gateway resources that belong to NGF. -func GetAllNsNames(gws map[types.NamespacedName]*v1.Gateway) []types.NamespacedName { - allNsNames := make([]types.NamespacedName, 0, len(gws)) - - for nsName := range gws { - allNsNames = append(allNsNames, nsName) - } - - return allNsNames + // 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. + Conditions []conditions.Condition + // Policies holds the policies attached to the Gateway. + Policies []*Policy + // Valid indicates whether the Gateway Spec is valid. + Valid bool } // processGateways determines which Gateway resource belong to NGF (determined by the Gateway GatewayClassName field). @@ -41,7 +41,7 @@ func processGateways( gws map[types.NamespacedName]*v1.Gateway, gcName string, ) map[types.NamespacedName]*v1.Gateway { - referencedGws := make(map[types.NamespacedName]*v1.Gateway, len(gws)) + referencedGws := make(map[types.NamespacedName]*v1.Gateway) for gwNsName, gw := range gws { if string(gw.Spec.GatewayClassName) != gcName { @@ -58,20 +58,20 @@ func processGateways( return referencedGws } -func buildGateway( +func buildGateways( gws map[types.NamespacedName]*v1.Gateway, secretResolver *secretResolver, gc *GatewayClass, refGrantResolver *referenceGrantResolver, nps map[types.NamespacedName]*NginxProxy, ) map[types.NamespacedName]*Gateway { - if gws == nil { + if len(gws) == 0 { return nil } builtGateways := make(map[types.NamespacedName]*Gateway, len(gws)) - for gwNsNames, gw := range gws { + for gwNsName, gw := range gws { var np *NginxProxy var npNsName types.NamespacedName if gw.Spec.Infrastructure != nil && gw.Spec.Infrastructure.ParametersRef != nil { @@ -97,22 +97,29 @@ func buildGateway( protectedPorts[metricsPort] = "MetricsPort" } + deploymentName := types.NamespacedName{ + Namespace: gw.GetNamespace(), + Name: controller.CreateNginxResourceName(gw.GetName(), string(gw.Spec.GatewayClassName)), + } + if !valid { - builtGateways[gwNsNames] = &Gateway{ + builtGateways[gwNsName] = &Gateway{ Source: gw, Valid: false, NginxProxy: np, EffectiveNginxProxy: effectiveNginxProxy, Conditions: conds, + DeploymentName: deploymentName, } } else { - builtGateways[gwNsNames] = &Gateway{ + builtGateways[gwNsName] = &Gateway{ Source: gw, Listeners: buildListeners(gw, secretResolver, refGrantResolver, protectedPorts), NginxProxy: np, EffectiveNginxProxy: effectiveNginxProxy, Valid: true, Conditions: conds, + DeploymentName: deploymentName, } } } @@ -120,19 +127,6 @@ func buildGateway( return builtGateways } -func addDeploymentNameToGateway(gws map[types.NamespacedName]*Gateway) { - for _, gw := range gws { - if gw == nil { - continue - } - - gw.DeploymentName = types.NamespacedName{ - Namespace: gw.Source.Namespace, - Name: controller.CreateNginxResourceName(gw.Source.Name, string(gw.Source.Spec.GatewayClassName)), - } - } -} - func validateGatewayParametersRef(npCfg *NginxProxy, ref v1.LocalParametersReference) []conditions.Condition { var conds []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 1442ddc955..9c95f84401 100644 --- a/internal/mode/static/state/graph/gateway_test.go +++ b/internal/mode/static/state/graph/gateway_test.go @@ -15,59 +15,12 @@ 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() - gateway1 := &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "gateway-1", - }, - } - gateway2 := &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "gateway-2", - }, - } - - tests := []struct { - gws map[types.NamespacedName]*v1.Gateway - name string - expected []types.NamespacedName - }{ - { - gws: nil, - expected: []types.NamespacedName{}, - name: "no gateways", - }, - { - gws: map[types.NamespacedName]*v1.Gateway{ - client.ObjectKeyFromObject(gateway1): gateway1, - client.ObjectKeyFromObject(gateway2): gateway2, - }, - expected: []types.NamespacedName{ - client.ObjectKeyFromObject(gateway1), - client.ObjectKeyFromObject(gateway2), - }, - name: "all gateways", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := GetAllNsNames(test.gws) - g.Expect(result).To(ConsistOf(test.expected)) - }) - } -} - func TestProcessGateways(t *testing.T) { t.Parallel() const gcName = "test-gc" @@ -453,6 +406,7 @@ func TestBuildGateway(t *testing.T) { Listeners: []*Listener{ { Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo80Listener1, Valid: true, Attachable: true, @@ -462,6 +416,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "foo-8080", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo8080Listener, Valid: true, Attachable: true, @@ -470,6 +425,10 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, Valid: true, }, }, @@ -486,6 +445,7 @@ func TestBuildGateway(t *testing.T) { Listeners: []*Listener{ { Name: "foo-443-https-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo443HTTPSListener1, Valid: true, Attachable: true, @@ -496,6 +456,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "foo-8443-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo8443HTTPSListener, Valid: true, Attachable: true, @@ -505,6 +466,10 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway-https", gcName), + }, Valid: true, }, }, @@ -519,6 +484,7 @@ func TestBuildGateway(t *testing.T) { Listeners: []*Listener{ { Name: "listener-with-allowed-routes", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: listenerAllowedRoutes, Valid: true, Attachable: true, @@ -530,6 +496,10 @@ func TestBuildGateway(t *testing.T) { }, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, Valid: true, }, }, @@ -568,6 +538,7 @@ func TestBuildGateway(t *testing.T) { Listeners: []*Listener{ { Name: "listener-cross-ns-secret", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: crossNamespaceSecretListener, Valid: true, Attachable: true, @@ -577,6 +548,10 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, Valid: true, }, }, @@ -595,6 +570,7 @@ func TestBuildGateway(t *testing.T) { Listeners: []*Listener{ { Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo80Listener1, Valid: true, Attachable: true, @@ -603,6 +579,10 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway-valid-np", gcName), + }, Valid: true, NginxProxy: &NginxProxy{ Source: validGwNp, @@ -631,6 +611,7 @@ func TestBuildGateway(t *testing.T) { Listeners: []*Listener{ { Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo80Listener1, Valid: true, Attachable: true, @@ -639,6 +620,10 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway-valid-np", gcName), + }, Valid: true, NginxProxy: &NginxProxy{ Source: validGwNp, @@ -656,14 +641,15 @@ func TestBuildGateway(t *testing.T) { 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: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), Listeners: []*Listener{ { Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo80Listener1, Valid: true, Attachable: true, @@ -672,6 +658,10 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, Valid: true, EffectiveNginxProxy: &EffectiveNginxProxy{ IPFamily: helpers.GetPointer(ngfAPIv1alpha2.Dual), @@ -681,17 +671,18 @@ func TestBuildGateway(t *testing.T) { 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: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), Listeners: []*Listener{ { - Name: "listener-cross-ns-secret", - Source: crossNamespaceSecretListener, - Valid: false, - Attachable: true, + 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`, ), @@ -700,23 +691,28 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, 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: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), Listeners: []*Listener{ { - Name: "listener-with-invalid-selector", - Source: listenerInvalidSelector, - Valid: false, - Attachable: true, + 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`, ), @@ -727,23 +723,28 @@ func TestBuildGateway(t *testing.T) { }, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, 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: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), Listeners: []*Listener{ { - Name: "invalid-protocol", - Source: invalidProtocolListener, - Valid: false, - Attachable: false, + 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"`, ), @@ -751,6 +752,10 @@ func TestBuildGateway(t *testing.T) { L4Routes: map[L4RouteKey]*L4Route{}, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, Valid: true, }, }, @@ -759,6 +764,7 @@ func TestBuildGateway(t *testing.T) { { gateway: createGateway( gatewayCfg{ + name: "gateway1", listeners: []v1.Listener{ invalidPortListener, invalidHTTPSPortListener, @@ -768,14 +774,15 @@ func TestBuildGateway(t *testing.T) { ), gatewayClass: validGC, expected: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), Listeners: []*Listener{ { - Name: "invalid-port", - Source: invalidPortListener, - Valid: false, - Attachable: true, + 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`, ), @@ -784,10 +791,11 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, { - Name: "invalid-https-port", - Source: invalidHTTPSPortListener, - Valid: false, - Attachable: true, + 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`, ), @@ -796,10 +804,11 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, { - Name: "invalid-protected-port", - Source: invalidProtectedPortListener, - Valid: false, - Attachable: true, + 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`, ), @@ -808,6 +817,10 @@ func TestBuildGateway(t *testing.T) { L4Routes: map[L4RouteKey]*L4Route{}, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, Valid: true, }, }, @@ -815,15 +828,16 @@ func TestBuildGateway(t *testing.T) { }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{invalidHostnameListener, invalidHTTPSHostnameListener}}, + gatewayCfg{name: "gateway1", listeners: []v1.Listener{invalidHostnameListener, invalidHTTPSHostnameListener}}, ), gatewayClass: validGC, expected: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), Listeners: []*Listener{ { Name: "invalid-hostname", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: invalidHostnameListener, Valid: false, Conditions: staticConds.NewListenerUnsupportedValue(invalidHostnameMsg), @@ -833,6 +847,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "invalid-https-hostname", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: invalidHTTPSHostnameListener, Valid: false, Conditions: staticConds.NewListenerUnsupportedValue(invalidHostnameMsg), @@ -841,31 +856,40 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, Valid: true, }, }, name: "invalid hostnames", }, { - gateway: createGateway(gatewayCfg{listeners: []v1.Listener{invalidTLSConfigListener}}), + gateway: createGateway(gatewayCfg{name: "gateway1", listeners: []v1.Listener{invalidTLSConfigListener}}), gatewayClass: validGC, expected: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), Listeners: []*Listener{ { - Name: "invalid-tls-config", - Source: invalidTLSConfigListener, - Valid: false, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, + 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, }, }, @@ -874,6 +898,7 @@ func TestBuildGateway(t *testing.T) { { gateway: createGateway( gatewayCfg{ + name: "gateway1", listeners: []v1.Listener{ foo80Listener1, foo8080Listener, @@ -888,11 +913,12 @@ func TestBuildGateway(t *testing.T) { ), gatewayClass: validGC, expected: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), Listeners: []*Listener{ { Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo80Listener1, Valid: true, Attachable: true, @@ -902,6 +928,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "foo-8080", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo8080Listener, Valid: true, Attachable: true, @@ -911,6 +938,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "foo-8081", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo8081Listener, Valid: true, Attachable: true, @@ -920,6 +948,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "foo-443-https-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo443HTTPSListener1, Valid: true, Attachable: true, @@ -930,6 +959,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "foo-8443-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo8443HTTPSListener, Valid: true, Attachable: true, @@ -940,6 +970,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "bar-80", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: bar80Listener, Valid: true, Attachable: true, @@ -949,6 +980,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "bar-443-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: bar443HTTPSListener, Valid: true, Attachable: true, @@ -959,6 +991,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "bar-8443-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: bar8443HTTPSListener, Valid: true, Attachable: true, @@ -968,6 +1001,10 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, Valid: true, }, }, @@ -976,6 +1013,7 @@ func TestBuildGateway(t *testing.T) { { gateway: createGateway( gatewayCfg{ + name: "gateway1", listeners: []v1.Listener{ foo80Listener1, bar80Listener, @@ -988,11 +1026,12 @@ func TestBuildGateway(t *testing.T) { ), gatewayClass: validGC, expected: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), Listeners: []*Listener{ { Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo80Listener1, Valid: false, Attachable: true, @@ -1003,6 +1042,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "bar-80", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: bar80Listener, Valid: false, Attachable: true, @@ -1013,6 +1053,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "foo-443-http", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo443HTTPListener, Valid: false, Attachable: true, @@ -1023,6 +1064,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "foo-80-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo80HTTPSListener, Valid: false, Attachable: true, @@ -1034,6 +1076,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "foo-443-https-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo443HTTPSListener1, Valid: false, Attachable: true, @@ -1045,6 +1088,7 @@ func TestBuildGateway(t *testing.T) { }, { Name: "bar-443-https", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: bar443HTTPSListener, Valid: false, Attachable: true, @@ -1055,6 +1099,10 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, Valid: true, }, }, @@ -1063,15 +1111,20 @@ func TestBuildGateway(t *testing.T) { { gateway: createGateway( gatewayCfg{ + name: "gateway1", listeners: []v1.Listener{foo80Listener1, foo443HTTPSListener1}, addresses: []v1.GatewayAddress{{}}, }, ), gatewayClass: validGC, expected: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), - Valid: false, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: false, Conditions: staticConds.NewGatewayUnsupportedValue("spec." + "addresses: Forbidden: addresses are not supported", ), @@ -1086,12 +1139,16 @@ func TestBuildGateway(t *testing.T) { }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo80Listener1, invalidProtocolListener}}, + gatewayCfg{name: "gateway1", listeners: []v1.Listener{foo80Listener1, invalidProtocolListener}}, ), gatewayClass: invalidGC, expected: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { - Source: getLastCreatedGateway(), + {Namespace: "test", Name: "gateway1"}: { + Source: getLastCreatedGateway(), + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, Valid: false, Conditions: staticConds.NewGatewayInvalid("GatewayClass is invalid"), }, @@ -1100,12 +1157,16 @@ func TestBuildGateway(t *testing.T) { }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo80Listener1, invalidProtocolListener}}, + gatewayCfg{name: "gateway1", listeners: []v1.Listener{foo80Listener1, invalidProtocolListener}}, ), gatewayClass: nil, expected: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { - Source: getLastCreatedGateway(), + {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"), }, @@ -1114,28 +1175,34 @@ func TestBuildGateway(t *testing.T) { }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo443TLSListener, foo443HTTPListener}}, + gatewayCfg{name: "gateway1", listeners: []v1.Listener{foo443TLSListener, foo443HTTPListener}}, ), gatewayClass: validGC, expected: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), - Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + 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), + 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, @@ -1151,28 +1218,34 @@ func TestBuildGateway(t *testing.T) { }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo443TLSListener, splat443HTTPSListener}}, + gatewayCfg{name: "gateway1", listeners: []v1.Listener{foo443TLSListener, splat443HTTPSListener}}, ), gatewayClass: validGC, expected: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), - Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + 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), + 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, @@ -1189,27 +1262,33 @@ func TestBuildGateway(t *testing.T) { }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo443TLSListener, bar443HTTPSListener}}, + gatewayCfg{name: "gateway1", listeners: []v1.Listener{foo443TLSListener, bar443HTTPSListener}}, ), gatewayClass: validGC, expected: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), - Valid: true, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: true, Listeners: []*Listener{ { - Name: "foo-443-tls", - Source: foo443TLSListener, - Valid: true, - Attachable: true, - Routes: map[RouteKey]*L7Route{}, - L4Routes: map[L4RouteKey]*L4Route{}, + 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, @@ -1224,14 +1303,21 @@ func TestBuildGateway(t *testing.T) { 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: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), Listeners: []*Listener{ { Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo80Listener1, Valid: true, Attachable: true, @@ -1240,6 +1326,10 @@ func TestBuildGateway(t *testing.T) { 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( @@ -1256,14 +1346,21 @@ func TestBuildGateway(t *testing.T) { 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: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), Listeners: []*Listener{ { Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo80Listener1, Valid: true, Attachable: true, @@ -1272,6 +1369,10 @@ func TestBuildGateway(t *testing.T) { 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(), @@ -1284,14 +1385,21 @@ func TestBuildGateway(t *testing.T) { 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: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), Listeners: []*Listener{ { Name: "foo-80-1", + GatewayName: client.ObjectKeyFromObject(getLastCreatedGateway()), Source: foo80Listener1, Valid: true, Attachable: true, @@ -1300,6 +1408,10 @@ func TestBuildGateway(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, }, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, Valid: true, // invalid NginxProxy does not invalidate Gateway. NginxProxy: &NginxProxy{ Source: invalidGwNp, @@ -1318,13 +1430,20 @@ func TestBuildGateway(t *testing.T) { }, { gateway: createGateway( - gatewayCfg{listeners: []v1.Listener{foo80Listener1, invalidProtocolListener}, ref: invalidGwNpRef}, + gatewayCfg{ + name: "gateway1", + listeners: []v1.Listener{foo80Listener1, invalidProtocolListener}, ref: invalidGwNpRef, + }, ), gatewayClass: invalidGC, expected: map[types.NamespacedName]*Gateway{ - {Namespace: "test"}: { + {Namespace: "test", Name: "gateway1"}: { Source: getLastCreatedGateway(), - Valid: false, + DeploymentName: types.NamespacedName{ + Namespace: "test", + Name: controller.CreateNginxResourceName("gateway1", gcName), + }, + Valid: false, NginxProxy: &NginxProxy{ Source: invalidGwNp, ErrMsgs: field.ErrorList{ @@ -1363,7 +1482,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 e3095085c2..4b925f17f0 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -66,7 +66,7 @@ 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 @@ -125,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 @@ -177,7 +177,7 @@ func (g *Graph) gatewayAPIResourceExist(ref v1alpha2.LocalPolicyTargetReference, switch kind := ref.Kind; kind { case kinds.Gateway: - if g.Gateways == nil { + if len(g.Gateways) == 0 { return false } @@ -224,7 +224,7 @@ func BuildGraph( refGrantResolver := newReferenceGrantResolver(state.ReferenceGrants) - gws := buildGateway( + gws := buildGateways( processedGws, secretResolver, gc, @@ -232,8 +232,6 @@ func BuildGraph( processedNginxProxies, ) - addDeploymentNameToGateway(gws) - processedBackendTLSPolicies := processBackendTLSPolicies( state.BackendTLSPolicies, configMapResolver, @@ -244,32 +242,28 @@ func BuildGraph( processedSnippetsFilters := processSnippetsFilters(state.SnippetsFilters) - nsNamesForProcessedGateways := GetAllNsNames(processedGws) routes := buildRoutesForGateways( validators.HTTPFieldsValidator, state.HTTPRoutes, state.GRPCRoutes, - nsNamesForProcessedGateways, gws, processedSnippetsFilters, ) l4routes := buildL4RoutesForGateways( state.TLSRoutes, - nsNamesForProcessedGateways, state.Services, gws, refGrantResolver, ) - bindRoutesToListeners(routes, l4routes, gws, state.Namespaces) addBackendRefsToRouteRules( routes, refGrantResolver, state.Services, processedBackendTLSPolicies, - gws, ) + bindRoutesToListeners(routes, l4routes, gws, state.Namespaces) referencedNamespaces := buildReferencedNamespaces(state.Namespaces, gws) @@ -281,7 +275,6 @@ func BuildGraph( processedPolicies := processPolicies( state.NGFPolicies, validators.PolicyValidator, - processedGws, routes, referencedServices, gws, @@ -306,13 +299,13 @@ func BuildGraph( PlusSecrets: plusSecrets, } - g.attachPolicies(controllerName) + g.attachPolicies(validators.PolicyValidator, controllerName) return g } -func gatewayExists[T any](gwNsName types.NamespacedName, gateways map[types.NamespacedName]*T) bool { - if gateways == nil { +func gatewayExists(gwNsName types.NamespacedName, gateways map[types.NamespacedName]*Gateway) bool { + if len(gateways) == 0 { return false } diff --git a/internal/mode/static/state/graph/graph_test.go b/internal/mode/static/state/graph/graph_test.go index 7e6dd18ed0..98afa07a28 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -53,9 +53,6 @@ func TestBuildGraph(t *testing.T) { staticConds.NewPolicyAccepted(), staticConds.NewPolicyAccepted(), staticConds.NewPolicyAccepted(), - staticConds.NewPolicyAccepted(), - staticConds.NewPolicyAccepted(), - staticConds.NewPolicyAccepted(), } btp := BackendTLSPolicy{ @@ -168,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{ @@ -373,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)}, + }, }, }, }, @@ -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, }, }, }, @@ -867,13 +926,14 @@ func TestBuildGraph(t *testing.T) { }, Gateways: map[types.NamespacedName]*Gateway{ {Namespace: testNs, Name: "gateway-1"}: { - Source: gw1, + Source: gw1.Source, Listeners: []*Listener{ { - Name: "listener-80-1", - Source: gw1.Spec.Listeners[0], - Valid: true, - Attachable: true, + 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, @@ -884,7 +944,8 @@ func TestBuildGraph(t *testing.T) { }, { Name: "listener-443-1", - Source: gw1.Spec.Listeners[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}, @@ -893,23 +954,25 @@ func TestBuildGraph(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, { - Name: "listener-443-2", - Source: gw1.Spec.Listeners[2], - Valid: true, - Attachable: true, - L4Routes: map[L4RouteKey]*L4Route{CreateRouteKeyL4(tr): routeTR}, - Routes: map[RouteKey]*L7Route{}, + 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", - Source: gw1.Spec.Listeners[3], - Valid: true, - Attachable: true, - L4Routes: map[L4RouteKey]*L4Route{CreateRouteKeyL4(tr): routeTR}, - Routes: map[RouteKey]*L7Route{}, + 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)}, }, @@ -945,11 +1008,12 @@ func TestBuildGraph(t *testing.T) { }, }, {Namespace: testNs, Name: "gateway-2"}: { - Source: gw2, + Source: gw2.Source, Listeners: []*Listener{ { Name: "listener-80-1", - Source: gw2.Spec.Listeners[0], + GatewayName: types.NamespacedName{Namespace: testNs, Name: "gateway-2"}, + Source: gw2.Source.Spec.Listeners[0], Valid: true, Attachable: true, Routes: map[RouteKey]*L7Route{}, @@ -959,7 +1023,8 @@ func TestBuildGraph(t *testing.T) { }, { Name: "listener-443-1", - Source: gw2.Spec.Listeners[1], + GatewayName: types.NamespacedName{Namespace: testNs, Name: "gateway-2"}, + Source: gw2.Source.Spec.Listeners[1], Valid: true, Attachable: true, Routes: map[RouteKey]*L7Route{}, @@ -968,23 +1033,25 @@ func TestBuildGraph(t *testing.T) { SupportedKinds: supportedKindsForListeners, }, { - Name: "listener-443-2", - Source: gw2.Spec.Listeners[2], - Valid: true, - Attachable: true, - L4Routes: map[L4RouteKey]*L4Route{}, - Routes: map[RouteKey]*L7Route{}, + 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", - Source: gw2.Spec.Listeners[3], - Valid: true, - Attachable: true, - L4Routes: map[L4RouteKey]*L4Route{}, - Routes: map[RouteKey]*L7Route{}, + 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)}, }, 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 7cf9e5aa40..502adebc30 100644 --- a/internal/mode/static/state/graph/grpcroute_test.go +++ b/internal/mode/static/state/graph/grpcroute_test.go @@ -141,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, @@ -154,7 +154,7 @@ func TestBuildGRPCRoutes(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gwNsName, + Gateway: CreateParentRefGateway(gateways[gwNsName]), SectionName: gr.Spec.ParentRefs[0].SectionName, }, }, @@ -201,9 +201,9 @@ func TestBuildGRPCRoutes(t *testing.T) { name: "normal case", }, { - gwNsNames: []types.NamespacedName{}, - expected: nil, - name: "no gateways", + gateways: nil, + expected: nil, + name: "no gateways", }, } @@ -228,8 +228,7 @@ func TestBuildGRPCRoutes(t *testing.T) { validator, map[types.NamespacedName]*v1.HTTPRoute{}, grRoutes, - test.gwNsNames, - gateways, + test.gateways, snippetsFilters, ) g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) @@ -239,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") @@ -498,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(), @@ -513,7 +524,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grBoth.Spec.ParentRefs[0].SectionName, }, }, @@ -554,7 +565,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grEmptyMatch.Spec.ParentRefs[0].SectionName, }, }, @@ -586,7 +597,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grValidFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -629,7 +640,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidMatchesEmptyMethodFields.Spec.ParentRefs[0].SectionName, }, }, @@ -673,7 +684,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidMatchesInvalidMethodFields.Spec.ParentRefs[0].SectionName, }, }, @@ -710,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, @@ -743,7 +732,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grOneInvalid.Spec.ParentRefs[0].SectionName, }, }, @@ -789,7 +778,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidHeadersInvalidType.Spec.ParentRefs[0].SectionName, }, }, @@ -827,7 +816,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidHeadersEmptyType.Spec.ParentRefs[0].SectionName, }, }, @@ -865,7 +854,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidMatchesNilMethodType.Spec.ParentRefs[0].SectionName, }, }, @@ -902,7 +891,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -947,7 +936,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidHostname.Spec.ParentRefs[0].SectionName, }, }, @@ -970,7 +959,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -1008,7 +997,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grUnresolvableSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -1047,7 +1036,7 @@ func TestBuildGRPCRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: grInvalidAndUnresolvableSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -1081,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) { @@ -1091,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 89659b3cc9..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, @@ -131,25 +144,13 @@ func TestBuildHTTPRoutes(t *testing.T) { }, } - gateways := map[types.NamespacedName]*Gateway{ - {}: { - Source: &gatewayv1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "gateway", - }, - }, - Valid: true, - }, - } - 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, @@ -157,7 +158,7 @@ func TestBuildHTTPRoutes(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gwNsName, + Gateway: CreateParentRefGateway(gateways[gwNsName]), SectionName: hr.Spec.ParentRefs[0].SectionName, }, }, @@ -204,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", }, } @@ -231,8 +232,7 @@ func TestBuildHTTPRoutes(t *testing.T) { validator, hrRoutes, map[types.NamespacedName]*gatewayv1.GRPCRoute{}, - test.gwNsNames, - gateways, + test.gateways, snippetsFilters, ) g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) @@ -247,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{ @@ -369,7 +378,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hr.Spec.ParentRefs[0].SectionName, }, }, @@ -412,7 +421,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidMatchesEmptyPathType.Spec.ParentRefs[0].SectionName, }, }, @@ -458,7 +467,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidMatchesEmptyPathValue.Spec.ParentRefs[0].SectionName, }, }, @@ -501,7 +510,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidHostname.Spec.ParentRefs[0].SectionName, }, }, @@ -524,7 +533,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidMatches.Spec.ParentRefs[0].SectionName, }, }, @@ -561,7 +570,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidFilters.Spec.ParentRefs[0].SectionName, }, }, @@ -599,7 +608,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrDroppedInvalidMatches.Spec.ParentRefs[0].SectionName, }, }, @@ -646,7 +655,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrDroppedInvalidMatchesAndInvalidFilters.Spec.ParentRefs[0].SectionName, }, }, @@ -705,7 +714,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrDroppedInvalidFilters.Spec.ParentRefs[0].SectionName, }, }, @@ -752,7 +761,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrValidSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -794,7 +803,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -832,7 +841,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrUnresolvableSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -871,7 +880,7 @@ func TestBuildHTTPRoute(t *testing.T) { ParentRefs: []ParentRef{ { Idx: 0, - Gateway: gatewayNsName, + Gateway: CreateParentRefGateway(gw), SectionName: hrInvalidAndUnresolvableSnippetsFilter.Spec.ParentRefs[0].SectionName, }, }, @@ -907,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) { @@ -918,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 index b30beffa0b..472e1cac15 100644 --- a/internal/mode/static/state/graph/multiple_gateways_test.go +++ b/internal/mode/static/state/graph/multiple_gateways_test.go @@ -185,6 +185,7 @@ func createListener( func convertListener( listener gatewayv1.Listener, + gatewayNSName types.NamespacedName, secret *v1.Secret, supportedKinds []gatewayv1.RouteGroupKind, l7Route map[RouteKey]*L7Route, @@ -192,6 +193,7 @@ func convertListener( ) *Listener { l := &Listener{ Name: string(listener.Name), + GatewayName: gatewayNSName, Source: listener, L4Routes: l4Route, Routes: l7Route, @@ -599,6 +601,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { []*Listener{ convertListener( gateway1.Spec.Listeners[0], + client.ObjectKeyFromObject(gateway1), secretDiffNs, supportedHTTPGRPC, map[RouteKey]*L7Route{}, @@ -614,6 +617,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { []*Listener{ convertListener( gateway2.Spec.Listeners[0], + client.ObjectKeyFromObject(gateway2), secretDiffNs, supportedHTTPGRPC, map[RouteKey]*L7Route{}, @@ -670,6 +674,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { []*Listener{ convertListener( gatewayMultipleListeners1.Spec.Listeners[0], + client.ObjectKeyFromObject(gatewayMultipleListeners1), nil, supportedHTTPGRPC, map[RouteKey]*L7Route{}, @@ -677,6 +682,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { ), convertListener( gatewayMultipleListeners1.Spec.Listeners[1], + client.ObjectKeyFromObject(gatewayMultipleListeners1), secretSameNs, supportedHTTPGRPC, map[RouteKey]*L7Route{}, @@ -684,6 +690,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { ), convertListener( gatewayMultipleListeners1.Spec.Listeners[2], + client.ObjectKeyFromObject(gatewayMultipleListeners1), nil, supportedTLS, map[RouteKey]*L7Route{}, @@ -699,6 +706,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { []*Listener{ convertListener( gatewayMultipleListeners2.Spec.Listeners[0], + client.ObjectKeyFromObject(gatewayMultipleListeners2), nil, supportedHTTPGRPC, map[RouteKey]*L7Route{}, @@ -706,6 +714,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { ), convertListener( gatewayMultipleListeners2.Spec.Listeners[1], + client.ObjectKeyFromObject(gatewayMultipleListeners2), secretSameNs, supportedHTTPGRPC, map[RouteKey]*L7Route{}, @@ -713,6 +722,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { ), convertListener( gatewayMultipleListeners2.Spec.Listeners[2], + client.ObjectKeyFromObject(gatewayMultipleListeners2), nil, supportedTLS, map[RouteKey]*L7Route{}, @@ -728,6 +738,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { []*Listener{ convertListener( gatewayMultipleListeners3.Spec.Listeners[0], + client.ObjectKeyFromObject(gatewayMultipleListeners3), nil, supportedHTTPGRPC, map[RouteKey]*L7Route{}, @@ -735,6 +746,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { ), convertListener( gatewayMultipleListeners3.Spec.Listeners[1], + client.ObjectKeyFromObject(gatewayMultipleListeners3), secretSameNs, supportedHTTPGRPC, map[RouteKey]*L7Route{}, @@ -742,6 +754,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { ), convertListener( gatewayMultipleListeners3.Spec.Listeners[2], + client.ObjectKeyFromObject(gatewayMultipleListeners3), nil, supportedTLS, map[RouteKey]*L7Route{}, @@ -796,6 +809,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { []*Listener{ convertListener( gatewayTLSSamePortHostname.Spec.Listeners[0], + client.ObjectKeyFromObject(gatewayTLSSamePortHostname), nil, supportedTLS, map[RouteKey]*L7Route{}, @@ -811,6 +825,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { []*Listener{ convertListener( gatewayHTTPSSamePortHostname.Spec.Listeners[0], + client.ObjectKeyFromObject(gatewayHTTPSSamePortHostname), secretSameNs, supportedHTTPGRPC, map[RouteKey]*L7Route{}, diff --git a/internal/mode/static/state/graph/namespace.go b/internal/mode/static/state/graph/namespace.go index 27cda2acc9..8cbda90f7e 100644 --- a/internal/mode/static/state/graph/namespace.go +++ b/internal/mode/static/state/graph/namespace.go @@ -29,7 +29,7 @@ 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, gws map[types.NamespacedName]*Gateway) bool { - if gws == nil || ns == nil { + if ns == nil || len(gws) == 0 { return false } diff --git a/internal/mode/static/state/graph/nginxproxy.go b/internal/mode/static/state/graph/nginxproxy.go index baa9bebfd2..3b72161233 100644 --- a/internal/mode/static/state/graph/nginxproxy.go +++ b/internal/mode/static/state/graph/nginxproxy.go @@ -152,9 +152,10 @@ func processNginxProxies( 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 7b5eba3902..4e21c7283d 100644 --- a/internal/mode/static/state/graph/nginxproxy_test.go +++ b/internal/mode/static/state/graph/nginxproxy_test.go @@ -563,7 +563,7 @@ func TestProcessNginxProxies(t *testing.T) { gc: gatewayClass, gws: gateway, validator: createValidValidator(), - expResult: nil, + expResult: map[types.NamespacedName]*NginxProxy{gatewayNpName: nil}, }, { name: "gateway class param ref is missing namespace", diff --git a/internal/mode/static/state/graph/policies.go b/internal/mode/static/state/graph/policies.go index 74bf9ffffd..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,7 +70,7 @@ 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) { +func (g *Graph) attachPolicies(validator validation.PolicyValidator, ctlrName string) { if len(g.Gateways) == 0 { return } @@ -83,7 +86,7 @@ func (g *Graph) attachPolicies(ctlrName string) { continue } - attachPolicyToRoute(policy, route, ctlrName) + attachPolicyToRoute(policy, route, validator, ctlrName) case kinds.Service: svc, exists := g.ReferencedServices[ref.Nsname] if !exists { @@ -102,12 +105,13 @@ func attachPolicyToService( gws map[types.NamespacedName]*Gateway, ctlrName string, ) { - for gwNsNames, gw := range gws { - if ngfPolicyAncestorsFull(policy, ctlrName) { - return - } + if ngfPolicyAncestorsFull(policy, ctlrName) { + return + } - if _, belongsToGw := svc.GatewayNsNames[gwNsNames]; !belongsToGw { + var validForAGateway bool + for gwNsName, gw := range gws { + if _, belongsToGw := svc.GatewayNsNames[gwNsName]; !belongsToGw { continue } @@ -116,24 +120,33 @@ func attachPolicyToService( } if !gw.Valid { + policy.InvalidForGateways[gwNsName] = struct{}{} ancestor.Conditions = []conditions.Condition{staticConds.NewPolicyTargetNotFound("Parent Gateway is invalid")} if ancestorsContainsAncestorRef(policy.Ancestors, ancestor.Ancestor) { - return + continue } policy.Ancestors = append(policy.Ancestors, ancestor) - return + continue } if !ancestorsContainsAncestorRef(policy.Ancestors, ancestor.Ancestor) { policy.Ancestors = append(policy.Ancestors, ancestor) } + validForAGateway = true + } + 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 @@ -145,18 +158,32 @@ 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) } @@ -166,9 +193,8 @@ func attachPolicyToGateway( gateways map[types.NamespacedName]*Gateway, ctlrName string, ) { - gw, exists := gateways[ref.Nsname] - - if !exists && gw != nil && gw.Source == nil { + if ngfPolicyAncestorsFull(policy, ctlrName) { + // FIXME (kate-osborn): https://github.com/nginx/nginx-gateway-fabric/issues/1987 return } @@ -176,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 !exists { - 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 @@ -200,83 +225,73 @@ func attachPolicyToGateway( func processPolicies( pols map[PolicyKey]policies.Policy, validator validation.PolicyValidator, - processedGateways map[types.NamespacedName]*v1.Gateway, routes map[RouteKey]*L7Route, services map[types.NamespacedName]*ReferencedService, gws map[types.NamespacedName]*Gateway, ) map[PolicyKey]*Policy { - if len(pols) == 0 || len(processedGateways) == 0 { + if len(pols) == 0 || len(gws) == 0 { return nil } processedPolicies := make(map[PolicyKey]*Policy) - for _, gw := range gws { - for key, policy := range pols { - var conds []conditions.Condition + for key, policy := range pols { + var conds []conditions.Condition - 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), - } - } + targetRefs := make([]PolicyTargetRef, 0, len(policy.GetTargetRefs())) + targetedRoutes := make(map[types.NamespacedName]*L7Route) - targetRefs := make([]PolicyTargetRef, 0, len(policy.GetTargetRefs())) - targetedRoutes := make(map[types.NamespacedName]*L7Route) + for _, ref := range policy.GetTargetRefs() { + refNsName := types.NamespacedName{Name: string(ref.Name), Namespace: policy.GetNamespace()} - for _, ref := range policy.GetTargetRefs() { - refNsName := types.NamespacedName{Name: string(ref.Name), Namespace: policy.GetNamespace()} - - switch refGroupKind(ref.Group, ref.Kind) { - case gatewayGroupKind: - if !gatewayExists(refNsName, processedGateways) { - continue - } - case hrGroupKind, grpcGroupKind: - if route, exists := routes[routeKeyForKind(ref.Kind, refNsName)]; exists { - targetedRoutes[client.ObjectKeyFromObject(route.Source)] = route - } else { - continue - } - case serviceGroupKind: - if _, exists := services[refNsName]; !exists { - continue - } - default: + switch refGroupKind(ref.Group, ref.Kind) { + case gatewayGroupKind: + if !gatewayExists(refNsName, gws) { continue } - - targetRefs = append(targetRefs, - PolicyTargetRef{ - Kind: ref.Kind, - Group: ref.Group, - Nsname: refNsName, - }) - } - - if len(targetRefs) == 0 { + case hrGroupKind, grpcGroupKind: + if route, exists := routes[routeKeyForKind(ref.Kind, refNsName)]; exists { + targetedRoutes[client.ObjectKeyFromObject(route.Source)] = route + } else { + continue + } + case serviceGroupKind: + if _, exists := services[refNsName]; !exists { + continue + } + default: continue } - overlapConds := checkTargetRoutesForOverlap(targetedRoutes, routes) - conds = append(conds, overlapConds...) - - conds = append(conds, validator.Validate(policy, globalSettings)...) + targetRefs = append(targetRefs, + PolicyTargetRef{ + Kind: ref.Kind, + Group: ref.Group, + Nsname: refNsName, + }) + } - processedPolicies[key] = &Policy{ - Source: policy, - Valid: len(conds) == 0, - Conditions: conds, - TargetRefs: targetRefs, - Ancestors: make([]PolicyAncestor, 0, len(targetRefs)), - } + if len(targetRefs) == 0 { + continue } - markConflictedPolicies(processedPolicies, validator) + overlapConds := checkTargetRoutesForOverlap(targetedRoutes, routes) + conds = append(conds, overlapConds...) + + 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)), + InvalidForGateways: make(map[types.NamespacedName]struct{}), + } } + markConflictedPolicies(processedPolicies, validator) + return processedPolicies } diff --git a/internal/mode/static/state/graph/policies_test.go b/internal/mode/static/state/graph/policies_test.go index 479fe1e575..3dd7775f5b 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" @@ -110,7 +111,7 @@ func TestAttachPolicies(t *testing.T) { expectSvcPolicyAttachment := func(g *WithT, graph *Graph) { for _, r := range graph.ReferencedServices { - g.Expect(r.Policies).To(HaveLen(2)) + g.Expect(r.Policies).To(HaveLen(1)) } } @@ -148,7 +149,7 @@ func TestAttachPolicies(t *testing.T) { ) } - getGateway := func() map[types.NamespacedName]*Gateway { + getGateways := func() map[types.NamespacedName]*Gateway { return map[types.NamespacedName]*Gateway{ {Namespace: testNs, Name: "gateway"}: { Source: &v1.Gateway{ @@ -199,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){ @@ -212,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, @@ -224,7 +225,7 @@ func TestAttachPolicies(t *testing.T) { routes: getRoutes(), svcs: getServices(), ngfPolicies: getPolicies(), - gateway: getGateway(), + gateway: getGateways(), expects: expectAllAttachmentList, }, } @@ -241,7 +242,7 @@ func TestAttachPolicies(t *testing.T) { NGFPolicies: test.ngfPolicies, } - graph.attachPolicies("nginx-gateway") + graph.attachPolicies(nil, "nginx-gateway") for _, expect := range test.expects { expect(g, graph) } @@ -296,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{ @@ -337,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), @@ -349,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), @@ -361,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), @@ -375,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 { @@ -386,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)) @@ -437,6 +550,7 @@ func TestAttachPolicyToGateway(t *testing.T) { Kind: "Gateway", }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, gws: newGatewayMap(true, []types.NamespacedName{gatewayNsName}), expAncestors: []PolicyAncestor{ @@ -454,6 +568,7 @@ func TestAttachPolicyToGateway(t *testing.T) { Kind: "Gateway", }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, Ancestors: []PolicyAncestor{ {Ancestor: getGatewayParentRef(gatewayNsName)}, }, @@ -466,7 +581,7 @@ func TestAttachPolicyToGateway(t *testing.T) { expAttached: true, }, { - name: "not attached; gateway is invalid", + name: "not attached; gateway is not found", policy: &Policy{ Source: &policiesfakes.FakePolicy{}, TargetRefs: []PolicyTargetRef{ @@ -475,12 +590,13 @@ func TestAttachPolicyToGateway(t *testing.T) { Kind: "Gateway", }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, gws: newGatewayMap(true, []types.NamespacedName{gatewayNsName}), expAncestors: []PolicyAncestor{ { Ancestor: getGatewayParentRef(gateway2NsName), - Conditions: []conditions.Condition{staticConds.NewPolicyTargetNotFound("TargetRef is ignored")}, + Conditions: []conditions.Condition{staticConds.NewPolicyTargetNotFound("TargetRef is not found")}, }, }, expAttached: false, @@ -495,6 +611,7 @@ func TestAttachPolicyToGateway(t *testing.T) { Kind: "Gateway", }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, gws: newGatewayMap(false, []types.NamespacedName{gatewayNsName}), expAncestors: []PolicyAncestor{ @@ -505,26 +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", - }, - }, - }, - gws: newGatewayMap(true, []types.NamespacedName{gatewayNsName}), - expAncestors: []PolicyAncestor{ - { - Ancestor: getGatewayParentRef(gateway2NsName), - Conditions: []conditions.Condition{staticConds.NewPolicyTargetNotFound("TargetRef is ignored")}, - }, - }, - expAttached: false, - }, { name: "not attached; max ancestors", policy: &Policy{ @@ -535,6 +632,7 @@ func TestAttachPolicyToGateway(t *testing.T) { Kind: "Gateway", }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, gws: newGatewayMap(true, []types.NamespacedName{gatewayNsName}), expAncestors: nil, @@ -594,7 +692,7 @@ func TestAttachPolicyToService(t *testing.T) { }{ { name: "attachment", - policy: &Policy{Source: &policiesfakes.FakePolicy{}}, + policy: &Policy{Source: &policiesfakes.FakePolicy{}, InvalidForGateways: map[types.NamespacedName]struct{}{}}, svc: &ReferencedService{ GatewayNsNames: map[types.NamespacedName]struct{}{ gwNsname: {}, @@ -617,6 +715,7 @@ func TestAttachPolicyToService(t *testing.T) { Ancestor: getGatewayParentRef(gwNsname), }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, svc: &ReferencedService{ GatewayNsNames: map[types.NamespacedName]struct{}{ @@ -640,6 +739,7 @@ func TestAttachPolicyToService(t *testing.T) { Ancestor: getGatewayParentRef(gw2Nsname), }, }, + InvalidForGateways: map[types.NamespacedName]struct{}{}, }, svc: &ReferencedService{ GatewayNsNames: map[types.NamespacedName]struct{}{ @@ -660,7 +760,7 @@ func TestAttachPolicyToService(t *testing.T) { }, { name: "no attachment; gateway is invalid", - policy: &Policy{Source: &policiesfakes.FakePolicy{}}, + policy: &Policy{Source: &policiesfakes.FakePolicy{}, InvalidForGateways: map[types.NamespacedName]struct{}{}}, svc: &ReferencedService{ GatewayNsNames: map[types.NamespacedName]struct{}{ gwNsname: {}, @@ -677,7 +777,7 @@ func TestAttachPolicyToService(t *testing.T) { }, { name: "no attachment; max ancestor", - policy: &Policy{Source: createTestPolicyWithAncestors(16)}, + policy: &Policy{Source: createTestPolicyWithAncestors(16), InvalidForGateways: map[types.NamespacedName]struct{}{}}, svc: &ReferencedService{ GatewayNsNames: map[types.NamespacedName]struct{}{ gwNsname: {}, @@ -776,8 +876,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, @@ -788,8 +889,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, @@ -800,8 +902,9 @@ func TestProcessPolicies(t *testing.T) { Group: v1.GroupName, }, }, - Ancestors: []PolicyAncestor{}, - Valid: true, + Ancestors: []PolicyAncestor{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: true, }, pol4Key: { Source: pol4, @@ -812,8 +915,9 @@ func TestProcessPolicies(t *testing.T) { Group: v1.GroupName, }, }, - Ancestors: []PolicyAncestor{}, - Valid: true, + Ancestors: []PolicyAncestor{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: true, }, pol10Key: { Source: pol10, @@ -824,18 +928,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")} } @@ -860,8 +962,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, @@ -872,8 +975,9 @@ func TestProcessPolicies(t *testing.T) { Group: v1.GroupName, }, }, - Ancestors: []PolicyAncestor{}, - Valid: true, + Ancestors: []PolicyAncestor{}, + InvalidForGateways: map[types.NamespacedName]struct{}{}, + Valid: true, }, }, }, @@ -898,8 +1002,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, @@ -913,29 +1018,14 @@ 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, }, }, }, } - processedGateways := map[types.NamespacedName]*v1.Gateway{ - {Namespace: testNs, Name: "gw"}: { - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: testNs, - }, - }, - - {Namespace: testNs, Name: "gw2"}: { - ObjectMeta: metav1.ObjectMeta{ - Name: "gw2", - Namespace: testNs, - }, - }, - } - gateways := map[types.NamespacedName]*Gateway{ {Namespace: testNs, Name: "gw"}: { Source: &v1.Gateway{ @@ -985,7 +1075,7 @@ func TestProcessPolicies(t *testing.T) { t.Parallel() g := NewWithT(t) - processed := processPolicies(test.policies, test.validator, processedGateways, routes, services, gateways) + processed := processPolicies(test.policies, test.validator, routes, services, gateways) g.Expect(processed).To(BeEquivalentTo(test.expProcessedPolicies)) }) } @@ -1111,15 +1201,6 @@ func TestProcessPolicies_RouteOverlap(t *testing.T) { }, } - processedGateways := map[types.NamespacedName]*v1.Gateway{ - {Namespace: testNs, Name: "gw"}: { - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - Namespace: testNs, - }, - }, - } - gateways := map[types.NamespacedName]*Gateway{ {Namespace: testNs, Name: "gw"}: { Source: &v1.Gateway{ @@ -1137,7 +1218,7 @@ func TestProcessPolicies_RouteOverlap(t *testing.T) { t.Parallel() g := NewWithT(t) - processed := processPolicies(test.policies, test.validator, processedGateways, test.routes, nil, gateways) + 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 e109164309..e2b104b3d6 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,31 @@ 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 describes why the ParentRef is not attached to the Gateway. They are + // set when Attached is false. + 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 +185,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,34 +204,24 @@ func (e routeRuleErrors) append(newErrors routeRuleErrors) routeRuleErrors { func buildL4RoutesForGateways( tlsRoutes map[types.NamespacedName]*v1alpha.TLSRoute, - gatewayNsNames []types.NamespacedName, services map[types.NamespacedName]*apiv1.Service, gws map[types.NamespacedName]*Gateway, resolver *referenceGrantResolver, ) map[L4RouteKey]*L4Route { - if len(gatewayNsNames) == 0 || len(gws) == 0 { + if len(gws) == 0 { return nil } routes := make(map[L4RouteKey]*L4Route) - - for _, gw := range gws { - if gw == nil { - continue - } - - npCfg := gw.EffectiveNginxProxy - for _, route := range tlsRoutes { - r := buildTLSRoute( - route, - gatewayNsNames, - services, - npCfg, - resolver.refAllowedFrom(fromTLSRoute(route.Namespace)), - ) - if r != nil { - routes[CreateRouteKeyL4(route)] = r - } + for _, route := range tlsRoutes { + r := buildTLSRoute( + route, + gws, + services, + resolver.refAllowedFrom(fromTLSRoute(route.Namespace)), + ) + if r != nil { + routes[CreateRouteKeyL4(route)] = r } } @@ -224,56 +233,36 @@ func buildRoutesForGateways( validator validation.HTTPFieldsValidator, httpRoutes map[types.NamespacedName]*v1.HTTPRoute, grpcRoutes map[types.NamespacedName]*v1.GRPCRoute, - processedGwsNsNames []types.NamespacedName, gateways map[types.NamespacedName]*Gateway, snippetsFilters map[types.NamespacedName]*SnippetsFilter, ) map[RouteKey]*L7Route { - if len(processedGwsNsNames) == 0 || len(gateways) == 0 { + if len(gateways) == 0 { return nil } routes := make(map[RouteKey]*L7Route) - for _, gw := range gateways { - if gw == nil { - continue - } - http2disabled := isHTTP2Disabled(gw.EffectiveNginxProxy) - - for _, route := range httpRoutes { - r := buildHTTPRoute(validator, route, processedGwsNsNames, snippetsFilters) - if r != nil { - routes[CreateRouteKey(route)] = r - } + for _, route := range httpRoutes { + r := buildHTTPRoute(validator, route, gateways, snippetsFilters) + if r != nil { + routes[CreateRouteKey(route)] = r } + } - for _, route := range grpcRoutes { - r := buildGRPCRoute(validator, route, processedGwsNsNames, http2disabled, snippetsFilters) - if r != nil { - routes[CreateRouteKey(route)] = r - } + for _, route := range grpcRoutes { + r := buildGRPCRoute(validator, route, gateways, snippetsFilters) + if r != nil { + routes[CreateRouteKey(route)] = r } } 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)) @@ -284,8 +273,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 } @@ -294,19 +283,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, }) @@ -318,28 +308,31 @@ 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( @@ -387,23 +380,23 @@ func bindRoutesToListeners( } type hostPort struct { - gwNsNames types.NamespacedName - hostname string - port v1.PortNumber + gwNsName types.NamespacedName + hostname string + port v1.PortNumber } func getListenerHostPortMap(listeners []*Listener, gw *Gateway) map[string]hostPort { listenerHostPortMap := make(map[string]hostPort, len(listeners)) - gwNsNames := types.NamespacedName{ + gwNsName := types.NamespacedName{ Name: gw.Source.Name, Namespace: gw.Source.Namespace, } for _, l := range listeners { - key := fmt.Sprintf("%s,%s,%s", l.Name, gw.Source.Name, gw.Source.Namespace) + key := CreateGatewayListenerKey(client.ObjectKeyFromObject(gw.Source), l.Name) listenerHostPortMap[key] = hostPort{ - hostname: getHostname(l.Source.Hostname), - port: l.Source.Port, - gwNsNames: gwNsNames, + hostname: getHostname(l.Source.Hostname), + port: l.Source.Port, + gwNsName: gwNsName, } } @@ -442,14 +435,14 @@ func isolateHostnamesForParentRefs(parentRef []ParentRef, listenerHostnameMap ma 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.gwNsNames != ref.Gateway { + if lHostPort.gwNsName != ref.Gateway.NamespacedName { continue } @@ -460,11 +453,7 @@ func isolateHostnamesForParentRefs(parentRef []ParentRef, listenerHostnameMap ma // for L7Routes, we compare the hostname, port and listenerName combination // to identify if hostname needs to be isolated. - splitLName := strings.Split(lName, ",") - if len(splitLName) > 1 { - lName = splitLName[0] - } - 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. @@ -476,7 +465,7 @@ func isolateHostnamesForParentRefs(parentRef []ParentRef, listenerHostnameMap ma } isolatedHostnames := removeHostnames(hostnames, hostnamesToRemoves) - ref.Attachment.AcceptedHostnames[listenerName] = isolatedHostnames + ref.Attachment.AcceptedHostnames[key] = isolatedHostnames } } } @@ -512,7 +501,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 } @@ -520,16 +509,19 @@ func validateParentRef( if ref.Port != nil { valErr := field.Forbidden(path.Child("port"), "cannot be set") - attachment.FailedCondition = staticConds.NewRouteUnsupportedValue(valErr.Error()) + attachment.FailedConditions = append( + attachment.FailedConditions, staticConds.NewRouteUnsupportedValue(valErr.Error()), + ) return attachment, attachableListeners } // 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 } @@ -551,13 +543,17 @@ func bindL4RouteToListeners( Namespace: gw.Source.Namespace, } - if ref.Gateway != gwNsName { + if ref.Gateway.NamespacedName != gwNsName { continue } attachment, attachableListeners := validateParentRef(ref, gw) - if attachment.FailedCondition != (conditions.Condition{}) { + if cond, ok := route.Spec.BackendRef.InvalidForGateways[gwNsName]; ok { + attachment.FailedConditions = append(attachment.FailedConditions, cond) + } + + if len(attachment.FailedConditions) > 0 { continue } @@ -572,7 +568,7 @@ func bindL4RouteToListeners( portHostnamesMap, ) if !attached { - attachment.FailedCondition = cond + attachment.FailedConditions = append(attachment.FailedConditions, cond) continue } if cond != (conditions.Condition{}) { @@ -689,7 +685,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 @@ -712,13 +708,28 @@ func bindL7RouteToListeners( Namespace: gw.Source.Namespace, } - if ref.Gateway != gwNsName { + 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), + ) + } + + for _, rule := range route.Spec.Rules { + for _, backendRef := range rule.BackendRefs { + if cond, ok := backendRef.InvalidForGateways[gwNsName]; ok { + attachment.FailedConditions = append(attachment.FailedConditions, cond) + } + } + } + + if len(attachment.FailedConditions) > 0 { continue } @@ -732,7 +743,7 @@ func bindL7RouteToListeners( namespaces, ) if !attached { - attachment.FailedCondition = cond + attachment.FailedConditions = append(attachment.FailedConditions, cond) continue } if cond != (conditions.Condition{}) { @@ -743,6 +754,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 @@ -775,7 +798,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 61001cf0a4..b2efa4c1c0 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,7 +438,7 @@ 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, }, @@ -415,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, }, }, @@ -473,7 +509,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, }, }, @@ -505,12 +541,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"}, }, }, }, @@ -536,12 +575,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"}, }, }, }, @@ -568,13 +610,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"}, }, }, }, @@ -605,11 +653,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{}, }, }, @@ -631,13 +679,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, @@ -660,11 +710,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{}, }, }, @@ -686,11 +736,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{}, }, }, @@ -712,11 +762,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{}, }, }, @@ -738,7 +788,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, }, @@ -760,11 +810,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{}, }, }, @@ -788,12 +838,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"}, }, }, }, @@ -821,12 +874,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"}, }, }, }, @@ -854,12 +910,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"}, }, }, }, @@ -895,11 +954,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{}, }, }, @@ -937,12 +996,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"}, }, }, }, @@ -981,11 +1043,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{}, }, }, @@ -1019,12 +1081,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"}, }, }, }, @@ -1061,12 +1126,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"}, }, }, }, @@ -1104,11 +1172,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{}, }, }, @@ -1125,6 +1193,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{ @@ -1146,12 +1263,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"}, }, }, }, @@ -1180,6 +1300,7 @@ func TestBindRouteToListeners(t *testing.T) { }, }, } + for _, test := range tests { t.Run(test.name, func(t *testing.T) { g := NewWithT(t) @@ -1450,6 +1571,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")), @@ -1528,7 +1653,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, }, }, @@ -1547,7 +1672,7 @@ func TestBindL4RouteToListeners(t *testing.T) { noMatchingParentAttachment := ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), + FailedConditions: []conditions.Condition{staticConds.NewRouteNoMatchingParent()}, } notAttachableRoute := &L4Route{ @@ -1559,7 +1684,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, }, }, @@ -1573,7 +1698,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, Port: helpers.GetPointer[gatewayv1.PortNumber](80), }, @@ -1605,12 +1730,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"}, }, }, }, @@ -1640,7 +1768,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, }, }, @@ -1666,7 +1794,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, }, }, @@ -1692,16 +1820,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), }, @@ -1728,16 +1855,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, }, }, @@ -1757,6 +1879,7 @@ func TestBindL4RouteToListeners(t *testing.T) { }, 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"), @@ -1768,21 +1891,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"), @@ -1810,11 +1929,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, }, @@ -1828,11 +1950,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, }, @@ -1864,11 +1989,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()}, }, }, }, @@ -1897,11 +2022,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"}, }, }, }, @@ -1933,11 +2061,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](""), @@ -1967,7 +2098,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, }, }, @@ -1992,11 +2123,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"), @@ -2029,10 +2163,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"), }, @@ -2099,7 +2233,6 @@ func TestBuildL4RoutesForGateways_NoGateways(t *testing.T) { g.Expect(buildL4RoutesForGateways( tlsRoutes, - nil, services, nil, refGrantResolver, @@ -2261,11 +2394,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, @@ -2278,29 +2408,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{ @@ -2341,26 +2471,30 @@ func TestIsolateL4Listeners(t *testing.T) { } listenerMapHostnameIntersection := map[string]hostPort{ - "empty-hostname,test,gateway": {hostname: "", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, - "wildcard-example-com,test,gateway": { - hostname: "*.example.com", - port: 80, - gwNsNames: client.ObjectKeyFromObject(gw), + 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), }, - "foo-wildcard-example-com,test,gateway": { - hostname: "*.foo.example.com", - port: 80, - gwNsNames: client.ObjectKeyFromObject(gw), + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "foo-wildcard-example-com"): { + hostname: "*.foo.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), }, - "abc-com,test,gateway": { - hostname: "abc.foo.example.com", - port: 80, - gwNsNames: client.ObjectKeyFromObject(gw), + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "abc-com"): { + hostname: "abc.foo.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), }, - "no-match,test,gateway": { - hostname: "no-match.cafe.com", - port: 80, - gwNsNames: client.ObjectKeyFromObject(gw), + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "no-match"): { + hostname: "no-match.cafe.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), }, } @@ -2368,11 +2502,11 @@ func TestIsolateL4Listeners(t *testing.T) { "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, @@ -2382,11 +2516,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, @@ -2396,11 +2533,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, @@ -2410,11 +2550,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, @@ -2424,11 +2564,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, @@ -2548,7 +2688,7 @@ func TestIsolateL4Listeners(t *testing.T) { "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"}, @@ -2563,7 +2703,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"}, @@ -2578,7 +2718,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"}, @@ -2602,11 +2742,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: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: acceptedHostanamesMultipleGateways, @@ -2615,11 +2752,8 @@ func TestIsolateL4Listeners(t *testing.T) { }, }, { - Idx: 0, - Gateway: client.ObjectKey{ - Namespace: gw1.Namespace, - Name: gw1.Name, - }, + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: acceptedHostanamesMultipleGateways, @@ -2636,11 +2770,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: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: acceptedHostanamesMultipleGateways, @@ -2649,11 +2780,8 @@ func TestIsolateL4Listeners(t *testing.T) { }, }, { - Idx: 0, - Gateway: client.ObjectKey{ - Namespace: gw1.Namespace, - Name: gw1.Name, - }, + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: acceptedHostanamesMultipleGateways, @@ -2666,21 +2794,21 @@ func TestIsolateL4Listeners(t *testing.T) { }, listenerMap: map[string]hostPort{ "wildcard-example-com,test,gateway": { - hostname: "*.example.com", - port: 443, - gwNsNames: client.ObjectKeyFromObject(gw), + hostname: "*.example.com", + port: 443, + gwNsName: client.ObjectKeyFromObject(gw), }, "wildcard-example-com,test,gateway1": { - hostname: "*.example.com", - port: 443, - gwNsNames: client.ObjectKeyFromObject(gw), + hostname: "*.example.com", + port: 443, + gwNsName: client.ObjectKeyFromObject(gw), }, }, expectedResult: map[string][]ParentRef{ "tls_coffee": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tlsCoffeeRoute1.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ @@ -2693,7 +2821,7 @@ func TestIsolateL4Listeners(t *testing.T) { }, { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw1), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tlsCoffeeRoute1.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ @@ -2708,7 +2836,7 @@ func TestIsolateL4Listeners(t *testing.T) { "tls_flavor": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tlsFlavorRoute1.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ @@ -2721,7 +2849,7 @@ func TestIsolateL4Listeners(t *testing.T) { }, { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw1), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: tlsCoffeeRoute1.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ @@ -2809,11 +2937,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, @@ -2883,29 +3008,29 @@ func TestIsolateL7Listeners(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 := []*L7Route{ @@ -2947,26 +3072,30 @@ func TestIsolateL7Listeners(t *testing.T) { } listenerMapHostnameIntersection := map[string]hostPort{ - "empty-hostname,test,gateway": {hostname: "", port: 80, gwNsNames: client.ObjectKeyFromObject(gw)}, - "wildcard-example-com,test,gateway": { - hostname: "*.example.com", - port: 80, - gwNsNames: client.ObjectKeyFromObject(gw), + 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), }, - "foo-wildcard-example-com,test,gateway": { - hostname: "*.foo.example.com", - port: 80, - gwNsNames: client.ObjectKeyFromObject(gw), + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "foo-wildcard-example-com"): { + hostname: "*.foo.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), }, - "abc-com,test,gateway": { - hostname: "abc.foo.example.com", - port: 80, - gwNsNames: client.ObjectKeyFromObject(gw), + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "abc-com"): { + hostname: "abc.foo.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), }, - "no-match,test,gateway": { - hostname: "no-match.cafe.com", - port: 80, - gwNsNames: client.ObjectKeyFromObject(gw), + CreateGatewayListenerKey(client.ObjectKeyFromObject(gw), "no-match"): { + hostname: "no-match.cafe.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), }, } @@ -2974,11 +3103,11 @@ func TestIsolateL7Listeners(t *testing.T) { "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, @@ -2988,11 +3117,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, @@ -3002,11 +3134,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, @@ -3016,11 +3151,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, @@ -3030,11 +3165,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, @@ -3160,7 +3295,7 @@ func TestIsolateL7Listeners(t *testing.T) { "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{ @@ -3207,7 +3342,7 @@ func TestIsolateL7Listeners(t *testing.T) { "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"}, @@ -3222,7 +3357,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"}, @@ -3237,7 +3372,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"}, @@ -3261,11 +3396,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: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: acceptedHostNamesMultipleGateway, @@ -3274,11 +3406,8 @@ func TestIsolateL7Listeners(t *testing.T) { }, }, { - Idx: 0, - Gateway: client.ObjectKey{ - Namespace: gw1.Namespace, - Name: gw1.Name, - }, + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: acceptedHostNamesMultipleGateway, @@ -3295,11 +3424,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: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: acceptedHostNamesMultipleGateway, @@ -3308,11 +3434,8 @@ func TestIsolateL7Listeners(t *testing.T) { }, }, { - Idx: 0, - Gateway: client.ObjectKey{ - Namespace: gw1.Namespace, - Name: gw1.Name, - }, + Idx: 0, + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: helpers.GetPointer[gatewayv1.SectionName]("wildcard-example-com"), Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: acceptedHostNamesMultipleGateway, @@ -3325,21 +3448,21 @@ func TestIsolateL7Listeners(t *testing.T) { }, listenersMap: map[string]hostPort{ "wildcard-example-com,test,gateway": { - hostname: "*.example.com", - port: 80, - gwNsNames: client.ObjectKeyFromObject(gw), + hostname: "*.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw), }, "wildcard-example-com,test,gateway1": { - hostname: "*.example.com", - port: 80, - gwNsNames: client.ObjectKeyFromObject(gw1), + hostname: "*.example.com", + port: 80, + gwNsName: client.ObjectKeyFromObject(gw1), }, }, expectedResult: map[string][]ParentRef{ "hr_coffee": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrCoffeeRoute1.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ @@ -3352,7 +3475,7 @@ func TestIsolateL7Listeners(t *testing.T) { }, { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw1), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrCoffeeRoute1.Spec.ParentRefs[1].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ @@ -3367,7 +3490,7 @@ func TestIsolateL7Listeners(t *testing.T) { "hr_flavor": { { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrFlavorRoute1.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ @@ -3380,7 +3503,7 @@ func TestIsolateL7Listeners(t *testing.T) { }, { Idx: 0, - Gateway: client.ObjectKeyFromObject(gw1), + Gateway: &ParentRefGateway{NamespacedName: client.ObjectKeyFromObject(gw)}, SectionName: hrFlavorRoute1.Spec.ParentRefs[0].SectionName, Attachment: &ParentRefAttachmentStatus{ AcceptedHostnames: map[string][]string{ diff --git a/internal/mode/static/state/graph/service.go b/internal/mode/static/state/graph/service.go index aceae55bf8..7a41b07132 100644 --- a/internal/mode/static/state/graph/service.go +++ b/internal/mode/static/state/graph/service.go @@ -9,8 +9,10 @@ import ( // 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 []*Policy + // Policies is a list of NGF Policies that target this Service. + Policies []*Policy } func buildReferencedServices( @@ -19,14 +21,14 @@ func buildReferencedServices( gws map[types.NamespacedName]*Gateway, ) map[types.NamespacedName]*ReferencedService { referencedServices := make(map[types.NamespacedName]*ReferencedService) - for gwNsNames, gw := range gws { + for gwNsName, gw := range gws { if gw == nil { continue } belongsToGw := func(refs []ParentRef) bool { for _, ref := range refs { - if ref.Gateway == client.ObjectKeyFromObject(gw.Source) { + if ref.Gateway.NamespacedName == client.ObjectKeyFromObject(gw.Source) { return true } } @@ -43,7 +45,7 @@ func buildReferencedServices( // Processes both valid and invalid BackendRefs as invalid ones still have referenced services // we may want to track. - addServicesAndGatewayForL7Routes(route.Spec.Rules, gwNsNames, referencedServices) + addServicesAndGatewayForL7Routes(route.Spec.Rules, gwNsName, referencedServices) } for _, route := range l4Routes { @@ -51,11 +53,7 @@ func buildReferencedServices( continue } - addServicesAndGatewayForL4Routes(route, gwNsNames, referencedServices) - } - - if len(referencedServices) == 0 { - continue + addServicesAndGatewayForL4Routes(route, gwNsName, referencedServices) } } @@ -68,7 +66,7 @@ func buildReferencedServices( func addServicesAndGatewayForL4Routes( route *L4Route, - gwNsNames types.NamespacedName, + gwNsName types.NamespacedName, referencedServices map[types.NamespacedName]*ReferencedService, ) { nsname := route.Spec.BackendRef.SvcNsName @@ -79,13 +77,13 @@ func addServicesAndGatewayForL4Routes( GatewayNsNames: make(map[types.NamespacedName]struct{}), } } - referencedServices[nsname].GatewayNsNames[gwNsNames] = struct{}{} + referencedServices[nsname].GatewayNsNames[gwNsName] = struct{}{} } } func addServicesAndGatewayForL7Routes( routeRules []RouteRule, - gwNsNames types.NamespacedName, + gwNsName types.NamespacedName, referencedServices map[types.NamespacedName]*ReferencedService, ) { for _, rule := range routeRules { @@ -98,7 +96,7 @@ func addServicesAndGatewayForL7Routes( } } - referencedServices[ref.SvcNsName].GatewayNsNames[gwNsNames] = 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 b8db40e66c..3ec411cae3 100644 --- a/internal/mode/static/state/graph/service_test.go +++ b/internal/mode/static/state/graph/service_test.go @@ -12,31 +12,31 @@ import ( func TestBuildReferencedServices(t *testing.T) { t.Parallel() - gwNsname := types.NamespacedName{Namespace: "test", Name: "gwNsname"} - gw2NsNames := types.NamespacedName{Namespace: "test", Name: "gw2Nsname"} - gw3NsNames := types.NamespacedName{Namespace: "test", Name: "gw3Nsname"} + 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: { + gwNsName: { Source: &v1.Gateway{ ObjectMeta: metav1.ObjectMeta{ - Namespace: gwNsname.Namespace, - Name: gwNsname.Name, + Namespace: gwNsName.Namespace, + Name: gwNsName.Name, }, }, }, - gw2NsNames: { + gw2NsName: { Source: &v1.Gateway{ ObjectMeta: metav1.ObjectMeta{ - Namespace: gw2NsNames.Namespace, - Name: gw2NsNames.Name, + Namespace: gw2NsName.Namespace, + Name: gw2NsName.Name, }, }, }, - gw3NsNames: { + gw3NsName: { Source: &v1.Gateway{ ObjectMeta: metav1.ObjectMeta{ - Namespace: gw3NsNames.Namespace, - Name: gw3NsNames.Name, + Namespace: gw3NsName.Namespace, + Name: gw3NsName.Name, }, }, }, @@ -44,10 +44,10 @@ func TestBuildReferencedServices(t *testing.T) { parentRefs := []ParentRef{ { - Gateway: gwNsname, + Gateway: &ParentRefGateway{NamespacedName: gwNsName}, }, { - Gateway: gw2NsNames, + Gateway: &ParentRefGateway{NamespacedName: gw2NsName}, }, } 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 a895cd3fc6..b1ed52fdcc 100644 --- a/internal/mode/static/status/prepare_requests.go +++ b/internal/mode/static/status/prepare_requests.go @@ -121,7 +121,7 @@ func prepareRouteStatus( allConds = append(allConds, defaultConds...) allConds = append(allConds, conds...) if failedAttachmentCondCount == 1 { - allConds = append(allConds, ref.Attachment.FailedCondition) + 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), @@ -372,11 +372,11 @@ func PrepareBackendTLSPolicyRequests( apiConds := conditions.ConvertConditions(conds, pol.Source.Generation, transitionTime) policyAncestors := make([]v1alpha2.PolicyAncestorStatus, 0, len(pol.Gateways)) - for _, gwNsNames := range pol.Gateways { + for _, gwNsName := range pol.Gateways { policyAncestorStatus := v1alpha2.PolicyAncestorStatus{ AncestorRef: v1.ParentReference{ - Namespace: helpers.GetPointer(v1.Namespace(gwNsNames.Namespace)), - Name: v1.ObjectName(gwNsNames.Name), + 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), }, diff --git a/internal/mode/static/status/prepare_requests_test.go b/internal/mode/static/status/prepare_requests_test.go index 604429992f..844f36f93a 100644 --- a/internal/mode/static/status/prepare_requests_test.go +++ b/internal/mode/static/status/prepare_requests_test.go @@ -85,7 +85,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 +93,11 @@ 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}, }, }, } @@ -105,7 +105,7 @@ var ( parentRefsInvalid = []graph.ParentRef{ { Idx: 0, - Gateway: gwNsName, + Gateway: &graph.ParentRefGateway{NamespacedName: gwNsName}, Attachment: nil, SectionName: commonRouteSpecInvalid.ParentRefs[0].SectionName, }, @@ -475,7 +475,7 @@ func TestBuildRouteStatusesNginxErr(t *testing.T) { ParentRefs: []graph.ParentRef{ { Idx: 0, - Gateway: gwNsName, + Gateway: &graph.ParentRefGateway{NamespacedName: gwNsName}, Attachment: &graph.ParentRefAttachmentStatus{ Attached: true, }, diff --git a/tests/suite/manifests/clientsettings/invalid-route-csp.yaml b/tests/suite/manifests/clientsettings/invalid-route-csp.yaml index 894b8b4775..e856d6e5e9 100644 --- a/tests/suite/manifests/clientsettings/invalid-route-csp.yaml +++ b/tests/suite/manifests/clientsettings/invalid-route-csp.yaml @@ -30,4 +30,4 @@ spec: kind: HTTPRoute name: invalid-route keepAlive: - requests: 200 \ No newline at end of file + requests: 200 From b10a61c74105b86cbd20a4a0765354ec1d7af6bb Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Mon, 14 Apr 2025 14:04:52 -0600 Subject: [PATCH 07/19] Remove unnecessary logic --- internal/mode/static/handler.go | 9 --------- internal/mode/static/handler_test.go | 2 ++ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/internal/mode/static/handler.go b/internal/mode/static/handler.go index df027fb716..d0cc5403d9 100644 --- a/internal/mode/static/handler.go +++ b/internal/mode/static/handler.go @@ -194,15 +194,6 @@ func (h *eventHandlerImpl) sendNginxConfig( } for _, gw := range gr.Gateways { - if gw == nil { - // still need to update GatewayClass status - obj := &status.QueueObject{ - UpdateType: status.UpdateAll, - } - h.cfg.statusQueue.Enqueue(obj) - return - } - go func() { if err := h.cfg.nginxProvisioner.RegisterGateway(ctx, gw, gw.DeploymentName.Name); err != nil { logger.Error(err, "error from provisioner") diff --git a/internal/mode/static/handler_test.go b/internal/mode/static/handler_test.go index da939e932c..8c6753a6b3 100644 --- a/internal/mode/static/handler_test.go +++ b/internal/mode/static/handler_test.go @@ -212,6 +212,8 @@ 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 + Expect(fakeStatusUpdater.UpdateGroupCallCount()).Should(Equal(1)) }) }) From 095f23d8cda9b5856699e86076ff888a163f38ea Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Mon, 14 Apr 2025 14:36:33 -0600 Subject: [PATCH 08/19] Fix test timing issue --- internal/mode/static/handler_test.go | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/internal/mode/static/handler_test.go b/internal/mode/static/handler_test.go index 8c6753a6b3..1c2dcda3a3 100644 --- a/internal/mode/static/handler_test.go +++ b/internal/mode/static/handler_test.go @@ -213,7 +213,10 @@ var _ = Describe("eventHandler", func() { Expect(fakeProvisioner.RegisterGatewayCallCount()).Should(Equal(0)) Expect(fakeGenerator.GenerateCallCount()).Should(Equal(0)) // status update for GatewayClass should still occur - Expect(fakeStatusUpdater.UpdateGroupCallCount()).Should(Equal(1)) + Eventually( + func() int { + return fakeStatusUpdater.UpdateGroupCallCount() + }).Should(Equal(1)) }) }) @@ -260,7 +263,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)) @@ -275,7 +282,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)) @@ -303,7 +314,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()) From c1dd27050af6470dff8fecf332885a3350b62a6d Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Tue, 15 Apr 2025 09:57:39 -0600 Subject: [PATCH 09/19] Fix invalid backend attachment --- .../mode/static/state/graph/route_common.go | 21 ++-- .../static/state/graph/route_common_test.go | 98 +++++++++++++++++++ .../mode/static/status/prepare_requests.go | 6 +- .../static/status/prepare_requests_test.go | 44 +++++++++ 4 files changed, 156 insertions(+), 13 deletions(-) diff --git a/internal/mode/static/state/graph/route_common.go b/internal/mode/static/state/graph/route_common.go index e2b104b3d6..8e84ae0c32 100644 --- a/internal/mode/static/state/graph/route_common.go +++ b/internal/mode/static/state/graph/route_common.go @@ -42,8 +42,9 @@ type ParentRefAttachmentStatus struct { // AcceptedHostnames is an intersection between the hostnames supported by an attached Listener // and the hostnames from this Route. Key is , value is list of hostnames. AcceptedHostnames map[string][]string - // FailedConditions are the conditions that describes why the ParentRef is not attached to the Gateway. They are - // set when Attached is false. + // 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 @@ -549,14 +550,14 @@ func bindL4RouteToListeners( attachment, attachableListeners := validateParentRef(ref, gw) - if cond, ok := route.Spec.BackendRef.InvalidForGateways[gwNsName]; ok { - attachment.FailedConditions = append(attachment.FailedConditions, cond) - } - if len(attachment.FailedConditions) > 0 { continue } + 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( @@ -721,6 +722,10 @@ func bindL7RouteToListeners( ) } + if len(attachment.FailedConditions) > 0 { + continue + } + for _, rule := range route.Spec.Rules { for _, backendRef := range rule.BackendRefs { if cond, ok := backendRef.InvalidForGateways[gwNsName]; ok { @@ -729,10 +734,6 @@ func bindL7RouteToListeners( } } - if len(attachment.FailedConditions) > 0 { - continue - } - // Try to attach Route to all matching listeners cond, attached := tryToAttachL7RouteToListeners( diff --git a/internal/mode/static/state/graph/route_common_test.go b/internal/mode/static/state/graph/route_common_test.go index b2efa4c1c0..349df9d847 100644 --- a/internal/mode/static/state/graph/route_common_test.go +++ b/internal/mode/static/state/graph/route_common_test.go @@ -465,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, @@ -1290,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{ @@ -1706,6 +1756,13 @@ func TestBindL4RouteToListeners(t *testing.T) { Attachable: true, } + routeWithInvalidBackendRefs := createNormalRoute(gw) + routeWithInvalidBackendRefs.Spec.BackendRef = BackendRef{ + InvalidForGateways: map[types.NamespacedName]conditions.Condition{ + client.ObjectKeyFromObject(gw): {Message: "invalid backend"}, + }, + } + tests := []struct { route *L4Route gateway *Gateway @@ -2178,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{ diff --git a/internal/mode/static/status/prepare_requests.go b/internal/mode/static/status/prepare_requests.go index b1ed52fdcc..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,7 +120,7 @@ func prepareRouteStatus( // ensured by DeduplicateConditions. allConds = append(allConds, defaultConds...) allConds = append(allConds, conds...) - if failedAttachmentCondCount == 1 { + if failedAttachmentCondCount > 0 { allConds = append(allConds, ref.Attachment.FailedConditions...) } diff --git a/internal/mode/static/status/prepare_requests_test.go b/internal/mode/static/status/prepare_requests_test.go index 844f36f93a..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"), + }, }, } @@ -100,6 +103,15 @@ var ( 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}, + }, + }, } parentRefsInvalid = []graph.ParentRef{ @@ -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, + }, + }, + }, }, } From 8e0fb93066e5a783d1475bd62b4e4305e452a0d4 Mon Sep 17 00:00:00 2001 From: salonichf5 <146118978+salonichf5@users.noreply.github.com> Date: Wed, 16 Apr 2025 03:17:58 +0530 Subject: [PATCH 10/19] add retry logic to get pods --- tests/framework/resourcemanager.go | 74 +++++++++++++++++++----------- 1 file changed, 46 insertions(+), 28 deletions(-) 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 { From 55389903d2a8c344ed528c715d61478d8b6e4902 Mon Sep 17 00:00:00 2001 From: salonichf5 <146118978+salonichf5@users.noreply.github.com> Date: Wed, 16 Apr 2025 11:54:23 +0530 Subject: [PATCH 11/19] update test timeouts --- tests/suite/advanced_routing_test.go | 2 +- tests/suite/client_settings_test.go | 2 +- tests/suite/nginxgateway_test.go | 2 +- tests/suite/snippets_filter_test.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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 a7d24ab13d..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)) 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/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)) From b34fde7bb0a9144e8d1bee2428ba3772d80e3195 Mon Sep 17 00:00:00 2001 From: salonichf5 <146118978+salonichf5@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:40:04 +0530 Subject: [PATCH 12/19] update timeout for functional tests --- tests/suite/sample_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)) From e4caf7808605e21a66a9326890a1ac8e7e6a4961 Mon Sep 17 00:00:00 2001 From: salonichf5 <146118978+salonichf5@users.noreply.github.com> Date: Wed, 16 Apr 2025 13:22:57 +0530 Subject: [PATCH 13/19] update timeout duration for all tests --- tests/suite/graceful_recovery_test.go | 8 ++++---- tests/suite/tracing_test.go | 2 +- tests/suite/upstream_settings_test.go | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/suite/graceful_recovery_test.go b/tests/suite/graceful_recovery_test.go index 2e844f46e6..a891678ec8 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). @@ -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/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)) From 5d1192bf490c913120873f1a8f3f40866e51a9fd Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Wed, 16 Apr 2025 08:10:34 -0600 Subject: [PATCH 14/19] Temporary debug logs --- tests/framework/resourcemanager.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/framework/resourcemanager.go b/tests/framework/resourcemanager.go index 912c321090..c51b6faaaf 100644 --- a/tests/framework/resourcemanager.go +++ b/tests/framework/resourcemanager.go @@ -361,9 +361,11 @@ func (rm *ResourceManager) WaitForPodsToBeReady(ctx context.Context, namespace s if err := rm.K8sClient.List(ctx, &podList, client.InNamespace(namespace)); err != nil { return false, err } + fmt.Println("POD LIST ", podList.Items) var podsReady int for _, pod := range podList.Items { + fmt.Println("POD STATUS ", pod.Name, pod.Status) for _, cond := range pod.Status.Conditions { if cond.Type == core.PodReady && cond.Status == core.ConditionTrue { podsReady++ From dda9624af5be0c6c654916be42cfce0a2c596d77 Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Wed, 16 Apr 2025 08:42:20 -0600 Subject: [PATCH 15/19] Fix nil check; remove logs --- internal/mode/static/handler.go | 4 ++++ tests/framework/resourcemanager.go | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/mode/static/handler.go b/internal/mode/static/handler.go index d0cc5403d9..af510dadfc 100644 --- a/internal/mode/static/handler.go +++ b/internal/mode/static/handler.go @@ -317,6 +317,10 @@ func (h *eventHandlerImpl) waitForStatusUpdates(ctx context.Context) { case status.UpdateAll: h.updateStatuses(ctx, gr, gw) case status.UpdateGateway: + if gw == nil { + continue + } + gwAddresses, err := getGatewayAddresses( ctx, h.cfg.k8sClient, diff --git a/tests/framework/resourcemanager.go b/tests/framework/resourcemanager.go index c51b6faaaf..912c321090 100644 --- a/tests/framework/resourcemanager.go +++ b/tests/framework/resourcemanager.go @@ -361,11 +361,9 @@ func (rm *ResourceManager) WaitForPodsToBeReady(ctx context.Context, namespace s if err := rm.K8sClient.List(ctx, &podList, client.InNamespace(namespace)); err != nil { return false, err } - fmt.Println("POD LIST ", podList.Items) var podsReady int for _, pod := range podList.Items { - fmt.Println("POD STATUS ", pod.Name, pod.Status) for _, cond := range pod.Status.Conditions { if cond.Type == core.PodReady && cond.Status == core.ConditionTrue { podsReady++ From a14b8afde1f0d20a4105e9c677c34a1f31ab7b45 Mon Sep 17 00:00:00 2001 From: salonichf5 <146118978+salonichf5@users.noreply.github.com> Date: Thu, 17 Apr 2025 17:51:25 +0530 Subject: [PATCH 16/19] add unit tests for coverage --- internal/mode/static/handler_test.go | 41 ++++++++++++++++++- .../nginx/config/policies/validator_test.go | 18 ++++++++ .../state/graph/backend_tls_policy_test.go | 11 ++++- .../mode/static/state/graph/gateway_test.go | 12 ++++++ .../mode/static/state/graph/graph_test.go | 9 ++++ .../mode/static/state/graph/policies_test.go | 38 +++++++++++++++++ .../mode/static/state/graph/service_test.go | 4 +- 7 files changed, 130 insertions(+), 3 deletions(-) diff --git a/internal/mode/static/handler_test.go b/internal/mode/static/handler_test.go index 1c2dcda3a3..6d62be42b3 100644 --- a/internal/mode/static/handler_test.go +++ b/internal/mode/static/handler_test.go @@ -184,7 +184,6 @@ var _ = Describe("eventHandler", func() { expectReconfig(dcfg, fakeCfgFiles) Expect(helpers.Diff(handler.GetLatestConfiguration(), &dcfg)).To(BeEmpty()) }) - It("should process Delete", func() { e := &events.DeleteEvent{ Type: &gatewayv1.HTTPRoute{}, @@ -218,6 +217,46 @@ var _ = Describe("eventHandler", func() { 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)) + }) }) When("a batch has multiple events", func() { diff --git a/internal/mode/static/nginx/config/policies/validator_test.go b/internal/mode/static/nginx/config/policies/validator_test.go index b2659fe223..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": @@ -64,6 +71,10 @@ var _ = Describe("Policy CompositeValidator", func() { }, GVK: orangeGVK, }, + policies.ManagerConfig{ + Validator: &policiesfakes.FakeValidator{}, + GVK: bananaGVK, + }, ) Context("Validation", func() { @@ -105,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/state/graph/backend_tls_policy_test.go b/internal/mode/static/state/graph/backend_tls_policy_test.go index 0dd08a640d..12f0d7264f 100644 --- a/internal/mode/static/state/graph/backend_tls_policy_test.go +++ b/internal/mode/static/state/graph/backend_tls_policy_test.go @@ -95,6 +95,15 @@ func TestValidateBackendTLSPolicy(t *testing.T) { }, } + targetRefInvalidKind := []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: "Invalid", + Name: "service1", + }, + }, + } + localObjectRefNormalCase := []gatewayv1.LocalObjectReference{ { Kind: "ConfigMap", @@ -301,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", diff --git a/internal/mode/static/state/graph/gateway_test.go b/internal/mode/static/state/graph/gateway_test.go index 9c95f84401..beb9d4bb53 100644 --- a/internal/mode/static/state/graph/gateway_test.go +++ b/internal/mode/static/state/graph/gateway_test.go @@ -331,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{ @@ -592,6 +596,10 @@ func TestBuildGateway(t *testing.T) { 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()}, }, @@ -634,6 +642,10 @@ func TestBuildGateway(t *testing.T) { 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()}, }, diff --git a/internal/mode/static/state/graph/graph_test.go b/internal/mode/static/state/graph/graph_test.go index 98afa07a28..464f085b8d 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -1747,3 +1747,12 @@ func TestIsNGFPolicyRelevantPanics(t *testing.T) { g.Expect(isRelevant).To(Panic()) } + +func TestGatewayExists(t *testing.T) { + t.Parallel() + g := NewWithT(t) + gwNsName := types.NamespacedName{Namespace: "test", Name: "gw"} + + result := gatewayExists(gwNsName, nil) + g.Expect(result).To(BeFalse()) +} diff --git a/internal/mode/static/state/graph/policies_test.go b/internal/mode/static/state/graph/policies_test.go index 3dd7775f5b..4adda19357 100644 --- a/internal/mode/static/state/graph/policies_test.go +++ b/internal/mode/static/state/graph/policies_test.go @@ -787,6 +787,44 @@ func TestAttachPolicyToService(t *testing.T) { 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 { diff --git a/internal/mode/static/state/graph/service_test.go b/internal/mode/static/state/graph/service_test.go index 3ec411cae3..e0ef7180ce 100644 --- a/internal/mode/static/state/graph/service_test.go +++ b/internal/mode/static/state/graph/service_test.go @@ -319,7 +319,9 @@ func TestBuildReferencedServices(t *testing.T) { }, { name: "nil gateway", - gws: nil, + gws: map[types.NamespacedName]*Gateway{ + gwNsName: nil, + }, l7Routes: map[RouteKey]*L7Route{ {NamespacedName: types.NamespacedName{Name: "no-service-nsname"}}: validRouteNoServiceNsName, }, From 8713cd657008f1aeb30b39a259757895362a2e05 Mon Sep 17 00:00:00 2001 From: salonichf5 <146118978+salonichf5@users.noreply.github.com> Date: Fri, 18 Apr 2025 23:01:41 +0530 Subject: [PATCH 17/19] update unit tests --- .../mode/static/state/graph/graph_test.go | 32 +++++++++++++++++-- .../static/state/graph/route_common_test.go | 9 ++++++ tests/suite/graceful_recovery_test.go | 4 +-- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/internal/mode/static/state/graph/graph_test.go b/internal/mode/static/state/graph/graph_test.go index 464f085b8d..0fdeecaeb3 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -1751,8 +1751,34 @@ func TestIsNGFPolicyRelevantPanics(t *testing.T) { func TestGatewayExists(t *testing.T) { t.Parallel() g := NewWithT(t) - gwNsName := types.NamespacedName{Namespace: "test", Name: "gw"} - result := gatewayExists(gwNsName, nil) - g.Expect(result).To(BeFalse()) + 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/route_common_test.go b/internal/mode/static/state/graph/route_common_test.go index 349df9d847..dc127bb3b8 100644 --- a/internal/mode/static/state/graph/route_common_test.go +++ b/internal/mode/static/state/graph/route_common_test.go @@ -3677,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/tests/suite/graceful_recovery_test.go b/tests/suite/graceful_recovery_test.go index a891678ec8..bd71551933 100644 --- a/tests/suite/graceful_recovery_test.go +++ b/tests/suite/graceful_recovery_test.go @@ -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)) From 451f8848344174296fdc1cfb06b78288af438fb4 Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Mon, 21 Apr 2025 12:12:33 -0600 Subject: [PATCH 18/19] Fix conformance test --- apis/v1alpha2/nginxproxy_types.go | 2 +- internal/mode/static/provisioner/objects.go | 9 ++++++--- tests/conformance/conformance-rbac.yaml | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) 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/internal/mode/static/provisioner/objects.go b/internal/mode/static/provisioner/objects.go index 56883e447e..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)) 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 From 9c180d567e8be469e8508d59bda72d503d19c146 Mon Sep 17 00:00:00 2001 From: Saylor Berman Date: Mon, 21 Apr 2025 15:36:52 -0600 Subject: [PATCH 19/19] Address code review --- .../static/state/graph/backend_refs_test.go | 1 - internal/mode/static/state/graph/gateway.go | 4 +- .../mode/static/state/graph/gateway_test.go | 2 +- internal/mode/static/state/graph/graph.go | 2 +- .../state/graph/multiple_gateways_test.go | 99 ++++++++++--------- 5 files changed, 58 insertions(+), 50 deletions(-) diff --git a/internal/mode/static/state/graph/backend_refs_test.go b/internal/mode/static/state/graph/backend_refs_test.go index 236bb70e38..6f02ff7a82 100644 --- a/internal/mode/static/state/graph/backend_refs_test.go +++ b/internal/mode/static/state/graph/backend_refs_test.go @@ -1009,7 +1009,6 @@ func TestCreateBackend(t *testing.T) { policies := map[types.NamespacedName]*BackendTLSPolicy{ client.ObjectKeyFromObject(btp.Source): &btp, client.ObjectKeyFromObject(btp2.Source): &btp2, - // client.ObjectKeyFromObject(btpDupe.Source): &btpDupe, } refPath := field.NewPath("test") diff --git a/internal/mode/static/state/graph/gateway.go b/internal/mode/static/state/graph/gateway.go index 2b31464f89..da9a2a46c8 100644 --- a/internal/mode/static/state/graph/gateway.go +++ b/internal/mode/static/state/graph/gateway.go @@ -12,7 +12,7 @@ import ( staticConds "github.com/nginx/nginx-gateway-fabric/internal/mode/static/state/conditions" ) -// Gateway represents the a Gateway resource. +// Gateway represents a Gateway resource. type Gateway struct { // LatestReloadResult is the result of the last nginx reload attempt. LatestReloadResult NginxReloadResult @@ -36,7 +36,7 @@ type Gateway struct { Valid bool } -// 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, diff --git a/internal/mode/static/state/graph/gateway_test.go b/internal/mode/static/state/graph/gateway_test.go index beb9d4bb53..bb0bbe263c 100644 --- a/internal/mode/static/state/graph/gateway_test.go +++ b/internal/mode/static/state/graph/gateway_test.go @@ -221,7 +221,7 @@ func TestBuildGateway(t *testing.T) { foo8081Listener := createHTTPListener("foo-8081", "foo.example.com", 8081) foo443HTTPListener := createHTTPListener("foo-443-http", "foo.example.com", 443) - // // foo https listeners + // foo https listeners foo80HTTPSListener := createHTTPSListener("foo-80-https", "foo.example.com", 80, gatewayTLSConfigSameNs) foo443HTTPSListener1 := createHTTPSListener("foo-443-https-1", "foo.example.com", 443, gatewayTLSConfigSameNs) foo8443HTTPSListener := createHTTPSListener("foo-8443-https", "foo.example.com", 8443, gatewayTLSConfigSameNs) diff --git a/internal/mode/static/state/graph/graph.go b/internal/mode/static/state/graph/graph.go index 4b925f17f0..61d39fb44c 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -45,7 +45,7 @@ type ClusterState struct { type Graph struct { // GatewayClass holds the GatewayClass resource. GatewayClass *GatewayClass - // Gateway holds the all Gateway resource. + // 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 diff --git a/internal/mode/static/state/graph/multiple_gateways_test.go b/internal/mode/static/state/graph/multiple_gateways_test.go index 472e1cac15..497fbd9680 100644 --- a/internal/mode/static/state/graph/multiple_gateways_test.go +++ b/internal/mode/static/state/graph/multiple_gateways_test.go @@ -67,24 +67,29 @@ var ( } ) -func createGateway(name, nginxProxyName string, listeners []gatewayv1.Listener) *gatewayv1.Gateway { - return &gatewayv1.Gateway{ +func createGateway(name, namespace, nginxProxyName string, listeners []gatewayv1.Listener) *gatewayv1.Gateway { + gateway := &gatewayv1.Gateway{ ObjectMeta: metav1.ObjectMeta{ - Namespace: testNs, + Namespace: namespace, Name: name, }, Spec: gatewayv1.GatewaySpec{ GatewayClassName: gcName, - Infrastructure: &gatewayv1.GatewayInfrastructure{ - ParametersRef: &gatewayv1.LocalParametersReference{ - Group: ngfAPIv1alpha2.GroupName, - Kind: kinds.NginxProxy, - Name: nginxProxyName, - }, - }, - Listeners: listeners, + 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 { @@ -145,7 +150,7 @@ func convertedGateway( nginxProxy *NginxProxy, effectiveNp *EffectiveNginxProxy, listeners []*Listener, - conds ...conditions.Condition, + conds []conditions.Condition, ) *Gateway { return &Gateway{ Source: gw, @@ -222,7 +227,7 @@ func Test_MultipleGateways_WithNginxProxy(t *testing.T) { }, }) - nginxProxyGateway3 := createNginxProxy("nginx-proxy-gateway-3", testNs, ngfAPIv1alpha2.NginxProxySpec{ + nginxProxyGateway3 := createNginxProxy("nginx-proxy-gateway-3", "test2", ngfAPIv1alpha2.NginxProxySpec{ Kubernetes: &ngfAPIv1alpha2.KubernetesSpec{ Deployment: &ngfAPIv1alpha2.DeploymentSpec{ Replicas: helpers.GetPointer(int32(3)), @@ -232,12 +237,12 @@ func Test_MultipleGateways_WithNginxProxy(t *testing.T) { }) gatewayClass := createGatewayClass(gcName, controllerName, "nginx-proxy", testNs) - gateway1 := createGateway("gateway-1", "nginx-proxy", []gatewayv1.Listener{}) - gateway2 := createGateway("gateway-2", "nginx-proxy", []gatewayv1.Listener{}) - gateway3 := createGateway("gateway-3", "nginx-proxy", []gatewayv1.Listener{}) + 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", "nginx-proxy-gateway-1", []gatewayv1.Listener{}) - gateway3withNP := createGateway("gateway-3", "nginx-proxy-gateway-3", []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()} @@ -269,24 +274,24 @@ func Test_MultipleGateways_WithNginxProxy(t *testing.T) { Gateways: map[types.NamespacedName]*Gateway{ client.ObjectKeyFromObject(gateway1): convertedGateway( gateway1, - &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + nil, &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, []*Listener{}, - gcConditions..., + nil, ), client.ObjectKeyFromObject(gateway2): convertedGateway( gateway2, - &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + nil, &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, []*Listener{}, - gcConditions..., + nil, ), client.ObjectKeyFromObject(gateway3): convertedGateway( gateway3, - &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + nil, &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, []*Listener{}, - gcConditions..., + nil, ), }, ReferencedNginxProxies: map[types.NamespacedName]*NginxProxy{ @@ -307,9 +312,9 @@ func Test_MultipleGateways_WithNginxProxy(t *testing.T) { client.ObjectKeyFromObject(gatewayClass): gatewayClass, }, Gateways: map[types.NamespacedName]*gatewayv1.Gateway{ - client.ObjectKeyFromObject(gateway1): gateway1withNP, - client.ObjectKeyFromObject(gateway2): gateway2, - client.ObjectKeyFromObject(gateway3): gateway3withNP, + client.ObjectKeyFromObject(gateway1withNP): gateway1withNP, + client.ObjectKeyFromObject(gateway2): gateway2, + client.ObjectKeyFromObject(gateway3withNP): gateway3withNP, }, NginxProxies: map[types.NamespacedName]*ngfAPIv1alpha2.NginxProxy{ client.ObjectKeyFromObject(nginxProxyGlobal): nginxProxyGlobal, @@ -334,14 +339,14 @@ func Test_MultipleGateways_WithNginxProxy(t *testing.T) { DisableHTTP2: helpers.GetPointer(true), }, []*Listener{}, - gcConditions..., + gcConditions, ), client.ObjectKeyFromObject(gateway2): convertedGateway( gateway2, - &NginxProxy{Source: nginxProxyGlobal, Valid: true}, + nil, &EffectiveNginxProxy{DisableHTTP2: helpers.GetPointer(true)}, []*Listener{}, - gcConditions..., + nil, ), client.ObjectKeyFromObject(gateway3withNP): convertedGateway( gateway3withNP, @@ -355,7 +360,7 @@ func Test_MultipleGateways_WithNginxProxy(t *testing.T) { DisableHTTP2: helpers.GetPointer(false), }, []*Listener{}, - gcConditions..., + gcConditions, ), }, ReferencedNginxProxies: map[types.NamespacedName]*NginxProxy{ @@ -457,7 +462,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { }, } - gateway1 := createGateway("gateway-1", "nginx-proxy", []gatewayv1.Listener{ + gateway1 := createGateway("gateway-1", testNs, "nginx-proxy", []gatewayv1.Listener{ createListener( "listener-tls-mode-terminate", "*.example.com", @@ -467,7 +472,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { allowedRoutesHTTPGRPC, ), }) - gateway2 := createGateway("gateway-2", "nginx-proxy", []gatewayv1.Listener{ + gateway2 := createGateway("gateway-2", testNs, "nginx-proxy", []gatewayv1.Listener{ createListener( "listener-tls-mode-terminate", "*.example.com", @@ -532,12 +537,14 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { allowedRoutesTLS, ), } - gatewayMultipleListeners1 := createGateway("gateway-multiple-listeners-1", "nginx-proxy", listeners) - gatewayMultipleListeners2 := createGateway("gateway-multiple-listeners-2", "nginx-proxy", listeners) - gatewayMultipleListeners3 := createGateway("gateway-multiple-listeners-3", "nginx-proxy", listeners) + 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", + gatewayTLSSamePortHostname := createGateway( + "gateway-tls-foo", + testNs, "nginx-proxy", []gatewayv1.Listener{ createListener( @@ -551,7 +558,9 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { }, ) - gatewayHTTPSSamePortHostname := createGateway("gateway-http-foo", + gatewayHTTPSSamePortHostname := createGateway( + "gateway-http-foo", + testNs, "nginx-proxy", []gatewayv1.Listener{ createListener( @@ -608,7 +617,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { map[L4RouteKey]*L4Route{}, ), }, - staticConds.NewGatewayClassResolvedRefs(), + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, ), client.ObjectKeyFromObject(gateway2): convertedGateway( gateway2, @@ -624,7 +633,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { map[L4RouteKey]*L4Route{}, ), }, - staticConds.NewGatewayClassResolvedRefs(), + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, ), }, Routes: map[RouteKey]*L7Route{}, @@ -697,7 +706,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { map[L4RouteKey]*L4Route{}, ), }, - staticConds.NewGatewayClassResolvedRefs(), + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, ), client.ObjectKeyFromObject(gatewayMultipleListeners2): convertedGateway( gatewayMultipleListeners2, @@ -729,7 +738,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { map[L4RouteKey]*L4Route{}, ), }, - staticConds.NewGatewayClassResolvedRefs(), + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, ), client.ObjectKeyFromObject(gatewayMultipleListeners3): convertedGateway( gatewayMultipleListeners3, @@ -761,7 +770,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { map[L4RouteKey]*L4Route{}, ), }, - staticConds.NewGatewayClassResolvedRefs(), + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, ), }, Routes: map[RouteKey]*L7Route{}, @@ -816,7 +825,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { map[L4RouteKey]*L4Route{}, ), }, - staticConds.NewGatewayClassResolvedRefs(), + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, ), client.ObjectKeyFromObject(gatewayHTTPSSamePortHostname): convertedGateway( gatewayHTTPSSamePortHostname, @@ -832,7 +841,7 @@ func Test_MultipleGateways_WithListeners(t *testing.T) { map[L4RouteKey]*L4Route{}, ), }, - staticConds.NewGatewayClassResolvedRefs(), + []conditions.Condition{staticConds.NewGatewayClassResolvedRefs()}, ), }, Routes: map[RouteKey]*L7Route{},