From 85648e26355f9f7b63f82fb61b1cb9b4bf034547 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Fri, 29 Dec 2023 13:17:11 -0800 Subject: [PATCH 01/41] Add Service and EndpointSlice logic to graph and some tests --- internal/mode/static/state/graph/graph.go | 32 +++++-- .../mode/static/state/graph/graph_test.go | 94 ++++++++++++++++++- internal/mode/static/state/graph/service.go | 46 +++++++++ 3 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 internal/mode/static/state/graph/service.go diff --git a/internal/mode/static/state/graph/graph.go b/internal/mode/static/state/graph/graph.go index c539886c48..f65f79dd7f 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -2,12 +2,14 @@ package graph import ( v1 "k8s.io/api/core/v1" + discoveryV1 "k8s.io/api/discovery/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" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/controller/index" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation" ) @@ -46,6 +48,8 @@ type Graph struct { ReferencedSecrets map[types.NamespacedName]*Secret // ReferencedNamespaces includes Namespaces with labels that match the Gateway Listener's label selector. ReferencedNamespaces map[types.NamespacedName]*v1.Namespace + // ReferencedServicesNames includes the names of all the Services that are referenced by at least one HTTPRoute. + ReferencedServicesNames map[types.NamespacedName]struct{} } // ProtectedPorts are the ports that may not be configured by a listener with a descriptive name of each port. @@ -78,6 +82,17 @@ func (g *Graph) IsReferenced(resourceType client.Object, nsname types.Namespaced _, existed := g.ReferencedNamespaces[nsname] exists := isNamespaceReferenced(obj, g.Gateway) return existed || exists + // Service reference exists if at least one HTTPRoute references it. + case *v1.Service: + _, exists := g.ReferencedServicesNames[nsname] + return exists + // EndpointSlice reference exists if its Service owner is referenced by at least one HTTPRoute. + case *discoveryV1.EndpointSlice: + svcName := index.GetServiceNameFromEndpointSlice(obj) + + // Service Namespace should be the same Namespace as the EndpointSlice + _, exists := g.ReferencedServicesNames[types.NamespacedName{Namespace: nsname.Namespace, Name: svcName}] + return exists default: return false } @@ -112,14 +127,17 @@ func BuildGraph( referencedNamespaces := buildReferencedNamespaces(state.Namespaces, gw) + referencedServicesNames := buildReferencedServicesNames(state.HTTPRoutes) + g := &Graph{ - GatewayClass: gc, - Gateway: gw, - Routes: routes, - IgnoredGatewayClasses: processedGwClasses.Ignored, - IgnoredGateways: processedGws.Ignored, - ReferencedSecrets: secretResolver.getResolvedSecrets(), - ReferencedNamespaces: referencedNamespaces, + GatewayClass: gc, + Gateway: gw, + Routes: routes, + IgnoredGatewayClasses: processedGwClasses.Ignored, + IgnoredGateways: processedGws.Ignored, + ReferencedSecrets: secretResolver.getResolvedSecrets(), + ReferencedNamespaces: referencedNamespaces, + ReferencedServicesNames: referencedServicesNames, } return g diff --git a/internal/mode/static/state/graph/graph_test.go b/internal/mode/static/state/graph/graph_test.go index bc705d3d7d..45491f20e1 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -6,6 +6,7 @@ import ( . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" + discoveryV1 "k8s.io/api/discovery/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" @@ -13,6 +14,7 @@ import ( gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" "sigs.k8s.io/gateway-api/apis/v1beta1" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/controller/index" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation/validationfakes" @@ -336,6 +338,9 @@ func TestBuildGraph(t *testing.T) { ReferencedNamespaces: map[types.NamespacedName]*v1.Namespace{ client.ObjectKeyFromObject(ns): ns, }, + ReferencedServicesNames: map[types.NamespacedName]struct{}{ + client.ObjectKeyFromObject(svc): {}, + }, } } @@ -427,6 +432,39 @@ func TestIsReferenced(t *testing.T) { }, } + serviceInGraph := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "serviceInGraph", + }, + } + serviceNotInGraph := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "serviceNotInGraph", + }, + } + serviceNotInGraphSameNameDifferentNS := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "not-default", + Name: "serviceInGraph", + }, + } + emptyService := &v1.Service{} + + createEndpointSlice := func(name string, svcName string) *discoveryV1.EndpointSlice { + return &discoveryV1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: name, + Labels: map[string]string{index.KubernetesServiceNameLabel: svcName}, + }, + } + } + endpointSliceInGraph := createEndpointSlice("endpointSliceInGraph", "serviceInGraph") + endpointSliceNotInGraph := createEndpointSlice("endpointSliceNotInGraph", "serviceNotInGraph") + emptyEndpointSlice := &discoveryV1.EndpointSlice{} + gw := &Gateway{ Listeners: []*Listener{ { @@ -457,6 +495,9 @@ func TestIsReferenced(t *testing.T) { ReferencedNamespaces: map[types.NamespacedName]*v1.Namespace{ client.ObjectKeyFromObject(nsInGraph): nsInGraph, }, + ReferencedServicesNames: map[types.NamespacedName]struct{}{ + client.ObjectKeyFromObject(serviceInGraph): {}, + }, } tests := []struct { @@ -465,6 +506,7 @@ func TestIsReferenced(t *testing.T) { name string expected bool }{ + // Namespace tests { name: "Namespace in graph's ReferencedNamespaces passes", resource: nsInGraph, @@ -483,6 +525,8 @@ func TestIsReferenced(t *testing.T) { graph: graph, expected: true, }, + + // Secret tests { name: "Secret in graph's ReferencedSecrets passes", resource: baseSecret, @@ -501,9 +545,57 @@ func TestIsReferenced(t *testing.T) { graph: graph, expected: false, }, + + // Service tests + { + name: "Service in graph's ReferencedServicesNames passes", + resource: serviceInGraph, + graph: graph, + expected: true, + }, + { + name: "Service not in graph's ReferencedServicesNames fails", + resource: serviceNotInGraph, + graph: graph, + expected: false, + }, + { + name: "Service with same name but different namespace as one in graph's ReferencedServicesNames fails", + resource: serviceNotInGraphSameNameDifferentNS, + graph: graph, + expected: false, + }, + { + name: "Empty Service fails", + resource: emptyService, + graph: graph, + expected: false, + }, + + // EndpointSlice tests + { + name: "EndpointSlice with Service owner in graph's ReferencedServicesNames passes", + resource: endpointSliceInGraph, + graph: graph, + expected: true, + }, + { + name: "EndpointSlice with Service owner not in graph's ReferencedServicesNames fails", + resource: endpointSliceNotInGraph, + graph: graph, + expected: false, + }, + { + name: "Empty EndpointSlice fails", + resource: emptyEndpointSlice, + graph: graph, + expected: false, + }, + + // Edge cases { name: "Resource is not supported by IsReferenced", - resource: &v1.Service{}, + resource: &gatewayv1.HTTPRoute{}, graph: graph, expected: false, }, diff --git a/internal/mode/static/state/graph/service.go b/internal/mode/static/state/graph/service.go new file mode 100644 index 0000000000..4bb88ce7c3 --- /dev/null +++ b/internal/mode/static/state/graph/service.go @@ -0,0 +1,46 @@ +package graph + +import ( + "k8s.io/apimachinery/pkg/types" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +func buildReferencedServicesNames( + clusterHTTPRoutes map[types.NamespacedName]*gatewayv1.HTTPRoute, +) map[types.NamespacedName]struct{} { + svcNames := make(map[types.NamespacedName]struct{}) + + // Get all the service names referenced from all the HTTPRoutes + for _, hr := range clusterHTTPRoutes { + tempSvcNames := getBackendServiceNamesFromRoute(hr) + for k, v := range tempSvcNames { + svcNames[k] = v + } + } + + if len(svcNames) == 0 { + return nil + } + return svcNames +} + +func getBackendServiceNamesFromRoute(hr *gatewayv1.HTTPRoute) map[types.NamespacedName]struct{} { + svcNames := make(map[types.NamespacedName]struct{}) + + for _, rule := range hr.Spec.Rules { + for _, ref := range rule.BackendRefs { + if ref.Kind != nil && *ref.Kind != "Service" { + continue + } + + ns := hr.Namespace + if ref.Namespace != nil { + ns = string(*ref.Namespace) + } + + svcNames[types.NamespacedName{Namespace: ns, Name: string(ref.Name)}] = struct{}{} + } + } + + return svcNames +} From 3abafb05498588a9cac6c39310bb94c4dbd2104d Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Fri, 29 Dec 2023 15:15:14 -0800 Subject: [PATCH 02/41] WIP, all tests pass but awaiting feedback --- .../mode/static/state/change_processor.go | 7 +- .../static/state/change_processor_test.go | 36 +- internal/mode/static/state/graph/graph.go | 1 + .../static/state/relationship/capturer.go | 10 +- .../state/relationship/capturer_test.go | 667 +++++++++--------- 5 files changed, 370 insertions(+), 351 deletions(-) diff --git a/internal/mode/static/state/change_processor.go b/internal/mode/static/state/change_processor.go index ee43d1c468..3426dd9ee5 100644 --- a/internal/mode/static/state/change_processor.go +++ b/internal/mode/static/state/change_processor.go @@ -99,6 +99,7 @@ func NewChangeProcessorImpl(cfg ChangeProcessorConfig) *ChangeProcessorImpl { ReferenceGrants: make(map[types.NamespacedName]*v1beta1.ReferenceGrant), Secrets: make(map[types.NamespacedName]*apiv1.Secret), CRDMetadata: make(map[types.NamespacedName]*metav1.PartialObjectMetadata), + EndpointSlices: make(map[types.NamespacedName]*discoveryV1.EndpointSlice), } extractGVK := func(obj client.Object) schema.GroupVersionKind { @@ -151,12 +152,12 @@ func NewChangeProcessorImpl(cfg ChangeProcessorConfig) *ChangeProcessorImpl { { gvk: extractGVK(&apiv1.Service{}), store: newObjectStoreMapAdapter(clusterStore.Services), - predicate: nil, + predicate: funcPredicate{stateChanged: isReferenced}, }, { gvk: extractGVK(&discoveryV1.EndpointSlice{}), - store: nil, - predicate: nil, + store: newObjectStoreMapAdapter(clusterStore.EndpointSlices), + predicate: funcPredicate{stateChanged: isReferenced}, }, { gvk: extractGVK(&apiv1.Secret{}), diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 24d64e254f..0d208e7655 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -283,15 +283,15 @@ var _ = Describe("ChangeProcessor", func() { Describe("Process gateway resources", Ordered, func() { var ( - gcUpdated *v1.GatewayClass - diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret - hr1, hr1Updated, hr2 *v1.HTTPRoute - gw1, gw1Updated, gw2 *v1.Gateway - refGrant1, refGrant2 *v1beta1.ReferenceGrant - expGraph *graph.Graph - expRouteHR1, expRouteHR2 *graph.Route - hr1Name, hr2Name types.NamespacedName - gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata + gcUpdated *v1.GatewayClass + diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret + hr1, hr1Updated, hr2 *v1.HTTPRoute + gw1, gw1Updated, gw2 *v1.Gateway + refGrant1, refGrant2 *v1beta1.ReferenceGrant + expGraph, graphWithJustReferencedServicesNames *graph.Graph + expRouteHR1, expRouteHR2 *graph.Route + hr1Name, hr2Name types.NamespacedName + gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata ) BeforeAll(func() { gcUpdated = gc.DeepCopy() @@ -509,6 +509,20 @@ var _ = Describe("ChangeProcessor", func() { {Namespace: "test", Name: "hr-1"}: expRouteHR1, }, ReferencedSecrets: map[types.NamespacedName]*graph.Secret{}, + ReferencedServicesNames: map[types.NamespacedName]struct{}{ + { + Namespace: "service-ns", + Name: "service", + }: {}, + }, + } + graphWithJustReferencedServicesNames = &graph.Graph{ + ReferencedServicesNames: map[types.NamespacedName]struct{}{ + { + Namespace: "service-ns", + Name: "service", + }: {}, + }, } }) When("no upsert has occurred", func() { @@ -535,7 +549,7 @@ var _ = Describe("ChangeProcessor", func() { changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) - Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(graphWithJustReferencedServicesNames, graphCfg)).To(BeEmpty()) }) }) When("the different namespace TLS Secret is upserted", func() { @@ -912,7 +926,7 @@ var _ = Describe("ChangeProcessor", func() { changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) - Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(graphWithJustReferencedServicesNames, graphCfg)).To(BeEmpty()) }) }) When("the first HTTPRoute is deleted", func() { diff --git a/internal/mode/static/state/graph/graph.go b/internal/mode/static/state/graph/graph.go index f65f79dd7f..e47ff6137f 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -23,6 +23,7 @@ type ClusterState struct { ReferenceGrants map[types.NamespacedName]*v1beta1.ReferenceGrant Secrets map[types.NamespacedName]*v1.Secret CRDMetadata map[types.NamespacedName]*metav1.PartialObjectMetadata + EndpointSlices map[types.NamespacedName]*discoveryV1.EndpointSlice } // Graph is a Graph-like representation of Gateway API resources. diff --git a/internal/mode/static/state/relationship/capturer.go b/internal/mode/static/state/relationship/capturer.go index c8af779ea9..10f72f929a 100644 --- a/internal/mode/static/state/relationship/capturer.go +++ b/internal/mode/static/state/relationship/capturer.go @@ -75,13 +75,15 @@ func (c *CapturerImpl) Remove(resourceType client.Object, nsname types.Namespace } // Exists returns true if the given object has a relationship with another object. -func (c *CapturerImpl) Exists(resourceType client.Object, nsname types.NamespacedName) bool { +func (c *CapturerImpl) Exists(resourceType client.Object, _ types.NamespacedName) bool { switch resourceType.(type) { case *v1.Service: - return c.serviceRefCount[nsname] > 0 + return false + // return c.serviceRefCount[nsname] > 0 case *discoveryV1.EndpointSlice: - svcOwner, exists := c.endpointSliceOwners[nsname] - return exists && c.serviceRefCount[svcOwner] > 0 + return false + // svcOwner, exists := c.endpointSliceOwners[nsname] + // return exists && c.serviceRefCount[svcOwner] > 0 } return false diff --git a/internal/mode/static/state/relationship/capturer_test.go b/internal/mode/static/state/relationship/capturer_test.go index 38437d34c4..e91b49b099 100644 --- a/internal/mode/static/state/relationship/capturer_test.go +++ b/internal/mode/static/state/relationship/capturer_test.go @@ -1,335 +1,336 @@ package relationship_test -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - v1 "k8s.io/api/core/v1" - discoveryV1 "k8s.io/api/discovery/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" - - "github.com/nginxinc/nginx-gateway-fabric/internal/framework/controller/index" - "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" - "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/relationship" -) - -func createBackendRefs(backendNames ...gatewayv1.ObjectName) []gatewayv1.HTTPBackendRef { - refs := make([]gatewayv1.HTTPBackendRef, 0, len(backendNames)) - for _, name := range backendNames { - refs = append(refs, gatewayv1.HTTPBackendRef{ - BackendRef: gatewayv1.BackendRef{ - BackendObjectReference: gatewayv1.BackendObjectReference{ - Kind: (*gatewayv1.Kind)(helpers.GetPointer("Service")), - Name: name, - Namespace: (*gatewayv1.Namespace)(helpers.GetPointer("test")), - }, - }, - }) - } - - return refs -} - -func createRules(backendRefs ...[]gatewayv1.HTTPBackendRef) []gatewayv1.HTTPRouteRule { - rules := make([]gatewayv1.HTTPRouteRule, 0, len(backendRefs)) - for _, refs := range backendRefs { - rules = append(rules, gatewayv1.HTTPRouteRule{BackendRefs: refs}) - } - - return rules -} - -func createRoute(name string, rules []gatewayv1.HTTPRouteRule) *gatewayv1.HTTPRoute { - return &gatewayv1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: name}, - Spec: gatewayv1.HTTPRouteSpec{Rules: rules}, - } -} - -var _ = Describe("Capturer", func() { - var ( - capturer *relationship.CapturerImpl - - backendRef1 = createBackendRefs("svc1") - backendRef2 = createBackendRefs("svc2") - backendRef3 = createBackendRefs("svc3") - backendRef4 = createBackendRefs("svc4") - - hr1 = createRoute("hr1", createRules(backendRef1)) - hr2 = createRoute("hr2", createRules(backendRef2, backendRef3, backendRef4)) - - hrSvc1AndSvc2 = createRoute("hr-svc1-svc2", createRules(backendRef1, backendRef2)) - hrSvc1AndSvc3 = createRoute("hr-svc1-svc3", createRules(backendRef3, backendRef1)) - hrSvc1AndSvc4 = createRoute("hr-svc1-svc4", createRules(backendRef1, backendRef4)) - - hr1Name = types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name} - hr2Name = types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name} - hrSvc1AndSvc2Name = types.NamespacedName{Namespace: hrSvc1AndSvc2.Namespace, Name: hrSvc1AndSvc2.Name} - hrSvc1AndSvc3Name = types.NamespacedName{Namespace: hrSvc1AndSvc3.Namespace, Name: hrSvc1AndSvc3.Name} - hrSvc1AndSvc4Name = types.NamespacedName{Namespace: hrSvc1AndSvc4.Namespace, Name: hrSvc1AndSvc4.Name} - - svc1 = types.NamespacedName{Namespace: "test", Name: "svc1"} - svc2 = types.NamespacedName{Namespace: "test", Name: "svc2"} - svc3 = types.NamespacedName{Namespace: "test", Name: "svc3"} - svc4 = types.NamespacedName{Namespace: "test", Name: "svc4"} - ) - - Describe("Capture service relationships for routes", func() { - BeforeEach(OncePerOrdered, func() { - capturer = relationship.NewCapturerImpl() - }) - - assertServiceExists := func(svcName types.NamespacedName, exists bool, refCount int) { - ExpectWithOffset(1, capturer.Exists(&v1.Service{}, svcName)).To(Equal(exists)) - ExpectWithOffset(1, capturer.GetRefCountForService(svcName)).To(Equal(refCount)) - } - - Describe("Normal cases", Ordered, func() { - When("a route with a backend service is captured", func() { - It("reports a service relationship", func() { - capturer.Capture(hr1) - - assertServiceExists(svc1, true, 1) - }) - }) - When("a route with multiple backend services is captured", func() { - It("reports all service relationships for all captured routes", func() { - capturer.Capture(hr2) - - assertServiceExists(svc1, true, 1) - assertServiceExists(svc2, true, 1) - assertServiceExists(svc3, true, 1) - assertServiceExists(svc4, true, 1) - }) - }) - When("one backend service is removed from a captured route", func() { - It("removes the correct service relationship", func() { - hr2Updated := hr2.DeepCopy() - hr2Updated.Spec.Rules = hr2Updated.Spec.Rules[0:2] // remove the last rule - - capturer.Capture(hr2Updated) - - assertServiceExists(svc1, true, 1) - assertServiceExists(svc2, true, 1) - assertServiceExists(svc3, true, 1) - assertServiceExists(svc4, false, 0) - }) - }) - When("one backend service is added to a captured route", func() { - It("adds the correct service relationship", func() { - capturer.Capture(hr2) - - assertServiceExists(svc1, true, 1) - assertServiceExists(svc2, true, 1) - assertServiceExists(svc3, true, 1) - assertServiceExists(svc4, true, 1) - }) - }) - When("a route with multiple backend services is removed", func() { - It("removes all service relationships", func() { - capturer.Remove(&gatewayv1.HTTPRoute{}, hr2Name) - - assertServiceExists(svc2, false, 0) - assertServiceExists(svc3, false, 0) - assertServiceExists(svc4, false, 0) - - // Service referenced by hr1 still exists - assertServiceExists(svc1, true, 1) - }) - }) - When("a route is removed", func() { - It("removes service relationships", func() { - capturer.Remove(&gatewayv1.HTTPRoute{}, hr1Name) - - assertServiceExists(svc1, false, 0) - }) - }) - }) - Describe("Multiple routes that reference the same service", Ordered, func() { - When("multiple routes are captured that all reference the same service", func() { - It("reports all service relationships", func() { - capturer.Capture(hr1) - capturer.Capture(hrSvc1AndSvc2) - capturer.Capture(hrSvc1AndSvc3) - capturer.Capture(hrSvc1AndSvc4) - - assertServiceExists(svc1, true, 4) - assertServiceExists(svc2, true, 1) - assertServiceExists(svc3, true, 1) - assertServiceExists(svc4, true, 1) - }) - }) - When("one route is removed", func() { - It("reports remaining service relationships", func() { - capturer.Remove(&gatewayv1.HTTPRoute{}, hr1Name) - - // ref count for svc1 should decrease by one - assertServiceExists(svc1, true, 3) - - // all other ref counts stay the same - assertServiceExists(svc2, true, 1) - assertServiceExists(svc3, true, 1) - assertServiceExists(svc4, true, 1) - }) - }) - When("another route is removed", func() { - It("reports remaining service relationships", func() { - capturer.Remove(&gatewayv1.HTTPRoute{}, hrSvc1AndSvc2Name) - - // svc2 should no longer exist - assertServiceExists(svc2, false, 0) - - // ref count for svc1 should decrease by one - assertServiceExists(svc1, true, 2) - - // all other ref counts stay the same - assertServiceExists(svc3, true, 1) - assertServiceExists(svc4, true, 1) - }) - }) - When("another route is removed", func() { - It("reports remaining service relationships", func() { - capturer.Remove(&gatewayv1.HTTPRoute{}, hrSvc1AndSvc3Name) - - // svc3 should no longer exist - assertServiceExists(svc3, false, 0) - - // svc2 should still not exist - assertServiceExists(svc2, false, 0) - - // ref count for svc1 should decrease by one - assertServiceExists(svc1, true, 1) - - // svc4 ref count should stay the same - assertServiceExists(svc4, true, 1) - }) - When("final route is removed", func() { - It("removes all service relationships", func() { - capturer.Remove(&gatewayv1.HTTPRoute{}, hrSvc1AndSvc4Name) - - // no services should exist and all ref counts should be 0 - assertServiceExists(svc1, false, 0) - assertServiceExists(svc2, false, 0) - assertServiceExists(svc3, false, 0) - assertServiceExists(svc4, false, 0) - }) - }) - When("route is removed again", func() { - It("service ref counts remain at 0", func() { - capturer.Remove(&gatewayv1.HTTPRoute{}, hrSvc1AndSvc4Name) - - // no services should exist and all ref counts should still be 0 - assertServiceExists(svc1, false, 0) - assertServiceExists(svc2, false, 0) - assertServiceExists(svc3, false, 0) - assertServiceExists(svc4, false, 0) - }) - }) - }) - }) - Describe("Capture endpoint slice relationships", func() { - var ( - slice1 = &discoveryV1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "es1", - Labels: map[string]string{index.KubernetesServiceNameLabel: "svc1"}, - }, - } - - slice2 = &discoveryV1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "es2", - Labels: map[string]string{index.KubernetesServiceNameLabel: "svc1"}, - }, - } - - slice1Name = types.NamespacedName{Namespace: slice1.Namespace, Name: slice1.Name} - slice2Name = types.NamespacedName{Namespace: slice2.Namespace, Name: slice2.Name} - ) - - BeforeEach(OncePerOrdered, func() { - capturer = relationship.NewCapturerImpl() - }) - - Describe("Normal cases", Ordered, func() { - When("an endpoint slice is captured that has an unrelated service owner", func() { - It("does not report an endpoint slice relationship", func() { - capturer.Capture(slice1) - - Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeFalse()) - }) - }) - When("a relationship is captured for the service owner", func() { - It("adds an endpoint slice relationship", func() { - capturer.Capture(hr1) - - Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeTrue()) - }) - }) - When("another endpoint slice is captured with the same service owner", func() { - It("adds another endpoint slice relationship", func() { - capturer.Capture(slice2) - - Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeTrue()) - Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice2Name)).To(BeTrue()) - }) - }) - When("an endpoint slice is removed", func() { - It("removes the endpoint slice relationship", func() { - capturer.Remove(&discoveryV1.EndpointSlice{}, slice2Name) - - Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice2Name)).To(BeFalse()) - - // slice 1 relationship should still exist - Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeTrue()) - }) - }) - When("endpoint slice service owner changes to an unrelated service owner", func() { - It("removes the endpoint slice relationship", func() { - updatedSlice1 := slice1.DeepCopy() - updatedSlice1.Labels[index.KubernetesServiceNameLabel] = "unrelated-svc" - - capturer.Capture(updatedSlice1) - - Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeFalse()) - }) - }) - When("endpoint slice service owner changes to a related service owner", func() { - It("adds an endpoint slice relationship", func() { - capturer.Capture(slice1) - - Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeTrue()) - }) - }) - When("service relationship is removed", func() { - It("removes the endpoint slice relationship", func() { - capturer.Remove(&gatewayv1.HTTPRoute{}, hr1Name) - - Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeFalse()) - }) - }) - }) - }) - }) - Describe("Edge cases", func() { - BeforeEach(func() { - capturer = relationship.NewCapturerImpl() - }) - It("Capture does not panic when passed an unsupported resource type", func() { - Expect(func() { - capturer.Capture(&gatewayv1.GatewayClass{}) - }).ToNot(Panic()) - }) - It("Remove does not panic when passed an unsupported resource type", func() { - Expect(func() { - capturer.Remove(&gatewayv1.GatewayClass{}, types.NamespacedName{}) - }).ToNot(Panic()) - }) - It("Exist returns false if passed an unsupported resource type", func() { - Expect(capturer.Exists(&gatewayv1.GatewayClass{}, types.NamespacedName{})).To(BeFalse()) - }) - }) -}) +// +//import ( +// . "github.com/onsi/ginkgo/v2" +// . "github.com/onsi/gomega" +// v1 "k8s.io/api/core/v1" +// discoveryV1 "k8s.io/api/discovery/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// "k8s.io/apimachinery/pkg/types" +// gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" +// +// "github.com/nginxinc/nginx-gateway-fabric/internal/framework/controller/index" +// "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" +// "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/relationship" +//) +// +//func createBackendRefs(backendNames ...gatewayv1.ObjectName) []gatewayv1.HTTPBackendRef { +// refs := make([]gatewayv1.HTTPBackendRef, 0, len(backendNames)) +// for _, name := range backendNames { +// refs = append(refs, gatewayv1.HTTPBackendRef{ +// BackendRef: gatewayv1.BackendRef{ +// BackendObjectReference: gatewayv1.BackendObjectReference{ +// Kind: (*gatewayv1.Kind)(helpers.GetPointer("Service")), +// Name: name, +// Namespace: (*gatewayv1.Namespace)(helpers.GetPointer("test")), +// }, +// }, +// }) +// } +// +// return refs +//} +// +//func createRules(backendRefs ...[]gatewayv1.HTTPBackendRef) []gatewayv1.HTTPRouteRule { +// rules := make([]gatewayv1.HTTPRouteRule, 0, len(backendRefs)) +// for _, refs := range backendRefs { +// rules = append(rules, gatewayv1.HTTPRouteRule{BackendRefs: refs}) +// } +// +// return rules +//} +// +//func createRoute(name string, rules []gatewayv1.HTTPRouteRule) *gatewayv1.HTTPRoute { +// return &gatewayv1.HTTPRoute{ +// ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: name}, +// Spec: gatewayv1.HTTPRouteSpec{Rules: rules}, +// } +//} +// +//var _ = Describe("Capturer", func() { +// var ( +// capturer *relationship.CapturerImpl +// +// backendRef1 = createBackendRefs("svc1") +// backendRef2 = createBackendRefs("svc2") +// backendRef3 = createBackendRefs("svc3") +// backendRef4 = createBackendRefs("svc4") +// +// hr1 = createRoute("hr1", createRules(backendRef1)) +// hr2 = createRoute("hr2", createRules(backendRef2, backendRef3, backendRef4)) +// +// hrSvc1AndSvc2 = createRoute("hr-svc1-svc2", createRules(backendRef1, backendRef2)) +// hrSvc1AndSvc3 = createRoute("hr-svc1-svc3", createRules(backendRef3, backendRef1)) +// hrSvc1AndSvc4 = createRoute("hr-svc1-svc4", createRules(backendRef1, backendRef4)) +// +// hr1Name = types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name} +// hr2Name = types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name} +// hrSvc1AndSvc2Name = types.NamespacedName{Namespace: hrSvc1AndSvc2.Namespace, Name: hrSvc1AndSvc2.Name} +// hrSvc1AndSvc3Name = types.NamespacedName{Namespace: hrSvc1AndSvc3.Namespace, Name: hrSvc1AndSvc3.Name} +// hrSvc1AndSvc4Name = types.NamespacedName{Namespace: hrSvc1AndSvc4.Namespace, Name: hrSvc1AndSvc4.Name} +// +// svc1 = types.NamespacedName{Namespace: "test", Name: "svc1"} +// svc2 = types.NamespacedName{Namespace: "test", Name: "svc2"} +// svc3 = types.NamespacedName{Namespace: "test", Name: "svc3"} +// svc4 = types.NamespacedName{Namespace: "test", Name: "svc4"} +// ) +// +// Describe("Capture service relationships for routes", func() { +// BeforeEach(OncePerOrdered, func() { +// capturer = relationship.NewCapturerImpl() +// }) +// +// assertServiceExists := func(svcName types.NamespacedName, exists bool, refCount int) { +// ExpectWithOffset(1, capturer.Exists(&v1.Service{}, svcName)).To(Equal(exists)) +// ExpectWithOffset(1, capturer.GetRefCountForService(svcName)).To(Equal(refCount)) +// } +// +// Describe("Normal cases", Ordered, func() { +// When("a route with a backend service is captured", func() { +// It("reports a service relationship", func() { +// capturer.Capture(hr1) +// +// assertServiceExists(svc1, true, 1) +// }) +// }) +// When("a route with multiple backend services is captured", func() { +// It("reports all service relationships for all captured routes", func() { +// capturer.Capture(hr2) +// +// assertServiceExists(svc1, true, 1) +// assertServiceExists(svc2, true, 1) +// assertServiceExists(svc3, true, 1) +// assertServiceExists(svc4, true, 1) +// }) +// }) +// When("one backend service is removed from a captured route", func() { +// It("removes the correct service relationship", func() { +// hr2Updated := hr2.DeepCopy() +// hr2Updated.Spec.Rules = hr2Updated.Spec.Rules[0:2] // remove the last rule +// +// capturer.Capture(hr2Updated) +// +// assertServiceExists(svc1, true, 1) +// assertServiceExists(svc2, true, 1) +// assertServiceExists(svc3, true, 1) +// assertServiceExists(svc4, false, 0) +// }) +// }) +// When("one backend service is added to a captured route", func() { +// It("adds the correct service relationship", func() { +// capturer.Capture(hr2) +// +// assertServiceExists(svc1, true, 1) +// assertServiceExists(svc2, true, 1) +// assertServiceExists(svc3, true, 1) +// assertServiceExists(svc4, true, 1) +// }) +// }) +// When("a route with multiple backend services is removed", func() { +// It("removes all service relationships", func() { +// capturer.Remove(&gatewayv1.HTTPRoute{}, hr2Name) +// +// assertServiceExists(svc2, false, 0) +// assertServiceExists(svc3, false, 0) +// assertServiceExists(svc4, false, 0) +// +// // Service referenced by hr1 still exists +// assertServiceExists(svc1, true, 1) +// }) +// }) +// When("a route is removed", func() { +// It("removes service relationships", func() { +// capturer.Remove(&gatewayv1.HTTPRoute{}, hr1Name) +// +// assertServiceExists(svc1, false, 0) +// }) +// }) +// }) +// Describe("Multiple routes that reference the same service", Ordered, func() { +// When("multiple routes are captured that all reference the same service", func() { +// It("reports all service relationships", func() { +// capturer.Capture(hr1) +// capturer.Capture(hrSvc1AndSvc2) +// capturer.Capture(hrSvc1AndSvc3) +// capturer.Capture(hrSvc1AndSvc4) +// +// assertServiceExists(svc1, true, 4) +// assertServiceExists(svc2, true, 1) +// assertServiceExists(svc3, true, 1) +// assertServiceExists(svc4, true, 1) +// }) +// }) +// When("one route is removed", func() { +// It("reports remaining service relationships", func() { +// capturer.Remove(&gatewayv1.HTTPRoute{}, hr1Name) +// +// // ref count for svc1 should decrease by one +// assertServiceExists(svc1, true, 3) +// +// // all other ref counts stay the same +// assertServiceExists(svc2, true, 1) +// assertServiceExists(svc3, true, 1) +// assertServiceExists(svc4, true, 1) +// }) +// }) +// When("another route is removed", func() { +// It("reports remaining service relationships", func() { +// capturer.Remove(&gatewayv1.HTTPRoute{}, hrSvc1AndSvc2Name) +// +// // svc2 should no longer exist +// assertServiceExists(svc2, false, 0) +// +// // ref count for svc1 should decrease by one +// assertServiceExists(svc1, true, 2) +// +// // all other ref counts stay the same +// assertServiceExists(svc3, true, 1) +// assertServiceExists(svc4, true, 1) +// }) +// }) +// When("another route is removed", func() { +// It("reports remaining service relationships", func() { +// capturer.Remove(&gatewayv1.HTTPRoute{}, hrSvc1AndSvc3Name) +// +// // svc3 should no longer exist +// assertServiceExists(svc3, false, 0) +// +// // svc2 should still not exist +// assertServiceExists(svc2, false, 0) +// +// // ref count for svc1 should decrease by one +// assertServiceExists(svc1, true, 1) +// +// // svc4 ref count should stay the same +// assertServiceExists(svc4, true, 1) +// }) +// When("final route is removed", func() { +// It("removes all service relationships", func() { +// capturer.Remove(&gatewayv1.HTTPRoute{}, hrSvc1AndSvc4Name) +// +// // no services should exist and all ref counts should be 0 +// assertServiceExists(svc1, false, 0) +// assertServiceExists(svc2, false, 0) +// assertServiceExists(svc3, false, 0) +// assertServiceExists(svc4, false, 0) +// }) +// }) +// When("route is removed again", func() { +// It("service ref counts remain at 0", func() { +// capturer.Remove(&gatewayv1.HTTPRoute{}, hrSvc1AndSvc4Name) +// +// // no services should exist and all ref counts should still be 0 +// assertServiceExists(svc1, false, 0) +// assertServiceExists(svc2, false, 0) +// assertServiceExists(svc3, false, 0) +// assertServiceExists(svc4, false, 0) +// }) +// }) +// }) +// }) +// Describe("Capture endpoint slice relationships", func() { +// var ( +// slice1 = &discoveryV1.EndpointSlice{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: "test", +// Name: "es1", +// Labels: map[string]string{index.KubernetesServiceNameLabel: "svc1"}, +// }, +// } +// +// slice2 = &discoveryV1.EndpointSlice{ +// ObjectMeta: metav1.ObjectMeta{ +// Namespace: "test", +// Name: "es2", +// Labels: map[string]string{index.KubernetesServiceNameLabel: "svc1"}, +// }, +// } +// +// slice1Name = types.NamespacedName{Namespace: slice1.Namespace, Name: slice1.Name} +// slice2Name = types.NamespacedName{Namespace: slice2.Namespace, Name: slice2.Name} +// ) +// +// BeforeEach(OncePerOrdered, func() { +// capturer = relationship.NewCapturerImpl() +// }) +// +// Describe("Normal cases", Ordered, func() { +// When("an endpoint slice is captured that has an unrelated service owner", func() { +// It("does not report an endpoint slice relationship", func() { +// capturer.Capture(slice1) +// +// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeFalse()) +// }) +// }) +// When("a relationship is captured for the service owner", func() { +// It("adds an endpoint slice relationship", func() { +// capturer.Capture(hr1) +// +// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeTrue()) +// }) +// }) +// When("another endpoint slice is captured with the same service owner", func() { +// It("adds another endpoint slice relationship", func() { +// capturer.Capture(slice2) +// +// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeTrue()) +// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice2Name)).To(BeTrue()) +// }) +// }) +// When("an endpoint slice is removed", func() { +// It("removes the endpoint slice relationship", func() { +// capturer.Remove(&discoveryV1.EndpointSlice{}, slice2Name) +// +// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice2Name)).To(BeFalse()) +// +// // slice 1 relationship should still exist +// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeTrue()) +// }) +// }) +// When("endpoint slice service owner changes to an unrelated service owner", func() { +// It("removes the endpoint slice relationship", func() { +// updatedSlice1 := slice1.DeepCopy() +// updatedSlice1.Labels[index.KubernetesServiceNameLabel] = "unrelated-svc" +// +// capturer.Capture(updatedSlice1) +// +// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeFalse()) +// }) +// }) +// When("endpoint slice service owner changes to a related service owner", func() { +// It("adds an endpoint slice relationship", func() { +// capturer.Capture(slice1) +// +// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeTrue()) +// }) +// }) +// When("service relationship is removed", func() { +// It("removes the endpoint slice relationship", func() { +// capturer.Remove(&gatewayv1.HTTPRoute{}, hr1Name) +// +// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeFalse()) +// }) +// }) +// }) +// }) +// }) +// Describe("Edge cases", func() { +// BeforeEach(func() { +// capturer = relationship.NewCapturerImpl() +// }) +// It("Capture does not panic when passed an unsupported resource type", func() { +// Expect(func() { +// capturer.Capture(&gatewayv1.GatewayClass{}) +// }).ToNot(Panic()) +// }) +// It("Remove does not panic when passed an unsupported resource type", func() { +// Expect(func() { +// capturer.Remove(&gatewayv1.GatewayClass{}, types.NamespacedName{}) +// }).ToNot(Panic()) +// }) +// It("Exist returns false if passed an unsupported resource type", func() { +// Expect(capturer.Exists(&gatewayv1.GatewayClass{}, types.NamespacedName{})).To(BeFalse()) +// }) +// }) +//}) From 148dcb260c37600133df664d6562f921b2e14b1d Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Wed, 3 Jan 2024 11:32:51 -0800 Subject: [PATCH 03/41] Remove EndpointSlice from store and refactor upsert and delete --- .../mode/static/state/change_processor.go | 3 +-- internal/mode/static/state/graph/graph.go | 1 - internal/mode/static/state/graph/service.go | 3 +-- internal/mode/static/state/store.go | 27 +++++++++---------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/internal/mode/static/state/change_processor.go b/internal/mode/static/state/change_processor.go index 3426dd9ee5..0c65740132 100644 --- a/internal/mode/static/state/change_processor.go +++ b/internal/mode/static/state/change_processor.go @@ -99,7 +99,6 @@ func NewChangeProcessorImpl(cfg ChangeProcessorConfig) *ChangeProcessorImpl { ReferenceGrants: make(map[types.NamespacedName]*v1beta1.ReferenceGrant), Secrets: make(map[types.NamespacedName]*apiv1.Secret), CRDMetadata: make(map[types.NamespacedName]*metav1.PartialObjectMetadata), - EndpointSlices: make(map[types.NamespacedName]*discoveryV1.EndpointSlice), } extractGVK := func(obj client.Object) schema.GroupVersionKind { @@ -156,7 +155,7 @@ func NewChangeProcessorImpl(cfg ChangeProcessorConfig) *ChangeProcessorImpl { }, { gvk: extractGVK(&discoveryV1.EndpointSlice{}), - store: newObjectStoreMapAdapter(clusterStore.EndpointSlices), + store: nil, predicate: funcPredicate{stateChanged: isReferenced}, }, { diff --git a/internal/mode/static/state/graph/graph.go b/internal/mode/static/state/graph/graph.go index e47ff6137f..f65f79dd7f 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -23,7 +23,6 @@ type ClusterState struct { ReferenceGrants map[types.NamespacedName]*v1beta1.ReferenceGrant Secrets map[types.NamespacedName]*v1.Secret CRDMetadata map[types.NamespacedName]*metav1.PartialObjectMetadata - EndpointSlices map[types.NamespacedName]*discoveryV1.EndpointSlice } // Graph is a Graph-like representation of Gateway API resources. diff --git a/internal/mode/static/state/graph/service.go b/internal/mode/static/state/graph/service.go index 4bb88ce7c3..d8dbe336f8 100644 --- a/internal/mode/static/state/graph/service.go +++ b/internal/mode/static/state/graph/service.go @@ -12,8 +12,7 @@ func buildReferencedServicesNames( // Get all the service names referenced from all the HTTPRoutes for _, hr := range clusterHTTPRoutes { - tempSvcNames := getBackendServiceNamesFromRoute(hr) - for k, v := range tempSvcNames { + for k, v := range getBackendServiceNamesFromRoute(hr) { svcNames[k] = v } } diff --git a/internal/mode/static/state/store.go b/internal/mode/static/state/store.go index d288e44cb2..0898c9ad1f 100644 --- a/internal/mode/static/state/store.go +++ b/internal/mode/static/state/store.go @@ -182,13 +182,14 @@ func (s *changeTrackingUpdater) assertSupportedGVK(gvk schema.GroupVersionKind) func (s *changeTrackingUpdater) upsert(obj client.Object) (changed bool) { objTypeGVK := s.extractGVK(obj) - if !s.persistedGVKs.contains(objTypeGVK) { - return false - } + var oldObj client.Object - oldObj := s.store.get(obj, client.ObjectKeyFromObject(obj)) + // persistedGVKs will only contain objTypeGVK if the resource is being tracked in the store. + if s.persistedGVKs.contains(objTypeGVK) { + oldObj = s.store.get(obj, client.ObjectKeyFromObject(obj)) - s.store.upsert(obj) + s.store.upsert(obj) + } stateChanged, ok := s.stateChangedPredicates[objTypeGVK] if !ok { @@ -219,23 +220,21 @@ func (s *changeTrackingUpdater) Upsert(obj client.Object) { func (s *changeTrackingUpdater) delete(objType client.Object, nsname types.NamespacedName) (changed bool) { objTypeGVK := s.extractGVK(objType) - if !s.persistedGVKs.contains(objTypeGVK) { - return false - } + // persistedGVKs will only contain objTypeGVK if the resource is being tracked in the store. + if s.persistedGVKs.contains(objTypeGVK) { + if s.store.get(objType, nsname) == nil { + return false + } - obj := s.store.get(objType, nsname) - if obj == nil { - return false + s.store.delete(objType, nsname) } - s.store.delete(objType, nsname) - stateChanged, ok := s.stateChangedPredicates[objTypeGVK] if !ok { return false } - return stateChanged.delete(obj) + return stateChanged.delete(objType) } func (s *changeTrackingUpdater) Delete(objType client.Object, nsname types.NamespacedName) { From c7eaca8e255ebbfa069275ef62df9950311d6dae Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Wed, 3 Jan 2024 17:14:55 -0800 Subject: [PATCH 04/41] Refactor upsert and delete functions by adding nsname as parameter --- .../mode/static/state/change_processor.go | 3 +-- .../mode/static/state/changed_predicate.go | 27 ++++++++++--------- .../static/state/changed_predicate_test.go | 13 ++++----- internal/mode/static/state/store.go | 4 +-- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/internal/mode/static/state/change_processor.go b/internal/mode/static/state/change_processor.go index 0c65740132..e4c0c34442 100644 --- a/internal/mode/static/state/change_processor.go +++ b/internal/mode/static/state/change_processor.go @@ -114,8 +114,7 @@ func NewChangeProcessorImpl(cfg ChangeProcessorConfig) *ChangeProcessorImpl { clusterState: clusterStore, } - isReferenced := func(obj client.Object) bool { - nsname := types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()} + isReferenced := func(obj client.Object, nsname types.NamespacedName) bool { return processor.latestGraph != nil && processor.latestGraph.IsReferenced(obj, nsname) } diff --git a/internal/mode/static/state/changed_predicate.go b/internal/mode/static/state/changed_predicate.go index 08fc3301b7..b48eb87c55 100644 --- a/internal/mode/static/state/changed_predicate.go +++ b/internal/mode/static/state/changed_predicate.go @@ -1,35 +1,38 @@ package state -import "sigs.k8s.io/controller-runtime/pkg/client" +import ( + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) // stateChangedPredicate determines whether upsert and delete events constitute a change in state. type stateChangedPredicate interface { // upsert returns true if the newObject changes state. - upsert(oldObject, newObject client.Object) bool + upsert(oldObject, newObject client.Object, nsname types.NamespacedName) bool // delete returns true if the deletion of the object changes state. - delete(object client.Object) bool + delete(object client.Object, nsname types.NamespacedName) bool } // funcPredicate applies the stateChanged function on upsert and delete. On upsert, the newObject is passed. // Implements stateChangedPredicate. type funcPredicate struct { - stateChanged func(object client.Object) bool + stateChanged func(object client.Object, nsname types.NamespacedName) bool } -func (f funcPredicate) upsert(_, newObject client.Object) bool { - return f.stateChanged(newObject) +func (f funcPredicate) upsert(_, newObject client.Object, nsname types.NamespacedName) bool { + return f.stateChanged(newObject, nsname) } -func (f funcPredicate) delete(object client.Object) bool { - return f.stateChanged(object) +func (f funcPredicate) delete(object client.Object, nsname types.NamespacedName) bool { + return f.stateChanged(object, nsname) } // FIXME(kevin85421): We should remove this predicate and update changeTrackingUpdater once #1432 is merged. type alwaysProcess struct{} -func (alwaysProcess) delete(_ client.Object) bool { return true } +func (alwaysProcess) delete(_ client.Object, _ types.NamespacedName) bool { return true } -func (alwaysProcess) upsert(_, _ client.Object) bool { return true } +func (alwaysProcess) upsert(_, _ client.Object, _ types.NamespacedName) bool { return true } // annotationChangedPredicate implements stateChangedPredicate based on the value of the annotation provided. // This predicate will return true on upsert if the annotation's value has changed. @@ -38,7 +41,7 @@ type annotationChangedPredicate struct { annotation string } -func (a annotationChangedPredicate) upsert(oldObject, newObject client.Object) bool { +func (a annotationChangedPredicate) upsert(oldObject, newObject client.Object, _ types.NamespacedName) bool { if oldObject == nil { return true } @@ -53,4 +56,4 @@ func (a annotationChangedPredicate) upsert(oldObject, newObject client.Object) b return oldAnnotation != newAnnotation } -func (a annotationChangedPredicate) delete(_ client.Object) bool { return true } +func (a annotationChangedPredicate) delete(_ client.Object, _ types.NamespacedName) bool { return true } diff --git a/internal/mode/static/state/changed_predicate_test.go b/internal/mode/static/state/changed_predicate_test.go index 9902ca61a8..bebc68cae4 100644 --- a/internal/mode/static/state/changed_predicate_test.go +++ b/internal/mode/static/state/changed_predicate_test.go @@ -6,25 +6,26 @@ import ( . "github.com/onsi/gomega" 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" ) func TestFuncPredicate(t *testing.T) { - alwaysTrueFunc := func(object client.Object) bool { return true } + alwaysTrueFunc := func(object client.Object, _ types.NamespacedName) bool { return true } p := funcPredicate{stateChanged: alwaysTrueFunc} g := NewWithT(t) - g.Expect(p.delete(nil)).To(BeTrue()) - g.Expect(p.upsert(nil, nil)).To(BeTrue()) + g.Expect(p.delete(nil, types.NamespacedName{})).To(BeTrue()) + g.Expect(p.upsert(nil, nil, types.NamespacedName{})).To(BeTrue()) } func TestAnnotationChangedPredicate_Delete(t *testing.T) { p := annotationChangedPredicate{} g := NewWithT(t) - g.Expect(p.delete(nil)).To(BeTrue()) + g.Expect(p.delete(nil, types.NamespacedName{})).To(BeTrue()) } func TestAnnotationChangedPredicate_Update(t *testing.T) { @@ -126,11 +127,11 @@ func TestAnnotationChangedPredicate_Update(t *testing.T) { g := NewWithT(t) if test.expPanic { upsert := func() { - p.upsert(test.oldObj, test.newObj) + p.upsert(test.oldObj, test.newObj, types.NamespacedName{}) } g.Expect(upsert).Should(Panic()) } else { - g.Expect(p.upsert(test.oldObj, test.newObj)).To(Equal(test.stateChanged)) + g.Expect(p.upsert(test.oldObj, test.newObj, types.NamespacedName{})).To(Equal(test.stateChanged)) } }) } diff --git a/internal/mode/static/state/store.go b/internal/mode/static/state/store.go index 0898c9ad1f..f43f8d459d 100644 --- a/internal/mode/static/state/store.go +++ b/internal/mode/static/state/store.go @@ -196,7 +196,7 @@ func (s *changeTrackingUpdater) upsert(obj client.Object) (changed bool) { return false } - return stateChanged.upsert(oldObj, obj) + return stateChanged.upsert(oldObj, obj, types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}) } func (s *changeTrackingUpdater) Upsert(obj client.Object) { @@ -234,7 +234,7 @@ func (s *changeTrackingUpdater) delete(objType client.Object, nsname types.Names return false } - return stateChanged.delete(objType) + return stateChanged.delete(objType, nsname) } func (s *changeTrackingUpdater) Delete(objType client.Object, nsname types.NamespacedName) { From de5801cb7c98cb2e380ff9a4aa16d5c9b801e204 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 4 Jan 2024 11:09:15 -0800 Subject: [PATCH 05/41] Remove nsname from update parameters --- internal/mode/static/state/changed_predicate.go | 17 ++++++++++++----- .../mode/static/state/changed_predicate_test.go | 6 +++--- internal/mode/static/state/store.go | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/internal/mode/static/state/changed_predicate.go b/internal/mode/static/state/changed_predicate.go index b48eb87c55..22be400d51 100644 --- a/internal/mode/static/state/changed_predicate.go +++ b/internal/mode/static/state/changed_predicate.go @@ -8,7 +8,7 @@ import ( // stateChangedPredicate determines whether upsert and delete events constitute a change in state. type stateChangedPredicate interface { // upsert returns true if the newObject changes state. - upsert(oldObject, newObject client.Object, nsname types.NamespacedName) bool + upsert(oldObject, newObject client.Object) bool // delete returns true if the deletion of the object changes state. delete(object client.Object, nsname types.NamespacedName) bool } @@ -19,8 +19,15 @@ type funcPredicate struct { stateChanged func(object client.Object, nsname types.NamespacedName) bool } -func (f funcPredicate) upsert(_, newObject client.Object, nsname types.NamespacedName) bool { - return f.stateChanged(newObject, nsname) +func (f funcPredicate) upsert(_, newObject client.Object) bool { + if newObject == nil { + panic("New object cannot be nil") + } + + return f.stateChanged(newObject, types.NamespacedName{ + Namespace: newObject.GetNamespace(), + Name: newObject.GetName(), + }) } func (f funcPredicate) delete(object client.Object, nsname types.NamespacedName) bool { @@ -32,7 +39,7 @@ type alwaysProcess struct{} func (alwaysProcess) delete(_ client.Object, _ types.NamespacedName) bool { return true } -func (alwaysProcess) upsert(_, _ client.Object, _ types.NamespacedName) bool { return true } +func (alwaysProcess) upsert(_, _ client.Object) bool { return true } // annotationChangedPredicate implements stateChangedPredicate based on the value of the annotation provided. // This predicate will return true on upsert if the annotation's value has changed. @@ -41,7 +48,7 @@ type annotationChangedPredicate struct { annotation string } -func (a annotationChangedPredicate) upsert(oldObject, newObject client.Object, _ types.NamespacedName) bool { +func (a annotationChangedPredicate) upsert(oldObject, newObject client.Object) bool { if oldObject == nil { return true } diff --git a/internal/mode/static/state/changed_predicate_test.go b/internal/mode/static/state/changed_predicate_test.go index bebc68cae4..e13e7cd7ef 100644 --- a/internal/mode/static/state/changed_predicate_test.go +++ b/internal/mode/static/state/changed_predicate_test.go @@ -18,7 +18,7 @@ func TestFuncPredicate(t *testing.T) { g := NewWithT(t) g.Expect(p.delete(nil, types.NamespacedName{})).To(BeTrue()) - g.Expect(p.upsert(nil, nil, types.NamespacedName{})).To(BeTrue()) + g.Expect(p.upsert(nil, nil)).To(BeTrue()) } func TestAnnotationChangedPredicate_Delete(t *testing.T) { @@ -127,11 +127,11 @@ func TestAnnotationChangedPredicate_Update(t *testing.T) { g := NewWithT(t) if test.expPanic { upsert := func() { - p.upsert(test.oldObj, test.newObj, types.NamespacedName{}) + p.upsert(test.oldObj, test.newObj) } g.Expect(upsert).Should(Panic()) } else { - g.Expect(p.upsert(test.oldObj, test.newObj, types.NamespacedName{})).To(Equal(test.stateChanged)) + g.Expect(p.upsert(test.oldObj, test.newObj)).To(Equal(test.stateChanged)) } }) } diff --git a/internal/mode/static/state/store.go b/internal/mode/static/state/store.go index f43f8d459d..73dff58d65 100644 --- a/internal/mode/static/state/store.go +++ b/internal/mode/static/state/store.go @@ -196,7 +196,7 @@ func (s *changeTrackingUpdater) upsert(obj client.Object) (changed bool) { return false } - return stateChanged.upsert(oldObj, obj, types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}) + return stateChanged.upsert(oldObj, obj) } func (s *changeTrackingUpdater) Upsert(obj client.Object) { From 34ff73ed5efd699c132f1cfdbb68646fa791ea8c Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Tue, 9 Jan 2024 11:01:42 -0800 Subject: [PATCH 06/41] Refactor backendRefs to store ServicePort and SvcNsName --- .../static/state/change_processor_test.go | 651 +++++++++--------- .../static/state/dataplane/configuration.go | 2 +- .../state/dataplane/configuration_test.go | 26 +- .../mode/static/state/graph/backend_refs.go | 91 ++- .../static/state/graph/backend_refs_test.go | 234 +++++-- internal/mode/static/state/graph/graph.go | 2 +- .../mode/static/state/graph/graph_test.go | 37 +- internal/mode/static/state/graph/httproute.go | 2 + internal/mode/static/state/graph/service.go | 47 +- .../mode/static/state/resolver/resolver.go | 44 +- .../static/state/resolver/resolver_test.go | 53 +- .../resolverfakes/fake_service_resolver.go | 17 +- .../state/resolver/service_resolver_test.go | 19 +- 13 files changed, 688 insertions(+), 537 deletions(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 0d208e7655..463432f9b4 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -19,7 +19,6 @@ import ( "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/conditions" - "github.com/nginxinc/nginx-gateway-fabric/internal/framework/controller/index" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/gatewayclass" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state" @@ -132,29 +131,29 @@ func createGatewayWithTLSListener(name string, tlsSecret *apiv1.Secret) *v1.Gate return gw } -func createRouteWithMultipleRules( - name, gateway, hostname string, - rules []v1.HTTPRouteRule, -) *v1.HTTPRoute { - hr := createRoute(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 createRouteWithMultipleRules( +// name, gateway, hostname string, +// rules []v1.HTTPRouteRule, +//) *v1.HTTPRoute { +// hr := createRoute(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 createBackendRef( kind *v1.Kind, @@ -283,15 +282,15 @@ var _ = Describe("ChangeProcessor", func() { Describe("Process gateway resources", Ordered, func() { var ( - gcUpdated *v1.GatewayClass - diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret - hr1, hr1Updated, hr2 *v1.HTTPRoute - gw1, gw1Updated, gw2 *v1.Gateway - refGrant1, refGrant2 *v1beta1.ReferenceGrant - expGraph, graphWithJustReferencedServicesNames *graph.Graph - expRouteHR1, expRouteHR2 *graph.Route - hr1Name, hr2Name types.NamespacedName - gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata + gcUpdated *v1.GatewayClass + diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret + hr1, hr1Updated, hr2 *v1.HTTPRoute + gw1, gw1Updated, gw2 *v1.Gateway + refGrant1, refGrant2 *v1beta1.ReferenceGrant + expGraph *graph.Graph + expRouteHR1, expRouteHR2 *graph.Route + hr1Name, hr2Name types.NamespacedName + gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata ) BeforeAll(func() { gcUpdated = gc.DeepCopy() @@ -430,7 +429,8 @@ var _ = Describe("ChangeProcessor", func() { { BackendRefs: []graph.BackendRef{ { - Weight: 1, + SvcNsName: types.NamespacedName{Namespace: "service-ns", Name: "service"}, + Weight: 1, }, }, ValidMatches: true, @@ -444,6 +444,7 @@ var _ = Describe("ChangeProcessor", func() { "spec.rules[0].backendRefs[0].name: Not found: \"service\"", ), }, + ServiceNames: map[types.NamespacedName]struct{}{{Namespace: "service-ns", Name: "service"}: {}}, } expRouteHR2 = &graph.Route{ @@ -516,14 +517,6 @@ var _ = Describe("ChangeProcessor", func() { }: {}, }, } - graphWithJustReferencedServicesNames = &graph.Graph{ - ReferencedServicesNames: map[types.NamespacedName]struct{}{ - { - Namespace: "service-ns", - Name: "service", - }: {}, - }, - } }) When("no upsert has occurred", func() { It("returns nil graph", func() { @@ -549,7 +542,7 @@ var _ = Describe("ChangeProcessor", func() { changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) - Expect(helpers.Diff(graphWithJustReferencedServicesNames, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) }) }) When("the different namespace TLS Secret is upserted", func() { @@ -587,6 +580,10 @@ var _ = Describe("ChangeProcessor", func() { } expGraph.ReferencedSecrets = nil + expGraph.ReferencedServicesNames = nil + + expRouteHR1.ServiceNames = nil + expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) @@ -637,6 +634,10 @@ var _ = Describe("ChangeProcessor", func() { expGraph.Routes[hr1Name].ParentRefs[1].Attachment = expAttachment443 expGraph.ReferencedSecrets = nil + expGraph.ReferencedServicesNames = nil + + expRouteHR1.ServiceNames = nil + expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) @@ -657,6 +658,10 @@ var _ = Describe("ChangeProcessor", func() { Source: diffNsTLSSecret, } + expGraph.ReferencedServicesNames = nil + expRouteHR1.ServiceNames = nil + expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) @@ -863,6 +868,10 @@ var _ = Describe("ChangeProcessor", func() { Source: sameNsTLSSecret, } + expRouteHR1.ServiceNames = nil + expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServicesNames = nil + changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) @@ -892,6 +901,10 @@ var _ = Describe("ChangeProcessor", func() { Source: sameNsTLSSecret, } + expRouteHR1.ServiceNames = nil + expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServicesNames = nil + changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) @@ -912,6 +925,10 @@ var _ = Describe("ChangeProcessor", func() { expGraph.Routes = map[types.NamespacedName]*graph.Route{} expGraph.ReferencedSecrets = nil + expRouteHR1.ServiceNames = nil + expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServicesNames = nil + changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) @@ -924,9 +941,13 @@ var _ = Describe("ChangeProcessor", func() { types.NamespacedName{Namespace: "test", Name: "gateway-2"}, ) + expRouteHR1.ServiceNames = nil + expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServicesNames = nil + changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) - Expect(helpers.Diff(graphWithJustReferencedServicesNames, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) }) }) When("the first HTTPRoute is deleted", func() { @@ -936,344 +957,350 @@ var _ = Describe("ChangeProcessor", func() { types.NamespacedName{Namespace: "test", Name: "hr-1"}, ) + expRouteHR1.ServiceNames = nil + expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServicesNames = nil + changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) }) }) }) - 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 - ) + /* + 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 + ) - createSvc := func(name string) *apiv1.Service { - return &apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: name, - }, + 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}, - }, + 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}, + }, + } } - } - BeforeAll(func() { - testNamespace := v1.Namespace("test") - kindService := v1.Kind("Service") - kindInvalid := v1.Kind("Invalid") - - // backend Refs - fooRef := createBackendRef(&kindService, "foo-svc", &testNamespace) - baz1NilNamespace := createBackendRef(&kindService, "baz-svc-v1", &testNamespace) - barRef := createBackendRef(&kindService, "bar-svc", nil) - baz2Ref := createBackendRef(&kindService, "baz-svc-v2", &testNamespace) - baz3Ref := createBackendRef(&kindService, "baz-svc-v3", &testNamespace) - invalidKindRef := createBackendRef(&kindInvalid, "bar-svc", &testNamespace) - - // httproutes - hr1 = createRoute("hr1", "gw", "foo.example.com", fooRef) - hr2 = createRoute("hr2", "gw", "bar.example.com", barRef) - // hr3 shares the same backendRef as hr2 - hr3 = createRoute("hr3", "gw", "bar.2.example.com", barRef) - hrInvalidBackendRef = createRoute("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), - }, - ) + BeforeAll(func() { + testNamespace := v1.Namespace("test") + kindService := v1.Kind("Service") + kindInvalid := v1.Kind("Invalid") + + // backend Refs + fooRef := createBackendRef(&kindService, "foo-svc", &testNamespace) + baz1NilNamespace := createBackendRef(&kindService, "baz-svc-v1", &testNamespace) + barRef := createBackendRef(&kindService, "bar-svc", nil) + baz2Ref := createBackendRef(&kindService, "baz-svc-v2", &testNamespace) + baz3Ref := createBackendRef(&kindService, "baz-svc-v3", &testNamespace) + invalidKindRef := createBackendRef(&kindInvalid, "bar-svc", &testNamespace) + + // httproutes + hr1 = createRoute("hr1", "gw", "foo.example.com", fooRef) + hr2 = createRoute("hr2", "gw", "bar.example.com", barRef) + // hr3 shares the same backendRef as hr2 + hr3 = createRoute("hr3", "gw", "bar.2.example.com", barRef) + hrInvalidBackendRef = createRoute("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", "") - }) + // 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") - testProcessChangedVal := func(expChanged bool) { - changed, _ := processor.Process() - Expect(changed).To(Equal(expChanged)) - } + // 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", "") + }) - testUpsertTriggersChange := func(obj client.Object, expChanged bool) { - processor.CaptureUpsertChange(obj) - testProcessChangedVal(expChanged) - } + testProcessChangedVal := func(expChanged bool) { + changed, _ := processor.Process() + Expect(changed).To(Equal(expChanged)) + } - testDeleteTriggersChange := func(obj client.Object, nsname types.NamespacedName, expChanged bool) { - processor.CaptureDeleteChange(obj, nsname) - testProcessChangedVal(expChanged) - } - When("hr1 is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1, true) - }) - }) - When("a hr1 service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1svc, true) - }) - }) - When("an hr1 endpoint slice is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1slice1, true) - }) - }) - When("an hr1 service is updated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1svc, true) - }) - }) - When("another hr1 endpoint slice is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1slice2, true) - }) - }) - When("an endpoint slice with a missing svc name label is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(missingSvcNameSlice, false) - }) - }) - When("an hr1 endpoint slice is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr1slice1, - types.NamespacedName{Namespace: hr1slice1.Namespace, Name: hr1slice1.Name}, - true, - ) - }) - }) - 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}, - true, - ) - }) - }) - When("the second hr1 endpoint slice is recreated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1slice2, true) - }) - }) - When("hr1 is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr1, - types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name}, - true, - ) - }) - }) - When("hr1 service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - hr1svc, - types.NamespacedName{Namespace: hr1svc.Namespace, Name: hr1svc.Name}, - false, - ) - }) - }) - 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}, - false, - ) - }) - }) - When("hr2 is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr2, true) - }) - }) - When("a hr3, that shares a backend service with hr2, is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr3, true) + testUpsertTriggersChange := func(obj client.Object, expChanged bool) { + processor.CaptureUpsertChange(obj) + testProcessChangedVal(expChanged) + } + + testDeleteTriggersChange := func(obj client.Object, nsname types.NamespacedName, expChanged bool) { + processor.CaptureDeleteChange(obj, nsname) + testProcessChangedVal(expChanged) + } + When("hr1 is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1, true) + }) }) - }) - When("sharedSvc, a service referenced by both hr2 and hr3, is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(sharedSvc, true) + When("a hr1 service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1svc, true) + }) }) - }) - When("hr2 is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr2, - types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name}, - true, - ) + When("an hr1 endpoint slice is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1slice1, true) + }) }) - }) - When("sharedSvc is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - sharedSvc, - types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, - true, - ) + When("an hr1 service is updated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1svc, true) + }) }) - }) - When("sharedSvc is recreated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(sharedSvc, true) + When("another hr1 endpoint slice is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1slice2, true) + }) }) - }) - When("hr3 is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr3, - types.NamespacedName{Namespace: hr3.Namespace, Name: hr3.Name}, - true, - ) + When("an endpoint slice with a missing svc name label is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(missingSvcNameSlice, false) + }) }) - }) - When("sharedSvc is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - sharedSvc, - types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, - false, - ) + When("an hr1 endpoint slice is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr1slice1, + types.NamespacedName{Namespace: hr1slice1.Namespace, Name: hr1slice1.Name}, + true, + ) + }) }) - }) - When("a service that is not referenced by any route is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(notRefSvc, false) + 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}, + true, + ) + }) }) - }) - When("a route with an invalid backend ref type is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hrInvalidBackendRef, true) + When("the second hr1 endpoint slice is recreated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1slice2, true) + }) }) - }) - When("a service with a namespace name that matches invalid backend ref is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(invalidSvc, false) + When("hr1 is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr1, + types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name}, + true, + ) + }) }) - }) - When("an endpoint slice that is not owned by a referenced service is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(noRefSlice, false) + When("hr1 service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + hr1svc, + types.NamespacedName{Namespace: hr1svc.Namespace, Name: hr1svc.Name}, + false, + ) + }) }) - }) - 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}, - false, - ) + 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}, + false, + ) + }) }) - }) - Context("processing a route with multiple rules and three unique backend services", func() { - When("route is added", func() { + When("hr2 is added", func() { It("should trigger a change", func() { - testUpsertTriggersChange(hrMultipleRules, true) + testUpsertTriggersChange(hr2, true) }) }) - When("first referenced service is added", func() { + When("a hr3, that shares a backend service with hr2, is added", func() { It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc1, true) + testUpsertTriggersChange(hr3, true) }) }) - When("second referenced service is added", func() { + When("sharedSvc, a service referenced by both hr2 and hr3, is added", func() { It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc2, true) + testUpsertTriggersChange(sharedSvc, true) }) }) - When("first referenced service is deleted", func() { + When("hr2 is deleted", func() { It("should trigger a change", func() { testDeleteTriggersChange( - bazSvc1, - types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, + hr2, + types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name}, true, ) }) }) - When("first referenced service is recreated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc1, true) - }) - }) - When("third referenced service is added", func() { + When("sharedSvc is deleted", func() { It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc3, true) + testDeleteTriggersChange( + sharedSvc, + types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, + true, + ) }) }) - When("third referenced service is updated", func() { + When("sharedSvc is recreated", func() { It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc3, true) + testUpsertTriggersChange(sharedSvc, true) }) }) - When("route is deleted", func() { + When("hr3 is deleted", func() { It("should trigger a change", func() { testDeleteTriggersChange( - hrMultipleRules, - types.NamespacedName{ - Namespace: hrMultipleRules.Namespace, - Name: hrMultipleRules.Name, - }, + hr3, + types.NamespacedName{Namespace: hr3.Namespace, Name: hr3.Name}, true, ) }) }) - When("first referenced service is deleted", func() { + When("sharedSvc is deleted", func() { It("should not trigger a change", func() { testDeleteTriggersChange( - bazSvc1, - types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, + sharedSvc, + types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, false, ) }) }) - When("second referenced service is deleted", func() { + When("a service that is not referenced by any route is added", func() { It("should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc2, - types.NamespacedName{Namespace: bazSvc2.Namespace, Name: bazSvc2.Name}, - false, - ) + testUpsertTriggersChange(notRefSvc, false) + }) + }) + When("a route with an invalid backend ref type is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hrInvalidBackendRef, true) + }) + }) + When("a service with a namespace name that matches invalid backend ref is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(invalidSvc, false) + }) + }) + When("an endpoint slice that is not owned by a referenced service is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(noRefSlice, false) }) }) - When("final referenced service is deleted", func() { + When("an endpoint slice that is not owned by a referenced service is deleted", func() { It("should not trigger a change", func() { testDeleteTriggersChange( - bazSvc3, - types.NamespacedName{Namespace: bazSvc3.Namespace, Name: bazSvc3.Name}, + noRefSlice, + types.NamespacedName{Namespace: noRefSlice.Namespace, Name: noRefSlice.Name}, false, ) }) }) + 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, true) + }) + }) + When("first referenced service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc1, true) + }) + }) + When("second referenced service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc2, true) + }) + }) + When("first referenced service is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + bazSvc1, + types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, + true, + ) + }) + }) + When("first referenced service is recreated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc1, true) + }) + }) + When("third referenced service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc3, true) + }) + }) + When("third referenced service is updated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc3, true) + }) + }) + When("route is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hrMultipleRules, + types.NamespacedName{ + Namespace: hrMultipleRules.Namespace, + Name: hrMultipleRules.Name, + }, + true, + ) + }) + }) + When("first referenced service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + bazSvc1, + types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, + false, + ) + }) + }) + When("second referenced service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + bazSvc2, + types.NamespacedName{Namespace: bazSvc2.Namespace, Name: bazSvc2.Name}, + false, + ) + }) + }) + When("final referenced service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + bazSvc3, + types.NamespacedName{Namespace: bazSvc3.Namespace, Name: bazSvc3.Name}, + false, + ) + }) + }) + }) }) - }) + */ Describe("namespace changes", Ordered, func() { var ( ns, nsDifferentLabels, nsNoLabels *apiv1.Namespace diff --git a/internal/mode/static/state/dataplane/configuration.go b/internal/mode/static/state/dataplane/configuration.go index 81ebb817c3..d48a304b32 100644 --- a/internal/mode/static/state/dataplane/configuration.go +++ b/internal/mode/static/state/dataplane/configuration.go @@ -402,7 +402,7 @@ func buildUpstreams( var errMsg string - eps, err := resolver.Resolve(ctx, br.Svc, br.Port) + eps, err := resolver.Resolve(ctx, br.SvcNsName, br.ServicePort) if err != nil { errMsg = err.Error() } diff --git a/internal/mode/static/state/dataplane/configuration_test.go b/internal/mode/static/state/dataplane/configuration_test.go index d4d8a3a7bf..cce6f0e749 100644 --- a/internal/mode/static/state/dataplane/configuration_test.go +++ b/internal/mode/static/state/dataplane/configuration_test.go @@ -82,16 +82,14 @@ func TestBuildConfiguration(t *testing.T) { Endpoints: fooEndpoints, } - fooSvc := &apiv1.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test"}} - fakeResolver := &resolverfakes.FakeServiceResolver{} fakeResolver.ResolveReturns(fooEndpoints, nil) validBackendRef := graph.BackendRef{ - Svc: fooSvc, - Port: 80, - Valid: true, - Weight: 1, + SvcNsName: types.NamespacedName{Name: "foo", Namespace: "test"}, + ServicePort: apiv1.ServicePort{Port: 80}, + Valid: true, + Weight: 1, } expValidBackend := Backend{ @@ -1877,9 +1875,9 @@ func TestBuildUpstreams(t *testing.T) { var backends []graph.BackendRef for _, name := range serviceNames { backends = append(backends, graph.BackendRef{ - Svc: &apiv1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: name}}, - Port: 80, - Valid: name != "", + SvcNsName: types.NamespacedName{Namespace: "test", Name: name}, + ServicePort: apiv1.ServicePort{Port: 80}, + Valid: name != "", }) } return backends @@ -1995,8 +1993,12 @@ func TestBuildUpstreams(t *testing.T) { } fakeResolver := &resolverfakes.FakeServiceResolver{} - fakeResolver.ResolveCalls(func(ctx context.Context, svc *apiv1.Service, port int32) ([]resolver.Endpoint, error) { - switch svc.Name { + fakeResolver.ResolveCalls(func( + ctx context.Context, + svcNsName types.NamespacedName, + servicePort apiv1.ServicePort, + ) ([]resolver.Endpoint, error) { + switch svcNsName.Name { case "bar": return barEndpoints, nil case "baz": @@ -2012,7 +2014,7 @@ func TestBuildUpstreams(t *testing.T) { case "abc": return abcEndpoints, nil default: - return nil, fmt.Errorf("unexpected service %s", svc.Name) + return nil, fmt.Errorf("unexpected service %s", svcNsName.Name) } }) diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index 5bc1203ebd..26114d4d93 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -15,9 +15,13 @@ import ( // BackendRef is an internal representation of a backendRef in an HTTPRoute. type BackendRef struct { // Svc is the service referenced by the backendRef. - Svc *v1.Service + // Svc *v1.Service + // 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. + ServicePort v1.ServicePort // Port is the port of the backendRef. - Port int32 + // Port int32 // Weight is the weight of the backendRef. Weight int32 // Valid indicates whether the backendRef is valid. @@ -26,10 +30,14 @@ type BackendRef struct { // ServicePortReference returns a string representation for the service and port that is referenced by the BackendRef. func (b BackendRef) ServicePortReference() string { - if b.Svc == nil { + // If the ServicePort's Port is 0 it means that the Port on the BackendRef + // did not match any ports on the Service's ServicePorts. + // + // If the SvcNsName Name or Namespace are empty strings, it means that the BackendRef failed validation. + if b.SvcNsName.Name == "" || b.SvcNsName.Namespace == "" || b.ServicePort.Port == 0 { return "" } - return fmt.Sprintf("%s_%s_%d", b.Svc.Namespace, b.Svc.Name, b.Port) + return fmt.Sprintf("%s_%s_%d", b.SvcNsName.Namespace, b.SvcNsName.Name, b.ServicePort.Port) } func addBackendRefsToRouteRules( @@ -78,10 +86,38 @@ func addBackendRefsToRules( if cond != nil { route.Conditions = append(route.Conditions, *cond) } + + // This should fill ServiceNames with the NamespacedName of all the Services which + // come from a Valid route and rules. + // All the routes here have populated ParentRef's as it is checked when the Route was built. + // + // ref.SvcNsName could be an empty SvcNsName if validateHTTPBackendRef returned invalid. + // Otherwise, it should be populated with the Service NsName on the backendRef even if + // the Service does not exist. + // + // ref.ServicePort can be empty and that is fine as we still want to populate route.ServiceNames + // if a Service does not exist. + if ref.SvcNsName.Name != "" && ref.SvcNsName.Namespace != "" { + if route.ServiceNames == nil { + route.ServiceNames = make(map[types.NamespacedName]struct{}) + } + + route.ServiceNames[ref.SvcNsName] = struct{}{} + } } + // Some of the backendRef's could be invalid, but when we use them in configuration.go when building the + // Upstreams, we skip over the ones that are not valid. route.Rules[idx].BackendRefs = backendRefs } + // If any of the ParentRefs of the route have Attachment.Attached set to true, we want to generate NGINX + // configuration and thus can return the method with route.ServiceNames populated. Else, we set ServiceNames to nil. + for _, ref := range route.ParentRefs { + if ref.Attachment.Attached { + return + } + } + route.ServiceNames = nil } func createBackendRef( @@ -116,11 +152,13 @@ func createBackendRef( return backendRef, &cond } - svc, port, err := getServiceAndPortFromRef(ref.BackendRef, sourceNamespace, services, refPath) + svcNsName, svcPort, err := getServiceAndPortFromRef(ref.BackendRef, sourceNamespace, services, refPath) if err != nil { backendRef = BackendRef{ - Weight: weight, - Valid: false, + SvcNsName: svcNsName, + ServicePort: svcPort, + Weight: weight, + Valid: false, } cond := staticConds.NewRouteBackendRefRefBackendNotFound(err.Error()) @@ -128,35 +166,48 @@ func createBackendRef( } backendRef = BackendRef{ - Svc: svc, - Port: port, - Valid: true, - Weight: weight, + SvcNsName: svcNsName, + ServicePort: svcPort, + Valid: true, + Weight: weight, } return backendRef, nil } +// The v1.ServicePort returned can be empty in two cases: +// 1. The Service referenced from the BackendRef does not exist in the cluster/state. +// 2. The Port on the BackendRef does not match any of the ServicePorts on the Service. func getServiceAndPortFromRef( ref gatewayv1.BackendRef, routeNamespace string, services map[types.NamespacedName]*v1.Service, refPath *field.Path, -) (*v1.Service, int32, error) { +) (types.NamespacedName, v1.ServicePort, error) { ns := routeNamespace if ref.Namespace != nil { ns = string(*ref.Namespace) } + // TODO: Is this right that the svcNsName name is the name of the backendRef? svcNsName := types.NamespacedName{Name: string(ref.Name), Namespace: ns} + // If the service is unable to be found, svcNsName will still be populated with what the BackendRef + // has listed, however the ServicePort returned will be empty. svc, ok := services[svcNsName] if !ok { - return nil, 0, field.NotFound(refPath.Child("name"), ref.Name) + return svcNsName, v1.ServicePort{}, field.NotFound(refPath.Child("name"), ref.Name) + } + + // svcPort can be an empty v1.ServicePort{} if the BackendRef.Port did not match any ServicePorts + // + // safe to dereference port here because we already validated that the port is not nil in validateBackendRef. + svcPort, err := getServicePort(svc, int32(*ref.Port)) + if err != nil { + return svcNsName, v1.ServicePort{}, err } - // safe to dereference port here because we already validated that the port is not nil. - return svc, int32(*ref.Port), nil + return svcNsName, svcPort, nil } func validateHTTPBackendRef( @@ -233,3 +284,13 @@ func validateWeight(weight int32) error { return nil } + +func getServicePort(svc *v1.Service, port int32) (v1.ServicePort, error) { + for _, p := range svc.Spec.Ports { + if p.Port == port { + return p, nil + } + } + + return v1.ServicePort{}, fmt.Errorf("no matching port for Service %s and port %d", svc.Name, port) +} diff --git a/internal/mode/static/state/graph/backend_refs_test.go b/internal/mode/static/state/graph/backend_refs_test.go index d81cf386bc..1aebaac3b5 100644 --- a/internal/mode/static/state/graph/backend_refs_test.go +++ b/internal/mode/static/state/graph/backend_refs_test.go @@ -244,6 +244,17 @@ func TestGetServiceAndPortFromRef(t *testing.T) { Name: "service1", Namespace: "test", }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + { + Port: 80, + }, + }, + }, + } + svc1NsName := types.NamespacedName{ + Namespace: "test", + Name: "service1", } svc2 := &v1.Service{ @@ -254,17 +265,17 @@ func TestGetServiceAndPortFromRef(t *testing.T) { } tests := []struct { - ref gatewayv1.BackendRef - expService *v1.Service - name string - expPort int32 - expErr bool + ref gatewayv1.BackendRef + expServiceNsName types.NamespacedName + name string + expServicePort v1.ServicePort + expErr bool }{ { - name: "normal case", - ref: getNormalRef(), - expService: svc1, - expPort: 80, + name: "normal case", + ref: getNormalRef(), + expServiceNsName: svc1NsName, + expServicePort: v1.ServicePort{Port: 80}, }, { name: "service does not exist", @@ -272,7 +283,9 @@ func TestGetServiceAndPortFromRef(t *testing.T) { backend.Name = "does-not-exist" return backend }), - expErr: true, + expErr: true, + expServiceNsName: types.NamespacedName{Name: "does-not-exist", Namespace: "test"}, + expServicePort: v1.ServicePort{}, }, } @@ -287,11 +300,11 @@ func TestGetServiceAndPortFromRef(t *testing.T) { t.Run(test.name, func(t *testing.T) { g := NewWithT(t) - svc, port, err := getServiceAndPortFromRef(test.ref, "test", services, refPath) + svcNsName, servicePort, err := getServiceAndPortFromRef(test.ref, "test", services, refPath) g.Expect(err != nil).To(Equal(test.expErr)) - g.Expect(svc).To(Equal(test.expService)) - g.Expect(port).To(Equal(test.expPort)) + g.Expect(svcNsName).To(Equal(test.expServiceNsName)) + g.Expect(servicePort).To(Equal(test.expServicePort)) }) } } @@ -374,7 +387,26 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { hrWithZeroBackendRefs := createRoute("hr4", "Service", 1, "svc1") hrWithZeroBackendRefs.Spec.Rules[0].BackendRefs = nil - svc1 := &v1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "svc1"}} + svc1 := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "svc1", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + { + Port: 80, + }, + { + Port: 81, + }, + }, + }, + } + svc1NsName := types.NamespacedName{ + Namespace: "test", + Name: "svc1", + } services := map[types.NamespacedName]*v1.Service{ {Namespace: "test", Name: "svc1"}: svc1, @@ -388,17 +420,18 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }{ { route: &Route{ - Source: hrWithOneBackend, - ParentRefs: sectionNameRefs, - Valid: true, - Rules: createRules(hrWithOneBackend, allValid, allValid), + Source: hrWithOneBackend, + ParentRefs: sectionNameRefs, + Valid: true, + Rules: createRules(hrWithOneBackend, allValid, allValid), + ServiceNames: make(map[types.NamespacedName]struct{}), }, expectedBackendRefs: []BackendRef{ { - Svc: svc1, - Port: 80, - Valid: true, - Weight: 1, + SvcNsName: svc1NsName, + ServicePort: svc1.Spec.Ports[0], + Valid: true, + Weight: 1, }, }, expectedConditions: nil, @@ -406,23 +439,24 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }, { route: &Route{ - Source: hrWithTwoBackends, - ParentRefs: sectionNameRefs, - Valid: true, - Rules: createRules(hrWithTwoBackends, allValid, allValid), + Source: hrWithTwoBackends, + ParentRefs: sectionNameRefs, + Valid: true, + Rules: createRules(hrWithTwoBackends, allValid, allValid), + ServiceNames: make(map[types.NamespacedName]struct{}), }, expectedBackendRefs: []BackendRef{ { - Svc: svc1, - Port: 80, - Valid: true, - Weight: 1, + SvcNsName: svc1NsName, + ServicePort: svc1.Spec.Ports[0], + Valid: true, + Weight: 1, }, { - Svc: svc1, - Port: 81, - Valid: true, - Weight: 5, + SvcNsName: svc1NsName, + ServicePort: svc1.Spec.Ports[1], + Valid: true, + Weight: 5, }, }, expectedConditions: nil, @@ -430,9 +464,10 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }, { route: &Route{ - Source: hrWithOneBackend, - ParentRefs: sectionNameRefs, - Valid: false, + Source: hrWithOneBackend, + ParentRefs: sectionNameRefs, + Valid: false, + ServiceNames: make(map[types.NamespacedName]struct{}), }, expectedBackendRefs: nil, expectedConditions: nil, @@ -440,10 +475,11 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }, { route: &Route{ - Source: hrWithOneBackend, - ParentRefs: sectionNameRefs, - Valid: true, - Rules: createRules(hrWithOneBackend, allInvalid, allValid), + Source: hrWithOneBackend, + ParentRefs: sectionNameRefs, + Valid: true, + Rules: createRules(hrWithOneBackend, allInvalid, allValid), + ServiceNames: make(map[types.NamespacedName]struct{}), }, expectedBackendRefs: nil, expectedConditions: nil, @@ -451,10 +487,11 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }, { route: &Route{ - Source: hrWithOneBackend, - ParentRefs: sectionNameRefs, - Valid: true, - Rules: createRules(hrWithOneBackend, allValid, allInvalid), + Source: hrWithOneBackend, + ParentRefs: sectionNameRefs, + Valid: true, + Rules: createRules(hrWithOneBackend, allValid, allInvalid), + ServiceNames: make(map[types.NamespacedName]struct{}), }, expectedBackendRefs: nil, expectedConditions: nil, @@ -462,10 +499,11 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }, { route: &Route{ - Source: hrWithInvalidRule, - ParentRefs: sectionNameRefs, - Valid: true, - Rules: createRules(hrWithInvalidRule, allValid, allValid), + Source: hrWithInvalidRule, + ParentRefs: sectionNameRefs, + Valid: true, + Rules: createRules(hrWithInvalidRule, allValid, allValid), + ServiceNames: make(map[types.NamespacedName]struct{}), }, expectedBackendRefs: []BackendRef{ { @@ -481,10 +519,11 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }, { route: &Route{ - Source: hrWithZeroBackendRefs, - ParentRefs: sectionNameRefs, - Valid: true, - Rules: createRules(hrWithZeroBackendRefs, allValid, allValid), + Source: hrWithZeroBackendRefs, + ParentRefs: sectionNameRefs, + Valid: true, + Rules: createRules(hrWithZeroBackendRefs, allValid, allValid), + ServiceNames: make(map[types.NamespacedName]struct{}), }, expectedBackendRefs: nil, expectedConditions: nil, @@ -510,7 +549,23 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { } func TestCreateBackend(t *testing.T) { - svc1 := &v1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "service1"}} + svc1 := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "service1", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + { + Port: 80, + }, + }, + }, + } + svc1NamespacedName := types.NamespacedName{ + Namespace: "test", + Name: "service1", + } tests := []struct { expectedCondition *conditions.Condition @@ -524,10 +579,10 @@ func TestCreateBackend(t *testing.T) { BackendRef: getNormalRef(), }, expectedBackend: BackendRef{ - Svc: svc1, - Port: 80, - Weight: 5, - Valid: true, + SvcNsName: svc1NamespacedName, + ServicePort: svc1.Spec.Ports[0], + Weight: 5, + Valid: true, }, expectedServicePortReference: "test_service1_80", expectedCondition: nil, @@ -541,10 +596,10 @@ func TestCreateBackend(t *testing.T) { }), }, expectedBackend: BackendRef{ - Svc: svc1, - Port: 80, - Weight: 1, - Valid: true, + SvcNsName: svc1NamespacedName, + ServicePort: svc1.Spec.Ports[0], + Weight: 1, + Valid: true, }, expectedServicePortReference: "test_service1_80", expectedCondition: nil, @@ -558,10 +613,10 @@ func TestCreateBackend(t *testing.T) { }), }, expectedBackend: BackendRef{ - Svc: nil, - Port: 0, - Weight: 0, - Valid: false, + SvcNsName: types.NamespacedName{}, + ServicePort: v1.ServicePort{}, + Weight: 0, + Valid: false, }, expectedServicePortReference: "", expectedCondition: helpers.GetPointer( @@ -579,10 +634,10 @@ func TestCreateBackend(t *testing.T) { }), }, expectedBackend: BackendRef{ - Svc: nil, - Port: 0, - Weight: 5, - Valid: false, + SvcNsName: types.NamespacedName{}, + ServicePort: v1.ServicePort{}, + Weight: 5, + Valid: false, }, expectedServicePortReference: "", expectedCondition: helpers.GetPointer( @@ -600,10 +655,10 @@ func TestCreateBackend(t *testing.T) { }), }, expectedBackend: BackendRef{ - Svc: nil, - Port: 0, - Weight: 5, - Valid: false, + SvcNsName: types.NamespacedName{Name: "not-exist", Namespace: "test"}, + ServicePort: v1.ServicePort{}, + Weight: 5, + Valid: false, }, expectedServicePortReference: "", expectedCondition: helpers.GetPointer( @@ -635,3 +690,34 @@ func TestCreateBackend(t *testing.T) { }) } } + +func TestGetServicePort(t *testing.T) { + svc := &v1.Service{ + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + { + Port: 80, + }, + { + Port: 81, + }, + { + Port: 82, + }, + }, + }, + } + + g := NewWithT(t) + // ports exist + for _, p := range []int32{80, 81, 82} { + port, err := getServicePort(svc, p) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(port.Port).To(Equal(p)) + } + + // port doesn't exist + port, err := getServicePort(svc, 83) + g.Expect(err).Should(HaveOccurred()) + g.Expect(port.Port).To(Equal(int32(0))) +} diff --git a/internal/mode/static/state/graph/graph.go b/internal/mode/static/state/graph/graph.go index f65f79dd7f..3a1aa30b3e 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -127,7 +127,7 @@ func BuildGraph( referencedNamespaces := buildReferencedNamespaces(state.Namespaces, gw) - referencedServicesNames := buildReferencedServicesNames(state.HTTPRoutes) + referencedServicesNames := buildReferencedServicesNames(routes) g := &Graph{ GatewayClass: gc, diff --git a/internal/mode/static/state/graph/graph_test.go b/internal/mode/static/state/graph/graph_test.go index 45491f20e1..fcdcddb08f 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -90,23 +90,21 @@ func TestBuildGraph(t *testing.T) { hr2 := createRoute("hr-2", "wrong-gateway", "listener-80-1") hr3 := createRoute("hr-3", "gateway-1", "listener-443-1") // https listener; should not conflict with hr1 - fooSvc := &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "service"}} - hr1Refs := []BackendRef{ { - Svc: fooSvc, - Port: 80, - Valid: true, - Weight: 1, + SvcNsName: types.NamespacedName{Namespace: "service", Name: "foo"}, + ServicePort: v1.ServicePort{Port: 80}, + Valid: true, + Weight: 1, }, } hr3Refs := []BackendRef{ { - Svc: fooSvc, - Port: 80, - Valid: true, - Weight: 1, + SvcNsName: types.NamespacedName{Namespace: "service", Name: "foo"}, + ServicePort: v1.ServicePort{Port: 80}, + Valid: true, + Weight: 1, }, } @@ -181,7 +179,18 @@ func TestBuildGraph(t *testing.T) { gw1 := createGateway("gateway-1") gw2 := createGateway("gateway-2") - svc := &v1.Service{ObjectMeta: metav1.ObjectMeta{Namespace: "service", Name: "foo"}} + svc := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "service", Name: "foo", + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + { + Port: 80, + }, + }, + }, + } rgSecret := &v1beta1.ReferenceGrant{ ObjectMeta: metav1.ObjectMeta{ @@ -270,6 +279,9 @@ func TestBuildGraph(t *testing.T) { }, }, Rules: []Rule{createValidRuleWithBackendRefs(hr1Refs)}, + ServiceNames: map[types.NamespacedName]struct{}{ + {Namespace: "service", Name: "foo"}: {}, + }, } routeHR3 := &Route{ @@ -287,6 +299,9 @@ func TestBuildGraph(t *testing.T) { }, }, Rules: []Rule{createValidRuleWithBackendRefs(hr3Refs)}, + ServiceNames: map[types.NamespacedName]struct{}{ + {Namespace: "service", Name: "foo"}: {}, + }, } createExpectedGraphWithGatewayClass := func(gc *gatewayv1.GatewayClass) *Graph { diff --git a/internal/mode/static/state/graph/httproute.go b/internal/mode/static/state/graph/httproute.go index c8f42997e2..63505c65ff 100644 --- a/internal/mode/static/state/graph/httproute.go +++ b/internal/mode/static/state/graph/httproute.go @@ -58,6 +58,8 @@ type ParentRefAttachmentStatus struct { type Route struct { // Source is the source resource of the Route. Source *v1.HTTPRoute + // ServiceNames include the NamespacedName of all the Services that are referenced by the HTTPRoute + ServiceNames map[types.NamespacedName]struct{} // ParentRefs includes ParentRefs with NGF Gateways only. ParentRefs []ParentRef // Conditions include Conditions for the HTTPRoute. diff --git a/internal/mode/static/state/graph/service.go b/internal/mode/static/state/graph/service.go index d8dbe336f8..737bcbf467 100644 --- a/internal/mode/static/state/graph/service.go +++ b/internal/mode/static/state/graph/service.go @@ -2,17 +2,16 @@ package graph import ( "k8s.io/apimachinery/pkg/types" - gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" ) func buildReferencedServicesNames( - clusterHTTPRoutes map[types.NamespacedName]*gatewayv1.HTTPRoute, + routes map[types.NamespacedName]*Route, ) map[types.NamespacedName]struct{} { svcNames := make(map[types.NamespacedName]struct{}) // Get all the service names referenced from all the HTTPRoutes - for _, hr := range clusterHTTPRoutes { - for k, v := range getBackendServiceNamesFromRoute(hr) { + for _, route := range routes { + for k, v := range route.ServiceNames { svcNames[k] = v } } @@ -23,23 +22,23 @@ func buildReferencedServicesNames( return svcNames } -func getBackendServiceNamesFromRoute(hr *gatewayv1.HTTPRoute) map[types.NamespacedName]struct{} { - svcNames := make(map[types.NamespacedName]struct{}) - - for _, rule := range hr.Spec.Rules { - for _, ref := range rule.BackendRefs { - if ref.Kind != nil && *ref.Kind != "Service" { - continue - } - - ns := hr.Namespace - if ref.Namespace != nil { - ns = string(*ref.Namespace) - } - - svcNames[types.NamespacedName{Namespace: ns, Name: string(ref.Name)}] = struct{}{} - } - } - - return svcNames -} +//func getBackendServiceNamesFromRoute(hr *gatewayv1.HTTPRoute) map[types.NamespacedName]struct{} { +// svcNames := make(map[types.NamespacedName]struct{}) +// +// for _, rule := range hr.Spec.Rules { +// for _, ref := range rule.BackendRefs { +// if ref.Kind != nil && *ref.Kind != "Service" { +// continue +// } +// +// ns := hr.Namespace +// if ref.Namespace != nil { +// ns = string(*ref.Namespace) +// } +// +// svcNames[types.NamespacedName{Namespace: ns, Name: string(ref.Name)}] = struct{}{} +// } +// } +// +// return svcNames +//} diff --git a/internal/mode/static/state/resolver/resolver.go b/internal/mode/static/state/resolver/resolver.go index 2dab45a392..9869f21182 100644 --- a/internal/mode/static/state/resolver/resolver.go +++ b/internal/mode/static/state/resolver/resolver.go @@ -7,6 +7,7 @@ import ( v1 "k8s.io/api/core/v1" discoveryV1 "k8s.io/api/discovery/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -15,10 +16,10 @@ import ( //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . ServiceResolver -// ServiceResolver resolves a Service and Service Port to a list of Endpoints. +// ServiceResolver resolves a Service's NamespacedName and ServicePort to a list of Endpoints. // Returns an error if the Service or Service Port cannot be resolved. type ServiceResolver interface { - Resolve(ctx context.Context, svc *v1.Service, svcPort int32) ([]Endpoint, error) + Resolve(ctx context.Context, svcNsName types.NamespacedName, svcPort v1.ServicePort) ([]Endpoint, error) } // Endpoint is the internal representation of a Kubernetes endpoint. @@ -39,10 +40,15 @@ func NewServiceResolverImpl(client client.Client) *ServiceResolverImpl { return &ServiceResolverImpl{client: client} } -// Resolve resolves a Service and Port to a list of Endpoints. +// Resolve resolves a Service and BackendRef Port to a list of Endpoints. // Returns an error if the Service or Port cannot be resolved. -func (e *ServiceResolverImpl) Resolve(ctx context.Context, svc *v1.Service, port int32) ([]Endpoint, error) { - if svc == nil { +// +// svcNsName is guaranteed to be a valid NamespacedName from when it is called in configuration.go. +// svcPort is guaranteed to be a valid non-empty ServicePort from when it is called in configuration.go +func (e *ServiceResolverImpl) Resolve(ctx context.Context, + svcNsName types.NamespacedName, svcPort v1.ServicePort, +) ([]Endpoint, error) { + if svcPort.Port == 0 || svcNsName.Name == "" || svcNsName.Namespace == "" { return nil, errors.New("cannot resolve a nil Service") } @@ -52,15 +58,15 @@ func (e *ServiceResolverImpl) Resolve(ctx context.Context, svc *v1.Service, port err := e.client.List( ctx, &endpointSliceList, - client.MatchingFields{index.KubernetesServiceNameIndexField: svc.Name}, - client.InNamespace(svc.Namespace), + client.MatchingFields{index.KubernetesServiceNameIndexField: svcNsName.Name}, + client.InNamespace(svcNsName.Namespace), ) if err != nil || len(endpointSliceList.Items) == 0 { - return nil, fmt.Errorf("no endpoints found for Service %s", client.ObjectKeyFromObject(svc)) + return nil, fmt.Errorf("no endpoints found for Service %s", svcNsName) } - return resolveEndpoints(svc, port, endpointSliceList, initEndpointSetWithCalculatedSize) + return resolveEndpoints(svcNsName, svcPort, endpointSliceList, initEndpointSetWithCalculatedSize) } type initEndpointSetFunc func([]discoveryV1.EndpointSlice) map[Endpoint]struct{} @@ -88,20 +94,14 @@ func calculateReadyEndpoints(endpointSlices []discoveryV1.EndpointSlice) int { } func resolveEndpoints( - svc *v1.Service, - port int32, + svcNsName types.NamespacedName, + svcPort v1.ServicePort, endpointSliceList discoveryV1.EndpointSliceList, initEndpointsSet initEndpointSetFunc, ) ([]Endpoint, error) { - svcPort, err := getServicePort(svc, port) - if err != nil { - return nil, err - } - filteredSlices := filterEndpointSliceList(endpointSliceList, svcPort) if len(filteredSlices) == 0 { - svcNsName := client.ObjectKeyFromObject(svc) return nil, fmt.Errorf("no valid endpoints found for Service %s and port %+v", svcNsName, svcPort) } @@ -135,16 +135,6 @@ func resolveEndpoints( return endpoints, nil } -func getServicePort(svc *v1.Service, port int32) (v1.ServicePort, error) { - for _, p := range svc.Spec.Ports { - if p.Port == port { - return p, nil - } - } - - return v1.ServicePort{}, fmt.Errorf("no matching port for Service %s and port %d", svc.Name, port) -} - // getDefaultPort returns the default port for a ServicePort. // This default port is used when the EndpointPort has a nil port which indicates all ports are valid. // If the ServicePort has a non-zero integer TargetPort, the TargetPort integer value is returned. diff --git a/internal/mode/static/state/resolver/resolver_test.go b/internal/mode/static/state/resolver/resolver_test.go index 411ca190e9..69c43e0240 100644 --- a/internal/mode/static/state/resolver/resolver_test.go +++ b/internal/mode/static/state/resolver/resolver_test.go @@ -7,6 +7,7 @@ import ( . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" discoveryV1 "k8s.io/api/discovery/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" @@ -105,37 +106,6 @@ func TestFilterEndpointSliceList(t *testing.T) { g.Expect(filteredSliceList).To(Equal(expFilteredList)) } -func TestGetServicePort(t *testing.T) { - svc := &v1.Service{ - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - { - Port: 80, - }, - { - Port: 81, - }, - { - Port: 82, - }, - }, - }, - } - - g := NewWithT(t) - // ports exist - for _, p := range []int32{80, 81, 82} { - port, err := getServicePort(svc, p) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(port.Port).To(Equal(p)) - } - - // port doesn't exist - port, err := getServicePort(svc, 83) - g.Expect(err).Should(HaveOccurred()) - g.Expect(port.Port).To(Equal(int32(0))) -} - func TestGetDefaultPort(t *testing.T) { testcases := []struct { msg string @@ -546,14 +516,9 @@ func BenchmarkResolve(b *testing.B) { 1000, } - svc := &v1.Service{ - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - { - Port: 80, - }, - }, - }, + svcNsName := types.NamespacedName{ + Namespace: "default", + Name: "default-name", } initEndpointSet := func([]discoveryV1.EndpointSlice) map[Endpoint]struct{} { @@ -564,17 +529,19 @@ func BenchmarkResolve(b *testing.B) { list := generateEndpointSliceList(count) b.Run(fmt.Sprintf("%d endpoints", count), func(b *testing.B) { - bench(b, svc, list, initEndpointSet, count) + bench(b, svcNsName, list, initEndpointSet, count) }) b.Run(fmt.Sprintf("%d endpoints with optimization", count), func(b *testing.B) { - bench(b, svc, list, initEndpointSetWithCalculatedSize, count) + bench(b, svcNsName, list, initEndpointSetWithCalculatedSize, count) }) } } -func bench(b *testing.B, svc *v1.Service, list discoveryV1.EndpointSliceList, initSet initEndpointSetFunc, n int) { +func bench(b *testing.B, svcNsName types.NamespacedName, + list discoveryV1.EndpointSliceList, initSet initEndpointSetFunc, n int, +) { for i := 0; i < b.N; i++ { - res, err := resolveEndpoints(svc, 80, list, initSet) + res, err := resolveEndpoints(svcNsName, v1.ServicePort{Port: 80}, list, initSet) if len(res) != n { b.Fatalf("expected %d endpoints, got %d", n, len(res)) } diff --git a/internal/mode/static/state/resolver/resolverfakes/fake_service_resolver.go b/internal/mode/static/state/resolver/resolverfakes/fake_service_resolver.go index c1116462fb..8fa8a9fcdc 100644 --- a/internal/mode/static/state/resolver/resolverfakes/fake_service_resolver.go +++ b/internal/mode/static/state/resolver/resolverfakes/fake_service_resolver.go @@ -7,15 +7,16 @@ import ( "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/resolver" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" ) type FakeServiceResolver struct { - ResolveStub func(context.Context, *v1.Service, int32) ([]resolver.Endpoint, error) + ResolveStub func(context.Context, types.NamespacedName, v1.ServicePort) ([]resolver.Endpoint, error) resolveMutex sync.RWMutex resolveArgsForCall []struct { arg1 context.Context - arg2 *v1.Service - arg3 int32 + arg2 types.NamespacedName + arg3 v1.ServicePort } resolveReturns struct { result1 []resolver.Endpoint @@ -29,13 +30,13 @@ type FakeServiceResolver struct { invocationsMutex sync.RWMutex } -func (fake *FakeServiceResolver) Resolve(arg1 context.Context, arg2 *v1.Service, arg3 int32) ([]resolver.Endpoint, error) { +func (fake *FakeServiceResolver) Resolve(arg1 context.Context, arg2 types.NamespacedName, arg3 v1.ServicePort) ([]resolver.Endpoint, error) { fake.resolveMutex.Lock() ret, specificReturn := fake.resolveReturnsOnCall[len(fake.resolveArgsForCall)] fake.resolveArgsForCall = append(fake.resolveArgsForCall, struct { arg1 context.Context - arg2 *v1.Service - arg3 int32 + arg2 types.NamespacedName + arg3 v1.ServicePort }{arg1, arg2, arg3}) stub := fake.ResolveStub fakeReturns := fake.resolveReturns @@ -56,13 +57,13 @@ func (fake *FakeServiceResolver) ResolveCallCount() int { return len(fake.resolveArgsForCall) } -func (fake *FakeServiceResolver) ResolveCalls(stub func(context.Context, *v1.Service, int32) ([]resolver.Endpoint, error)) { +func (fake *FakeServiceResolver) ResolveCalls(stub func(context.Context, types.NamespacedName, v1.ServicePort) ([]resolver.Endpoint, error)) { fake.resolveMutex.Lock() defer fake.resolveMutex.Unlock() fake.ResolveStub = stub } -func (fake *FakeServiceResolver) ResolveArgsForCall(i int) (context.Context, *v1.Service, int32) { +func (fake *FakeServiceResolver) ResolveArgsForCall(i int) (context.Context, types.NamespacedName, v1.ServicePort) { fake.resolveMutex.RLock() defer fake.resolveMutex.RUnlock() argsForCall := fake.resolveArgsForCall[i] diff --git a/internal/mode/static/state/resolver/service_resolver_test.go b/internal/mode/static/state/resolver/service_resolver_test.go index 412eb72fc1..c62cac2e26 100644 --- a/internal/mode/static/state/resolver/service_resolver_test.go +++ b/internal/mode/static/state/resolver/service_resolver_test.go @@ -9,6 +9,7 @@ import ( discoveryV1 "k8s.io/api/discovery/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -125,6 +126,11 @@ var _ = Describe("ServiceResolver", func() { }, } + svcNsName = types.NamespacedName{ + Namespace: "test", + Name: "svc", + } + slice1 = createSlice( "slice1", addresses1, @@ -212,22 +218,17 @@ var _ = Describe("ServiceResolver", func() { }, } - endpoints, err := serviceResolver.Resolve(context.TODO(), svc, 80) + endpoints, err := serviceResolver.Resolve(context.TODO(), svcNsName, svc.Spec.Ports[0]) Expect(err).ToNot(HaveOccurred()) Expect(endpoints).To(ConsistOf(expectedEndpoints)) }) - It("returns an error if port does not exist in service", func() { - endpoints, err := serviceResolver.Resolve(context.TODO(), svc, 8080) // service port does not exist - Expect(err).To(HaveOccurred()) - Expect(endpoints).To(BeNil()) - }) It("returns an error if there are no valid endpoint slices for the service and port", func() { // delete valid endpoint slices Expect(fakeK8sClient.Delete(context.TODO(), slice1)).To(Succeed()) Expect(fakeK8sClient.Delete(context.TODO(), slice2)).To(Succeed()) Expect(fakeK8sClient.Delete(context.TODO(), dupeEndpointSlice)).To(Succeed()) - endpoints, err := serviceResolver.Resolve(context.TODO(), svc, 80) + endpoints, err := serviceResolver.Resolve(context.TODO(), svcNsName, svc.Spec.Ports[0]) Expect(err).To(HaveOccurred()) Expect(endpoints).To(BeNil()) }) @@ -236,12 +237,12 @@ var _ = Describe("ServiceResolver", func() { Expect(fakeK8sClient.Delete(context.TODO(), sliceIPV6)).To(Succeed()) Expect(fakeK8sClient.Delete(context.TODO(), sliceNoMatchingPortName)).To(Succeed()) - endpoints, err := serviceResolver.Resolve(context.TODO(), svc, 80) + endpoints, err := serviceResolver.Resolve(context.TODO(), svcNsName, svc.Spec.Ports[0]) Expect(err).To(HaveOccurred()) Expect(endpoints).To(BeNil()) }) It("returns an error if the service is nil", func() { - endpoints, err := serviceResolver.Resolve(context.TODO(), nil, 80) + endpoints, err := serviceResolver.Resolve(context.TODO(), types.NamespacedName{}, svc.Spec.Ports[0]) Expect(err).To(HaveOccurred()) Expect(endpoints).To(BeNil()) }) From 06b723c0b29274df2654490d6b90315533698de5 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Tue, 9 Jan 2024 11:05:53 -0800 Subject: [PATCH 07/41] Clean up unused comments --- internal/mode/static/state/graph/backend_refs.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index 26114d4d93..8c0ed2eb37 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -14,14 +14,10 @@ import ( // BackendRef is an internal representation of a backendRef in an HTTPRoute. type BackendRef struct { - // Svc is the service referenced by the backendRef. - // Svc *v1.Service // 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. ServicePort v1.ServicePort - // Port is the port of the backendRef. - // Port int32 // Weight is the weight of the backendRef. Weight int32 // Valid indicates whether the backendRef is valid. From 3750ef8066a9538c8c54c9ade5044397bdd0db86 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Wed, 10 Jan 2024 16:38:42 -0800 Subject: [PATCH 08/41] Lowercase panic statements --- internal/mode/static/state/changed_predicate.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/mode/static/state/changed_predicate.go b/internal/mode/static/state/changed_predicate.go index 22be400d51..08d4d0af74 100644 --- a/internal/mode/static/state/changed_predicate.go +++ b/internal/mode/static/state/changed_predicate.go @@ -21,7 +21,7 @@ type funcPredicate struct { func (f funcPredicate) upsert(_, newObject client.Object) bool { if newObject == nil { - panic("New object cannot be nil") + panic("new object cannot be nil") } return f.stateChanged(newObject, types.NamespacedName{ @@ -54,7 +54,7 @@ func (a annotationChangedPredicate) upsert(oldObject, newObject client.Object) b } if newObject == nil { - panic("Cannot determine if annotation has changed on upsert because new object is nil") + panic("cannot determine if annotation has changed on upsert because new object is nil") } oldAnnotation := oldObject.GetAnnotations()[a.annotation] From 9df85c1d61fa7d8ede93a8fff22364bcfa06e538 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Wed, 10 Jan 2024 16:39:17 -0800 Subject: [PATCH 09/41] Remove TODO statement --- internal/mode/static/state/graph/backend_refs.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index 8c0ed2eb37..554aa4d5af 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -185,7 +185,6 @@ func getServiceAndPortFromRef( ns = string(*ref.Namespace) } - // TODO: Is this right that the svcNsName name is the name of the backendRef? svcNsName := types.NamespacedName{Name: string(ref.Name), Namespace: ns} // If the service is unable to be found, svcNsName will still be populated with what the BackendRef From c81b037ecf7e5c3499d17bf9c2f05f32e2f7bb2b Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Wed, 10 Jan 2024 16:40:46 -0800 Subject: [PATCH 10/41] Fix function argument spacing --- internal/mode/static/state/resolver/resolver.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/mode/static/state/resolver/resolver.go b/internal/mode/static/state/resolver/resolver.go index 9869f21182..76450836ab 100644 --- a/internal/mode/static/state/resolver/resolver.go +++ b/internal/mode/static/state/resolver/resolver.go @@ -45,8 +45,10 @@ func NewServiceResolverImpl(client client.Client) *ServiceResolverImpl { // // svcNsName is guaranteed to be a valid NamespacedName from when it is called in configuration.go. // svcPort is guaranteed to be a valid non-empty ServicePort from when it is called in configuration.go -func (e *ServiceResolverImpl) Resolve(ctx context.Context, - svcNsName types.NamespacedName, svcPort v1.ServicePort, +func (e *ServiceResolverImpl) Resolve( + ctx context.Context, + svcNsName types.NamespacedName, + svcPort v1.ServicePort, ) ([]Endpoint, error) { if svcPort.Port == 0 || svcNsName.Name == "" || svcNsName.Namespace == "" { return nil, errors.New("cannot resolve a nil Service") From 4d8f275d6143bc8da1fe8cd1b9294abd0b50c2ca Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Wed, 10 Jan 2024 16:50:04 -0800 Subject: [PATCH 11/41] Add panic statement to Resolve function --- internal/mode/static/state/resolver/resolver.go | 4 ++-- .../state/resolver/service_resolver_test.go | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/internal/mode/static/state/resolver/resolver.go b/internal/mode/static/state/resolver/resolver.go index 76450836ab..8dcc55c467 100644 --- a/internal/mode/static/state/resolver/resolver.go +++ b/internal/mode/static/state/resolver/resolver.go @@ -2,7 +2,6 @@ package resolver import ( "context" - "errors" "fmt" v1 "k8s.io/api/core/v1" @@ -51,7 +50,8 @@ func (e *ServiceResolverImpl) Resolve( svcPort v1.ServicePort, ) ([]Endpoint, error) { if svcPort.Port == 0 || svcNsName.Name == "" || svcNsName.Namespace == "" { - return nil, errors.New("cannot resolve a nil Service") + panic(fmt.Errorf("expected the following fields to be non-empty: name: %s, ns: %s, port: %d", + svcNsName.Name, svcNsName.Namespace, svcPort.Port)) } // We list EndpointSlices using the Service Name Index Field we added as an index to the EndpointSlice cache. diff --git a/internal/mode/static/state/resolver/service_resolver_test.go b/internal/mode/static/state/resolver/service_resolver_test.go index c62cac2e26..a04254d916 100644 --- a/internal/mode/static/state/resolver/service_resolver_test.go +++ b/internal/mode/static/state/resolver/service_resolver_test.go @@ -241,10 +241,17 @@ var _ = Describe("ServiceResolver", func() { Expect(err).To(HaveOccurred()) Expect(endpoints).To(BeNil()) }) - It("returns an error if the service is nil", func() { - endpoints, err := serviceResolver.Resolve(context.TODO(), types.NamespacedName{}, svc.Spec.Ports[0]) - Expect(err).To(HaveOccurred()) - Expect(endpoints).To(BeNil()) + It("panics if the service NamespacedName is empty", func() { + resolve := func() { + _, _ = serviceResolver.Resolve(context.TODO(), types.NamespacedName{}, svc.Spec.Ports[0]) + } + Expect(resolve).Should(Panic()) + }) + It("panics if the ServicePort is empty", func() { + resolve := func() { + _, _ = serviceResolver.Resolve(context.TODO(), types.NamespacedName{}, v1.ServicePort{}) + } + Expect(resolve).Should(Panic()) }) }) }) From fbe08a16cf8d8b5e41667d81a33b292e44a9f9c0 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Wed, 10 Jan 2024 16:51:44 -0800 Subject: [PATCH 12/41] Refactor to use client.ObjectKeyFromObject --- internal/mode/static/state/changed_predicate.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/mode/static/state/changed_predicate.go b/internal/mode/static/state/changed_predicate.go index 08d4d0af74..4f294a2a8c 100644 --- a/internal/mode/static/state/changed_predicate.go +++ b/internal/mode/static/state/changed_predicate.go @@ -24,10 +24,7 @@ func (f funcPredicate) upsert(_, newObject client.Object) bool { panic("new object cannot be nil") } - return f.stateChanged(newObject, types.NamespacedName{ - Namespace: newObject.GetNamespace(), - Name: newObject.GetName(), - }) + return f.stateChanged(newObject, client.ObjectKeyFromObject(newObject)) } func (f funcPredicate) delete(object client.Object, nsname types.NamespacedName) bool { From caa48f6b12267f34261db299eb34976907185e7a Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 11 Jan 2024 10:09:23 -0800 Subject: [PATCH 13/41] Change error message to only include port --- internal/mode/static/state/resolver/resolver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/mode/static/state/resolver/resolver.go b/internal/mode/static/state/resolver/resolver.go index 8dcc55c467..a4b3b9e834 100644 --- a/internal/mode/static/state/resolver/resolver.go +++ b/internal/mode/static/state/resolver/resolver.go @@ -104,7 +104,7 @@ func resolveEndpoints( filteredSlices := filterEndpointSliceList(endpointSliceList, svcPort) if len(filteredSlices) == 0 { - return nil, fmt.Errorf("no valid endpoints found for Service %s and port %+v", svcNsName, svcPort) + return nil, fmt.Errorf("no valid endpoints found for Service %s and port %d", svcNsName, svcPort.Port) } // Endpoints may be duplicated across multiple EndpointSlices. From 1eec574bc52731434e6f030f80b270107c1b418a Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 11 Jan 2024 11:50:05 -0800 Subject: [PATCH 14/41] Remove ServiceNames from Route and move logic to service.go --- .../static/state/change_processor_test.go | 9 --- .../mode/static/state/graph/backend_refs.go | 26 -------- .../static/state/graph/backend_refs_test.go | 61 ++++++++----------- .../mode/static/state/graph/graph_test.go | 6 -- internal/mode/static/state/graph/httproute.go | 2 - internal/mode/static/state/graph/service.go | 58 ++++++++++-------- 6 files changed, 61 insertions(+), 101 deletions(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 463432f9b4..4c4056f694 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -444,7 +444,6 @@ var _ = Describe("ChangeProcessor", func() { "spec.rules[0].backendRefs[0].name: Not found: \"service\"", ), }, - ServiceNames: map[types.NamespacedName]struct{}{{Namespace: "service-ns", Name: "service"}: {}}, } expRouteHR2 = &graph.Route{ @@ -582,7 +581,6 @@ var _ = Describe("ChangeProcessor", func() { expGraph.ReferencedSecrets = nil expGraph.ReferencedServicesNames = nil - expRouteHR1.ServiceNames = nil expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} changed, graphCfg := processor.Process() @@ -636,7 +634,6 @@ var _ = Describe("ChangeProcessor", func() { expGraph.ReferencedSecrets = nil expGraph.ReferencedServicesNames = nil - expRouteHR1.ServiceNames = nil expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} changed, graphCfg := processor.Process() @@ -659,7 +656,6 @@ var _ = Describe("ChangeProcessor", func() { } expGraph.ReferencedServicesNames = nil - expRouteHR1.ServiceNames = nil expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} changed, graphCfg := processor.Process() @@ -868,7 +864,6 @@ var _ = Describe("ChangeProcessor", func() { Source: sameNsTLSSecret, } - expRouteHR1.ServiceNames = nil expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} expGraph.ReferencedServicesNames = nil @@ -901,7 +896,6 @@ var _ = Describe("ChangeProcessor", func() { Source: sameNsTLSSecret, } - expRouteHR1.ServiceNames = nil expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} expGraph.ReferencedServicesNames = nil @@ -925,7 +919,6 @@ var _ = Describe("ChangeProcessor", func() { expGraph.Routes = map[types.NamespacedName]*graph.Route{} expGraph.ReferencedSecrets = nil - expRouteHR1.ServiceNames = nil expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} expGraph.ReferencedServicesNames = nil @@ -941,7 +934,6 @@ var _ = Describe("ChangeProcessor", func() { types.NamespacedName{Namespace: "test", Name: "gateway-2"}, ) - expRouteHR1.ServiceNames = nil expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} expGraph.ReferencedServicesNames = nil @@ -957,7 +949,6 @@ var _ = Describe("ChangeProcessor", func() { types.NamespacedName{Namespace: "test", Name: "hr-1"}, ) - expRouteHR1.ServiceNames = nil expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} expGraph.ReferencedServicesNames = nil diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index 554aa4d5af..c9f825f623 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -82,38 +82,12 @@ func addBackendRefsToRules( if cond != nil { route.Conditions = append(route.Conditions, *cond) } - - // This should fill ServiceNames with the NamespacedName of all the Services which - // come from a Valid route and rules. - // All the routes here have populated ParentRef's as it is checked when the Route was built. - // - // ref.SvcNsName could be an empty SvcNsName if validateHTTPBackendRef returned invalid. - // Otherwise, it should be populated with the Service NsName on the backendRef even if - // the Service does not exist. - // - // ref.ServicePort can be empty and that is fine as we still want to populate route.ServiceNames - // if a Service does not exist. - if ref.SvcNsName.Name != "" && ref.SvcNsName.Namespace != "" { - if route.ServiceNames == nil { - route.ServiceNames = make(map[types.NamespacedName]struct{}) - } - - route.ServiceNames[ref.SvcNsName] = struct{}{} - } } // Some of the backendRef's could be invalid, but when we use them in configuration.go when building the // Upstreams, we skip over the ones that are not valid. route.Rules[idx].BackendRefs = backendRefs } - // If any of the ParentRefs of the route have Attachment.Attached set to true, we want to generate NGINX - // configuration and thus can return the method with route.ServiceNames populated. Else, we set ServiceNames to nil. - for _, ref := range route.ParentRefs { - if ref.Attachment.Attached { - return - } - } - route.ServiceNames = nil } func createBackendRef( diff --git a/internal/mode/static/state/graph/backend_refs_test.go b/internal/mode/static/state/graph/backend_refs_test.go index 1aebaac3b5..7681625841 100644 --- a/internal/mode/static/state/graph/backend_refs_test.go +++ b/internal/mode/static/state/graph/backend_refs_test.go @@ -420,11 +420,10 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }{ { route: &Route{ - Source: hrWithOneBackend, - ParentRefs: sectionNameRefs, - Valid: true, - Rules: createRules(hrWithOneBackend, allValid, allValid), - ServiceNames: make(map[types.NamespacedName]struct{}), + Source: hrWithOneBackend, + ParentRefs: sectionNameRefs, + Valid: true, + Rules: createRules(hrWithOneBackend, allValid, allValid), }, expectedBackendRefs: []BackendRef{ { @@ -439,11 +438,10 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }, { route: &Route{ - Source: hrWithTwoBackends, - ParentRefs: sectionNameRefs, - Valid: true, - Rules: createRules(hrWithTwoBackends, allValid, allValid), - ServiceNames: make(map[types.NamespacedName]struct{}), + Source: hrWithTwoBackends, + ParentRefs: sectionNameRefs, + Valid: true, + Rules: createRules(hrWithTwoBackends, allValid, allValid), }, expectedBackendRefs: []BackendRef{ { @@ -464,10 +462,9 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }, { route: &Route{ - Source: hrWithOneBackend, - ParentRefs: sectionNameRefs, - Valid: false, - ServiceNames: make(map[types.NamespacedName]struct{}), + Source: hrWithOneBackend, + ParentRefs: sectionNameRefs, + Valid: false, }, expectedBackendRefs: nil, expectedConditions: nil, @@ -475,11 +472,10 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }, { route: &Route{ - Source: hrWithOneBackend, - ParentRefs: sectionNameRefs, - Valid: true, - Rules: createRules(hrWithOneBackend, allInvalid, allValid), - ServiceNames: make(map[types.NamespacedName]struct{}), + Source: hrWithOneBackend, + ParentRefs: sectionNameRefs, + Valid: true, + Rules: createRules(hrWithOneBackend, allInvalid, allValid), }, expectedBackendRefs: nil, expectedConditions: nil, @@ -487,11 +483,10 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }, { route: &Route{ - Source: hrWithOneBackend, - ParentRefs: sectionNameRefs, - Valid: true, - Rules: createRules(hrWithOneBackend, allValid, allInvalid), - ServiceNames: make(map[types.NamespacedName]struct{}), + Source: hrWithOneBackend, + ParentRefs: sectionNameRefs, + Valid: true, + Rules: createRules(hrWithOneBackend, allValid, allInvalid), }, expectedBackendRefs: nil, expectedConditions: nil, @@ -499,11 +494,10 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }, { route: &Route{ - Source: hrWithInvalidRule, - ParentRefs: sectionNameRefs, - Valid: true, - Rules: createRules(hrWithInvalidRule, allValid, allValid), - ServiceNames: make(map[types.NamespacedName]struct{}), + Source: hrWithInvalidRule, + ParentRefs: sectionNameRefs, + Valid: true, + Rules: createRules(hrWithInvalidRule, allValid, allValid), }, expectedBackendRefs: []BackendRef{ { @@ -519,11 +513,10 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { }, { route: &Route{ - Source: hrWithZeroBackendRefs, - ParentRefs: sectionNameRefs, - Valid: true, - Rules: createRules(hrWithZeroBackendRefs, allValid, allValid), - ServiceNames: make(map[types.NamespacedName]struct{}), + Source: hrWithZeroBackendRefs, + ParentRefs: sectionNameRefs, + Valid: true, + Rules: createRules(hrWithZeroBackendRefs, allValid, allValid), }, expectedBackendRefs: nil, expectedConditions: nil, diff --git a/internal/mode/static/state/graph/graph_test.go b/internal/mode/static/state/graph/graph_test.go index fcdcddb08f..5ba1147b6c 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -279,9 +279,6 @@ func TestBuildGraph(t *testing.T) { }, }, Rules: []Rule{createValidRuleWithBackendRefs(hr1Refs)}, - ServiceNames: map[types.NamespacedName]struct{}{ - {Namespace: "service", Name: "foo"}: {}, - }, } routeHR3 := &Route{ @@ -299,9 +296,6 @@ func TestBuildGraph(t *testing.T) { }, }, Rules: []Rule{createValidRuleWithBackendRefs(hr3Refs)}, - ServiceNames: map[types.NamespacedName]struct{}{ - {Namespace: "service", Name: "foo"}: {}, - }, } createExpectedGraphWithGatewayClass := func(gc *gatewayv1.GatewayClass) *Graph { diff --git a/internal/mode/static/state/graph/httproute.go b/internal/mode/static/state/graph/httproute.go index 63505c65ff..c8f42997e2 100644 --- a/internal/mode/static/state/graph/httproute.go +++ b/internal/mode/static/state/graph/httproute.go @@ -58,8 +58,6 @@ type ParentRefAttachmentStatus struct { type Route struct { // Source is the source resource of the Route. Source *v1.HTTPRoute - // ServiceNames include the NamespacedName of all the Services that are referenced by the HTTPRoute - ServiceNames map[types.NamespacedName]struct{} // ParentRefs includes ParentRefs with NGF Gateways only. ParentRefs []ParentRef // Conditions include Conditions for the HTTPRoute. diff --git a/internal/mode/static/state/graph/service.go b/internal/mode/static/state/graph/service.go index 737bcbf467..5342a8dc4e 100644 --- a/internal/mode/static/state/graph/service.go +++ b/internal/mode/static/state/graph/service.go @@ -4,15 +4,46 @@ import ( "k8s.io/apimachinery/pkg/types" ) +// routes all have populated ParentRefs from when they were created func buildReferencedServicesNames( routes map[types.NamespacedName]*Route, ) map[types.NamespacedName]struct{} { svcNames := make(map[types.NamespacedName]struct{}) - // Get all the service names referenced from all the HTTPRoutes + // Get all the service names referenced from all the HTTPRoutes. for _, route := range routes { - for k, v := range route.ServiceNames { - svcNames[k] = v + // If none of the ParentRefs are attached to the Gateway, we want to skip the route. + attached := false + for _, ref := range route.ParentRefs { + if ref.Attachment.Attached { + attached = true + break + } + } + if !attached { + continue + } + + if !route.Valid { + continue + } + + for idx := range route.Rules { + // Do I still need to do these checks? As the check is made in backend_ref, basically + // if the route rules are not valid, it will not populate route.Rules[idx].BackendRefs, thus + // if we just do the for loop over the route.Rules[idx].BackendRefs = backendRefs, we should be fine? + if !route.Rules[idx].ValidMatches { + continue + } + if !route.Rules[idx].ValidFilters { + continue + } + + for _, ref := range route.Rules[idx].BackendRefs { + if ref.SvcNsName.Name != "" && ref.SvcNsName.Namespace != "" { + svcNames[ref.SvcNsName] = struct{}{} + } + } } } @@ -21,24 +52,3 @@ func buildReferencedServicesNames( } return svcNames } - -//func getBackendServiceNamesFromRoute(hr *gatewayv1.HTTPRoute) map[types.NamespacedName]struct{} { -// svcNames := make(map[types.NamespacedName]struct{}) -// -// for _, rule := range hr.Spec.Rules { -// for _, ref := range rule.BackendRefs { -// if ref.Kind != nil && *ref.Kind != "Service" { -// continue -// } -// -// ns := hr.Namespace -// if ref.Namespace != nil { -// ns = string(*ref.Namespace) -// } -// -// svcNames[types.NamespacedName{Namespace: ns, Name: string(ref.Name)}] = struct{}{} -// } -// } -// -// return svcNames -//} From 5109dcfb6f151f9b509b851eae379a9915461dbf Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 11 Jan 2024 14:38:57 -0800 Subject: [PATCH 15/41] Remove capturer --- internal/mode/static/manager.go | 8 +- .../mode/static/state/change_processor.go | 4 - .../static/state/change_processor_test.go | 919 +++++++++--------- .../static/state/relationship/capturer.go | 157 --- .../state/relationship/capturer_suite_test.go | 13 - .../state/relationship/capturer_test.go | 336 ------- .../relationshipfakes/fake_capturer.go | 195 ---- .../state/relationship/relationships_test.go | 164 ---- internal/mode/static/state/store.go | 16 +- 9 files changed, 453 insertions(+), 1359 deletions(-) delete mode 100644 internal/mode/static/state/relationship/capturer.go delete mode 100644 internal/mode/static/state/relationship/capturer_suite_test.go delete mode 100644 internal/mode/static/state/relationship/capturer_test.go delete mode 100644 internal/mode/static/state/relationship/relationshipfakes/fake_capturer.go delete mode 100644 internal/mode/static/state/relationship/relationships_test.go diff --git a/internal/mode/static/manager.go b/internal/mode/static/manager.go index 84567cebd2..46272570c5 100644 --- a/internal/mode/static/manager.go +++ b/internal/mode/static/manager.go @@ -45,7 +45,6 @@ import ( "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/file" ngxruntime "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/runtime" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state" - "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/relationship" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/resolver" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/telemetry" @@ -138,10 +137,9 @@ func StartManager(cfg config.Config) error { } processor := state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: cfg.GatewayCtlrName, - GatewayClassName: cfg.GatewayClassName, - RelationshipCapturer: relationship.NewCapturerImpl(), - Logger: cfg.Logger.WithName("changeProcessor"), + GatewayCtlrName: cfg.GatewayCtlrName, + GatewayClassName: cfg.GatewayClassName, + Logger: cfg.Logger.WithName("changeProcessor"), Validators: validation.Validators{ HTTPFieldsValidator: ngxvalidation.HTTPValidator{}, }, diff --git a/internal/mode/static/state/change_processor.go b/internal/mode/static/state/change_processor.go index e4c0c34442..9f8bfcfdaf 100644 --- a/internal/mode/static/state/change_processor.go +++ b/internal/mode/static/state/change_processor.go @@ -22,7 +22,6 @@ import ( "github.com/nginxinc/nginx-gateway-fabric/internal/framework/gatewayclass" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/graph" - "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/relationship" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation" ) @@ -55,8 +54,6 @@ type ChangeProcessor interface { // ChangeProcessorConfig holds configuration parameters for ChangeProcessorImpl. type ChangeProcessorConfig struct { - // RelationshipCapturer captures relationships between Kubernetes API resources and Gateway API resources. - RelationshipCapturer relationship.Capturer // Validators validate resources according to data-plane specific rules. Validators validation.Validators // EventRecorder records events for Kubernetes resources. @@ -119,7 +116,6 @@ func NewChangeProcessorImpl(cfg ChangeProcessorConfig) *ChangeProcessorImpl { } trackingUpdater := newChangeTrackingUpdater( - cfg.RelationshipCapturer, extractGVK, []changeTrackingUpdaterObjectTypeCfg{ { diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 4c4056f694..0bba5a53df 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -24,8 +24,6 @@ import ( "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state" staticConds "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/conditions" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/graph" - "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/relationship" - "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/relationship/relationshipfakes" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation/validationfakes" ) @@ -271,12 +269,11 @@ var _ = Describe("ChangeProcessor", func() { BeforeEach(OncePerOrdered, func() { processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - RelationshipCapturer: relationship.NewCapturerImpl(), - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - Scheme: createScheme(), + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + Scheme: createScheme(), }) }) @@ -1345,12 +1342,11 @@ var _ = Describe("ChangeProcessor", func() { }, } processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - RelationshipCapturer: relationship.NewCapturerImpl(), - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - Scheme: createScheme(), + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + Scheme: createScheme(), }) processor.CaptureUpsertChange(gc) processor.CaptureUpsertChange(gw) @@ -1461,27 +1457,23 @@ var _ = Describe("ChangeProcessor", func() { var ( processor *state.ChangeProcessorImpl - fakeRelationshipCapturer *relationshipfakes.FakeCapturer gcNsName, gwNsName, hrNsName, hr2NsName, rgNsName types.NamespacedName - svcNsName, sliceNsName, secretNsName types.NamespacedName - gc, gcUpdated *v1.GatewayClass - gw1, gw1Updated, gw2 *v1.Gateway - hr1, hr1Updated, hr2 *v1.HTTPRoute - rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant - svc *apiv1.Service - slice *discoveryV1.EndpointSlice - secret *apiv1.Secret + // svcNsName, sliceNsName, secretNsName types.NamespacedName + gc, gcUpdated *v1.GatewayClass + gw1, gw1Updated, gw2 *v1.Gateway + hr1, hr1Updated, hr2 *v1.HTTPRoute + rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant + // svc *apiv1.Service + // slice *discoveryV1.EndpointSlice + // secret *apiv1.Secret ) BeforeEach(OncePerOrdered, func() { - fakeRelationshipCapturer = &relationshipfakes.FakeCapturer{} - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: "test.controller", - GatewayClassName: "my-class", - RelationshipCapturer: fakeRelationshipCapturer, - Validators: createAlwaysValidValidators(), - Scheme: createScheme(), + GatewayCtlrName: "test.controller", + GatewayClassName: "my-class", + Validators: createAlwaysValidValidators(), + Scheme: createScheme(), }) gcNsName = types.NamespacedName{Name: "my-class"} @@ -1530,23 +1522,23 @@ var _ = Describe("ChangeProcessor", func() { hr2 = hr1.DeepCopy() hr2.Name = hr2NsName.Name - svcNsName = types.NamespacedName{Namespace: "test", Name: "svc"} + // svcNsName = types.NamespacedName{Namespace: "test", Name: "svc"} - svc = &apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: svcNsName.Namespace, - Name: svcNsName.Name, - }, - } + //svc = &apiv1.Service{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: svcNsName.Namespace, + // Name: svcNsName.Name, + // }, + //} - sliceNsName = types.NamespacedName{Namespace: "test", Name: "slice"} + // sliceNsName = types.NamespacedName{Namespace: "test", Name: "slice"} - slice = &discoveryV1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: sliceNsName.Namespace, - Name: sliceNsName.Name, - }, - } + //slice = &discoveryV1.EndpointSlice{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: sliceNsName.Namespace, + // Name: sliceNsName.Name, + // }, + //} rgNsName = types.NamespacedName{Namespace: "test", Name: "rg-1"} @@ -1563,14 +1555,14 @@ var _ = Describe("ChangeProcessor", func() { rg2 = rg1.DeepCopy() rg2.Name = "rg-2" - secretNsName = types.NamespacedName{Namespace: "test", Name: "test-secret"} + // secretNsName = types.NamespacedName{Namespace: "test", Name: "test-secret"} - secret = &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: secretNsName.Namespace, - Name: secretNsName.Name, - }, - } + //secret = &apiv1.Secret{ + // ObjectMeta: metav1.ObjectMeta{ + // Namespace: secretNsName.Namespace, + // Name: secretNsName.Name, + // }, + //} }) // Changing change - a change that makes processor.Process() report changed // Non-changing change - a change that doesn't do that @@ -1652,518 +1644,503 @@ var _ = Describe("ChangeProcessor", func() { Expect(changed).To(BeFalse()) }) }) - Describe("Multiple Kubernetes API resource changes", Ordered, func() { - // Note: because secret resource is not used by the real relationship.Capturer, it is not used - // in the same way as service and endpoint slice in the tests below. - It("should report changed after multiple Upserts of related resources", func() { - fakeRelationshipCapturer.ExistsReturns(true) - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - - changed, _ := processor.Process() - Expect(changed).To(BeTrue()) - }) - - It("should report not changed after multiple Upserts of unrelated resources", func() { - fakeRelationshipCapturer.ExistsReturns(false) - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - - processor.CaptureUpsertChange(secret) - - changed, _ := processor.Process() - Expect(changed).To(BeFalse()) - }) - When("upserts of related resources are followed by upserts of unrelated resources", func() { - It("should report changed", func() { - // these are changing changes - fakeRelationshipCapturer.ExistsReturns(true) - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - - // there are non-changing changes - fakeRelationshipCapturer.ExistsReturns(false) + /* + Describe("Multiple Kubernetes API resource changes", Ordered, func() { + // Note: because secret resource is not used by the real relationship.Capturer, it is not used + // in the same way as service and endpoint slice in the tests below. + It("should report changed after multiple Upserts of related resources", func() { processor.CaptureUpsertChange(svc) processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(secret) changed, _ := processor.Process() Expect(changed).To(BeTrue()) }) - }) - When("deletes of related resources are followed by upserts of unrelated resources", func() { - It("should report changed", func() { - // these are changing changes - fakeRelationshipCapturer.ExistsReturns(true) - processor.CaptureDeleteChange(&apiv1.Service{}, svcNsName) - processor.CaptureDeleteChange(&discoveryV1.EndpointSlice{}, sliceNsName) - // these are non-changing changes - fakeRelationshipCapturer.ExistsReturns(false) + It("should report not changed after multiple Upserts of unrelated resources", func() { processor.CaptureUpsertChange(svc) processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(secret) changed, _ := processor.Process() - Expect(changed).To(BeTrue()) + Expect(changed).To(BeFalse()) + }) + When("upserts of related resources are followed by upserts of unrelated resources", func() { + It("should report changed", func() { + // these are changing changes + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + + // there are non-changing changes + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(secret) + + changed, _ := processor.Process() + Expect(changed).To(BeTrue()) + }) + }) + 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) + + // these are non-changing changes + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(secret) + + changed, _ := processor.Process() + Expect(changed).To(BeTrue()) + }) }) }) - }) - 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 - fakeRelationshipCapturer.ExistsReturns(false) - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(rg1) - // related Kubernetes API resources - fakeRelationshipCapturer.ExistsReturns(true) - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) + */ - changed, _ := processor.Process() - Expect(changed).To(BeTrue()) - }) + /* + 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(hr1) + processor.CaptureUpsertChange(rg1) + + + It("should report not changed after multiple Upserts of unrelated and unchanged resources", func() { + // unchanged Gateway API resources + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(rg1) + + // unrelated Kubernetes API resources + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(secret) + + changed, _ := processor.Process() + Expect(changed).To(BeFalse()) + }) - It("should report not changed after multiple Upserts of unrelated resources", func() { - // unrelated Kubernetes API resources - fakeRelationshipCapturer.ExistsReturns(false) - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(secret) + It("should report changed after upserting related resources followed by upserting unchanged resources", + func() { + // these are changing changes + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) - changed, _ := processor.Process() - Expect(changed).To(BeFalse()) - }) + // these are non-changing changes + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(rg1) + processor.CaptureUpsertChange(secret) - It("should report changed after upserting related resources followed by upserting unchanged resources", - func() { - // these are changing changes - fakeRelationshipCapturer.ExistsReturns(true) - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) + changed, _ := processor.Process() + Expect(changed).To(BeTrue()) + }, + ) - // these are non-changing changes - fakeRelationshipCapturer.ExistsReturns(false) - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(rg1) - processor.CaptureUpsertChange(secret) + 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(rg1Updated) - changed, _ := processor.Process() - Expect(changed).To(BeTrue()) - }, - ) + // these are non-changing changes + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(secret) - It("should report changed after upserting changed resources followed by upserting unrelated resources", - func() { - // these are changing changes - fakeRelationshipCapturer.ExistsReturns(false) - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(rg1Updated) + changed, _ := processor.Process() + Expect(changed).To(BeTrue()) + }, + ) + It( + "should report changed after upserting related resources followed by upserting unchanged resources", + func() { + // these are changing changes + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + + // these are non-changing changes + processor.CaptureUpsertChange(gcUpdated) + processor.CaptureUpsertChange(gw1Updated) + processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(rg1Updated) + processor.CaptureUpsertChange(secret) + + changed, _ := processor.Process() + Expect(changed).To(BeTrue()) + }, + ) + }) + }) - // these are non-changing changes - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(secret) + */ - changed, _ := processor.Process() - Expect(changed).To(BeTrue()) - }, - ) - It( - "should report changed after upserting related resources followed by upserting unchanged resources", - func() { - // these are changing changes - fakeRelationshipCapturer.ExistsReturns(true) - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) + Describe("Webhook validation cases", Ordered, func() { + var ( + processor state.ChangeProcessor + fakeEventRecorder *record.FakeRecorder - // these are non-changing changes - fakeRelationshipCapturer.ExistsReturns(false) - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(rg1Updated) - processor.CaptureUpsertChange(secret) + gc *v1.GatewayClass - changed, _ := processor.Process() - Expect(changed).To(BeTrue()) - }, + gwNsName, hrNsName types.NamespacedName + gw, gwInvalid *v1.Gateway + hr, hrInvalid *v1.HTTPRoute ) - }) - }) - - Describe("Webhook validation cases", Ordered, func() { - var ( - processor state.ChangeProcessor - fakeEventRecorder *record.FakeRecorder - - gc *v1.GatewayClass - - gwNsName, hrNsName types.NamespacedName - gw, gwInvalid *v1.Gateway - hr, hrInvalid *v1.HTTPRoute - ) - BeforeAll(func() { - fakeEventRecorder = record.NewFakeRecorder(2 /* number of buffered events */) - - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - RelationshipCapturer: relationship.NewCapturerImpl(), - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - EventRecorder: fakeEventRecorder, - Scheme: createScheme(), - }) - - gc = &v1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: gcName, - Generation: 1, - }, - Spec: v1.GatewayClassSpec{ - ControllerName: controllerName, - }, - } - - gwNsName = types.NamespacedName{Namespace: "test", Name: "gateway"} - hrNsName = types.NamespacedName{Namespace: "test", Name: "hr"} + BeforeAll(func() { + fakeEventRecorder = record.NewFakeRecorder(2 /* number of buffered events */) - gw = &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gwNsName.Namespace, - Name: gwNsName.Name, - }, - Spec: v1.GatewaySpec{ + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: controllerName, GatewayClassName: gcName, - Listeners: []v1.Listener{ - { - Name: "listener-80-1", - Hostname: helpers.GetPointer[v1.Hostname]("foo.example.com"), - Port: 80, - Protocol: v1.HTTPProtocolType, - }, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + EventRecorder: fakeEventRecorder, + Scheme: createScheme(), + }) + + gc = &v1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: gcName, + Generation: 1, }, - }, - } + Spec: v1.GatewayClassSpec{ + ControllerName: controllerName, + }, + } - gwInvalid = gw.DeepCopy() - // cannot have hostname for TCP protocol - gwInvalid.Spec.Listeners[0].Protocol = v1.TCPProtocolType + gwNsName = types.NamespacedName{Namespace: "test", Name: "gateway"} + hrNsName = types.NamespacedName{Namespace: "test", Name: "hr"} - hr = &v1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: hrNsName.Namespace, - Name: hrNsName.Name, - }, - Spec: v1.HTTPRouteSpec{ - CommonRouteSpec: v1.CommonRouteSpec{ - ParentRefs: []v1.ParentReference{ + gw = &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: gwNsName.Namespace, + Name: gwNsName.Name, + }, + Spec: v1.GatewaySpec{ + GatewayClassName: gcName, + Listeners: []v1.Listener{ { - Namespace: (*v1.Namespace)(&gw.Namespace), - Name: v1.ObjectName(gw.Name), - SectionName: (*v1.SectionName)( - helpers.GetPointer("listener-80-1"), - ), + Name: "listener-80-1", + Hostname: helpers.GetPointer[v1.Hostname]("foo.example.com"), + Port: 80, + Protocol: v1.HTTPProtocolType, }, }, }, - Hostnames: []v1.Hostname{ - "foo.example.com", + } + + gwInvalid = gw.DeepCopy() + // cannot have hostname for TCP protocol + gwInvalid.Spec.Listeners[0].Protocol = v1.TCPProtocolType + + hr = &v1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: hrNsName.Namespace, + Name: hrNsName.Name, }, - Rules: []v1.HTTPRouteRule{ - { - Matches: []v1.HTTPRouteMatch{ + Spec: v1.HTTPRouteSpec{ + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ { - Path: &v1.HTTPPathMatch{ - Type: helpers.GetPointer(v1.PathMatchPathPrefix), - Value: helpers.GetPointer("/"), + Namespace: (*v1.Namespace)(&gw.Namespace), + Name: v1.ObjectName(gw.Name), + SectionName: (*v1.SectionName)( + helpers.GetPointer("listener-80-1"), + ), + }, + }, + }, + Hostnames: []v1.Hostname{ + "foo.example.com", + }, + Rules: []v1.HTTPRouteRule{ + { + Matches: []v1.HTTPRouteMatch{ + { + Path: &v1.HTTPPathMatch{ + Type: helpers.GetPointer(v1.PathMatchPathPrefix), + Value: helpers.GetPointer("/"), + }, }, }, }, }, }, - }, - } + } - hrInvalid = hr.DeepCopy() - hrInvalid.Spec.Rules[0].Matches[0].Path.Type = nil // cannot be nil - }) + hrInvalid = hr.DeepCopy() + hrInvalid.Spec.Rules[0].Matches[0].Path.Type = nil // cannot be nil + }) - assertHREvent := func() { - var e string - EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(&e)) - ExpectWithOffset(1, e).To(ContainSubstring("Rejected")) - ExpectWithOffset(1, e).To(ContainSubstring("spec.rules[0].matches[0].path.type")) - } + assertHREvent := func() { + var e string + EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(&e)) + ExpectWithOffset(1, e).To(ContainSubstring("Rejected")) + ExpectWithOffset(1, e).To(ContainSubstring("spec.rules[0].matches[0].path.type")) + } - assertGwEvent := func() { - var e string - EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(&e)) - ExpectWithOffset(1, e).To(ContainSubstring("Rejected")) - ExpectWithOffset(1, e).To(ContainSubstring("spec.listeners[0].hostname")) - } + assertGwEvent := func() { + var e string + EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(&e)) + ExpectWithOffset(1, e).To(ContainSubstring("Rejected")) + ExpectWithOffset(1, e).To(ContainSubstring("spec.listeners[0].hostname")) + } - It("should process GatewayClass", func() { - processor.CaptureUpsertChange(gc) + It("should process GatewayClass", func() { + processor.CaptureUpsertChange(gc) - changed, graphCfg := processor.Process() - Expect(changed).To(BeTrue()) - Expect(graphCfg.GatewayClass).ToNot(BeNil()) - Expect(fakeEventRecorder.Events).To(HaveLen(0)) - }) + changed, graphCfg := processor.Process() + Expect(changed).To(BeTrue()) + Expect(graphCfg.GatewayClass).ToNot(BeNil()) + Expect(fakeEventRecorder.Events).To(HaveLen(0)) + }) - When("resources are invalid", func() { - It("should not process them", func() { - processor.CaptureUpsertChange(gwInvalid) - processor.CaptureUpsertChange(hrInvalid) + When("resources are invalid", func() { + It("should not process them", func() { + processor.CaptureUpsertChange(gwInvalid) + processor.CaptureUpsertChange(hrInvalid) - changed, graphCfg := processor.Process() + changed, graphCfg := processor.Process() - Expect(changed).To(BeFalse()) - Expect(graphCfg).To(BeNil()) + Expect(changed).To(BeFalse()) + Expect(graphCfg).To(BeNil()) - Expect(fakeEventRecorder.Events).To(HaveLen(2)) - assertGwEvent() - assertHREvent() + Expect(fakeEventRecorder.Events).To(HaveLen(2)) + assertGwEvent() + assertHREvent() + }) }) - }) - When("resources are valid", func() { - It("should process them", func() { - processor.CaptureUpsertChange(gw) - processor.CaptureUpsertChange(hr) + When("resources are valid", func() { + It("should process them", func() { + processor.CaptureUpsertChange(gw) + processor.CaptureUpsertChange(hr) - changed, graphCfg := processor.Process() + changed, graphCfg := processor.Process() - Expect(changed).To(BeTrue()) - Expect(graphCfg).ToNot(BeNil()) - Expect(graphCfg.Gateway).ToNot(BeNil()) - Expect(graphCfg.Routes).To(HaveLen(1)) + Expect(changed).To(BeTrue()) + Expect(graphCfg).ToNot(BeNil()) + Expect(graphCfg.Gateway).ToNot(BeNil()) + Expect(graphCfg.Routes).To(HaveLen(1)) - Expect(fakeEventRecorder.Events).To(HaveLen(0)) + Expect(fakeEventRecorder.Events).To(HaveLen(0)) + }) }) - }) - When("a new version of HTTPRoute is invalid", func() { - It("it should delete the configuration for the old one and not process the new one", func() { - processor.CaptureUpsertChange(hrInvalid) + When("a new version of HTTPRoute is invalid", func() { + It("it should delete the configuration for the old one and not process the new one", func() { + processor.CaptureUpsertChange(hrInvalid) - changed, graphCfg := processor.Process() + changed, graphCfg := processor.Process() - Expect(changed).To(BeTrue()) - Expect(graphCfg.Routes).To(HaveLen(0)) + Expect(changed).To(BeTrue()) + Expect(graphCfg.Routes).To(HaveLen(0)) - Expect(fakeEventRecorder.Events).To(HaveLen(1)) - assertHREvent() + Expect(fakeEventRecorder.Events).To(HaveLen(1)) + assertHREvent() + }) }) - }) - When("a new version of Gateway is invalid", func() { - It("it should delete the configuration for the old one and not process the new one", func() { - processor.CaptureUpsertChange(gwInvalid) + When("a new version of Gateway is invalid", func() { + It("it should delete the configuration for the old one and not process the new one", func() { + processor.CaptureUpsertChange(gwInvalid) - changed, graphCfg := processor.Process() + changed, graphCfg := processor.Process() - Expect(changed).To(BeTrue()) - Expect(graphCfg.Gateway).To(BeNil()) + Expect(changed).To(BeTrue()) + Expect(graphCfg.Gateway).To(BeNil()) - Expect(fakeEventRecorder.Events).To(HaveLen(1)) - assertGwEvent() + Expect(fakeEventRecorder.Events).To(HaveLen(1)) + assertGwEvent() + }) }) - }) - Describe("Webhook assumptions", func() { - var processor state.ChangeProcessor + Describe("Webhook assumptions", func() { + var processor state.ChangeProcessor - BeforeEach(func() { - fakeEventRecorder = record.NewFakeRecorder(1 /* number of buffered events */) + BeforeEach(func() { + fakeEventRecorder = record.NewFakeRecorder(1 /* number of buffered events */) - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - RelationshipCapturer: relationship.NewCapturerImpl(), - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - EventRecorder: fakeEventRecorder, - Scheme: createScheme(), + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + EventRecorder: fakeEventRecorder, + Scheme: createScheme(), + }) }) - }) - createInvalidHTTPRoute := func(invalidator func(hr *v1.HTTPRoute)) *v1.HTTPRoute { - hr := createRoute( - "hr", - "gateway", - "foo.example.com", - createBackendRef( - helpers.GetPointer[v1.Kind]("Service"), - "test", - helpers.GetPointer[v1.Namespace]("namespace"), + createInvalidHTTPRoute := func(invalidator func(hr *v1.HTTPRoute)) *v1.HTTPRoute { + hr := createRoute( + "hr", + "gateway", + "foo.example.com", + createBackendRef( + helpers.GetPointer[v1.Kind]("Service"), + "test", + helpers.GetPointer[v1.Namespace]("namespace"), + ), + ) + invalidator(hr) + return hr + } + + createInvalidGateway := func(invalidator func(gw *v1.Gateway)) *v1.Gateway { + gw := createGateway("gateway") + invalidator(gw) + return gw + } + + assertRejectedEvent := func() { + EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(ContainSubstring("Rejected"))) + } + + DescribeTable("Invalid HTTPRoutes", + func(hr *v1.HTTPRoute) { + processor.CaptureUpsertChange(hr) + + changed, graphCfg := processor.Process() + + Expect(changed).To(BeFalse()) + Expect(graphCfg).To(BeNil()) + + assertRejectedEvent() + }, + Entry( + "duplicate parentRefs", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.ParentRefs = append(hr.Spec.ParentRefs, hr.Spec.ParentRefs[len(hr.Spec.ParentRefs)-1]) + }), + ), + Entry( + "nil path.Type", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.Rules[0].Matches[0].Path.Type = nil + }), + ), + Entry("nil path.Value", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.Rules[0].Matches[0].Path.Value = nil + }), + ), + Entry( + "nil request.Redirect", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.Rules[0].Filters = append(hr.Spec.Rules[0].Filters, v1.HTTPRouteFilter{ + Type: v1.HTTPRouteFilterRequestRedirect, + RequestRedirect: nil, + }) + }), + ), + Entry("nil port in BackendRef", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.Rules[0].BackendRefs[0].Port = nil + }), ), ) - invalidator(hr) - return hr - } - createInvalidGateway := func(invalidator func(gw *v1.Gateway)) *v1.Gateway { - gw := createGateway("gateway") - invalidator(gw) - return gw - } + DescribeTable("Invalid Gateway resources", + func(gw *v1.Gateway) { + processor.CaptureUpsertChange(gw) - assertRejectedEvent := func() { - EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(ContainSubstring("Rejected"))) - } + changed, graphCfg := processor.Process() - DescribeTable("Invalid HTTPRoutes", - func(hr *v1.HTTPRoute) { - processor.CaptureUpsertChange(hr) + Expect(changed).To(BeFalse()) + Expect(graphCfg).To(BeNil()) - changed, graphCfg := processor.Process() + assertRejectedEvent() + }, + Entry("tls in HTTP listener", + createInvalidGateway(func(gw *v1.Gateway) { + gw.Spec.Listeners[0].TLS = &v1.GatewayTLSConfig{} + }), + ), + Entry("tls is nil in HTTPS listener", + createInvalidGateway(func(gw *v1.Gateway) { + gw.Spec.Listeners[0].Protocol = v1.HTTPSProtocolType + gw.Spec.Listeners[0].TLS = nil + }), + ), + Entry("zero certificateRefs in HTTPS listener", + createInvalidGateway(func(gw *v1.Gateway) { + gw.Spec.Listeners[0].Protocol = v1.HTTPSProtocolType + gw.Spec.Listeners[0].TLS = &v1.GatewayTLSConfig{ + Mode: helpers.GetPointer(v1.TLSModeTerminate), + CertificateRefs: nil, + } + }), + ), + Entry("listener hostnames conflict", + createInvalidGateway(func(gw *v1.Gateway) { + gw.Spec.Listeners = append(gw.Spec.Listeners, v1.Listener{ + Name: "listener-80-2", + Hostname: nil, + Port: 80, + Protocol: v1.HTTPProtocolType, + }) + }), + ), + ) + }) + }) - Expect(changed).To(BeFalse()) - Expect(graphCfg).To(BeNil()) + 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(), + Scheme: createScheme(), + }) + }) - assertRejectedEvent() + DescribeTable("CaptureUpsertChange must panic", + func(obj client.Object) { + process := func() { + processor.CaptureUpsertChange(obj) + } + Expect(process).Should(Panic()) }, Entry( - "duplicate parentRefs", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.ParentRefs = append(hr.Spec.ParentRefs, hr.Spec.ParentRefs[len(hr.Spec.ParentRefs)-1]) - }), + "an unsupported resource", + &v1alpha2.TCPRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "tcp"}}, ), Entry( - "nil path.Type", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.Rules[0].Matches[0].Path.Type = nil - }), - ), - Entry("nil path.Value", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.Rules[0].Matches[0].Path.Value = nil - }), - ), - Entry( - "nil request.Redirect", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.Rules[0].Filters = append(hr.Spec.Rules[0].Filters, v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterRequestRedirect, - RequestRedirect: nil, - }) - }), - ), - Entry("nil port in BackendRef", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.Rules[0].BackendRefs[0].Port = nil - }), + "nil resource", + nil, ), ) - DescribeTable("Invalid Gateway resources", - func(gw *v1.Gateway) { - processor.CaptureUpsertChange(gw) - - changed, graphCfg := processor.Process() - - Expect(changed).To(BeFalse()) - Expect(graphCfg).To(BeNil()) - - assertRejectedEvent() + DescribeTable( + "CaptureDeleteChange must panic", + func(resourceType client.Object, nsname types.NamespacedName) { + process := func() { + processor.CaptureDeleteChange(resourceType, nsname) + } + Expect(process).Should(Panic()) }, - Entry("tls in HTTP listener", - createInvalidGateway(func(gw *v1.Gateway) { - gw.Spec.Listeners[0].TLS = &v1.GatewayTLSConfig{} - }), - ), - Entry("tls is nil in HTTPS listener", - createInvalidGateway(func(gw *v1.Gateway) { - gw.Spec.Listeners[0].Protocol = v1.HTTPSProtocolType - gw.Spec.Listeners[0].TLS = nil - }), - ), - Entry("zero certificateRefs in HTTPS listener", - createInvalidGateway(func(gw *v1.Gateway) { - gw.Spec.Listeners[0].Protocol = v1.HTTPSProtocolType - gw.Spec.Listeners[0].TLS = &v1.GatewayTLSConfig{ - Mode: helpers.GetPointer(v1.TLSModeTerminate), - CertificateRefs: nil, - } - }), + Entry( + "an unsupported resource", + &v1alpha2.TCPRoute{}, + types.NamespacedName{Namespace: "test", Name: "tcp"}, ), - Entry("listener hostnames conflict", - createInvalidGateway(func(gw *v1.Gateway) { - gw.Spec.Listeners = append(gw.Spec.Listeners, v1.Listener{ - Name: "listener-80-2", - Hostname: nil, - Port: 80, - Protocol: v1.HTTPProtocolType, - }) - }), + Entry( + "nil resource type", + nil, + types.NamespacedName{Namespace: "test", Name: "resource"}, ), ) }) }) - - Describe("Edge cases with panic", func() { - var ( - processor state.ChangeProcessor - fakeRelationshipCapturer *relationshipfakes.FakeCapturer - ) - - BeforeEach(func() { - fakeRelationshipCapturer = &relationshipfakes.FakeCapturer{} - - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: "test.controller", - GatewayClassName: "my-class", - RelationshipCapturer: fakeRelationshipCapturer, - Validators: createAlwaysValidValidators(), - Scheme: 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 client.Object, 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/relationship/capturer.go b/internal/mode/static/state/relationship/capturer.go deleted file mode 100644 index 10f72f929a..0000000000 --- a/internal/mode/static/state/relationship/capturer.go +++ /dev/null @@ -1,157 +0,0 @@ -package relationship - -import ( - v1 "k8s.io/api/core/v1" - discoveryV1 "k8s.io/api/discovery/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" - - "github.com/nginxinc/nginx-gateway-fabric/internal/framework/controller/index" -) - -//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . Capturer - -// Capturer captures relationships between Kubernetes objects and can be queried for whether a relationship exists -// for a given object. -// -// The relationships between HTTPRoutes -> Services are many to 1, -// so these relationships are tracked using a counter. -// A Service relationship exists if at least one HTTPRoute references it. -// An EndpointSlice relationship exists if its Service owner is referenced by at least one HTTPRoute. -type Capturer interface { - Capture(obj client.Object) - Remove(resourceType client.Object, nsname types.NamespacedName) - Exists(resourceType client.Object, nsname types.NamespacedName) bool -} - -type ( - // routeToServicesMap maps HTTPRoute names to the set of Services it references. - routeToServicesMap map[types.NamespacedName]map[types.NamespacedName]struct{} - // serviceRefCountMap maps Service names to the number of HTTPRoutes that reference it. - serviceRefCountMap map[types.NamespacedName]int -) - -// CapturerImpl implements the Capturer interface. -type CapturerImpl struct { - routesToServices routeToServicesMap - serviceRefCount serviceRefCountMap - endpointSliceOwners map[types.NamespacedName]types.NamespacedName -} - -// NewCapturerImpl creates a new instance of CapturerImpl. -func NewCapturerImpl() *CapturerImpl { - return &CapturerImpl{ - routesToServices: make(routeToServicesMap), - serviceRefCount: make(serviceRefCountMap), - endpointSliceOwners: make(map[types.NamespacedName]types.NamespacedName), - } -} - -// Capture captures relationships for the given object. -func (c *CapturerImpl) Capture(obj client.Object) { - switch o := obj.(type) { - case *gatewayv1.HTTPRoute: - c.upsertForRoute(o) - case *discoveryV1.EndpointSlice: - svcName := index.GetServiceNameFromEndpointSlice(o) - if svcName != "" { - c.endpointSliceOwners[client.ObjectKeyFromObject(o)] = types.NamespacedName{ - Namespace: o.Namespace, - Name: svcName, - } - } - } -} - -// Remove removes the relationship for the given object from the CapturerImpl. -func (c *CapturerImpl) Remove(resourceType client.Object, nsname types.NamespacedName) { - switch resourceType.(type) { - case *gatewayv1.HTTPRoute: - c.deleteForRoute(nsname) - case *discoveryV1.EndpointSlice: - delete(c.endpointSliceOwners, nsname) - } -} - -// Exists returns true if the given object has a relationship with another object. -func (c *CapturerImpl) Exists(resourceType client.Object, _ types.NamespacedName) bool { - switch resourceType.(type) { - case *v1.Service: - return false - // return c.serviceRefCount[nsname] > 0 - case *discoveryV1.EndpointSlice: - return false - // svcOwner, exists := c.endpointSliceOwners[nsname] - // return exists && c.serviceRefCount[svcOwner] > 0 - } - - return false -} - -// GetRefCountForService is used for unit testing purposes. It is not exposed through the Capturer interface. -func (c *CapturerImpl) GetRefCountForService(svcName types.NamespacedName) int { - return c.serviceRefCount[svcName] -} - -func (c *CapturerImpl) upsertForRoute(route *gatewayv1.HTTPRoute) { - oldServices := c.routesToServices[client.ObjectKeyFromObject(route)] - newServices := getBackendServiceNamesFromRoute(route) - - for svc := range oldServices { - if _, exist := newServices[svc]; !exist { - c.decrementRefCount(svc) - } - } - - for svc := range newServices { - if _, exist := oldServices[svc]; !exist { - c.serviceRefCount[svc]++ - } - } - - c.routesToServices[client.ObjectKeyFromObject(route)] = newServices -} - -func (c *CapturerImpl) deleteForRoute(routeName types.NamespacedName) { - services := c.routesToServices[routeName] - - for svc := range services { - c.decrementRefCount(svc) - } - - delete(c.routesToServices, routeName) -} - -func (c *CapturerImpl) decrementRefCount(svcName types.NamespacedName) { - if count, exist := c.serviceRefCount[svcName]; exist { - if count == 1 { - delete(c.serviceRefCount, svcName) - - return - } - - c.serviceRefCount[svcName]-- - } -} - -func getBackendServiceNamesFromRoute(hr *gatewayv1.HTTPRoute) map[types.NamespacedName]struct{} { - svcNames := make(map[types.NamespacedName]struct{}) - - for _, rule := range hr.Spec.Rules { - for _, ref := range rule.BackendRefs { - if ref.Kind != nil && *ref.Kind != "Service" { - continue - } - - ns := hr.Namespace - if ref.Namespace != nil { - ns = string(*ref.Namespace) - } - - svcNames[types.NamespacedName{Namespace: ns, Name: string(ref.Name)}] = struct{}{} - } - } - - return svcNames -} diff --git a/internal/mode/static/state/relationship/capturer_suite_test.go b/internal/mode/static/state/relationship/capturer_suite_test.go deleted file mode 100644 index 917ec274fa..0000000000 --- a/internal/mode/static/state/relationship/capturer_suite_test.go +++ /dev/null @@ -1,13 +0,0 @@ -package relationship_test - -import ( - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func TestRelationships(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Relationships Suite") -} diff --git a/internal/mode/static/state/relationship/capturer_test.go b/internal/mode/static/state/relationship/capturer_test.go deleted file mode 100644 index e91b49b099..0000000000 --- a/internal/mode/static/state/relationship/capturer_test.go +++ /dev/null @@ -1,336 +0,0 @@ -package relationship_test - -// -//import ( -// . "github.com/onsi/ginkgo/v2" -// . "github.com/onsi/gomega" -// v1 "k8s.io/api/core/v1" -// discoveryV1 "k8s.io/api/discovery/v1" -// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -// "k8s.io/apimachinery/pkg/types" -// gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" -// -// "github.com/nginxinc/nginx-gateway-fabric/internal/framework/controller/index" -// "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" -// "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/relationship" -//) -// -//func createBackendRefs(backendNames ...gatewayv1.ObjectName) []gatewayv1.HTTPBackendRef { -// refs := make([]gatewayv1.HTTPBackendRef, 0, len(backendNames)) -// for _, name := range backendNames { -// refs = append(refs, gatewayv1.HTTPBackendRef{ -// BackendRef: gatewayv1.BackendRef{ -// BackendObjectReference: gatewayv1.BackendObjectReference{ -// Kind: (*gatewayv1.Kind)(helpers.GetPointer("Service")), -// Name: name, -// Namespace: (*gatewayv1.Namespace)(helpers.GetPointer("test")), -// }, -// }, -// }) -// } -// -// return refs -//} -// -//func createRules(backendRefs ...[]gatewayv1.HTTPBackendRef) []gatewayv1.HTTPRouteRule { -// rules := make([]gatewayv1.HTTPRouteRule, 0, len(backendRefs)) -// for _, refs := range backendRefs { -// rules = append(rules, gatewayv1.HTTPRouteRule{BackendRefs: refs}) -// } -// -// return rules -//} -// -//func createRoute(name string, rules []gatewayv1.HTTPRouteRule) *gatewayv1.HTTPRoute { -// return &gatewayv1.HTTPRoute{ -// ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: name}, -// Spec: gatewayv1.HTTPRouteSpec{Rules: rules}, -// } -//} -// -//var _ = Describe("Capturer", func() { -// var ( -// capturer *relationship.CapturerImpl -// -// backendRef1 = createBackendRefs("svc1") -// backendRef2 = createBackendRefs("svc2") -// backendRef3 = createBackendRefs("svc3") -// backendRef4 = createBackendRefs("svc4") -// -// hr1 = createRoute("hr1", createRules(backendRef1)) -// hr2 = createRoute("hr2", createRules(backendRef2, backendRef3, backendRef4)) -// -// hrSvc1AndSvc2 = createRoute("hr-svc1-svc2", createRules(backendRef1, backendRef2)) -// hrSvc1AndSvc3 = createRoute("hr-svc1-svc3", createRules(backendRef3, backendRef1)) -// hrSvc1AndSvc4 = createRoute("hr-svc1-svc4", createRules(backendRef1, backendRef4)) -// -// hr1Name = types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name} -// hr2Name = types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name} -// hrSvc1AndSvc2Name = types.NamespacedName{Namespace: hrSvc1AndSvc2.Namespace, Name: hrSvc1AndSvc2.Name} -// hrSvc1AndSvc3Name = types.NamespacedName{Namespace: hrSvc1AndSvc3.Namespace, Name: hrSvc1AndSvc3.Name} -// hrSvc1AndSvc4Name = types.NamespacedName{Namespace: hrSvc1AndSvc4.Namespace, Name: hrSvc1AndSvc4.Name} -// -// svc1 = types.NamespacedName{Namespace: "test", Name: "svc1"} -// svc2 = types.NamespacedName{Namespace: "test", Name: "svc2"} -// svc3 = types.NamespacedName{Namespace: "test", Name: "svc3"} -// svc4 = types.NamespacedName{Namespace: "test", Name: "svc4"} -// ) -// -// Describe("Capture service relationships for routes", func() { -// BeforeEach(OncePerOrdered, func() { -// capturer = relationship.NewCapturerImpl() -// }) -// -// assertServiceExists := func(svcName types.NamespacedName, exists bool, refCount int) { -// ExpectWithOffset(1, capturer.Exists(&v1.Service{}, svcName)).To(Equal(exists)) -// ExpectWithOffset(1, capturer.GetRefCountForService(svcName)).To(Equal(refCount)) -// } -// -// Describe("Normal cases", Ordered, func() { -// When("a route with a backend service is captured", func() { -// It("reports a service relationship", func() { -// capturer.Capture(hr1) -// -// assertServiceExists(svc1, true, 1) -// }) -// }) -// When("a route with multiple backend services is captured", func() { -// It("reports all service relationships for all captured routes", func() { -// capturer.Capture(hr2) -// -// assertServiceExists(svc1, true, 1) -// assertServiceExists(svc2, true, 1) -// assertServiceExists(svc3, true, 1) -// assertServiceExists(svc4, true, 1) -// }) -// }) -// When("one backend service is removed from a captured route", func() { -// It("removes the correct service relationship", func() { -// hr2Updated := hr2.DeepCopy() -// hr2Updated.Spec.Rules = hr2Updated.Spec.Rules[0:2] // remove the last rule -// -// capturer.Capture(hr2Updated) -// -// assertServiceExists(svc1, true, 1) -// assertServiceExists(svc2, true, 1) -// assertServiceExists(svc3, true, 1) -// assertServiceExists(svc4, false, 0) -// }) -// }) -// When("one backend service is added to a captured route", func() { -// It("adds the correct service relationship", func() { -// capturer.Capture(hr2) -// -// assertServiceExists(svc1, true, 1) -// assertServiceExists(svc2, true, 1) -// assertServiceExists(svc3, true, 1) -// assertServiceExists(svc4, true, 1) -// }) -// }) -// When("a route with multiple backend services is removed", func() { -// It("removes all service relationships", func() { -// capturer.Remove(&gatewayv1.HTTPRoute{}, hr2Name) -// -// assertServiceExists(svc2, false, 0) -// assertServiceExists(svc3, false, 0) -// assertServiceExists(svc4, false, 0) -// -// // Service referenced by hr1 still exists -// assertServiceExists(svc1, true, 1) -// }) -// }) -// When("a route is removed", func() { -// It("removes service relationships", func() { -// capturer.Remove(&gatewayv1.HTTPRoute{}, hr1Name) -// -// assertServiceExists(svc1, false, 0) -// }) -// }) -// }) -// Describe("Multiple routes that reference the same service", Ordered, func() { -// When("multiple routes are captured that all reference the same service", func() { -// It("reports all service relationships", func() { -// capturer.Capture(hr1) -// capturer.Capture(hrSvc1AndSvc2) -// capturer.Capture(hrSvc1AndSvc3) -// capturer.Capture(hrSvc1AndSvc4) -// -// assertServiceExists(svc1, true, 4) -// assertServiceExists(svc2, true, 1) -// assertServiceExists(svc3, true, 1) -// assertServiceExists(svc4, true, 1) -// }) -// }) -// When("one route is removed", func() { -// It("reports remaining service relationships", func() { -// capturer.Remove(&gatewayv1.HTTPRoute{}, hr1Name) -// -// // ref count for svc1 should decrease by one -// assertServiceExists(svc1, true, 3) -// -// // all other ref counts stay the same -// assertServiceExists(svc2, true, 1) -// assertServiceExists(svc3, true, 1) -// assertServiceExists(svc4, true, 1) -// }) -// }) -// When("another route is removed", func() { -// It("reports remaining service relationships", func() { -// capturer.Remove(&gatewayv1.HTTPRoute{}, hrSvc1AndSvc2Name) -// -// // svc2 should no longer exist -// assertServiceExists(svc2, false, 0) -// -// // ref count for svc1 should decrease by one -// assertServiceExists(svc1, true, 2) -// -// // all other ref counts stay the same -// assertServiceExists(svc3, true, 1) -// assertServiceExists(svc4, true, 1) -// }) -// }) -// When("another route is removed", func() { -// It("reports remaining service relationships", func() { -// capturer.Remove(&gatewayv1.HTTPRoute{}, hrSvc1AndSvc3Name) -// -// // svc3 should no longer exist -// assertServiceExists(svc3, false, 0) -// -// // svc2 should still not exist -// assertServiceExists(svc2, false, 0) -// -// // ref count for svc1 should decrease by one -// assertServiceExists(svc1, true, 1) -// -// // svc4 ref count should stay the same -// assertServiceExists(svc4, true, 1) -// }) -// When("final route is removed", func() { -// It("removes all service relationships", func() { -// capturer.Remove(&gatewayv1.HTTPRoute{}, hrSvc1AndSvc4Name) -// -// // no services should exist and all ref counts should be 0 -// assertServiceExists(svc1, false, 0) -// assertServiceExists(svc2, false, 0) -// assertServiceExists(svc3, false, 0) -// assertServiceExists(svc4, false, 0) -// }) -// }) -// When("route is removed again", func() { -// It("service ref counts remain at 0", func() { -// capturer.Remove(&gatewayv1.HTTPRoute{}, hrSvc1AndSvc4Name) -// -// // no services should exist and all ref counts should still be 0 -// assertServiceExists(svc1, false, 0) -// assertServiceExists(svc2, false, 0) -// assertServiceExists(svc3, false, 0) -// assertServiceExists(svc4, false, 0) -// }) -// }) -// }) -// }) -// Describe("Capture endpoint slice relationships", func() { -// var ( -// slice1 = &discoveryV1.EndpointSlice{ -// ObjectMeta: metav1.ObjectMeta{ -// Namespace: "test", -// Name: "es1", -// Labels: map[string]string{index.KubernetesServiceNameLabel: "svc1"}, -// }, -// } -// -// slice2 = &discoveryV1.EndpointSlice{ -// ObjectMeta: metav1.ObjectMeta{ -// Namespace: "test", -// Name: "es2", -// Labels: map[string]string{index.KubernetesServiceNameLabel: "svc1"}, -// }, -// } -// -// slice1Name = types.NamespacedName{Namespace: slice1.Namespace, Name: slice1.Name} -// slice2Name = types.NamespacedName{Namespace: slice2.Namespace, Name: slice2.Name} -// ) -// -// BeforeEach(OncePerOrdered, func() { -// capturer = relationship.NewCapturerImpl() -// }) -// -// Describe("Normal cases", Ordered, func() { -// When("an endpoint slice is captured that has an unrelated service owner", func() { -// It("does not report an endpoint slice relationship", func() { -// capturer.Capture(slice1) -// -// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeFalse()) -// }) -// }) -// When("a relationship is captured for the service owner", func() { -// It("adds an endpoint slice relationship", func() { -// capturer.Capture(hr1) -// -// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeTrue()) -// }) -// }) -// When("another endpoint slice is captured with the same service owner", func() { -// It("adds another endpoint slice relationship", func() { -// capturer.Capture(slice2) -// -// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeTrue()) -// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice2Name)).To(BeTrue()) -// }) -// }) -// When("an endpoint slice is removed", func() { -// It("removes the endpoint slice relationship", func() { -// capturer.Remove(&discoveryV1.EndpointSlice{}, slice2Name) -// -// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice2Name)).To(BeFalse()) -// -// // slice 1 relationship should still exist -// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeTrue()) -// }) -// }) -// When("endpoint slice service owner changes to an unrelated service owner", func() { -// It("removes the endpoint slice relationship", func() { -// updatedSlice1 := slice1.DeepCopy() -// updatedSlice1.Labels[index.KubernetesServiceNameLabel] = "unrelated-svc" -// -// capturer.Capture(updatedSlice1) -// -// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeFalse()) -// }) -// }) -// When("endpoint slice service owner changes to a related service owner", func() { -// It("adds an endpoint slice relationship", func() { -// capturer.Capture(slice1) -// -// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeTrue()) -// }) -// }) -// When("service relationship is removed", func() { -// It("removes the endpoint slice relationship", func() { -// capturer.Remove(&gatewayv1.HTTPRoute{}, hr1Name) -// -// Expect(capturer.Exists(&discoveryV1.EndpointSlice{}, slice1Name)).To(BeFalse()) -// }) -// }) -// }) -// }) -// }) -// Describe("Edge cases", func() { -// BeforeEach(func() { -// capturer = relationship.NewCapturerImpl() -// }) -// It("Capture does not panic when passed an unsupported resource type", func() { -// Expect(func() { -// capturer.Capture(&gatewayv1.GatewayClass{}) -// }).ToNot(Panic()) -// }) -// It("Remove does not panic when passed an unsupported resource type", func() { -// Expect(func() { -// capturer.Remove(&gatewayv1.GatewayClass{}, types.NamespacedName{}) -// }).ToNot(Panic()) -// }) -// It("Exist returns false if passed an unsupported resource type", func() { -// Expect(capturer.Exists(&gatewayv1.GatewayClass{}, types.NamespacedName{})).To(BeFalse()) -// }) -// }) -//}) diff --git a/internal/mode/static/state/relationship/relationshipfakes/fake_capturer.go b/internal/mode/static/state/relationship/relationshipfakes/fake_capturer.go deleted file mode 100644 index ea4f3b6783..0000000000 --- a/internal/mode/static/state/relationship/relationshipfakes/fake_capturer.go +++ /dev/null @@ -1,195 +0,0 @@ -// Code generated by counterfeiter. DO NOT EDIT. -package relationshipfakes - -import ( - "sync" - - "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/relationship" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type FakeCapturer struct { - CaptureStub func(client.Object) - captureMutex sync.RWMutex - captureArgsForCall []struct { - arg1 client.Object - } - ExistsStub func(client.Object, types.NamespacedName) bool - existsMutex sync.RWMutex - existsArgsForCall []struct { - arg1 client.Object - arg2 types.NamespacedName - } - existsReturns struct { - result1 bool - } - existsReturnsOnCall map[int]struct { - result1 bool - } - RemoveStub func(client.Object, types.NamespacedName) - removeMutex sync.RWMutex - removeArgsForCall []struct { - arg1 client.Object - arg2 types.NamespacedName - } - invocations map[string][][]interface{} - invocationsMutex sync.RWMutex -} - -func (fake *FakeCapturer) Capture(arg1 client.Object) { - fake.captureMutex.Lock() - fake.captureArgsForCall = append(fake.captureArgsForCall, struct { - arg1 client.Object - }{arg1}) - stub := fake.CaptureStub - fake.recordInvocation("Capture", []interface{}{arg1}) - fake.captureMutex.Unlock() - if stub != nil { - fake.CaptureStub(arg1) - } -} - -func (fake *FakeCapturer) CaptureCallCount() int { - fake.captureMutex.RLock() - defer fake.captureMutex.RUnlock() - return len(fake.captureArgsForCall) -} - -func (fake *FakeCapturer) CaptureCalls(stub func(client.Object)) { - fake.captureMutex.Lock() - defer fake.captureMutex.Unlock() - fake.CaptureStub = stub -} - -func (fake *FakeCapturer) CaptureArgsForCall(i int) client.Object { - fake.captureMutex.RLock() - defer fake.captureMutex.RUnlock() - argsForCall := fake.captureArgsForCall[i] - return argsForCall.arg1 -} - -func (fake *FakeCapturer) Exists(arg1 client.Object, arg2 types.NamespacedName) bool { - fake.existsMutex.Lock() - ret, specificReturn := fake.existsReturnsOnCall[len(fake.existsArgsForCall)] - fake.existsArgsForCall = append(fake.existsArgsForCall, struct { - arg1 client.Object - arg2 types.NamespacedName - }{arg1, arg2}) - stub := fake.ExistsStub - fakeReturns := fake.existsReturns - fake.recordInvocation("Exists", []interface{}{arg1, arg2}) - fake.existsMutex.Unlock() - if stub != nil { - return stub(arg1, arg2) - } - if specificReturn { - return ret.result1 - } - return fakeReturns.result1 -} - -func (fake *FakeCapturer) ExistsCallCount() int { - fake.existsMutex.RLock() - defer fake.existsMutex.RUnlock() - return len(fake.existsArgsForCall) -} - -func (fake *FakeCapturer) ExistsCalls(stub func(client.Object, types.NamespacedName) bool) { - fake.existsMutex.Lock() - defer fake.existsMutex.Unlock() - fake.ExistsStub = stub -} - -func (fake *FakeCapturer) ExistsArgsForCall(i int) (client.Object, types.NamespacedName) { - fake.existsMutex.RLock() - defer fake.existsMutex.RUnlock() - argsForCall := fake.existsArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 -} - -func (fake *FakeCapturer) ExistsReturns(result1 bool) { - fake.existsMutex.Lock() - defer fake.existsMutex.Unlock() - fake.ExistsStub = nil - fake.existsReturns = struct { - result1 bool - }{result1} -} - -func (fake *FakeCapturer) ExistsReturnsOnCall(i int, result1 bool) { - fake.existsMutex.Lock() - defer fake.existsMutex.Unlock() - fake.ExistsStub = nil - if fake.existsReturnsOnCall == nil { - fake.existsReturnsOnCall = make(map[int]struct { - result1 bool - }) - } - fake.existsReturnsOnCall[i] = struct { - result1 bool - }{result1} -} - -func (fake *FakeCapturer) Remove(arg1 client.Object, arg2 types.NamespacedName) { - fake.removeMutex.Lock() - fake.removeArgsForCall = append(fake.removeArgsForCall, struct { - arg1 client.Object - arg2 types.NamespacedName - }{arg1, arg2}) - stub := fake.RemoveStub - fake.recordInvocation("Remove", []interface{}{arg1, arg2}) - fake.removeMutex.Unlock() - if stub != nil { - fake.RemoveStub(arg1, arg2) - } -} - -func (fake *FakeCapturer) RemoveCallCount() int { - fake.removeMutex.RLock() - defer fake.removeMutex.RUnlock() - return len(fake.removeArgsForCall) -} - -func (fake *FakeCapturer) RemoveCalls(stub func(client.Object, types.NamespacedName)) { - fake.removeMutex.Lock() - defer fake.removeMutex.Unlock() - fake.RemoveStub = stub -} - -func (fake *FakeCapturer) RemoveArgsForCall(i int) (client.Object, types.NamespacedName) { - fake.removeMutex.RLock() - defer fake.removeMutex.RUnlock() - argsForCall := fake.removeArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 -} - -func (fake *FakeCapturer) Invocations() map[string][][]interface{} { - fake.invocationsMutex.RLock() - defer fake.invocationsMutex.RUnlock() - fake.captureMutex.RLock() - defer fake.captureMutex.RUnlock() - fake.existsMutex.RLock() - defer fake.existsMutex.RUnlock() - fake.removeMutex.RLock() - defer fake.removeMutex.RUnlock() - copiedInvocations := map[string][][]interface{}{} - for key, value := range fake.invocations { - copiedInvocations[key] = value - } - return copiedInvocations -} - -func (fake *FakeCapturer) recordInvocation(key string, args []interface{}) { - fake.invocationsMutex.Lock() - defer fake.invocationsMutex.Unlock() - if fake.invocations == nil { - fake.invocations = map[string][][]interface{}{} - } - if fake.invocations[key] == nil { - fake.invocations[key] = [][]interface{}{} - } - fake.invocations[key] = append(fake.invocations[key], args) -} - -var _ relationship.Capturer = new(FakeCapturer) diff --git a/internal/mode/static/state/relationship/relationships_test.go b/internal/mode/static/state/relationship/relationships_test.go deleted file mode 100644 index fee64b5936..0000000000 --- a/internal/mode/static/state/relationship/relationships_test.go +++ /dev/null @@ -1,164 +0,0 @@ -package relationship - -import ( - "testing" - - . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - v1 "sigs.k8s.io/gateway-api/apis/v1" - - "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" -) - -func TestGetBackendServiceNamesFromRoute(t *testing.T) { - getNormalRefs := func(svcName v1.ObjectName) []v1.HTTPBackendRef { - return []v1.HTTPBackendRef{ - { - BackendRef: v1.BackendRef{ - BackendObjectReference: v1.BackendObjectReference{ - Kind: (*v1.Kind)(helpers.GetPointer("Service")), - Name: svcName, - Namespace: (*v1.Namespace)(helpers.GetPointer("test")), - Port: (*v1.PortNumber)(helpers.GetPointer[int32](80)), - }, - }, - }, - } - } - - getModifiedRefs := func( - svcName v1.ObjectName, - mod func([]v1.HTTPBackendRef) []v1.HTTPBackendRef, - ) []v1.HTTPBackendRef { - return mod(getNormalRefs(svcName)) - } - - hr := &v1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{Namespace: "test"}, - Spec: v1.HTTPRouteSpec{ - Rules: []v1.HTTPRouteRule{ - { - BackendRefs: getNormalRefs("svc1"), - }, - { - BackendRefs: getNormalRefs("svc1"), // duplicate - }, - { - BackendRefs: getModifiedRefs( - "invalid-kind", - func(refs []v1.HTTPBackendRef) []v1.HTTPBackendRef { - refs[0].Kind = (*v1.Kind)(helpers.GetPointer("Invalid")) - return refs - }, - ), - }, - { - BackendRefs: getModifiedRefs( - "nil-namespace", - func(refs []v1.HTTPBackendRef) []v1.HTTPBackendRef { - refs[0].Namespace = nil - return refs - }, - ), - }, - { - BackendRefs: getModifiedRefs( - "diff-namespace", - func(refs []v1.HTTPBackendRef) []v1.HTTPBackendRef { - refs[0].Namespace = (*v1.Namespace)( - helpers.GetPointer("not-test"), - ) - return refs - }, - ), - }, - { - BackendRefs: nil, - }, - { - BackendRefs: getNormalRefs("svc2"), - }, - { - BackendRefs: getModifiedRefs( - "multiple-refs", - func(refs []v1.HTTPBackendRef) []v1.HTTPBackendRef { - return append(refs, v1.HTTPBackendRef{ - BackendRef: v1.BackendRef{ - BackendObjectReference: v1.BackendObjectReference{ - Kind: (*v1.Kind)( - helpers.GetPointer("Service"), - ), - Name: "multiple-refs2", - Namespace: (*v1.Namespace)( - helpers.GetPointer("test"), - ), - Port: (*v1.PortNumber)( - helpers.GetPointer[int32](80), - ), - }, - }, - }) - }), - }, - }, - }, - } - - expNames := map[types.NamespacedName]struct{}{ - {Namespace: "test", Name: "svc1"}: {}, - {Namespace: "test", Name: "nil-namespace"}: {}, - {Namespace: "not-test", Name: "diff-namespace"}: {}, - {Namespace: "test", Name: "svc2"}: {}, - {Namespace: "test", Name: "multiple-refs"}: {}, - {Namespace: "test", Name: "multiple-refs2"}: {}, - } - - g := NewWithT(t) - names := getBackendServiceNamesFromRoute(hr) - g.Expect(names).To(Equal(expNames)) -} - -func TestCapturerImpl_DecrementRouteCount(t *testing.T) { - testcases := []struct { - msg string - startingRefCount int - expectedRefCount int - exists bool - }{ - { - msg: "service does not exist in map", - startingRefCount: 0, - expectedRefCount: 0, - exists: false, - }, - { - msg: "service has ref count of 1", - startingRefCount: 1, - expectedRefCount: 0, - exists: false, - }, - { - msg: "service has ref count of 2", - startingRefCount: 2, - expectedRefCount: 1, - exists: true, - }, - } - - capturer := NewCapturerImpl() - svc := types.NamespacedName{Namespace: "test", Name: "svc"} - - for _, tc := range testcases { - g := NewWithT(t) - if tc.startingRefCount > 0 { - capturer.serviceRefCount[svc] = tc.startingRefCount - } - - capturer.decrementRefCount(svc) - - count, exists := capturer.serviceRefCount[svc] - g.Expect(exists).To(Equal(tc.exists)) - g.Expect(count).To(Equal(tc.expectedRefCount)) - } -} diff --git a/internal/mode/static/state/store.go b/internal/mode/static/state/store.go index 73dff58d65..f56657cbc6 100644 --- a/internal/mode/static/state/store.go +++ b/internal/mode/static/state/store.go @@ -8,8 +8,6 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/relationship" ) // Updater updates the cluster state. @@ -127,7 +125,6 @@ type changeTrackingUpdaterObjectTypeCfg struct { // based on the decision by the relationship capturer. type changeTrackingUpdater struct { store *multiObjectStore - capturer relationship.Capturer stateChangedPredicates map[schema.GroupVersionKind]stateChangedPredicate extractGVK extractGVKFunc @@ -138,7 +135,6 @@ type changeTrackingUpdater struct { } func newChangeTrackingUpdater( - capturer relationship.Capturer, extractGVK extractGVKFunc, objectTypeCfgs []changeTrackingUpdaterObjectTypeCfg, ) *changeTrackingUpdater { @@ -168,7 +164,6 @@ func newChangeTrackingUpdater( extractGVK: extractGVK, supportedGVKs: supportedGVKs, persistedGVKs: persistedGVKs, - capturer: capturer, stateChangedPredicates: stateChangedPredicates, } } @@ -203,18 +198,13 @@ func (s *changeTrackingUpdater) Upsert(obj client.Object) { s.assertSupportedGVK(s.extractGVK(obj)) changingUpsert := s.upsert(obj) - relationshipExisted := s.capturer.Exists(obj, client.ObjectKeyFromObject(obj)) - - s.capturer.Capture(obj) - - relationshipExists := s.capturer.Exists(obj, client.ObjectKeyFromObject(obj)) // FIXME(pleshakov): Check generation in all cases to minimize the number of Graph regeneration. // s.changed can be true even if the generation of the object did not change, because // capturer and triggerStateChange don't take the generation into account. // See https://github.com/nginxinc/nginx-gateway-fabric/issues/825 - s.changed = s.changed || changingUpsert || relationshipExisted || relationshipExists + s.changed = s.changed || changingUpsert } func (s *changeTrackingUpdater) delete(objType client.Object, nsname types.NamespacedName) (changed bool) { @@ -242,9 +232,7 @@ func (s *changeTrackingUpdater) Delete(objType client.Object, nsname types.Names changingDelete := s.delete(objType, nsname) - s.changed = s.changed || changingDelete || s.capturer.Exists(objType, nsname) - - s.capturer.Remove(objType, nsname) + s.changed = s.changed || changingDelete } // getAndResetChangedStatus returns true if the previous updates (Upserts/Deletes) require an update of From 27fd27985efd6511bd9566e1ed914e6697b02881 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 11 Jan 2024 17:09:48 -0800 Subject: [PATCH 16/41] Add service tests --- .../mode/static/state/graph/service_test.go | 409 ++++++++++++++++++ .../mode/static/state/resolver/resolver.go | 9 +- 2 files changed, 413 insertions(+), 5 deletions(-) create mode 100644 internal/mode/static/state/graph/service_test.go diff --git a/internal/mode/static/state/graph/service_test.go b/internal/mode/static/state/graph/service_test.go new file mode 100644 index 0000000000..aa06b44cec --- /dev/null +++ b/internal/mode/static/state/graph/service_test.go @@ -0,0 +1,409 @@ +package graph + +import ( + "testing" + + . "github.com/onsi/gomega" + + "k8s.io/apimachinery/pkg/types" +) + +func TestBuildReferencedServicesNames(t *testing.T) { + normalRoute := &Route{ + ParentRefs: []ParentRef{ + { + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + }, + Valid: true, + Rules: []Rule{ + { + BackendRefs: []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "banana-ns", Name: "service"}, + Weight: 1, + }, + }, + ValidMatches: true, + ValidFilters: true, + }, + }, + } + + validRouteTwoServicesOneRule := &Route{ + ParentRefs: []ParentRef{ + { + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + }, + Valid: true, + Rules: []Rule{ + { + BackendRefs: []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "service-ns", Name: "service"}, + Weight: 1, + }, + { + SvcNsName: types.NamespacedName{Namespace: "service-ns2", Name: "service2"}, + Weight: 1, + }, + }, + ValidMatches: true, + ValidFilters: true, + }, + }, + } + + validRouteTwoServicesTwoRules := &Route{ + ParentRefs: []ParentRef{ + { + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + }, + Valid: true, + Rules: []Rule{ + { + BackendRefs: []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "service-ns", Name: "service"}, + Weight: 1, + }, + }, + ValidMatches: true, + ValidFilters: true, + }, + { + BackendRefs: []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "service-ns2", Name: "service2"}, + Weight: 1, + }, + }, + ValidMatches: true, + ValidFilters: true, + }, + }, + } + + invalidRoute := &Route{ + ParentRefs: []ParentRef{ + { + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + }, + Valid: false, + Rules: []Rule{ + { + BackendRefs: []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "service-ns", Name: "service"}, + Weight: 1, + }, + }, + ValidMatches: true, + ValidFilters: true, + }, + }, + } + + unattachedRoute := &Route{ + ParentRefs: []ParentRef{ + { + Attachment: &ParentRefAttachmentStatus{ + Attached: false, + }, + }, + }, + Valid: true, + Rules: []Rule{ + { + BackendRefs: []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "service-ns", Name: "service"}, + Weight: 1, + }, + }, + ValidMatches: true, + ValidFilters: true, + }, + }, + } + + attachedRouteWithManyParentRefs := &Route{ + ParentRefs: []ParentRef{ + { + Attachment: &ParentRefAttachmentStatus{ + Attached: false, + }, + }, + { + Attachment: &ParentRefAttachmentStatus{ + Attached: false, + }, + }, + { + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + }, + Valid: true, + Rules: []Rule{ + { + BackendRefs: []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "service-ns", Name: "service"}, + Weight: 1, + }, + }, + ValidMatches: true, + ValidFilters: true, + }, + }, + } + invalidMatchesRuleRoute := &Route{ + ParentRefs: []ParentRef{ + { + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + }, + Valid: true, + Rules: []Rule{ + { + BackendRefs: []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "banana-ns", Name: "service"}, + Weight: 1, + }, + }, + ValidMatches: false, + ValidFilters: true, + }, + }, + } + invalidFiltersRuleRoute := &Route{ + ParentRefs: []ParentRef{ + { + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + }, + Valid: true, + Rules: []Rule{ + { + BackendRefs: []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "banana-ns", Name: "service"}, + Weight: 1, + }, + }, + ValidMatches: true, + ValidFilters: false, + }, + }, + } + invalidAndValidRulesRoute := &Route{ + ParentRefs: []ParentRef{ + { + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + }, + Valid: true, + Rules: []Rule{ + { + BackendRefs: []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "banana-ns", Name: "service"}, + Weight: 1, + }, + }, + ValidMatches: true, + ValidFilters: false, + }, + { + BackendRefs: []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "orange-ns", Name: "service"}, + Weight: 1, + }, + }, + ValidMatches: false, + ValidFilters: true, + }, + { + BackendRefs: []BackendRef{ + { + SvcNsName: types.NamespacedName{Namespace: "grape-ns", Name: "service"}, + Weight: 1, + }, + }, + ValidMatches: true, + ValidFilters: true, + }, + }, + } + validRouteNoServiceNsName := &Route{ + ParentRefs: []ParentRef{ + { + Attachment: &ParentRefAttachmentStatus{ + Attached: true, + }, + }, + }, + Valid: true, + Rules: []Rule{ + { + BackendRefs: []BackendRef{ + { + Weight: 1, + }, + }, + ValidMatches: true, + ValidFilters: true, + }, + }, + } + + tests := []struct { + routes map[types.NamespacedName]*Route + exp map[types.NamespacedName]struct{} + name string + }{ + { + name: "normal route", + routes: map[types.NamespacedName]*Route{ + {Name: "normal-route"}: normalRoute, + }, + exp: map[types.NamespacedName]struct{}{ + {Namespace: "banana-ns", Name: "service"}: {}, + }, + }, + { + name: "route with two services in one Rule", + routes: map[types.NamespacedName]*Route{ + {Name: "two-svc-one-rule"}: validRouteTwoServicesOneRule, + }, + exp: map[types.NamespacedName]struct{}{ + {Namespace: "service-ns", Name: "service"}: {}, + {Namespace: "service-ns2", Name: "service2"}: {}, + }, + }, + { + name: "route with one service per rule", + routes: map[types.NamespacedName]*Route{ + {Name: "one-svc-per-rule"}: validRouteTwoServicesTwoRules, + }, + exp: map[types.NamespacedName]struct{}{ + {Namespace: "service-ns", Name: "service"}: {}, + {Namespace: "service-ns2", Name: "service2"}: {}, + }, + }, + { + name: "two valid routes with same services", + routes: map[types.NamespacedName]*Route{ + {Name: "one-svc-per-rule"}: validRouteTwoServicesTwoRules, + {Name: "two-svc-one-rule"}: validRouteTwoServicesOneRule, + }, + exp: map[types.NamespacedName]struct{}{ + {Namespace: "service-ns", Name: "service"}: {}, + {Namespace: "service-ns2", Name: "service2"}: {}, + }, + }, + { + name: "two valid routes with different services", + routes: map[types.NamespacedName]*Route{ + {Name: "one-svc-per-rule"}: validRouteTwoServicesTwoRules, + {Name: "normal-route"}: normalRoute, + }, + exp: map[types.NamespacedName]struct{}{ + {Namespace: "service-ns", Name: "service"}: {}, + {Namespace: "service-ns2", Name: "service2"}: {}, + {Namespace: "banana-ns", Name: "service"}: {}, + }, + }, + { + name: "invalid route", + routes: map[types.NamespacedName]*Route{ + {Name: "invalid-route"}: invalidRoute, + }, + exp: nil, + }, + { + name: "unattached route", + routes: map[types.NamespacedName]*Route{ + {Name: "unattached-route"}: unattachedRoute, + }, + exp: nil, + }, + { + name: "combination of valid and invalid routes", + routes: map[types.NamespacedName]*Route{ + {Name: "normal-route"}: normalRoute, + {Name: "invalid-route"}: invalidRoute, + }, + exp: map[types.NamespacedName]struct{}{ + {Namespace: "banana-ns", Name: "service"}: {}, + }, + }, + { + name: "route with many parentRefs and one is attached", + routes: map[types.NamespacedName]*Route{ + {Name: "multiple-parent-ref-route"}: attachedRouteWithManyParentRefs, + }, + exp: map[types.NamespacedName]struct{}{ + {Namespace: "service-ns", Name: "service"}: {}, + }, + }, + { + name: "route with invalid filters rule", + routes: map[types.NamespacedName]*Route{ + {Name: "invalid-filters-rule"}: invalidFiltersRuleRoute, + }, + exp: nil, + }, + { + name: "route with invalid matches rule", + routes: map[types.NamespacedName]*Route{ + {Name: "invalid-matches-rule"}: invalidMatchesRuleRoute, + }, + exp: nil, + }, + { + name: "route with invalid and valid rules", + routes: map[types.NamespacedName]*Route{ + {Name: "invalid-and-valid-rules"}: invalidAndValidRulesRoute, + }, + exp: map[types.NamespacedName]struct{}{ + {Namespace: "grape-ns", Name: "service"}: {}, + }, + }, + { + name: "valid route no service nsname", + routes: map[types.NamespacedName]*Route{ + {Name: "no-service-nsname"}: validRouteNoServiceNsName, + }, + exp: nil, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(buildReferencedServicesNames(test.routes)).To(Equal(test.exp)) + }) + } +} diff --git a/internal/mode/static/state/resolver/resolver.go b/internal/mode/static/state/resolver/resolver.go index a4b3b9e834..076c93cfc6 100644 --- a/internal/mode/static/state/resolver/resolver.go +++ b/internal/mode/static/state/resolver/resolver.go @@ -39,16 +39,15 @@ func NewServiceResolverImpl(client client.Client) *ServiceResolverImpl { return &ServiceResolverImpl{client: client} } -// Resolve resolves a Service and BackendRef Port to a list of Endpoints. -// Returns an error if the Service or Port cannot be resolved. -// -// svcNsName is guaranteed to be a valid NamespacedName from when it is called in configuration.go. -// svcPort is guaranteed to be a valid non-empty ServicePort from when it is called in configuration.go +// Resolve resolves a Service's NamespacedName and ServicePort to a list of Endpoints. +// Returns an error if the Service or ServicePort cannot be resolved. func (e *ServiceResolverImpl) Resolve( ctx context.Context, svcNsName types.NamespacedName, svcPort v1.ServicePort, ) ([]Endpoint, error) { + // svcNsName and svcPort are guaranteed to be valid non-empty variables from when they are passed in + // from configuration.go. if svcPort.Port == 0 || svcNsName.Name == "" || svcNsName.Namespace == "" { panic(fmt.Errorf("expected the following fields to be non-empty: name: %s, ns: %s, port: %d", svcNsName.Name, svcNsName.Namespace, svcPort.Port)) From 318302e33d3fe066af75fdf2a97a313cebe98538 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Fri, 12 Jan 2024 11:00:03 -0800 Subject: [PATCH 17/41] Add backendRef tests --- internal/mode/static/state/graph/backend_refs_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/mode/static/state/graph/backend_refs_test.go b/internal/mode/static/state/graph/backend_refs_test.go index 7681625841..d2205c752f 100644 --- a/internal/mode/static/state/graph/backend_refs_test.go +++ b/internal/mode/static/state/graph/backend_refs_test.go @@ -287,6 +287,16 @@ func TestGetServiceAndPortFromRef(t *testing.T) { expServiceNsName: types.NamespacedName{Name: "does-not-exist", Namespace: "test"}, expServicePort: v1.ServicePort{}, }, + { + name: "no matching port for service and port", + ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Port = helpers.GetPointer[gatewayv1.PortNumber](504) + return backend + }), + expErr: true, + expServiceNsName: svc1NsName, + expServicePort: v1.ServicePort{}, + }, } services := map[types.NamespacedName]*v1.Service{ From af48643aa7242a4bcd10617277055586ef47f20b Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Fri, 12 Jan 2024 15:20:37 -0800 Subject: [PATCH 18/41] Refactor part of change processor tests to work correctly --- .../static/state/change_processor_test.go | 592 +++++++++--------- 1 file changed, 300 insertions(+), 292 deletions(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 0bba5a53df..a2110fd05d 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -19,6 +19,7 @@ import ( "sigs.k8s.io/gateway-api/apis/v1beta1" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/conditions" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/controller/index" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/gatewayclass" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state" @@ -129,29 +130,29 @@ func createGatewayWithTLSListener(name string, tlsSecret *apiv1.Secret) *v1.Gate return gw } -//func createRouteWithMultipleRules( -// name, gateway, hostname string, -// rules []v1.HTTPRouteRule, -//) *v1.HTTPRoute { -// hr := createRoute(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 createRouteWithMultipleRules( + name, gateway, hostname string, + rules []v1.HTTPRouteRule, +) *v1.HTTPRoute { + hr := createRoute(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 createBackendRef( kind *v1.Kind, @@ -955,340 +956,347 @@ var _ = Describe("ChangeProcessor", func() { }) }) }) - /* - 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 - ) - createSvc := func(name string) *apiv1.Service { - return &apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: name, - }, - } + 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 + ) + + 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}, - }, - } + 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}, + }, } + } - BeforeAll(func() { - testNamespace := v1.Namespace("test") - kindService := v1.Kind("Service") - kindInvalid := v1.Kind("Invalid") - - // backend Refs - fooRef := createBackendRef(&kindService, "foo-svc", &testNamespace) - baz1NilNamespace := createBackendRef(&kindService, "baz-svc-v1", &testNamespace) - barRef := createBackendRef(&kindService, "bar-svc", nil) - baz2Ref := createBackendRef(&kindService, "baz-svc-v2", &testNamespace) - baz3Ref := createBackendRef(&kindService, "baz-svc-v3", &testNamespace) - invalidKindRef := createBackendRef(&kindInvalid, "bar-svc", &testNamespace) - - // httproutes - hr1 = createRoute("hr1", "gw", "foo.example.com", fooRef) - hr2 = createRoute("hr2", "gw", "bar.example.com", barRef) - // hr3 shares the same backendRef as hr2 - hr3 = createRoute("hr3", "gw", "bar.2.example.com", barRef) - hrInvalidBackendRef = createRoute("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), - }, - ) + BeforeAll(func() { + testNamespace := v1.Namespace("test") + kindService := v1.Kind("Service") + kindInvalid := v1.Kind("Invalid") + + // backend Refs + fooRef := createBackendRef(&kindService, "foo-svc", &testNamespace) + baz1NilNamespace := createBackendRef(&kindService, "baz-svc-v1", &testNamespace) + barRef := createBackendRef(&kindService, "bar-svc", nil) + baz2Ref := createBackendRef(&kindService, "baz-svc-v2", &testNamespace) + baz3Ref := createBackendRef(&kindService, "baz-svc-v3", &testNamespace) + invalidKindRef := createBackendRef(&kindInvalid, "bar-svc", &testNamespace) + + // httproutes + hr1 = createRoute("hr1", "gw", "foo.example.com", fooRef) + hr2 = createRoute("hr2", "gw", "bar.example.com", barRef) + // hr3 shares the same backendRef as hr2 + hr3 = createRoute("hr3", "gw", "bar.2.example.com", barRef) + hrInvalidBackendRef = createRoute("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") + // 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", "") + + gw = createGateway("gw") + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw) + processor.Process() + }) - // 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", "") - }) + testProcessChangedVal := func(expChanged bool) { + changed, _ := processor.Process() + Expect(changed).To(Equal(expChanged)) + } - testProcessChangedVal := func(expChanged bool) { - changed, _ := processor.Process() - Expect(changed).To(Equal(expChanged)) - } + testUpsertTriggersChange := func(obj client.Object, expChanged bool) { + processor.CaptureUpsertChange(obj) + testProcessChangedVal(expChanged) + } - testUpsertTriggersChange := func(obj client.Object, expChanged bool) { - processor.CaptureUpsertChange(obj) - testProcessChangedVal(expChanged) - } + testDeleteTriggersChange := func(obj client.Object, nsname types.NamespacedName, expChanged bool) { + processor.CaptureDeleteChange(obj, nsname) + testProcessChangedVal(expChanged) + } - testDeleteTriggersChange := func(obj client.Object, nsname types.NamespacedName, expChanged bool) { - processor.CaptureDeleteChange(obj, nsname) - testProcessChangedVal(expChanged) - } - When("hr1 is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1, true) - }) + When("hr1 is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1, true) }) - When("a hr1 service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1svc, true) - }) + }) + When("a hr1 service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1svc, true) }) - When("an hr1 endpoint slice is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1slice1, true) - }) + }) + When("an hr1 endpoint slice is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1slice1, true) }) - When("an hr1 service is updated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1svc, true) - }) + }) + When("an hr1 service is updated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1svc, true) }) - When("another hr1 endpoint slice is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1slice2, true) - }) + }) + When("another hr1 endpoint slice is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1slice2, true) }) - When("an endpoint slice with a missing svc name label is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(missingSvcNameSlice, false) - }) + }) + When("an endpoint slice with a missing svc name label is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(missingSvcNameSlice, false) }) - When("an hr1 endpoint slice is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr1slice1, - types.NamespacedName{Namespace: hr1slice1.Namespace, Name: hr1slice1.Name}, - true, - ) - }) + }) + When("an hr1 endpoint slice is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr1slice1, + types.NamespacedName{Namespace: hr1slice1.Namespace, Name: hr1slice1.Name}, + true, + ) }) - 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}, - true, - ) - }) + }) + 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}, + true, + ) }) - When("the second hr1 endpoint slice is recreated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1slice2, true) - }) + }) + When("the second hr1 endpoint slice is recreated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1slice2, true) }) - When("hr1 is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr1, - types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name}, - true, - ) - }) + }) + When("hr1 is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr1, + types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name}, + true, + ) }) - When("hr1 service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - hr1svc, - types.NamespacedName{Namespace: hr1svc.Namespace, Name: hr1svc.Name}, - false, - ) - }) + }) + When("hr1 service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + hr1svc, + types.NamespacedName{Namespace: hr1svc.Namespace, Name: hr1svc.Name}, + false, + ) }) - 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}, - false, - ) - }) + }) + 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}, + false, + ) + }) + }) + When("hr2 is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr2, true) + }) + }) + When("a hr3, that shares a backend service with hr2, is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr3, true) + }) + }) + When("sharedSvc, a service referenced by both hr2 and hr3, is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(sharedSvc, true) + }) + }) + When("hr2 is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr2, + types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name}, + true, + ) + }) + }) + When("sharedSvc is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + sharedSvc, + types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, + true, + ) }) - When("hr2 is added", func() { + }) + When("sharedSvc is recreated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(sharedSvc, true) + }) + }) + When("hr3 is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr3, + types.NamespacedName{Namespace: hr3.Namespace, Name: hr3.Name}, + true, + ) + }) + }) + When("sharedSvc is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + sharedSvc, + types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, + false, + ) + }) + }) + When("a service that is not referenced by any route is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(notRefSvc, false) + }) + }) + When("a route with an invalid backend ref type is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hrInvalidBackendRef, true) + }) + }) + When("a service with a namespace name that matches invalid backend ref is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(invalidSvc, false) + }) + }) + When("an endpoint slice that is not owned by a referenced service is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(noRefSlice, false) + }) + }) + 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}, + false, + ) + }) + }) + 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(hr2, true) + testUpsertTriggersChange(hrMultipleRules, true) }) }) - When("a hr3, that shares a backend service with hr2, is added", func() { + When("first referenced service is added", func() { It("should trigger a change", func() { - testUpsertTriggersChange(hr3, true) + testUpsertTriggersChange(bazSvc1, true) }) }) - When("sharedSvc, a service referenced by both hr2 and hr3, is added", func() { + When("second referenced service is added", func() { It("should trigger a change", func() { - testUpsertTriggersChange(sharedSvc, true) + testUpsertTriggersChange(bazSvc2, true) }) }) - When("hr2 is deleted", func() { + When("first referenced service is deleted", func() { It("should trigger a change", func() { testDeleteTriggersChange( - hr2, - types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name}, + bazSvc1, + types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, true, ) }) }) - When("sharedSvc is deleted", func() { + When("first referenced service is recreated", func() { It("should trigger a change", func() { - testDeleteTriggersChange( - sharedSvc, - types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, - true, - ) + testUpsertTriggersChange(bazSvc1, true) + }) + }) + When("third referenced service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc3, true) }) }) - When("sharedSvc is recreated", func() { + When("third referenced service is updated", func() { It("should trigger a change", func() { - testUpsertTriggersChange(sharedSvc, true) + testUpsertTriggersChange(bazSvc3, true) }) }) - When("hr3 is deleted", func() { + When("route is deleted", func() { It("should trigger a change", func() { testDeleteTriggersChange( - hr3, - types.NamespacedName{Namespace: hr3.Namespace, Name: hr3.Name}, + hrMultipleRules, + types.NamespacedName{ + Namespace: hrMultipleRules.Namespace, + Name: hrMultipleRules.Name, + }, true, ) }) }) - When("sharedSvc is deleted", func() { + When("first referenced service is deleted", func() { It("should not trigger a change", func() { testDeleteTriggersChange( - sharedSvc, - types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, + bazSvc1, + types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, false, ) }) }) - When("a service that is not referenced by any route is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(notRefSvc, false) - }) - }) - When("a route with an invalid backend ref type is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hrInvalidBackendRef, true) - }) - }) - When("a service with a namespace name that matches invalid backend ref is added", func() { + When("second referenced service is deleted", func() { It("should not trigger a change", func() { - testUpsertTriggersChange(invalidSvc, false) - }) - }) - When("an endpoint slice that is not owned by a referenced service is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(noRefSlice, false) + testDeleteTriggersChange( + bazSvc2, + types.NamespacedName{Namespace: bazSvc2.Namespace, Name: bazSvc2.Name}, + false, + ) }) }) - When("an endpoint slice that is not owned by a referenced service is deleted", func() { + When("final referenced service is deleted", func() { It("should not trigger a change", func() { testDeleteTriggersChange( - noRefSlice, - types.NamespacedName{Namespace: noRefSlice.Namespace, Name: noRefSlice.Name}, + bazSvc3, + types.NamespacedName{Namespace: bazSvc3.Namespace, Name: bazSvc3.Name}, false, ) }) }) - 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, true) - }) - }) - When("first referenced service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc1, true) - }) - }) - When("second referenced service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc2, true) - }) - }) - When("first referenced service is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - bazSvc1, - types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, - true, - ) - }) - }) - When("first referenced service is recreated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc1, true) - }) - }) - When("third referenced service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc3, true) - }) - }) - When("third referenced service is updated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc3, true) - }) - }) - When("route is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hrMultipleRules, - types.NamespacedName{ - Namespace: hrMultipleRules.Namespace, - Name: hrMultipleRules.Name, - }, - true, - ) - }) - }) - When("first referenced service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc1, - types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, - false, - ) - }) - }) - When("second referenced service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc2, - types.NamespacedName{Namespace: bazSvc2.Namespace, Name: bazSvc2.Name}, - false, - ) - }) - }) - When("final referenced service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc3, - types.NamespacedName{Namespace: bazSvc3.Namespace, Name: bazSvc3.Name}, - false, - ) - }) - }) - }) }) - */ + }) + Describe("namespace changes", Ordered, func() { var ( ns, nsDifferentLabels, nsNoLabels *apiv1.Namespace From 8e29ddddba92736800997c69571bed3163ac59b6 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Fri, 12 Jan 2024 15:21:42 -0800 Subject: [PATCH 19/41] Remove tests from change_processor --- .../static/state/change_processor_test.go | 178 +----------------- 1 file changed, 4 insertions(+), 174 deletions(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index a2110fd05d..4e67594e14 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -1466,14 +1466,10 @@ var _ = Describe("ChangeProcessor", func() { var ( processor *state.ChangeProcessorImpl gcNsName, gwNsName, hrNsName, hr2NsName, rgNsName types.NamespacedName - // svcNsName, sliceNsName, secretNsName types.NamespacedName - gc, gcUpdated *v1.GatewayClass - gw1, gw1Updated, gw2 *v1.Gateway - hr1, hr1Updated, hr2 *v1.HTTPRoute - rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant - // svc *apiv1.Service - // slice *discoveryV1.EndpointSlice - // secret *apiv1.Secret + gc, gcUpdated *v1.GatewayClass + gw1, gw1Updated, gw2 *v1.Gateway + hr1, hr1Updated, hr2 *v1.HTTPRoute + rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant ) BeforeEach(OncePerOrdered, func() { @@ -1530,24 +1526,6 @@ var _ = Describe("ChangeProcessor", func() { hr2 = hr1.DeepCopy() hr2.Name = hr2NsName.Name - // svcNsName = types.NamespacedName{Namespace: "test", Name: "svc"} - - //svc = &apiv1.Service{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: svcNsName.Namespace, - // Name: svcNsName.Name, - // }, - //} - - // sliceNsName = types.NamespacedName{Namespace: "test", Name: "slice"} - - //slice = &discoveryV1.EndpointSlice{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: sliceNsName.Namespace, - // Name: sliceNsName.Name, - // }, - //} - rgNsName = types.NamespacedName{Namespace: "test", Name: "rg-1"} rg1 = &v1beta1.ReferenceGrant{ @@ -1562,15 +1540,6 @@ var _ = Describe("ChangeProcessor", func() { rg2 = rg1.DeepCopy() rg2.Name = "rg-2" - - // secretNsName = types.NamespacedName{Namespace: "test", Name: "test-secret"} - - //secret = &apiv1.Secret{ - // ObjectMeta: metav1.ObjectMeta{ - // Namespace: secretNsName.Namespace, - // Name: secretNsName.Name, - // }, - //} }) // Changing change - a change that makes processor.Process() report changed // Non-changing change - a change that doesn't do that @@ -1652,145 +1621,6 @@ var _ = Describe("ChangeProcessor", func() { Expect(changed).To(BeFalse()) }) }) - /* - Describe("Multiple Kubernetes API resource changes", Ordered, func() { - // Note: because secret resource is not used by the real relationship.Capturer, it is not used - // in the same way as service and endpoint slice in the tests below. - It("should report changed after multiple Upserts of related resources", func() { - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - - changed, _ := processor.Process() - Expect(changed).To(BeTrue()) - }) - - It("should report not changed after multiple Upserts of unrelated resources", func() { - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - - processor.CaptureUpsertChange(secret) - - changed, _ := processor.Process() - Expect(changed).To(BeFalse()) - }) - When("upserts of related resources are followed by upserts of unrelated resources", func() { - It("should report changed", func() { - // these are changing changes - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - - // there are non-changing changes - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(secret) - - changed, _ := processor.Process() - Expect(changed).To(BeTrue()) - }) - }) - 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) - - // these are non-changing changes - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(secret) - - changed, _ := processor.Process() - Expect(changed).To(BeTrue()) - }) - }) - }) - - */ - - /* - 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(hr1) - processor.CaptureUpsertChange(rg1) - - - It("should report not changed after multiple Upserts of unrelated and unchanged resources", func() { - // unchanged Gateway API resources - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(rg1) - - // unrelated Kubernetes API resources - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(secret) - - changed, _ := processor.Process() - Expect(changed).To(BeFalse()) - }) - - It("should report changed after upserting related resources followed by upserting unchanged resources", - func() { - // these are changing changes - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - - // these are non-changing changes - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(rg1) - processor.CaptureUpsertChange(secret) - - changed, _ := processor.Process() - Expect(changed).To(BeTrue()) - }, - ) - - 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(rg1Updated) - - // these are non-changing changes - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(secret) - - changed, _ := processor.Process() - Expect(changed).To(BeTrue()) - }, - ) - It( - "should report changed after upserting related resources followed by upserting unchanged resources", - func() { - // these are changing changes - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - - // these are non-changing changes - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(rg1Updated) - processor.CaptureUpsertChange(secret) - - changed, _ := processor.Process() - Expect(changed).To(BeTrue()) - }, - ) - }) - }) - - */ - Describe("Webhook validation cases", Ordered, func() { var ( processor state.ChangeProcessor From d890a786965afe945ca5c51fba906c88a98393c7 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Fri, 12 Jan 2024 15:32:53 -0800 Subject: [PATCH 20/41] Fix function layout in change processor tests --- .../static/state/change_processor_test.go | 325 +++++++++--------- 1 file changed, 163 insertions(+), 162 deletions(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 4e67594e14..66fd9d66ab 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -1800,185 +1800,186 @@ var _ = Describe("ChangeProcessor", func() { assertGwEvent() }) }) + }) + }) + Describe("Webhook assumptions", func() { + var ( + processor state.ChangeProcessor + fakeEventRecorder *record.FakeRecorder + ) - Describe("Webhook assumptions", func() { - var processor state.ChangeProcessor + BeforeEach(func() { + fakeEventRecorder = record.NewFakeRecorder(1 /* number of buffered events */) - BeforeEach(func() { - fakeEventRecorder = record.NewFakeRecorder(1 /* number of buffered events */) + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + EventRecorder: fakeEventRecorder, + Scheme: createScheme(), + }) + }) - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - EventRecorder: fakeEventRecorder, - Scheme: createScheme(), - }) - }) + createInvalidHTTPRoute := func(invalidator func(hr *v1.HTTPRoute)) *v1.HTTPRoute { + hr := createRoute( + "hr", + "gateway", + "foo.example.com", + createBackendRef( + helpers.GetPointer[v1.Kind]("Service"), + "test", + helpers.GetPointer[v1.Namespace]("namespace"), + ), + ) + invalidator(hr) + return hr + } - createInvalidHTTPRoute := func(invalidator func(hr *v1.HTTPRoute)) *v1.HTTPRoute { - hr := createRoute( - "hr", - "gateway", - "foo.example.com", - createBackendRef( - helpers.GetPointer[v1.Kind]("Service"), - "test", - helpers.GetPointer[v1.Namespace]("namespace"), - ), - ) - invalidator(hr) - return hr - } + createInvalidGateway := func(invalidator func(gw *v1.Gateway)) *v1.Gateway { + gw := createGateway("gateway") + invalidator(gw) + return gw + } - createInvalidGateway := func(invalidator func(gw *v1.Gateway)) *v1.Gateway { - gw := createGateway("gateway") - invalidator(gw) - return gw - } + assertRejectedEvent := func() { + EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(ContainSubstring("Rejected"))) + } - assertRejectedEvent := func() { - EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(ContainSubstring("Rejected"))) - } + DescribeTable("Invalid HTTPRoutes", + func(hr *v1.HTTPRoute) { + processor.CaptureUpsertChange(hr) - DescribeTable("Invalid HTTPRoutes", - func(hr *v1.HTTPRoute) { - processor.CaptureUpsertChange(hr) + changed, graphCfg := processor.Process() - changed, graphCfg := processor.Process() + Expect(changed).To(BeFalse()) + Expect(graphCfg).To(BeNil()) - Expect(changed).To(BeFalse()) - Expect(graphCfg).To(BeNil()) + assertRejectedEvent() + }, + Entry( + "duplicate parentRefs", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.ParentRefs = append(hr.Spec.ParentRefs, hr.Spec.ParentRefs[len(hr.Spec.ParentRefs)-1]) + }), + ), + Entry( + "nil path.Type", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.Rules[0].Matches[0].Path.Type = nil + }), + ), + Entry("nil path.Value", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.Rules[0].Matches[0].Path.Value = nil + }), + ), + Entry( + "nil request.Redirect", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.Rules[0].Filters = append(hr.Spec.Rules[0].Filters, v1.HTTPRouteFilter{ + Type: v1.HTTPRouteFilterRequestRedirect, + RequestRedirect: nil, + }) + }), + ), + Entry("nil port in BackendRef", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.Rules[0].BackendRefs[0].Port = nil + }), + ), + ) - assertRejectedEvent() - }, - Entry( - "duplicate parentRefs", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.ParentRefs = append(hr.Spec.ParentRefs, hr.Spec.ParentRefs[len(hr.Spec.ParentRefs)-1]) - }), - ), - Entry( - "nil path.Type", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.Rules[0].Matches[0].Path.Type = nil - }), - ), - Entry("nil path.Value", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.Rules[0].Matches[0].Path.Value = nil - }), - ), - Entry( - "nil request.Redirect", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.Rules[0].Filters = append(hr.Spec.Rules[0].Filters, v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterRequestRedirect, - RequestRedirect: nil, - }) - }), - ), - Entry("nil port in BackendRef", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.Rules[0].BackendRefs[0].Port = nil - }), - ), - ) + DescribeTable("Invalid Gateway resources", + func(gw *v1.Gateway) { + processor.CaptureUpsertChange(gw) - DescribeTable("Invalid Gateway resources", - func(gw *v1.Gateway) { - processor.CaptureUpsertChange(gw) + changed, graphCfg := processor.Process() - changed, graphCfg := processor.Process() + Expect(changed).To(BeFalse()) + Expect(graphCfg).To(BeNil()) - Expect(changed).To(BeFalse()) - Expect(graphCfg).To(BeNil()) + assertRejectedEvent() + }, + Entry("tls in HTTP listener", + createInvalidGateway(func(gw *v1.Gateway) { + gw.Spec.Listeners[0].TLS = &v1.GatewayTLSConfig{} + }), + ), + Entry("tls is nil in HTTPS listener", + createInvalidGateway(func(gw *v1.Gateway) { + gw.Spec.Listeners[0].Protocol = v1.HTTPSProtocolType + gw.Spec.Listeners[0].TLS = nil + }), + ), + Entry("zero certificateRefs in HTTPS listener", + createInvalidGateway(func(gw *v1.Gateway) { + gw.Spec.Listeners[0].Protocol = v1.HTTPSProtocolType + gw.Spec.Listeners[0].TLS = &v1.GatewayTLSConfig{ + Mode: helpers.GetPointer(v1.TLSModeTerminate), + CertificateRefs: nil, + } + }), + ), + Entry("listener hostnames conflict", + createInvalidGateway(func(gw *v1.Gateway) { + gw.Spec.Listeners = append(gw.Spec.Listeners, v1.Listener{ + Name: "listener-80-2", + Hostname: nil, + Port: 80, + Protocol: v1.HTTPProtocolType, + }) + }), + ), + ) + }) + Describe("Edge cases with panic", func() { + var processor state.ChangeProcessor - assertRejectedEvent() - }, - Entry("tls in HTTP listener", - createInvalidGateway(func(gw *v1.Gateway) { - gw.Spec.Listeners[0].TLS = &v1.GatewayTLSConfig{} - }), - ), - Entry("tls is nil in HTTPS listener", - createInvalidGateway(func(gw *v1.Gateway) { - gw.Spec.Listeners[0].Protocol = v1.HTTPSProtocolType - gw.Spec.Listeners[0].TLS = nil - }), - ), - Entry("zero certificateRefs in HTTPS listener", - createInvalidGateway(func(gw *v1.Gateway) { - gw.Spec.Listeners[0].Protocol = v1.HTTPSProtocolType - gw.Spec.Listeners[0].TLS = &v1.GatewayTLSConfig{ - Mode: helpers.GetPointer(v1.TLSModeTerminate), - CertificateRefs: nil, - } - }), - ), - Entry("listener hostnames conflict", - createInvalidGateway(func(gw *v1.Gateway) { - gw.Spec.Listeners = append(gw.Spec.Listeners, v1.Listener{ - Name: "listener-80-2", - Hostname: nil, - Port: 80, - Protocol: v1.HTTPProtocolType, - }) - }), - ), - ) + BeforeEach(func() { + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: "test.controller", + GatewayClassName: "my-class", + Validators: createAlwaysValidValidators(), + Scheme: createScheme(), }) }) - 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(), - Scheme: 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("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 client.Object, 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"}, - ), - ) - }) + DescribeTable( + "CaptureDeleteChange must panic", + func(resourceType client.Object, 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"}, + ), + ) }) }) From 1e3d23b6591760441b522ac42dae5594577882f4 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Fri, 12 Jan 2024 15:37:12 -0800 Subject: [PATCH 21/41] Change function layout in change processor tests --- .../static/state/change_processor_test.go | 506 +++++++++--------- 1 file changed, 253 insertions(+), 253 deletions(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 66fd9d66ab..a19c2915dc 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -1621,317 +1621,317 @@ var _ = Describe("ChangeProcessor", func() { Expect(changed).To(BeFalse()) }) }) - Describe("Webhook validation cases", Ordered, func() { - var ( - processor state.ChangeProcessor - fakeEventRecorder *record.FakeRecorder + }) + Describe("Webhook validation cases", Ordered, func() { + var ( + processor state.ChangeProcessor + fakeEventRecorder *record.FakeRecorder - gc *v1.GatewayClass + gc *v1.GatewayClass - gwNsName, hrNsName types.NamespacedName - gw, gwInvalid *v1.Gateway - hr, hrInvalid *v1.HTTPRoute - ) - BeforeAll(func() { - fakeEventRecorder = record.NewFakeRecorder(2 /* number of buffered events */) + gwNsName, hrNsName types.NamespacedName + gw, gwInvalid *v1.Gateway + hr, hrInvalid *v1.HTTPRoute + ) + BeforeAll(func() { + fakeEventRecorder = record.NewFakeRecorder(2 /* number of buffered events */) - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - EventRecorder: fakeEventRecorder, - Scheme: createScheme(), - }) + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + EventRecorder: fakeEventRecorder, + Scheme: createScheme(), + }) - gc = &v1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: gcName, - Generation: 1, - }, - Spec: v1.GatewayClassSpec{ - ControllerName: controllerName, - }, - } + gc = &v1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: gcName, + Generation: 1, + }, + Spec: v1.GatewayClassSpec{ + ControllerName: controllerName, + }, + } - gwNsName = types.NamespacedName{Namespace: "test", Name: "gateway"} - hrNsName = types.NamespacedName{Namespace: "test", Name: "hr"} + gwNsName = types.NamespacedName{Namespace: "test", Name: "gateway"} + hrNsName = types.NamespacedName{Namespace: "test", Name: "hr"} - gw = &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gwNsName.Namespace, - Name: gwNsName.Name, + gw = &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: gwNsName.Namespace, + Name: gwNsName.Name, + }, + Spec: v1.GatewaySpec{ + GatewayClassName: gcName, + Listeners: []v1.Listener{ + { + Name: "listener-80-1", + Hostname: helpers.GetPointer[v1.Hostname]("foo.example.com"), + Port: 80, + Protocol: v1.HTTPProtocolType, + }, }, - Spec: v1.GatewaySpec{ - GatewayClassName: gcName, - Listeners: []v1.Listener{ + }, + } + + gwInvalid = gw.DeepCopy() + // cannot have hostname for TCP protocol + gwInvalid.Spec.Listeners[0].Protocol = v1.TCPProtocolType + + hr = &v1.HTTPRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: hrNsName.Namespace, + Name: hrNsName.Name, + }, + Spec: v1.HTTPRouteSpec{ + CommonRouteSpec: v1.CommonRouteSpec{ + ParentRefs: []v1.ParentReference{ { - Name: "listener-80-1", - Hostname: helpers.GetPointer[v1.Hostname]("foo.example.com"), - Port: 80, - Protocol: v1.HTTPProtocolType, + Namespace: (*v1.Namespace)(&gw.Namespace), + Name: v1.ObjectName(gw.Name), + SectionName: (*v1.SectionName)( + helpers.GetPointer("listener-80-1"), + ), }, }, }, - } - - gwInvalid = gw.DeepCopy() - // cannot have hostname for TCP protocol - gwInvalid.Spec.Listeners[0].Protocol = v1.TCPProtocolType - - hr = &v1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: hrNsName.Namespace, - Name: hrNsName.Name, + Hostnames: []v1.Hostname{ + "foo.example.com", }, - Spec: v1.HTTPRouteSpec{ - CommonRouteSpec: v1.CommonRouteSpec{ - ParentRefs: []v1.ParentReference{ + Rules: []v1.HTTPRouteRule{ + { + Matches: []v1.HTTPRouteMatch{ { - Namespace: (*v1.Namespace)(&gw.Namespace), - Name: v1.ObjectName(gw.Name), - SectionName: (*v1.SectionName)( - helpers.GetPointer("listener-80-1"), - ), - }, - }, - }, - Hostnames: []v1.Hostname{ - "foo.example.com", - }, - Rules: []v1.HTTPRouteRule{ - { - Matches: []v1.HTTPRouteMatch{ - { - Path: &v1.HTTPPathMatch{ - Type: helpers.GetPointer(v1.PathMatchPathPrefix), - Value: helpers.GetPointer("/"), - }, + Path: &v1.HTTPPathMatch{ + Type: helpers.GetPointer(v1.PathMatchPathPrefix), + Value: helpers.GetPointer("/"), }, }, }, }, }, - } + }, + } - hrInvalid = hr.DeepCopy() - hrInvalid.Spec.Rules[0].Matches[0].Path.Type = nil // cannot be nil - }) + hrInvalid = hr.DeepCopy() + hrInvalid.Spec.Rules[0].Matches[0].Path.Type = nil // cannot be nil + }) - assertHREvent := func() { - var e string - EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(&e)) - ExpectWithOffset(1, e).To(ContainSubstring("Rejected")) - ExpectWithOffset(1, e).To(ContainSubstring("spec.rules[0].matches[0].path.type")) - } + assertHREvent := func() { + var e string + EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(&e)) + ExpectWithOffset(1, e).To(ContainSubstring("Rejected")) + ExpectWithOffset(1, e).To(ContainSubstring("spec.rules[0].matches[0].path.type")) + } - assertGwEvent := func() { - var e string - EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(&e)) - ExpectWithOffset(1, e).To(ContainSubstring("Rejected")) - ExpectWithOffset(1, e).To(ContainSubstring("spec.listeners[0].hostname")) - } + assertGwEvent := func() { + var e string + EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(&e)) + ExpectWithOffset(1, e).To(ContainSubstring("Rejected")) + ExpectWithOffset(1, e).To(ContainSubstring("spec.listeners[0].hostname")) + } - It("should process GatewayClass", func() { - processor.CaptureUpsertChange(gc) + It("should process GatewayClass", func() { + processor.CaptureUpsertChange(gc) - changed, graphCfg := processor.Process() - Expect(changed).To(BeTrue()) - Expect(graphCfg.GatewayClass).ToNot(BeNil()) - Expect(fakeEventRecorder.Events).To(HaveLen(0)) - }) + changed, graphCfg := processor.Process() + Expect(changed).To(BeTrue()) + Expect(graphCfg.GatewayClass).ToNot(BeNil()) + Expect(fakeEventRecorder.Events).To(HaveLen(0)) + }) - When("resources are invalid", func() { - It("should not process them", func() { - processor.CaptureUpsertChange(gwInvalid) - processor.CaptureUpsertChange(hrInvalid) + When("resources are invalid", func() { + It("should not process them", func() { + processor.CaptureUpsertChange(gwInvalid) + processor.CaptureUpsertChange(hrInvalid) - changed, graphCfg := processor.Process() + changed, graphCfg := processor.Process() - Expect(changed).To(BeFalse()) - Expect(graphCfg).To(BeNil()) + Expect(changed).To(BeFalse()) + Expect(graphCfg).To(BeNil()) - Expect(fakeEventRecorder.Events).To(HaveLen(2)) - assertGwEvent() - assertHREvent() - }) + Expect(fakeEventRecorder.Events).To(HaveLen(2)) + assertGwEvent() + assertHREvent() }) + }) - When("resources are valid", func() { - It("should process them", func() { - processor.CaptureUpsertChange(gw) - processor.CaptureUpsertChange(hr) + When("resources are valid", func() { + It("should process them", func() { + processor.CaptureUpsertChange(gw) + processor.CaptureUpsertChange(hr) - changed, graphCfg := processor.Process() + changed, graphCfg := processor.Process() - Expect(changed).To(BeTrue()) - Expect(graphCfg).ToNot(BeNil()) - Expect(graphCfg.Gateway).ToNot(BeNil()) - Expect(graphCfg.Routes).To(HaveLen(1)) + Expect(changed).To(BeTrue()) + Expect(graphCfg).ToNot(BeNil()) + Expect(graphCfg.Gateway).ToNot(BeNil()) + Expect(graphCfg.Routes).To(HaveLen(1)) - Expect(fakeEventRecorder.Events).To(HaveLen(0)) - }) + Expect(fakeEventRecorder.Events).To(HaveLen(0)) }) + }) - When("a new version of HTTPRoute is invalid", func() { - It("it should delete the configuration for the old one and not process the new one", func() { - processor.CaptureUpsertChange(hrInvalid) + When("a new version of HTTPRoute is invalid", func() { + It("it should delete the configuration for the old one and not process the new one", func() { + processor.CaptureUpsertChange(hrInvalid) - changed, graphCfg := processor.Process() + changed, graphCfg := processor.Process() - Expect(changed).To(BeTrue()) - Expect(graphCfg.Routes).To(HaveLen(0)) + Expect(changed).To(BeTrue()) + Expect(graphCfg.Routes).To(HaveLen(0)) - Expect(fakeEventRecorder.Events).To(HaveLen(1)) - assertHREvent() - }) + Expect(fakeEventRecorder.Events).To(HaveLen(1)) + assertHREvent() }) + }) - When("a new version of Gateway is invalid", func() { - It("it should delete the configuration for the old one and not process the new one", func() { - processor.CaptureUpsertChange(gwInvalid) + When("a new version of Gateway is invalid", func() { + It("it should delete the configuration for the old one and not process the new one", func() { + processor.CaptureUpsertChange(gwInvalid) - changed, graphCfg := processor.Process() + changed, graphCfg := processor.Process() - Expect(changed).To(BeTrue()) - Expect(graphCfg.Gateway).To(BeNil()) + Expect(changed).To(BeTrue()) + Expect(graphCfg.Gateway).To(BeNil()) - Expect(fakeEventRecorder.Events).To(HaveLen(1)) - assertGwEvent() - }) + Expect(fakeEventRecorder.Events).To(HaveLen(1)) + assertGwEvent() }) }) - }) - Describe("Webhook assumptions", func() { - var ( - processor state.ChangeProcessor - fakeEventRecorder *record.FakeRecorder - ) + Describe("Webhook assumptions", func() { + var ( + processor state.ChangeProcessor + fakeEventRecorder *record.FakeRecorder + ) - BeforeEach(func() { - fakeEventRecorder = record.NewFakeRecorder(1 /* number of buffered events */) + BeforeEach(func() { + fakeEventRecorder = record.NewFakeRecorder(1 /* number of buffered events */) - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - EventRecorder: fakeEventRecorder, - Scheme: createScheme(), + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + EventRecorder: fakeEventRecorder, + Scheme: createScheme(), + }) }) - }) - createInvalidHTTPRoute := func(invalidator func(hr *v1.HTTPRoute)) *v1.HTTPRoute { - hr := createRoute( - "hr", - "gateway", - "foo.example.com", - createBackendRef( - helpers.GetPointer[v1.Kind]("Service"), - "test", - helpers.GetPointer[v1.Namespace]("namespace"), - ), - ) - invalidator(hr) - return hr - } + createInvalidHTTPRoute := func(invalidator func(hr *v1.HTTPRoute)) *v1.HTTPRoute { + hr := createRoute( + "hr", + "gateway", + "foo.example.com", + createBackendRef( + helpers.GetPointer[v1.Kind]("Service"), + "test", + helpers.GetPointer[v1.Namespace]("namespace"), + ), + ) + invalidator(hr) + return hr + } - createInvalidGateway := func(invalidator func(gw *v1.Gateway)) *v1.Gateway { - gw := createGateway("gateway") - invalidator(gw) - return gw - } + createInvalidGateway := func(invalidator func(gw *v1.Gateway)) *v1.Gateway { + gw := createGateway("gateway") + invalidator(gw) + return gw + } - assertRejectedEvent := func() { - EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(ContainSubstring("Rejected"))) - } + assertRejectedEvent := func() { + EventuallyWithOffset(1, fakeEventRecorder.Events).Should(Receive(ContainSubstring("Rejected"))) + } - DescribeTable("Invalid HTTPRoutes", - func(hr *v1.HTTPRoute) { - processor.CaptureUpsertChange(hr) + DescribeTable("Invalid HTTPRoutes", + func(hr *v1.HTTPRoute) { + processor.CaptureUpsertChange(hr) - changed, graphCfg := processor.Process() + changed, graphCfg := processor.Process() - Expect(changed).To(BeFalse()) - Expect(graphCfg).To(BeNil()) + Expect(changed).To(BeFalse()) + Expect(graphCfg).To(BeNil()) - assertRejectedEvent() - }, - Entry( - "duplicate parentRefs", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.ParentRefs = append(hr.Spec.ParentRefs, hr.Spec.ParentRefs[len(hr.Spec.ParentRefs)-1]) - }), - ), - Entry( - "nil path.Type", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.Rules[0].Matches[0].Path.Type = nil - }), - ), - Entry("nil path.Value", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.Rules[0].Matches[0].Path.Value = nil - }), - ), - Entry( - "nil request.Redirect", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.Rules[0].Filters = append(hr.Spec.Rules[0].Filters, v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterRequestRedirect, - RequestRedirect: nil, - }) - }), - ), - Entry("nil port in BackendRef", - createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { - hr.Spec.Rules[0].BackendRefs[0].Port = nil - }), - ), - ) + assertRejectedEvent() + }, + Entry( + "duplicate parentRefs", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.ParentRefs = append(hr.Spec.ParentRefs, hr.Spec.ParentRefs[len(hr.Spec.ParentRefs)-1]) + }), + ), + Entry( + "nil path.Type", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.Rules[0].Matches[0].Path.Type = nil + }), + ), + Entry("nil path.Value", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.Rules[0].Matches[0].Path.Value = nil + }), + ), + Entry( + "nil request.Redirect", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.Rules[0].Filters = append(hr.Spec.Rules[0].Filters, v1.HTTPRouteFilter{ + Type: v1.HTTPRouteFilterRequestRedirect, + RequestRedirect: nil, + }) + }), + ), + Entry("nil port in BackendRef", + createInvalidHTTPRoute(func(hr *v1.HTTPRoute) { + hr.Spec.Rules[0].BackendRefs[0].Port = nil + }), + ), + ) - DescribeTable("Invalid Gateway resources", - func(gw *v1.Gateway) { - processor.CaptureUpsertChange(gw) + DescribeTable("Invalid Gateway resources", + func(gw *v1.Gateway) { + processor.CaptureUpsertChange(gw) - changed, graphCfg := processor.Process() + changed, graphCfg := processor.Process() - Expect(changed).To(BeFalse()) - Expect(graphCfg).To(BeNil()) + Expect(changed).To(BeFalse()) + Expect(graphCfg).To(BeNil()) - assertRejectedEvent() - }, - Entry("tls in HTTP listener", - createInvalidGateway(func(gw *v1.Gateway) { - gw.Spec.Listeners[0].TLS = &v1.GatewayTLSConfig{} - }), - ), - Entry("tls is nil in HTTPS listener", - createInvalidGateway(func(gw *v1.Gateway) { - gw.Spec.Listeners[0].Protocol = v1.HTTPSProtocolType - gw.Spec.Listeners[0].TLS = nil - }), - ), - Entry("zero certificateRefs in HTTPS listener", - createInvalidGateway(func(gw *v1.Gateway) { - gw.Spec.Listeners[0].Protocol = v1.HTTPSProtocolType - gw.Spec.Listeners[0].TLS = &v1.GatewayTLSConfig{ - Mode: helpers.GetPointer(v1.TLSModeTerminate), - CertificateRefs: nil, - } - }), - ), - Entry("listener hostnames conflict", - createInvalidGateway(func(gw *v1.Gateway) { - gw.Spec.Listeners = append(gw.Spec.Listeners, v1.Listener{ - Name: "listener-80-2", - Hostname: nil, - Port: 80, - Protocol: v1.HTTPProtocolType, - }) - }), - ), - ) + assertRejectedEvent() + }, + Entry("tls in HTTP listener", + createInvalidGateway(func(gw *v1.Gateway) { + gw.Spec.Listeners[0].TLS = &v1.GatewayTLSConfig{} + }), + ), + Entry("tls is nil in HTTPS listener", + createInvalidGateway(func(gw *v1.Gateway) { + gw.Spec.Listeners[0].Protocol = v1.HTTPSProtocolType + gw.Spec.Listeners[0].TLS = nil + }), + ), + Entry("zero certificateRefs in HTTPS listener", + createInvalidGateway(func(gw *v1.Gateway) { + gw.Spec.Listeners[0].Protocol = v1.HTTPSProtocolType + gw.Spec.Listeners[0].TLS = &v1.GatewayTLSConfig{ + Mode: helpers.GetPointer(v1.TLSModeTerminate), + CertificateRefs: nil, + } + }), + ), + Entry("listener hostnames conflict", + createInvalidGateway(func(gw *v1.Gateway) { + gw.Spec.Listeners = append(gw.Spec.Listeners, v1.Listener{ + Name: "listener-80-2", + Hostname: nil, + Port: 80, + Protocol: v1.HTTPProtocolType, + }) + }), + ), + ) + }) }) Describe("Edge cases with panic", func() { var processor state.ChangeProcessor From 35b548afd6d7f65c54d1331ee170fe43af696d1c Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 18 Jan 2024 09:59:04 -0800 Subject: [PATCH 22/41] Adjust comments in backendRef --- internal/mode/static/state/graph/backend_refs.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index c9f825f623..b1724f5aeb 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -21,6 +21,7 @@ type BackendRef struct { // Weight is the weight of the backendRef. Weight int32 // Valid indicates whether the backendRef is valid. + // No configuration should be generated for an invalid BackendRef. Valid bool } @@ -84,8 +85,6 @@ func addBackendRefsToRules( } } - // Some of the backendRef's could be invalid, but when we use them in configuration.go when building the - // Upstreams, we skip over the ones that are not valid. route.Rules[idx].BackendRefs = backendRefs } } From 785282471c226b11f61b4b1d44290b50341ab686 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 18 Jan 2024 10:24:26 -0800 Subject: [PATCH 23/41] Remove FIXME --- internal/mode/static/state/graph/graph.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/mode/static/state/graph/graph.go b/internal/mode/static/state/graph/graph.go index 3a1aa30b3e..b933dcde8b 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -57,11 +57,6 @@ type ProtectedPorts map[int32]string // IsReferenced returns true if the Graph references the resource. func (g *Graph) IsReferenced(resourceType client.Object, nsname types.NamespacedName) bool { - // FIMXE(bjee19): For now, only works with Secrets and Namespaces. - // Support EndpointSlices so that we can remove relationship.Capturer and use the Graph - // as source to determine the relationships. - // See https://github.com/nginxinc/nginx-gateway-fabric/issues/824 - switch obj := resourceType.(type) { case *v1.Secret: _, exists := g.ReferencedSecrets[nsname] From 22227f89fd04936c852da51a4dc0adcde7732661 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 18 Jan 2024 10:43:11 -0800 Subject: [PATCH 24/41] Add assert on Process call --- internal/mode/static/state/change_processor_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index a19c2915dc..75734512b3 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -1032,7 +1032,8 @@ var _ = Describe("ChangeProcessor", func() { gw = createGateway("gw") processor.CaptureUpsertChange(gc) processor.CaptureUpsertChange(gw) - processor.Process() + changed, _ := processor.Process() + Expect(changed).To(BeTrue()) }) testProcessChangedVal := func(expChanged bool) { From 060d356537e712800a78c95e5df3325a93c2bc0e Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 18 Jan 2024 10:53:50 -0800 Subject: [PATCH 25/41] Refactor comments and logic in backendRefs --- internal/mode/static/state/graph/backend_refs.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index b1724f5aeb..f12ca30dc5 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -27,11 +27,7 @@ type BackendRef struct { // ServicePortReference returns a string representation for the service and port that is referenced by the BackendRef. func (b BackendRef) ServicePortReference() string { - // If the ServicePort's Port is 0 it means that the Port on the BackendRef - // did not match any ports on the Service's ServicePorts. - // - // If the SvcNsName Name or Namespace are empty strings, it means that the BackendRef failed validation. - if b.SvcNsName.Name == "" || b.SvcNsName.Namespace == "" || b.ServicePort.Port == 0 { + if !b.Valid { return "" } return fmt.Sprintf("%s_%s_%d", b.SvcNsName.Namespace, b.SvcNsName.Name, b.ServicePort.Port) From feef30a180d72649278b7562be56a8418c9bc991 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 18 Jan 2024 11:36:47 -0800 Subject: [PATCH 26/41] Remove unnecessary comments --- internal/mode/static/state/graph/backend_refs.go | 4 ---- internal/mode/static/state/resolver/resolver.go | 2 -- 2 files changed, 6 deletions(-) diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index f12ca30dc5..ac92869afd 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -156,15 +156,11 @@ func getServiceAndPortFromRef( svcNsName := types.NamespacedName{Name: string(ref.Name), Namespace: ns} - // If the service is unable to be found, svcNsName will still be populated with what the BackendRef - // has listed, however the ServicePort returned will be empty. svc, ok := services[svcNsName] if !ok { return svcNsName, v1.ServicePort{}, field.NotFound(refPath.Child("name"), ref.Name) } - // svcPort can be an empty v1.ServicePort{} if the BackendRef.Port did not match any ServicePorts - // // safe to dereference port here because we already validated that the port is not nil in validateBackendRef. svcPort, err := getServicePort(svc, int32(*ref.Port)) if err != nil { diff --git a/internal/mode/static/state/resolver/resolver.go b/internal/mode/static/state/resolver/resolver.go index 076c93cfc6..06a14f8675 100644 --- a/internal/mode/static/state/resolver/resolver.go +++ b/internal/mode/static/state/resolver/resolver.go @@ -46,8 +46,6 @@ func (e *ServiceResolverImpl) Resolve( svcNsName types.NamespacedName, svcPort v1.ServicePort, ) ([]Endpoint, error) { - // svcNsName and svcPort are guaranteed to be valid non-empty variables from when they are passed in - // from configuration.go. if svcPort.Port == 0 || svcNsName.Name == "" || svcNsName.Namespace == "" { panic(fmt.Errorf("expected the following fields to be non-empty: name: %s, ns: %s, port: %d", svcNsName.Name, svcNsName.Namespace, svcPort.Port)) From c7e6a392b6f650233ab81c834191685de7c7e029 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 18 Jan 2024 12:03:46 -0800 Subject: [PATCH 27/41] Remove mentions of capturer --- internal/mode/static/state/store.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/mode/static/state/store.go b/internal/mode/static/state/store.go index f56657cbc6..9c4a0ffd18 100644 --- a/internal/mode/static/state/store.go +++ b/internal/mode/static/state/store.go @@ -119,10 +119,7 @@ type changeTrackingUpdaterObjectTypeCfg struct { // // It only works with objects with the GVKs registered in changeTrackingUpdaterObjectTypeCfg. Otherwise, it panics. // -// A change is tracked when: -// - An object with a GVK with a non-nil store and the stateChangedPredicate for that object returns true. -// - An object is upserted or deleted, and it is related to another object, -// based on the decision by the relationship capturer. +// A change is tracked when an object with a GVK has its stateChangedPredicate return true. type changeTrackingUpdater struct { store *multiObjectStore stateChangedPredicates map[schema.GroupVersionKind]stateChangedPredicate From bd0dad73261cbb990a117a44e900762b4dd45d9b Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 18 Jan 2024 12:28:41 -0800 Subject: [PATCH 28/41] Refactor tests to not use pass/fail in name --- .../mode/static/state/graph/graph_test.go | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/internal/mode/static/state/graph/graph_test.go b/internal/mode/static/state/graph/graph_test.go index 5ba1147b6c..b254682b2c 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -517,19 +517,20 @@ func TestIsReferenced(t *testing.T) { }{ // Namespace tests { - name: "Namespace in graph's ReferencedNamespaces passes", + name: "Namespace in graph's ReferencedNamespaces is referenced", resource: nsInGraph, graph: graph, expected: true, }, { - name: "Namespace with a different name but same labels fails", + name: "Namespace with a different name but same labels is not referenced", resource: nsNotInGraph, graph: graph, expected: false, }, { - name: "Namespace not in ReferencedNamespaces but in Gateway Listener's AllowedRouteLabelSelector passes", + name: "Namespace not in ReferencedNamespaces but in Gateway Listener's AllowedRouteLabelSelector" + + " is referenced", resource: nsNotInGraphButInGateway, graph: graph, expected: true, @@ -537,19 +538,19 @@ func TestIsReferenced(t *testing.T) { // Secret tests { - name: "Secret in graph's ReferencedSecrets passes", + name: "Secret in graph's ReferencedSecrets is referenced", resource: baseSecret, graph: graph, expected: true, }, { - name: "Secret not in ReferencedSecrets with same Namespace and different Name fails", + name: "Secret not in ReferencedSecrets with same Namespace and different Name is not referenced", resource: sameNamespaceDifferentNameSecret, graph: graph, expected: false, }, { - name: "Secret not in ReferencedSecrets with different Namespace and same Name fails", + name: "Secret not in ReferencedSecrets with different Namespace and same Name is not referenced", resource: differentNamespaceSameNameSecret, graph: graph, expected: false, @@ -557,25 +558,25 @@ func TestIsReferenced(t *testing.T) { // Service tests { - name: "Service in graph's ReferencedServicesNames passes", + name: "Service is referenced", resource: serviceInGraph, graph: graph, expected: true, }, { - name: "Service not in graph's ReferencedServicesNames fails", + name: "Service is not referenced", resource: serviceNotInGraph, graph: graph, expected: false, }, { - name: "Service with same name but different namespace as one in graph's ReferencedServicesNames fails", + name: "Service with same name but different namespace is not referenced", resource: serviceNotInGraphSameNameDifferentNS, graph: graph, expected: false, }, { - name: "Empty Service fails", + name: "Empty Service", resource: emptyService, graph: graph, expected: false, @@ -583,19 +584,19 @@ func TestIsReferenced(t *testing.T) { // EndpointSlice tests { - name: "EndpointSlice with Service owner in graph's ReferencedServicesNames passes", + name: "EndpointSlice with Service owner in graph's ReferencedServicesNames is referenced", resource: endpointSliceInGraph, graph: graph, expected: true, }, { - name: "EndpointSlice with Service owner not in graph's ReferencedServicesNames fails", + name: "EndpointSlice with Service owner not in graph's ReferencedServicesNames is not referenced", resource: endpointSliceNotInGraph, graph: graph, expected: false, }, { - name: "Empty EndpointSlice fails", + name: "Empty EndpointSlice", resource: emptyEndpointSlice, graph: graph, expected: false, From 9bdc838c3e62f678e364c7b6ff6680e98f1b6a35 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 18 Jan 2024 12:46:01 -0800 Subject: [PATCH 29/41] Remove some comments --- internal/mode/static/state/graph/backend_refs.go | 2 +- internal/mode/static/state/graph/service.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index ac92869afd..62bdc2fd71 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -140,7 +140,7 @@ func createBackendRef( return backendRef, nil } -// The v1.ServicePort returned can be empty in two cases: +// getServiceAndPortFromRef can return an error and an empty v1.ServicePort in two cases: // 1. The Service referenced from the BackendRef does not exist in the cluster/state. // 2. The Port on the BackendRef does not match any of the ServicePorts on the Service. func getServiceAndPortFromRef( diff --git a/internal/mode/static/state/graph/service.go b/internal/mode/static/state/graph/service.go index 5342a8dc4e..665e013095 100644 --- a/internal/mode/static/state/graph/service.go +++ b/internal/mode/static/state/graph/service.go @@ -40,7 +40,9 @@ func buildReferencedServicesNames( } for _, ref := range route.Rules[idx].BackendRefs { - if ref.SvcNsName.Name != "" && ref.SvcNsName.Namespace != "" { + // Processes both valid and invalid BackendRefs as invalid ones still have referenced services + // we may want to track. + if ref.SvcNsName != (types.NamespacedName{}) { svcNames[ref.SvcNsName] = struct{}{} } } From 32307fbae83e90617cd0e692e84244a122cc98a7 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 18 Jan 2024 12:49:50 -0800 Subject: [PATCH 30/41] Rename to ReferencedServices --- .../static/state/change_processor_test.go | 18 ++++++------- internal/mode/static/state/graph/graph.go | 27 ++++++++++--------- .../mode/static/state/graph/graph_test.go | 8 +++--- internal/mode/static/state/graph/service.go | 2 +- .../mode/static/state/graph/service_test.go | 4 +-- 5 files changed, 30 insertions(+), 29 deletions(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 75734512b3..84ede14b29 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -507,7 +507,7 @@ var _ = Describe("ChangeProcessor", func() { {Namespace: "test", Name: "hr-1"}: expRouteHR1, }, ReferencedSecrets: map[types.NamespacedName]*graph.Secret{}, - ReferencedServicesNames: map[types.NamespacedName]struct{}{ + ReferencedServices: map[types.NamespacedName]struct{}{ { Namespace: "service-ns", Name: "service", @@ -577,7 +577,7 @@ var _ = Describe("ChangeProcessor", func() { } expGraph.ReferencedSecrets = nil - expGraph.ReferencedServicesNames = nil + expGraph.ReferencedServices = nil expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} @@ -630,7 +630,7 @@ var _ = Describe("ChangeProcessor", func() { expGraph.Routes[hr1Name].ParentRefs[1].Attachment = expAttachment443 expGraph.ReferencedSecrets = nil - expGraph.ReferencedServicesNames = nil + expGraph.ReferencedServices = nil expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} @@ -653,7 +653,7 @@ var _ = Describe("ChangeProcessor", func() { Source: diffNsTLSSecret, } - expGraph.ReferencedServicesNames = nil + expGraph.ReferencedServices = nil expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} changed, graphCfg := processor.Process() @@ -863,7 +863,7 @@ var _ = Describe("ChangeProcessor", func() { } expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServicesNames = nil + expGraph.ReferencedServices = nil changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) @@ -895,7 +895,7 @@ var _ = Describe("ChangeProcessor", func() { } expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServicesNames = nil + expGraph.ReferencedServices = nil changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) @@ -918,7 +918,7 @@ var _ = Describe("ChangeProcessor", func() { expGraph.ReferencedSecrets = nil expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServicesNames = nil + expGraph.ReferencedServices = nil changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) @@ -933,7 +933,7 @@ var _ = Describe("ChangeProcessor", func() { ) expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServicesNames = nil + expGraph.ReferencedServices = nil changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) @@ -948,7 +948,7 @@ var _ = Describe("ChangeProcessor", func() { ) expRouteHR1.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServicesNames = nil + expGraph.ReferencedServices = nil changed, graphCfg := processor.Process() Expect(changed).To(BeTrue()) diff --git a/internal/mode/static/state/graph/graph.go b/internal/mode/static/state/graph/graph.go index b933dcde8b..556c38496b 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -48,8 +48,9 @@ type Graph struct { ReferencedSecrets map[types.NamespacedName]*Secret // ReferencedNamespaces includes Namespaces with labels that match the Gateway Listener's label selector. ReferencedNamespaces map[types.NamespacedName]*v1.Namespace - // ReferencedServicesNames includes the names of all the Services that are referenced by at least one HTTPRoute. - ReferencedServicesNames map[types.NamespacedName]struct{} + // ReferencedServices includes the NamespacedNames of all the Services that are referenced by at least one HTTPRoute. + // Storing the whole resource is not necessary, compared to the similar maps above. + ReferencedServices map[types.NamespacedName]struct{} } // ProtectedPorts are the ports that may not be configured by a listener with a descriptive name of each port. @@ -79,14 +80,14 @@ func (g *Graph) IsReferenced(resourceType client.Object, nsname types.Namespaced return existed || exists // Service reference exists if at least one HTTPRoute references it. case *v1.Service: - _, exists := g.ReferencedServicesNames[nsname] + _, exists := g.ReferencedServices[nsname] return exists // EndpointSlice reference exists if its Service owner is referenced by at least one HTTPRoute. case *discoveryV1.EndpointSlice: svcName := index.GetServiceNameFromEndpointSlice(obj) // Service Namespace should be the same Namespace as the EndpointSlice - _, exists := g.ReferencedServicesNames[types.NamespacedName{Namespace: nsname.Namespace, Name: svcName}] + _, exists := g.ReferencedServices[types.NamespacedName{Namespace: nsname.Namespace, Name: svcName}] return exists default: return false @@ -122,17 +123,17 @@ func BuildGraph( referencedNamespaces := buildReferencedNamespaces(state.Namespaces, gw) - referencedServicesNames := buildReferencedServicesNames(routes) + referencedServices := buildReferencedServices(routes) g := &Graph{ - GatewayClass: gc, - Gateway: gw, - Routes: routes, - IgnoredGatewayClasses: processedGwClasses.Ignored, - IgnoredGateways: processedGws.Ignored, - ReferencedSecrets: secretResolver.getResolvedSecrets(), - ReferencedNamespaces: referencedNamespaces, - ReferencedServicesNames: referencedServicesNames, + GatewayClass: gc, + Gateway: gw, + Routes: routes, + IgnoredGatewayClasses: processedGwClasses.Ignored, + IgnoredGateways: processedGws.Ignored, + ReferencedSecrets: secretResolver.getResolvedSecrets(), + ReferencedNamespaces: referencedNamespaces, + ReferencedServices: referencedServices, } return g diff --git a/internal/mode/static/state/graph/graph_test.go b/internal/mode/static/state/graph/graph_test.go index b254682b2c..ed10c01d51 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -347,7 +347,7 @@ func TestBuildGraph(t *testing.T) { ReferencedNamespaces: map[types.NamespacedName]*v1.Namespace{ client.ObjectKeyFromObject(ns): ns, }, - ReferencedServicesNames: map[types.NamespacedName]struct{}{ + ReferencedServices: map[types.NamespacedName]struct{}{ client.ObjectKeyFromObject(svc): {}, }, } @@ -504,7 +504,7 @@ func TestIsReferenced(t *testing.T) { ReferencedNamespaces: map[types.NamespacedName]*v1.Namespace{ client.ObjectKeyFromObject(nsInGraph): nsInGraph, }, - ReferencedServicesNames: map[types.NamespacedName]struct{}{ + ReferencedServices: map[types.NamespacedName]struct{}{ client.ObjectKeyFromObject(serviceInGraph): {}, }, } @@ -584,13 +584,13 @@ func TestIsReferenced(t *testing.T) { // EndpointSlice tests { - name: "EndpointSlice with Service owner in graph's ReferencedServicesNames is referenced", + name: "EndpointSlice with Service owner in graph's ReferencedServices is referenced", resource: endpointSliceInGraph, graph: graph, expected: true, }, { - name: "EndpointSlice with Service owner not in graph's ReferencedServicesNames is not referenced", + name: "EndpointSlice with Service owner not in graph's ReferencedServices is not referenced", resource: endpointSliceNotInGraph, graph: graph, expected: false, diff --git a/internal/mode/static/state/graph/service.go b/internal/mode/static/state/graph/service.go index 665e013095..8cec39fd29 100644 --- a/internal/mode/static/state/graph/service.go +++ b/internal/mode/static/state/graph/service.go @@ -5,7 +5,7 @@ import ( ) // routes all have populated ParentRefs from when they were created -func buildReferencedServicesNames( +func buildReferencedServices( routes map[types.NamespacedName]*Route, ) map[types.NamespacedName]struct{} { svcNames := make(map[types.NamespacedName]struct{}) diff --git a/internal/mode/static/state/graph/service_test.go b/internal/mode/static/state/graph/service_test.go index aa06b44cec..a1e887d175 100644 --- a/internal/mode/static/state/graph/service_test.go +++ b/internal/mode/static/state/graph/service_test.go @@ -8,7 +8,7 @@ import ( "k8s.io/apimachinery/pkg/types" ) -func TestBuildReferencedServicesNames(t *testing.T) { +func TestBuildReferencedServices(t *testing.T) { normalRoute := &Route{ ParentRefs: []ParentRef{ { @@ -403,7 +403,7 @@ func TestBuildReferencedServicesNames(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { g := NewWithT(t) - g.Expect(buildReferencedServicesNames(test.routes)).To(Equal(test.exp)) + g.Expect(buildReferencedServices(test.routes)).To(Equal(test.exp)) }) } } From 58d9c5c396005e422049010b589ad8fb6f63a40d Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 18 Jan 2024 12:56:06 -0800 Subject: [PATCH 31/41] Remove unnecessary checks in service --- internal/mode/static/state/graph/service.go | 10 -- .../mode/static/state/graph/service_test.go | 109 ------------------ 2 files changed, 119 deletions(-) diff --git a/internal/mode/static/state/graph/service.go b/internal/mode/static/state/graph/service.go index 8cec39fd29..0ffd357d28 100644 --- a/internal/mode/static/state/graph/service.go +++ b/internal/mode/static/state/graph/service.go @@ -29,16 +29,6 @@ func buildReferencedServices( } for idx := range route.Rules { - // Do I still need to do these checks? As the check is made in backend_ref, basically - // if the route rules are not valid, it will not populate route.Rules[idx].BackendRefs, thus - // if we just do the for loop over the route.Rules[idx].BackendRefs = backendRefs, we should be fine? - if !route.Rules[idx].ValidMatches { - continue - } - if !route.Rules[idx].ValidFilters { - continue - } - for _, ref := range route.Rules[idx].BackendRefs { // Processes both valid and invalid BackendRefs as invalid ones still have referenced services // we may want to track. diff --git a/internal/mode/static/state/graph/service_test.go b/internal/mode/static/state/graph/service_test.go index a1e887d175..82cf2960e0 100644 --- a/internal/mode/static/state/graph/service_test.go +++ b/internal/mode/static/state/graph/service_test.go @@ -170,92 +170,6 @@ func TestBuildReferencedServices(t *testing.T) { }, }, } - invalidMatchesRuleRoute := &Route{ - ParentRefs: []ParentRef{ - { - Attachment: &ParentRefAttachmentStatus{ - Attached: true, - }, - }, - }, - Valid: true, - Rules: []Rule{ - { - BackendRefs: []BackendRef{ - { - SvcNsName: types.NamespacedName{Namespace: "banana-ns", Name: "service"}, - Weight: 1, - }, - }, - ValidMatches: false, - ValidFilters: true, - }, - }, - } - invalidFiltersRuleRoute := &Route{ - ParentRefs: []ParentRef{ - { - Attachment: &ParentRefAttachmentStatus{ - Attached: true, - }, - }, - }, - Valid: true, - Rules: []Rule{ - { - BackendRefs: []BackendRef{ - { - SvcNsName: types.NamespacedName{Namespace: "banana-ns", Name: "service"}, - Weight: 1, - }, - }, - ValidMatches: true, - ValidFilters: false, - }, - }, - } - invalidAndValidRulesRoute := &Route{ - ParentRefs: []ParentRef{ - { - Attachment: &ParentRefAttachmentStatus{ - Attached: true, - }, - }, - }, - Valid: true, - Rules: []Rule{ - { - BackendRefs: []BackendRef{ - { - SvcNsName: types.NamespacedName{Namespace: "banana-ns", Name: "service"}, - Weight: 1, - }, - }, - ValidMatches: true, - ValidFilters: false, - }, - { - BackendRefs: []BackendRef{ - { - SvcNsName: types.NamespacedName{Namespace: "orange-ns", Name: "service"}, - Weight: 1, - }, - }, - ValidMatches: false, - ValidFilters: true, - }, - { - BackendRefs: []BackendRef{ - { - SvcNsName: types.NamespacedName{Namespace: "grape-ns", Name: "service"}, - Weight: 1, - }, - }, - ValidMatches: true, - ValidFilters: true, - }, - }, - } validRouteNoServiceNsName := &Route{ ParentRefs: []ParentRef{ { @@ -368,29 +282,6 @@ func TestBuildReferencedServices(t *testing.T) { {Namespace: "service-ns", Name: "service"}: {}, }, }, - { - name: "route with invalid filters rule", - routes: map[types.NamespacedName]*Route{ - {Name: "invalid-filters-rule"}: invalidFiltersRuleRoute, - }, - exp: nil, - }, - { - name: "route with invalid matches rule", - routes: map[types.NamespacedName]*Route{ - {Name: "invalid-matches-rule"}: invalidMatchesRuleRoute, - }, - exp: nil, - }, - { - name: "route with invalid and valid rules", - routes: map[types.NamespacedName]*Route{ - {Name: "invalid-and-valid-rules"}: invalidAndValidRulesRoute, - }, - exp: map[types.NamespacedName]struct{}{ - {Namespace: "grape-ns", Name: "service"}: {}, - }, - }, { name: "valid route no service nsname", routes: map[types.NamespacedName]*Route{ From a86a0d0dc1c194f4c3e0cf62aaec44cb13c899e1 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 18 Jan 2024 12:57:27 -0800 Subject: [PATCH 32/41] Move comment --- internal/mode/static/state/graph/service.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/mode/static/state/graph/service.go b/internal/mode/static/state/graph/service.go index 0ffd357d28..67c88e69e3 100644 --- a/internal/mode/static/state/graph/service.go +++ b/internal/mode/static/state/graph/service.go @@ -4,12 +4,13 @@ import ( "k8s.io/apimachinery/pkg/types" ) -// routes all have populated ParentRefs from when they were created func buildReferencedServices( routes map[types.NamespacedName]*Route, ) map[types.NamespacedName]struct{} { svcNames := make(map[types.NamespacedName]struct{}) + // routes all have populated ParentRefs from when they were created. + // // Get all the service names referenced from all the HTTPRoutes. for _, route := range routes { // If none of the ParentRefs are attached to the Gateway, we want to skip the route. From 88e8d244ff1b4f1f99ed2ad9ebe806014753121c Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Thu, 18 Jan 2024 15:13:39 -0800 Subject: [PATCH 33/41] Rebase and remove FIXME --- .../mode/static/state/changed_predicate_test.go | 16 +++++++++++++++- internal/mode/static/state/store.go | 5 ----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/internal/mode/static/state/changed_predicate_test.go b/internal/mode/static/state/changed_predicate_test.go index e13e7cd7ef..15af3f82d0 100644 --- a/internal/mode/static/state/changed_predicate_test.go +++ b/internal/mode/static/state/changed_predicate_test.go @@ -12,13 +12,27 @@ import ( func TestFuncPredicate(t *testing.T) { alwaysTrueFunc := func(object client.Object, _ types.NamespacedName) bool { return true } + emptyObject := &v1.Pod{} p := funcPredicate{stateChanged: alwaysTrueFunc} g := NewWithT(t) g.Expect(p.delete(nil, types.NamespacedName{})).To(BeTrue()) - g.Expect(p.upsert(nil, nil)).To(BeTrue()) + g.Expect(p.upsert(nil, emptyObject)).To(BeTrue()) +} + +func TestFuncPredicate_Panic(t *testing.T) { + alwaysTrueFunc := func(object client.Object, _ types.NamespacedName) bool { return true } + + p := funcPredicate{stateChanged: alwaysTrueFunc} + + g := NewWithT(t) + + upsert := func() { + p.upsert(nil, nil) + } + g.Expect(upsert).Should(Panic()) } func TestAnnotationChangedPredicate_Delete(t *testing.T) { diff --git a/internal/mode/static/state/store.go b/internal/mode/static/state/store.go index 9c4a0ffd18..5094da50ce 100644 --- a/internal/mode/static/state/store.go +++ b/internal/mode/static/state/store.go @@ -196,11 +196,6 @@ func (s *changeTrackingUpdater) Upsert(obj client.Object) { changingUpsert := s.upsert(obj) - // FIXME(pleshakov): Check generation in all cases to minimize the number of Graph regeneration. - // s.changed can be true even if the generation of the object did not change, because - // capturer and triggerStateChange don't take the generation into account. - // See https://github.com/nginxinc/nginx-gateway-fabric/issues/825 - s.changed = s.changed || changingUpsert } From 8513f270a7b97959e74c73ef5ff5b1caf24ad1be Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Fri, 19 Jan 2024 12:33:29 -0800 Subject: [PATCH 34/41] Add feedback from review --- internal/mode/static/state/graph/service.go | 12 +++--- .../state/resolver/service_resolver_test.go | 42 +++++-------------- 2 files changed, 17 insertions(+), 37 deletions(-) diff --git a/internal/mode/static/state/graph/service.go b/internal/mode/static/state/graph/service.go index 67c88e69e3..9568b59df1 100644 --- a/internal/mode/static/state/graph/service.go +++ b/internal/mode/static/state/graph/service.go @@ -13,6 +13,10 @@ func buildReferencedServices( // // Get all the service names referenced from all the HTTPRoutes. for _, route := range routes { + if !route.Valid { + continue + } + // If none of the ParentRefs are attached to the Gateway, we want to skip the route. attached := false for _, ref := range route.ParentRefs { @@ -25,12 +29,8 @@ func buildReferencedServices( continue } - if !route.Valid { - continue - } - - for idx := range route.Rules { - for _, ref := range route.Rules[idx].BackendRefs { + for _, rule := range route.Rules { + for _, ref := range rule.BackendRefs { // Processes both valid and invalid BackendRefs as invalid ones still have referenced services // we may want to track. if ref.SvcNsName != (types.NamespacedName{}) { diff --git a/internal/mode/static/state/resolver/service_resolver_test.go b/internal/mode/static/state/resolver/service_resolver_test.go index a04254d916..ef605eb5ba 100644 --- a/internal/mode/static/state/resolver/service_resolver_test.go +++ b/internal/mode/static/state/resolver/service_resolver_test.go @@ -88,7 +88,6 @@ func createFakeK8sClient(initObjs ...client.Object) (client.Client, error) { var _ = Describe("ServiceResolver", func() { httpPortName := "http-svc-port" - httpsPortName := "https-svc-port" var ( addresses1 = []string{"9.0.0.1", "9.0.0.2"} @@ -97,33 +96,14 @@ var _ = Describe("ServiceResolver", func() { diffPortAddresses = []string{"11.0.0.1", "11.0.0.2"} dupeAddresses = []string{"9.0.0.1", "12.0.0.1", "9.0.0.2"} - svc = &v1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: "svc", - }, - Spec: v1.ServiceSpec{ - Ports: []v1.ServicePort{ - { - Name: httpPortName, - Port: 80, - TargetPort: intstr.IntOrString{ - Type: intstr.Int, - IntVal: 8080, - }, - Protocol: v1.ProtocolTCP, - }, - { - Name: httpsPortName, - Port: 443, - TargetPort: intstr.IntOrString{ - Type: intstr.String, - StrVal: "target-port", - }, - Protocol: v1.ProtocolTCP, - }, - }, + svcPort = v1.ServicePort{ + Name: httpPortName, + Port: 80, + TargetPort: intstr.IntOrString{ + Type: intstr.Int, + IntVal: 8080, }, + Protocol: v1.ProtocolTCP, } svcNsName = types.NamespacedName{ @@ -218,7 +198,7 @@ var _ = Describe("ServiceResolver", func() { }, } - endpoints, err := serviceResolver.Resolve(context.TODO(), svcNsName, svc.Spec.Ports[0]) + endpoints, err := serviceResolver.Resolve(context.TODO(), svcNsName, svcPort) Expect(err).ToNot(HaveOccurred()) Expect(endpoints).To(ConsistOf(expectedEndpoints)) }) @@ -228,7 +208,7 @@ var _ = Describe("ServiceResolver", func() { Expect(fakeK8sClient.Delete(context.TODO(), slice2)).To(Succeed()) Expect(fakeK8sClient.Delete(context.TODO(), dupeEndpointSlice)).To(Succeed()) - endpoints, err := serviceResolver.Resolve(context.TODO(), svcNsName, svc.Spec.Ports[0]) + endpoints, err := serviceResolver.Resolve(context.TODO(), svcNsName, svcPort) Expect(err).To(HaveOccurred()) Expect(endpoints).To(BeNil()) }) @@ -237,13 +217,13 @@ var _ = Describe("ServiceResolver", func() { Expect(fakeK8sClient.Delete(context.TODO(), sliceIPV6)).To(Succeed()) Expect(fakeK8sClient.Delete(context.TODO(), sliceNoMatchingPortName)).To(Succeed()) - endpoints, err := serviceResolver.Resolve(context.TODO(), svcNsName, svc.Spec.Ports[0]) + endpoints, err := serviceResolver.Resolve(context.TODO(), svcNsName, svcPort) Expect(err).To(HaveOccurred()) Expect(endpoints).To(BeNil()) }) It("panics if the service NamespacedName is empty", func() { resolve := func() { - _, _ = serviceResolver.Resolve(context.TODO(), types.NamespacedName{}, svc.Spec.Ports[0]) + _, _ = serviceResolver.Resolve(context.TODO(), types.NamespacedName{}, svcPort) } Expect(resolve).Should(Panic()) }) From 4efe47e59ec948ec0bda4bbd4c2a6bd2dd93b041 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Fri, 19 Jan 2024 12:49:03 -0800 Subject: [PATCH 35/41] Add persists to multiObjectStore --- internal/mode/static/state/store.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/internal/mode/static/state/store.go b/internal/mode/static/state/store.go index 5094da50ce..2c8f43c751 100644 --- a/internal/mode/static/state/store.go +++ b/internal/mode/static/state/store.go @@ -69,17 +69,20 @@ func (list gvkList) contains(gvk schema.GroupVersionKind) bool { } type multiObjectStore struct { - stores map[schema.GroupVersionKind]objectStore - extractGVK extractGVKFunc + stores map[schema.GroupVersionKind]objectStore + extractGVK extractGVKFunc + persistedGVKs gvkList } func newMultiObjectStore( stores map[schema.GroupVersionKind]objectStore, extractGVK extractGVKFunc, + persistedGVKs gvkList, ) *multiObjectStore { return &multiObjectStore{ - stores: stores, - extractGVK: extractGVK, + stores: stores, + extractGVK: extractGVK, + persistedGVKs: persistedGVKs, } } @@ -106,6 +109,10 @@ func (m *multiObjectStore) delete(objType client.Object, nsname types.Namespaced m.mustFindStoreForObj(objType).delete(nsname) } +func (m *multiObjectStore) persists(objTypeGVK schema.GroupVersionKind) bool { + return m.persistedGVKs.contains(objTypeGVK) +} + type changeTrackingUpdaterObjectTypeCfg struct { // store holds the objects of the gvk. If the store is nil, the objects of the gvk are not persisted. store objectStore @@ -157,7 +164,7 @@ func newChangeTrackingUpdater( } return &changeTrackingUpdater{ - store: newMultiObjectStore(stores, extractGVK), + store: newMultiObjectStore(stores, extractGVK, persistedGVKs), extractGVK: extractGVK, supportedGVKs: supportedGVKs, persistedGVKs: persistedGVKs, @@ -176,8 +183,7 @@ func (s *changeTrackingUpdater) upsert(obj client.Object) (changed bool) { var oldObj client.Object - // persistedGVKs will only contain objTypeGVK if the resource is being tracked in the store. - if s.persistedGVKs.contains(objTypeGVK) { + if s.store.persists(objTypeGVK) { oldObj = s.store.get(obj, client.ObjectKeyFromObject(obj)) s.store.upsert(obj) @@ -202,8 +208,7 @@ func (s *changeTrackingUpdater) Upsert(obj client.Object) { func (s *changeTrackingUpdater) delete(objType client.Object, nsname types.NamespacedName) (changed bool) { objTypeGVK := s.extractGVK(objType) - // persistedGVKs will only contain objTypeGVK if the resource is being tracked in the store. - if s.persistedGVKs.contains(objTypeGVK) { + if s.store.persists(objTypeGVK) { if s.store.get(objType, nsname) == nil { return false } From 1580fe1bac2254dabf14e357cffa38c3b3849c39 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Fri, 19 Jan 2024 14:34:37 -0800 Subject: [PATCH 36/41] Remove alwaysTrue predicate and resolve FIXME --- internal/mode/static/state/change_processor.go | 8 ++++---- internal/mode/static/state/changed_predicate.go | 7 ------- internal/mode/static/state/store.go | 10 +++++----- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/internal/mode/static/state/change_processor.go b/internal/mode/static/state/change_processor.go index 9f8bfcfdaf..ff0034636d 100644 --- a/internal/mode/static/state/change_processor.go +++ b/internal/mode/static/state/change_processor.go @@ -121,22 +121,22 @@ func NewChangeProcessorImpl(cfg ChangeProcessorConfig) *ChangeProcessorImpl { { gvk: extractGVK(&v1.GatewayClass{}), store: newObjectStoreMapAdapter(clusterStore.GatewayClasses), - predicate: alwaysProcess{}, + predicate: nil, }, { gvk: extractGVK(&v1.Gateway{}), store: newObjectStoreMapAdapter(clusterStore.Gateways), - predicate: alwaysProcess{}, + predicate: nil, }, { gvk: extractGVK(&v1.HTTPRoute{}), store: newObjectStoreMapAdapter(clusterStore.HTTPRoutes), - predicate: alwaysProcess{}, + predicate: nil, }, { gvk: extractGVK(&v1beta1.ReferenceGrant{}), store: newObjectStoreMapAdapter(clusterStore.ReferenceGrants), - predicate: alwaysProcess{}, + predicate: nil, }, { gvk: extractGVK(&apiv1.Namespace{}), diff --git a/internal/mode/static/state/changed_predicate.go b/internal/mode/static/state/changed_predicate.go index 4f294a2a8c..44b1ff71c9 100644 --- a/internal/mode/static/state/changed_predicate.go +++ b/internal/mode/static/state/changed_predicate.go @@ -31,13 +31,6 @@ func (f funcPredicate) delete(object client.Object, nsname types.NamespacedName) return f.stateChanged(object, nsname) } -// FIXME(kevin85421): We should remove this predicate and update changeTrackingUpdater once #1432 is merged. -type alwaysProcess struct{} - -func (alwaysProcess) delete(_ client.Object, _ types.NamespacedName) bool { return true } - -func (alwaysProcess) upsert(_, _ client.Object) bool { return true } - // annotationChangedPredicate implements stateChangedPredicate based on the value of the annotation provided. // This predicate will return true on upsert if the annotation's value has changed. // It always returns true on delete. diff --git a/internal/mode/static/state/store.go b/internal/mode/static/state/store.go index 2c8f43c751..c73d992462 100644 --- a/internal/mode/static/state/store.go +++ b/internal/mode/static/state/store.go @@ -116,8 +116,8 @@ func (m *multiObjectStore) persists(objTypeGVK schema.GroupVersionKind) bool { type changeTrackingUpdaterObjectTypeCfg struct { // store holds the objects of the gvk. If the store is nil, the objects of the gvk are not persisted. store objectStore - // predicate determines if upsert or delete event should trigger a change. - // If predicate is nil, then no upsert or delete event for this object will trigger a change. + // predicate determines how an upsert or delete event should trigger a change. + // If predicate is nil, then all upsert or delete events for this object will trigger a change. predicate stateChangedPredicate gvk schema.GroupVersionKind } @@ -126,7 +126,7 @@ type changeTrackingUpdaterObjectTypeCfg struct { // // It only works with objects with the GVKs registered in changeTrackingUpdaterObjectTypeCfg. Otherwise, it panics. // -// A change is tracked when an object with a GVK has its stateChangedPredicate return true. +// A change is tracked when an object with a GVK has its stateChangedPredicate return true or if its predicate is nil. type changeTrackingUpdater struct { store *multiObjectStore stateChangedPredicates map[schema.GroupVersionKind]stateChangedPredicate @@ -191,7 +191,7 @@ func (s *changeTrackingUpdater) upsert(obj client.Object) (changed bool) { stateChanged, ok := s.stateChangedPredicates[objTypeGVK] if !ok { - return false + return true } return stateChanged.upsert(oldObj, obj) @@ -218,7 +218,7 @@ func (s *changeTrackingUpdater) delete(objType client.Object, nsname types.Names stateChanged, ok := s.stateChangedPredicates[objTypeGVK] if !ok { - return false + return true } return stateChanged.delete(objType, nsname) From a42e2d2e3147d086b68dd37b4e8d4891a036c2b0 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Fri, 19 Jan 2024 18:00:04 -0800 Subject: [PATCH 37/41] Add back some change_processor tests --- .../static/state/change_processor_test.go | 172 ++++++++++++++++-- 1 file changed, 152 insertions(+), 20 deletions(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 84ede14b29..07df09d7e2 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -1465,23 +1465,25 @@ var _ = Describe("ChangeProcessor", func() { // -- this is done in 'Normal cases of processing changes' var ( - processor *state.ChangeProcessorImpl - gcNsName, gwNsName, hrNsName, hr2NsName, rgNsName types.NamespacedName - gc, gcUpdated *v1.GatewayClass - gw1, gw1Updated, gw2 *v1.Gateway - hr1, hr1Updated, hr2 *v1.HTTPRoute - rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant + processor *state.ChangeProcessorImpl + gcNsName, gwNsName, hrNsName, hr2NsName, rgNsName, svcNsName, sliceNsName types.NamespacedName + gc, gcUpdated *v1.GatewayClass + gw1, gw1Updated, gw2 *v1.Gateway + hr1, hr1Updated, hr2 *v1.HTTPRoute + rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant + svc, barSvc, unrelatedSvc *apiv1.Service + slice, barSlice, unrelatedSlice *discoveryV1.EndpointSlice ) BeforeEach(OncePerOrdered, func() { processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ GatewayCtlrName: "test.controller", - GatewayClassName: "my-class", + GatewayClassName: "test-class", Validators: createAlwaysValidValidators(), Scheme: createScheme(), }) - gcNsName = types.NamespacedName{Name: "my-class"} + gcNsName = types.NamespacedName{Name: "test-class"} gc = &v1.GatewayClass{ ObjectMeta: metav1.ObjectMeta{ @@ -1497,12 +1499,7 @@ var _ = Describe("ChangeProcessor", func() { gwNsName = types.NamespacedName{Namespace: "test", Name: "gw-1"} - gw1 = &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gwNsName.Namespace, - Name: gwNsName.Name, - }, - } + gw1 = createGateway("gw-1") gw1Updated = gw1.DeepCopy() gw1Updated.Generation++ @@ -1510,14 +1507,14 @@ var _ = Describe("ChangeProcessor", func() { gw2 = gw1.DeepCopy() gw2.Name = "gw-2" + testNamespace := v1.Namespace("test") + kindService := v1.Kind("Service") + fooRef := createBackendRef(&kindService, "foo-svc", &testNamespace) + barRef := createBackendRef(&kindService, "bar-svc", &testNamespace) + hrNsName = types.NamespacedName{Namespace: "test", Name: "hr-1"} - hr1 = &v1.HTTPRoute{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: hrNsName.Namespace, - Name: hrNsName.Name, - }, - } + hr1 = createRoute("hr-1", "gw-1", "foo.example.com", fooRef, barRef) hr1Updated = hr1.DeepCopy() hr1Updated.Generation++ @@ -1527,6 +1524,49 @@ var _ = Describe("ChangeProcessor", func() { hr2 = hr1.DeepCopy() hr2.Name = hr2NsName.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"}, + }, + } + rgNsName = types.NamespacedName{Namespace: "test", Name: "rg-1"} rg1 = &v1beta1.ReferenceGrant{ @@ -1622,6 +1662,98 @@ var _ = Describe("ChangeProcessor", func() { Expect(changed).To(BeFalse()) }) }) + Describe("Multiple Kubernetes API resource changes", Ordered, func() { + BeforeAll(func() { + // Set up graph + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(hr1) + changed, _ := processor.Process() + Expect(changed).To(BeTrue()) + }) + + It("should report changed after multiple Upserts of related resources", func() { + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + changed, _ := processor.Process() + Expect(changed).To(BeTrue()) + }) + It("should report not changed after multiple Upserts of unrelated resources", func() { + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + + changed, _ := processor.Process() + Expect(changed).To(BeFalse()) + }) + 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) + + // there are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + + changed, _ := processor.Process() + Expect(changed).To(BeTrue()) + }) + }) + 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) + + // these are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + + changed, _ := processor.Process() + Expect(changed).To(BeTrue()) + }) + }) + }) + 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(hr1) + processor.CaptureUpsertChange(rg1) + + // related Kubernetes API resources + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + + changed, _ := processor.Process() + Expect(changed).To(BeTrue()) + }) + It("should report not changed after multiple Upserts of unrelated resources", func() { + // unrelated Kubernetes API resources + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + + changed, _ := processor.Process() + Expect(changed).To(BeFalse()) + }) + 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(rg1Updated) + + // these are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + + changed, _ := processor.Process() + Expect(changed).To(BeTrue()) + }, + ) + }) }) Describe("Webhook validation cases", Ordered, func() { var ( From cebcc7a32908635da29106747c5afd267d3b307b Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Mon, 22 Jan 2024 13:36:34 -0800 Subject: [PATCH 38/41] Remove persistedGVKs from changeTrackingUpdater --- internal/mode/static/state/store.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/mode/static/state/store.go b/internal/mode/static/state/store.go index c73d992462..5e58311402 100644 --- a/internal/mode/static/state/store.go +++ b/internal/mode/static/state/store.go @@ -133,7 +133,6 @@ type changeTrackingUpdater struct { extractGVK extractGVKFunc supportedGVKs gvkList - persistedGVKs gvkList changed bool } @@ -167,7 +166,6 @@ func newChangeTrackingUpdater( store: newMultiObjectStore(stores, extractGVK, persistedGVKs), extractGVK: extractGVK, supportedGVKs: supportedGVKs, - persistedGVKs: persistedGVKs, stateChangedPredicates: stateChangedPredicates, } } From b8d9a89c3757bbd0d07c8d78e39f4e3d09988051 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Mon, 22 Jan 2024 13:39:03 -0800 Subject: [PATCH 39/41] Change function documentation --- internal/mode/static/state/graph/backend_refs.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index 62bdc2fd71..f8eab82d80 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -140,7 +140,8 @@ func createBackendRef( return backendRef, nil } -// getServiceAndPortFromRef can return an error and an empty v1.ServicePort in two cases: +// getServiceAndPortFromRef extracts the NamespacedName of the Service and the port from a BackendRef. +// It can return an error and an empty v1.ServicePort in two cases: // 1. The Service referenced from the BackendRef does not exist in the cluster/state. // 2. The Port on the BackendRef does not match any of the ServicePorts on the Service. func getServiceAndPortFromRef( From 3146c182d8139a7a6cb8b156ffc68ecd8e706e1e Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Mon, 22 Jan 2024 15:04:20 -0800 Subject: [PATCH 40/41] Add namespace resources to multi k8s api resource change tests --- .../static/state/change_processor_test.go | 75 ++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 07df09d7e2..f9da0b5e50 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -1473,6 +1473,7 @@ var _ = Describe("ChangeProcessor", func() { rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant svc, barSvc, unrelatedSvc *apiv1.Service slice, barSlice, unrelatedSlice *discoveryV1.EndpointSlice + ns, unrelatedNS, testNs, barNs *apiv1.Namespace ) BeforeEach(OncePerOrdered, func() { @@ -1499,7 +1500,34 @@ var _ = Describe("ChangeProcessor", func() { gwNsName = types.NamespacedName{Namespace: "test", Name: "gw-1"} - gw1 = createGateway("gw-1") + gw1 = &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-1", + Namespace: "test", + Generation: 1, + }, + Spec: v1.GatewaySpec{ + GatewayClassName: gcName, + Listeners: []v1.Listener{ + { + Name: "listener-80-1", + 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", + }, + }, + }, + }, + }, + }, + }, + } gw1Updated = gw1.DeepCopy() gw1Updated.Generation++ @@ -1567,6 +1595,39 @@ var _ = Describe("ChangeProcessor", func() { }, } + 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{ @@ -1593,6 +1654,7 @@ var _ = Describe("ChangeProcessor", func() { It("should report changed after multiple Upserts", func() { processor.CaptureUpsertChange(gc) processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(testNs) processor.CaptureUpsertChange(hr1) processor.CaptureUpsertChange(rg1) @@ -1667,6 +1729,7 @@ var _ = Describe("ChangeProcessor", func() { // Set up graph processor.CaptureUpsertChange(gc) processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(testNs) processor.CaptureUpsertChange(hr1) changed, _ := processor.Process() Expect(changed).To(BeTrue()) @@ -1675,12 +1738,14 @@ var _ = Describe("ChangeProcessor", func() { It("should report changed after multiple Upserts of related resources", func() { processor.CaptureUpsertChange(svc) processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(ns) changed, _ := processor.Process() Expect(changed).To(BeTrue()) }) It("should report not changed after multiple Upserts of unrelated resources", func() { processor.CaptureUpsertChange(unrelatedSvc) processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) changed, _ := processor.Process() Expect(changed).To(BeFalse()) @@ -1690,10 +1755,12 @@ var _ = Describe("ChangeProcessor", func() { // these are changing changes processor.CaptureUpsertChange(barSvc) processor.CaptureUpsertChange(barSlice) + processor.CaptureUpsertChange(barNs) // there are non-changing changes processor.CaptureUpsertChange(unrelatedSvc) processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) changed, _ := processor.Process() Expect(changed).To(BeTrue()) @@ -1704,10 +1771,12 @@ var _ = Describe("ChangeProcessor", func() { // these are changing changes processor.CaptureDeleteChange(&apiv1.Service{}, svcNsName) processor.CaptureDeleteChange(&discoveryV1.EndpointSlice{}, sliceNsName) + processor.CaptureDeleteChange(&apiv1.Namespace{}, types.NamespacedName{Name: "ns"}) // these are non-changing changes processor.CaptureUpsertChange(unrelatedSvc) processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) changed, _ := processor.Process() Expect(changed).To(BeTrue()) @@ -1719,12 +1788,14 @@ var _ = Describe("ChangeProcessor", func() { // new Gateway API resources processor.CaptureUpsertChange(gc) processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(testNs) processor.CaptureUpsertChange(hr1) processor.CaptureUpsertChange(rg1) // related Kubernetes API resources processor.CaptureUpsertChange(svc) processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(ns) changed, _ := processor.Process() Expect(changed).To(BeTrue()) @@ -1733,6 +1804,7 @@ var _ = Describe("ChangeProcessor", func() { // unrelated Kubernetes API resources processor.CaptureUpsertChange(unrelatedSvc) processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) changed, _ := processor.Process() Expect(changed).To(BeFalse()) @@ -1748,6 +1820,7 @@ var _ = Describe("ChangeProcessor", func() { // these are non-changing changes processor.CaptureUpsertChange(unrelatedSvc) processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) changed, _ := processor.Process() Expect(changed).To(BeTrue()) From 635178bdce6d31750ac9581a009a8071d0aac900 Mon Sep 17 00:00:00 2001 From: Benjamin Jee Date: Tue, 23 Jan 2024 11:11:09 -0800 Subject: [PATCH 41/41] Add secret resources to change processor tests --- .../static/state/change_processor_test.go | 104 ++++++++++++++++-- 1 file changed, 95 insertions(+), 9 deletions(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index f9da0b5e50..0376b43463 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -1465,15 +1465,16 @@ var _ = Describe("ChangeProcessor", func() { // -- this is done in 'Normal cases of processing changes' var ( - processor *state.ChangeProcessorImpl - gcNsName, gwNsName, hrNsName, hr2NsName, rgNsName, svcNsName, sliceNsName types.NamespacedName - gc, gcUpdated *v1.GatewayClass - gw1, gw1Updated, gw2 *v1.Gateway - hr1, hr1Updated, hr2 *v1.HTTPRoute - rg1, rg1Updated, rg2 *v1beta1.ReferenceGrant - svc, barSvc, unrelatedSvc *apiv1.Service - slice, barSlice, unrelatedSlice *discoveryV1.EndpointSlice - ns, unrelatedNS, testNs, barNs *apiv1.Namespace + processor *state.ChangeProcessorImpl + gcNsName, gwNsName, hrNsName, hr2NsName, rgNsName, svcNsName, sliceNsName, secretNsName types.NamespacedName + gc, gcUpdated *v1.GatewayClass + gw1, gw1Updated, gw2 *v1.Gateway + hr1, hr1Updated, hr2 *v1.HTTPRoute + 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 ) BeforeEach(OncePerOrdered, func() { @@ -1484,6 +1485,48 @@ var _ = Describe("ChangeProcessor", func() { Scheme: 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{ @@ -1525,6 +1568,38 @@ var _ = Describe("ChangeProcessor", func() { }, }, }, + { + Name: "listener-443-1", + 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), + }, + }, + }, + }, }, }, } @@ -1731,6 +1806,8 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(gw1) processor.CaptureUpsertChange(testNs) processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(secret) + processor.CaptureUpsertChange(barSecret) changed, _ := processor.Process() Expect(changed).To(BeTrue()) }) @@ -1739,6 +1816,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(svc) processor.CaptureUpsertChange(slice) processor.CaptureUpsertChange(ns) + processor.CaptureUpsertChange(secretUpdated) changed, _ := processor.Process() Expect(changed).To(BeTrue()) }) @@ -1746,6 +1824,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(unrelatedSvc) processor.CaptureUpsertChange(unrelatedSlice) processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) changed, _ := processor.Process() Expect(changed).To(BeFalse()) @@ -1756,11 +1835,13 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(barSvc) processor.CaptureUpsertChange(barSlice) processor.CaptureUpsertChange(barNs) + processor.CaptureUpsertChange(barSecretUpdated) // there are non-changing changes processor.CaptureUpsertChange(unrelatedSvc) processor.CaptureUpsertChange(unrelatedSlice) processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) changed, _ := processor.Process() Expect(changed).To(BeTrue()) @@ -1772,11 +1853,13 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureDeleteChange(&apiv1.Service{}, svcNsName) processor.CaptureDeleteChange(&discoveryV1.EndpointSlice{}, sliceNsName) processor.CaptureDeleteChange(&apiv1.Namespace{}, types.NamespacedName{Name: "ns"}) + processor.CaptureDeleteChange(&apiv1.Secret{}, secretNsName) // these are non-changing changes processor.CaptureUpsertChange(unrelatedSvc) processor.CaptureUpsertChange(unrelatedSlice) processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) changed, _ := processor.Process() Expect(changed).To(BeTrue()) @@ -1796,6 +1879,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(svc) processor.CaptureUpsertChange(slice) processor.CaptureUpsertChange(ns) + processor.CaptureUpsertChange(secret) changed, _ := processor.Process() Expect(changed).To(BeTrue()) @@ -1805,6 +1889,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(unrelatedSvc) processor.CaptureUpsertChange(unrelatedSlice) processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) changed, _ := processor.Process() Expect(changed).To(BeFalse()) @@ -1821,6 +1906,7 @@ var _ = Describe("ChangeProcessor", func() { processor.CaptureUpsertChange(unrelatedSvc) processor.CaptureUpsertChange(unrelatedSlice) processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) changed, _ := processor.Process() Expect(changed).To(BeTrue())