From ced8d6b1892ade13a8b7f50274ddf0454dbfbe8c Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Thu, 19 Sep 2024 11:56:41 -0600 Subject: [PATCH 01/16] Finish implementing SnippetsFilter --- .../templates/deployment.yaml | 10 +- config/tests/static-deployment.yaml | 10 +- deploy/aws-nlb/deploy.yaml | 10 +- deploy/azure/deploy.yaml | 10 +- deploy/default/deploy.yaml | 10 +- deploy/experimental-nginx-plus/deploy.yaml | 10 +- deploy/experimental/deploy.yaml | 10 +- deploy/nginx-plus/deploy.yaml | 10 +- deploy/nodeport/deploy.yaml | 10 +- deploy/openshift/deploy.yaml | 10 +- .../snippets-filters-nginx-plus/deploy.yaml | 10 +- deploy/snippets-filters/deploy.yaml | 10 +- examples/snippets-filter/example.yaml | 86 + examples/snippets-filter/snippets-filter.yaml | 10 - internal/framework/kinds/kinds.go | 2 + internal/mode/static/nginx/conf/nginx.conf | 2 +- .../static/nginx/config/base_http_config.go | 26 +- .../nginx/config/base_http_config_template.go | 4 + .../nginx/config/base_http_config_test.go | 77 +- .../mode/static/nginx/config/generator.go | 95 +- .../static/nginx/config/generator_test.go | 79 +- .../mode/static/nginx/config/http/config.go | 10 +- internal/mode/static/nginx/config/includes.go | 148 + .../mode/static/nginx/config/includes_test.go | 527 ++ .../mode/static/nginx/config/main_config.go | 36 + .../nginx/config/main_config_template.go | 11 + .../static/nginx/config/main_config_test.go | 111 + .../static/nginx/config/policies/generator.go | 2 +- internal/mode/static/nginx/config/servers.go | 92 +- .../mode/static/nginx/config/servers_test.go | 903 ++- .../mode/static/nginx/config/shared/config.go | 8 +- internal/mode/static/nginx/config/version.go | 10 +- .../mode/static/nginx/config/version_test.go | 15 +- .../mode/static/state/change_processor.go | 2 +- .../static/state/change_processor_test.go | 5140 +++++++++-------- .../static/state/conditions/conditions.go | 15 + .../static/state/dataplane/configuration.go | 191 +- .../state/dataplane/configuration_test.go | 3353 ++++++----- internal/mode/static/state/dataplane/types.go | 26 +- .../mode/static/state/graph/backend_refs.go | 8 +- .../static/state/graph/backend_refs_test.go | 416 +- .../mode/static/state/graph/common_filter.go | 396 ++ .../static/state/graph/common_filter_test.go | 671 +++ .../state/graph/extension_ref_filter.go | 49 + .../state/graph/extension_ref_filter_test.go | 107 + internal/mode/static/state/graph/graph.go | 1 + .../mode/static/state/graph/graph_test.go | 221 +- internal/mode/static/state/graph/grpcroute.go | 213 +- .../mode/static/state/graph/grpcroute_test.go | 485 +- internal/mode/static/state/graph/httproute.go | 191 +- .../mode/static/state/graph/httproute_test.go | 568 +- .../mode/static/state/graph/route_common.go | 231 +- .../static/state/graph/route_common_test.go | 1120 ++-- .../static/state/graph/snippets_filter.go | 63 +- .../state/graph/snippets_filter_test.go | 209 +- 55 files changed, 10199 insertions(+), 5851 deletions(-) create mode 100644 examples/snippets-filter/example.yaml delete mode 100644 examples/snippets-filter/snippets-filter.yaml create mode 100644 internal/mode/static/nginx/config/includes.go create mode 100644 internal/mode/static/nginx/config/includes_test.go create mode 100644 internal/mode/static/nginx/config/main_config.go create mode 100644 internal/mode/static/nginx/config/main_config_template.go create mode 100644 internal/mode/static/nginx/config/main_config_test.go create mode 100644 internal/mode/static/state/graph/common_filter.go create mode 100644 internal/mode/static/state/graph/common_filter_test.go create mode 100644 internal/mode/static/state/graph/extension_ref_filter.go create mode 100644 internal/mode/static/state/graph/extension_ref_filter_test.go diff --git a/charts/nginx-gateway-fabric/templates/deployment.yaml b/charts/nginx-gateway-fabric/templates/deployment.yaml index 948654d33e..69233691bd 100644 --- a/charts/nginx-gateway-fabric/templates/deployment.yaml +++ b/charts/nginx-gateway-fabric/templates/deployment.yaml @@ -134,8 +134,8 @@ spec: mountPath: /etc/nginx/conf.d - name: nginx-stream-conf mountPath: /etc/nginx/stream-conf.d - - name: module-includes - mountPath: /etc/nginx/module-includes + - name: nginx-main-includes + mountPath: /etc/nginx/main-includes - name: nginx-secrets mountPath: /etc/nginx/secrets - name: nginx-run @@ -173,8 +173,8 @@ spec: mountPath: /etc/nginx/conf.d - name: nginx-stream-conf mountPath: /etc/nginx/stream-conf.d - - name: module-includes - mountPath: /etc/nginx/module-includes + - name: nginx-main-includes + mountPath: /etc/nginx/main-includes - name: nginx-secrets mountPath: /etc/nginx/secrets - name: nginx-run @@ -209,7 +209,7 @@ spec: emptyDir: {} - name: nginx-stream-conf emptyDir: {} - - name: module-includes + - name: nginx-main-includes emptyDir: {} - name: nginx-secrets emptyDir: {} diff --git a/config/tests/static-deployment.yaml b/config/tests/static-deployment.yaml index bb2fb62765..30f85a9081 100644 --- a/config/tests/static-deployment.yaml +++ b/config/tests/static-deployment.yaml @@ -74,8 +74,8 @@ spec: mountPath: /etc/nginx/conf.d - name: nginx-stream-conf mountPath: /etc/nginx/stream-conf.d - - name: module-includes - mountPath: /etc/nginx/module-includes + - name: nginx-main-includes + mountPath: /etc/nginx/main-includes - name: nginx-secrets mountPath: /etc/nginx/secrets - name: nginx-run @@ -106,8 +106,8 @@ spec: mountPath: /etc/nginx/conf.d - name: nginx-stream-conf mountPath: /etc/nginx/stream-conf.d - - name: module-includes - mountPath: /etc/nginx/module-includes + - name: nginx-main-includes + mountPath: /etc/nginx/main-includes - name: nginx-secrets mountPath: /etc/nginx/secrets - name: nginx-run @@ -127,7 +127,7 @@ spec: emptyDir: {} - name: nginx-stream-conf emptyDir: {} - - name: module-includes + - name: nginx-main-includes emptyDir: {} - name: nginx-secrets emptyDir: {} diff --git a/deploy/aws-nlb/deploy.yaml b/deploy/aws-nlb/deploy.yaml index 49b29bf988..e363b9066a 100644 --- a/deploy/aws-nlb/deploy.yaml +++ b/deploy/aws-nlb/deploy.yaml @@ -248,8 +248,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -280,8 +280,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -302,7 +302,7 @@ spec: - emptyDir: {} name: nginx-stream-conf - emptyDir: {} - name: module-includes + name: nginx-main-includes - emptyDir: {} name: nginx-secrets - emptyDir: {} diff --git a/deploy/azure/deploy.yaml b/deploy/azure/deploy.yaml index 968c1a2926..faacc0332e 100644 --- a/deploy/azure/deploy.yaml +++ b/deploy/azure/deploy.yaml @@ -245,8 +245,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -277,8 +277,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -301,7 +301,7 @@ spec: - emptyDir: {} name: nginx-stream-conf - emptyDir: {} - name: module-includes + name: nginx-main-includes - emptyDir: {} name: nginx-secrets - emptyDir: {} diff --git a/deploy/default/deploy.yaml b/deploy/default/deploy.yaml index 6245a2bbc7..97e03edb4c 100644 --- a/deploy/default/deploy.yaml +++ b/deploy/default/deploy.yaml @@ -245,8 +245,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -277,8 +277,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -299,7 +299,7 @@ spec: - emptyDir: {} name: nginx-stream-conf - emptyDir: {} - name: module-includes + name: nginx-main-includes - emptyDir: {} name: nginx-secrets - emptyDir: {} diff --git a/deploy/experimental-nginx-plus/deploy.yaml b/deploy/experimental-nginx-plus/deploy.yaml index ed9c748e1a..002027468d 100644 --- a/deploy/experimental-nginx-plus/deploy.yaml +++ b/deploy/experimental-nginx-plus/deploy.yaml @@ -260,8 +260,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -292,8 +292,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -314,7 +314,7 @@ spec: - emptyDir: {} name: nginx-stream-conf - emptyDir: {} - name: module-includes + name: nginx-main-includes - emptyDir: {} name: nginx-secrets - emptyDir: {} diff --git a/deploy/experimental/deploy.yaml b/deploy/experimental/deploy.yaml index 28cc7b6d19..fbb09e917a 100644 --- a/deploy/experimental/deploy.yaml +++ b/deploy/experimental/deploy.yaml @@ -251,8 +251,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -283,8 +283,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -305,7 +305,7 @@ spec: - emptyDir: {} name: nginx-stream-conf - emptyDir: {} - name: module-includes + name: nginx-main-includes - emptyDir: {} name: nginx-secrets - emptyDir: {} diff --git a/deploy/nginx-plus/deploy.yaml b/deploy/nginx-plus/deploy.yaml index 76249e80c2..33043d74d5 100644 --- a/deploy/nginx-plus/deploy.yaml +++ b/deploy/nginx-plus/deploy.yaml @@ -256,8 +256,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -288,8 +288,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -310,7 +310,7 @@ spec: - emptyDir: {} name: nginx-stream-conf - emptyDir: {} - name: module-includes + name: nginx-main-includes - emptyDir: {} name: nginx-secrets - emptyDir: {} diff --git a/deploy/nodeport/deploy.yaml b/deploy/nodeport/deploy.yaml index db81fdf259..d45f2c51c2 100644 --- a/deploy/nodeport/deploy.yaml +++ b/deploy/nodeport/deploy.yaml @@ -245,8 +245,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -277,8 +277,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -299,7 +299,7 @@ spec: - emptyDir: {} name: nginx-stream-conf - emptyDir: {} - name: module-includes + name: nginx-main-includes - emptyDir: {} name: nginx-secrets - emptyDir: {} diff --git a/deploy/openshift/deploy.yaml b/deploy/openshift/deploy.yaml index cb78ce0f39..742441ad18 100644 --- a/deploy/openshift/deploy.yaml +++ b/deploy/openshift/deploy.yaml @@ -253,8 +253,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -285,8 +285,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -307,7 +307,7 @@ spec: - emptyDir: {} name: nginx-stream-conf - emptyDir: {} - name: module-includes + name: nginx-main-includes - emptyDir: {} name: nginx-secrets - emptyDir: {} diff --git a/deploy/snippets-filters-nginx-plus/deploy.yaml b/deploy/snippets-filters-nginx-plus/deploy.yaml index b0f42c3f6a..dfb0bcc72e 100644 --- a/deploy/snippets-filters-nginx-plus/deploy.yaml +++ b/deploy/snippets-filters-nginx-plus/deploy.yaml @@ -257,8 +257,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -289,8 +289,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -311,7 +311,7 @@ spec: - emptyDir: {} name: nginx-stream-conf - emptyDir: {} - name: module-includes + name: nginx-main-includes - emptyDir: {} name: nginx-secrets - emptyDir: {} diff --git a/deploy/snippets-filters/deploy.yaml b/deploy/snippets-filters/deploy.yaml index 7cabc5994a..d48e4b3f3c 100644 --- a/deploy/snippets-filters/deploy.yaml +++ b/deploy/snippets-filters/deploy.yaml @@ -248,8 +248,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -280,8 +280,8 @@ spec: name: nginx-conf - mountPath: /etc/nginx/stream-conf.d name: nginx-stream-conf - - mountPath: /etc/nginx/module-includes - name: module-includes + - mountPath: /etc/nginx/main-includes + name: nginx-main-includes - mountPath: /etc/nginx/secrets name: nginx-secrets - mountPath: /var/run/nginx @@ -302,7 +302,7 @@ spec: - emptyDir: {} name: nginx-stream-conf - emptyDir: {} - name: module-includes + name: nginx-main-includes - emptyDir: {} name: nginx-secrets - emptyDir: {} diff --git a/examples/snippets-filter/example.yaml b/examples/snippets-filter/example.yaml new file mode 100644 index 0000000000..a244ca6b84 --- /dev/null +++ b/examples/snippets-filter/example.yaml @@ -0,0 +1,86 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: coffee +spec: + replicas: 1 + selector: + matchLabels: + app: coffee + template: + metadata: + labels: + app: coffee + spec: + containers: + - name: coffee + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: coffee +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: coffee +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: gateway +spec: + gatewayClassName: nginx + listeners: + - name: http + port: 80 + protocol: HTTP + hostname: "*.example.com" +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: coffee +spec: + parentRefs: + - name: gateway + sectionName: http + hostnames: + - "cafe.example.com" + rules: + - matches: + - path: + type: PathPrefix + value: /coffee + filters: + - type: ExtensionRef + extensionRef: + group: gateway.nginx.org + kind: SnippetsFilter + name: test-all-contexts + backendRefs: + - name: coffee + port: 80 +--- +apiVersion: gateway.nginx.org/v1alpha1 +kind: SnippetsFilter +metadata: + name: test-all-contexts +spec: + snippets: + - context: main + value: worker_shutdown_timeout 120s; + - context: http + value: aio on; + - context: http.server + value: auth_delay 10s; + - context: http.server.location + value: | + allow 10.0.0.0/8; + deny all; diff --git a/examples/snippets-filter/snippets-filter.yaml b/examples/snippets-filter/snippets-filter.yaml deleted file mode 100644 index cefe9a6ccb..0000000000 --- a/examples/snippets-filter/snippets-filter.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: gateway.nginx.org/v1alpha1 -kind: SnippetsFilter -metadata: - name: access-control -spec: - snippets: - - context: http.server.location - value: | - allow 10.0.0.0/8; - deny all; diff --git a/internal/framework/kinds/kinds.go b/internal/framework/kinds/kinds.go index 471c526b98..7700ad39ce 100644 --- a/internal/framework/kinds/kinds.go +++ b/internal/framework/kinds/kinds.go @@ -31,6 +31,8 @@ const ( ObservabilityPolicy = "ObservabilityPolicy" // NginxProxy is the NginxProxy kind. NginxProxy = "NginxProxy" + // SnippetsFilter is the SnippetsFilter kind. + SnippetsFilter = "SnippetsFilter" ) // MustExtractGVK is a function that extracts the GroupVersionKind (GVK) of a client.object. diff --git a/internal/mode/static/nginx/conf/nginx.conf b/internal/mode/static/nginx/conf/nginx.conf index 2cbc09fa3f..9f12a7f8e8 100644 --- a/internal/mode/static/nginx/conf/nginx.conf +++ b/internal/mode/static/nginx/conf/nginx.conf @@ -1,5 +1,5 @@ load_module /usr/lib/nginx/modules/ngx_http_js_module.so; -include /etc/nginx/module-includes/*.conf; +include /etc/nginx/main-includes/*.conf; worker_processes auto; diff --git a/internal/mode/static/nginx/config/base_http_config.go b/internal/mode/static/nginx/config/base_http_config.go index abec446492..79fb579174 100644 --- a/internal/mode/static/nginx/config/base_http_config.go +++ b/internal/mode/static/nginx/config/base_http_config.go @@ -4,16 +4,34 @@ import ( gotemplate "text/template" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/shared" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/dataplane" ) var baseHTTPTemplate = gotemplate.Must(gotemplate.New("baseHttp").Parse(baseHTTPTemplateText)) +type httpConfig struct { + Includes []shared.Include + HTTP2 bool +} + func executeBaseHTTPConfig(conf dataplane.Configuration) []executeResult { - result := executeResult{ - dest: httpConfigFile, - data: helpers.MustExecuteTemplate(baseHTTPTemplate, conf.BaseHTTPConfig), + includes := createIncludesFromSnippets(conf.BaseHTTPConfig.Snippets) + + hc := httpConfig{ + HTTP2: conf.BaseHTTPConfig.HTTP2, + Includes: includes, } - return []executeResult{result} + results := make([]executeResult, 0, len(includes)+1) + results = append( + results, + executeResult{ + dest: httpConfigFile, + data: helpers.MustExecuteTemplate(baseHTTPTemplate, hc), + }, + ) + results = append(results, createIncludeExecuteResults(includes)...) + + return results } diff --git a/internal/mode/static/nginx/config/base_http_config_template.go b/internal/mode/static/nginx/config/base_http_config_template.go index bbe35a1018..5163904e26 100644 --- a/internal/mode/static/nginx/config/base_http_config_template.go +++ b/internal/mode/static/nginx/config/base_http_config_template.go @@ -23,4 +23,8 @@ map $http_upgrade $connection_upgrade { map $request_uri $request_uri_path { "~^(?P[^?]*)(\?.*)?$" $path; } + +{{ range $i := .Includes -}} +include {{ $i.Name }}; +{{ end -}} ` diff --git a/internal/mode/static/nginx/config/base_http_config_test.go b/internal/mode/static/nginx/config/base_http_config_test.go index f06c4aa725..0f636ed9c7 100644 --- a/internal/mode/static/nginx/config/base_http_config_test.go +++ b/internal/mode/static/nginx/config/base_http_config_test.go @@ -1,6 +1,7 @@ package config import ( + "sort" "strings" "testing" @@ -9,7 +10,7 @@ import ( "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/dataplane" ) -func TestExecuteBaseHttp(t *testing.T) { +func TestExecuteBaseHttp_HTTP2(t *testing.T) { t.Parallel() confOn := dataplane.Configuration{ BaseHTTPConfig: dataplane.BaseHTTPConfig{ @@ -43,16 +44,68 @@ func TestExecuteBaseHttp(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - res := executeBaseHTTPConfig(test.conf) - g.Expect(res).To(HaveLen(1)) - g.Expect(test.expCount).To(Equal(strings.Count(string(res[0].data), expSubStr))) - g.Expect(strings.Count(string(res[0].data), "map $http_host $gw_api_compliant_host {")).To(Equal(1)) - g.Expect(strings.Count(string(res[0].data), "map $http_upgrade $connection_upgrade {")).To(Equal(1)) - g.Expect(strings.Count(string(res[0].data), "map $request_uri $request_uri_path {")).To(Equal(1)) - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + res := executeBaseHTTPConfig(test.conf) + g.Expect(res).To(HaveLen(1)) + g.Expect(test.expCount).To(Equal(strings.Count(string(res[0].data), expSubStr))) + g.Expect(strings.Count(string(res[0].data), "map $http_host $gw_api_compliant_host {")).To(Equal(1)) + g.Expect(strings.Count(string(res[0].data), "map $http_upgrade $connection_upgrade {")).To(Equal(1)) + g.Expect(strings.Count(string(res[0].data), "map $request_uri $request_uri_path {")).To(Equal(1)) + }, + ) + } +} + +func TestExecuteBaseHttp_Snippets(t *testing.T) { + t.Parallel() + + conf := dataplane.Configuration{ + BaseHTTPConfig: dataplane.BaseHTTPConfig{ + Snippets: []dataplane.Snippet{ + { + Name: "snippet1", + Contents: "contents1", + }, + { + Name: "snippet2", + Contents: "contents2", + }, + }, + }, } + + g := NewWithT(t) + + res := executeBaseHTTPConfig(conf) + g.Expect(res).To(HaveLen(3)) + + sort.Slice( + res, func(i, j int) bool { + return res[i].dest < res[j].dest + }, + ) + + /* + Order of files: + /etc/nginx/conf.d/http.conf + /etc/nginx/includes/snippet1.conf + /etc/nginx/includes/snippet2.conf + */ + + httpRes := string(res[0].data) + g.Expect(httpRes).To(ContainSubstring("map $http_host $gw_api_compliant_host {")) + g.Expect(httpRes).To(ContainSubstring("map $http_upgrade $connection_upgrade {")) + g.Expect(httpRes).To(ContainSubstring("map $request_uri $request_uri_path {")) + g.Expect(httpRes).To(ContainSubstring("include /etc/nginx/includes/snippet1.conf;")) + g.Expect(httpRes).To(ContainSubstring("include /etc/nginx/includes/snippet2.conf;")) + + snippet1IncludeRes := string(res[1].data) + g.Expect(snippet1IncludeRes).To(ContainSubstring("contents1")) + + snippet2IncludeRes := string(res[2].data) + g.Expect(snippet2IncludeRes).To(ContainSubstring("contents2")) } diff --git a/internal/mode/static/nginx/config/generator.go b/internal/mode/static/nginx/config/generator.go index be2877bf29..235247e7d5 100644 --- a/internal/mode/static/nginx/config/generator.go +++ b/internal/mode/static/nginx/config/generator.go @@ -23,8 +23,9 @@ const ( // streamFolder is the folder where NGINX Stream configuration files are stored. streamFolder = configFolder + "/stream-conf.d" - // modulesIncludesFolder is the folder where the included "load_module" file is stored. - modulesIncludesFolder = configFolder + "/module-includes" + // mainIncludesFolder is the folder where the main context configuration files are stored. + // For example, these files include load_module directives and snippets that target the main context. + mainIncludesFolder = configFolder + "/main-includes" // secretsFolder is the folder where secrets (like TLS certs/keys) are stored. secretsFolder = configFolder + "/secrets" @@ -44,12 +45,12 @@ const ( // httpMatchVarsFile is the path to the http_match pairs configuration file. httpMatchVarsFile = httpFolder + "/matches.json" - // loadModulesFile is the path to the file containing any load_module directives. - loadModulesFile = modulesIncludesFolder + "/load-modules.conf" + // mainIncludeFile is the path to the file included in the main context. + mainIncludeFile = mainIncludesFolder + "/main.conf" ) // ConfigFolders is a list of folders where NGINX configuration files are stored. -var ConfigFolders = []string{httpFolder, secretsFolder, includesFolder, modulesIncludesFolder, streamFolder} +var ConfigFolders = []string{httpFolder, secretsFolder, includesFolder, mainIncludesFolder, streamFolder} // Generator generates NGINX configuration files. // This interface is used for testing purposes only. @@ -60,9 +61,7 @@ type Generator interface { // GeneratorImpl is an implementation of Generator. // -// It generates files to be written to the following locations, which must exist and available for writing: -// - httpFolder, for HTTP configuration files. -// - secretsFolder, for secrets. +// It generates files to be written to the ConfigFolders locations, which must exist and available for writing. // // It also expects that the main NGINX configuration file nginx.conf is located in configFolder and nginx.conf // includes (https://nginx.org/en/docs/ngx_core_module.html#include) the files from httpFolder. @@ -99,49 +98,16 @@ func (g GeneratorImpl) Generate(conf dataplane.Configuration) []file.File { observability.NewGenerator(conf.Telemetry), ) - files = append(files, g.generateHTTPConfig(conf, policyGenerator)...) - - files = append(files, generateConfigVersion(conf.Version)) + files = append(files, g.executeConfigTemplates(conf, policyGenerator)...) for id, bundle := range conf.CertBundles { files = append(files, generateCertBundle(id, bundle)) } - files = append(files, generateLoadModulesConf(conf)) - return files } -func generatePEM(id dataplane.SSLKeyPairID, cert []byte, key []byte) file.File { - c := make([]byte, 0, len(cert)+len(key)+1) - c = append(c, cert...) - c = append(c, '\n') - c = append(c, key...) - - return file.File{ - Content: c, - Path: generatePEMFileName(id), - Type: file.TypeSecret, - } -} - -func generatePEMFileName(id dataplane.SSLKeyPairID) string { - return filepath.Join(secretsFolder, string(id)+".pem") -} - -func generateCertBundle(id dataplane.CertBundleID, cert []byte) file.File { - return file.File{ - Content: cert, - Path: generateCertBundleFileName(id), - Type: file.TypeRegular, - } -} - -func generateCertBundleFileName(id dataplane.CertBundleID) string { - return filepath.Join(secretsFolder, string(id)+".crt") -} - -func (g GeneratorImpl) generateHTTPConfig( +func (g GeneratorImpl) executeConfigTemplates( conf dataplane.Configuration, generator policies.Generator, ) []file.File { @@ -156,11 +122,13 @@ func (g GeneratorImpl) generateHTTPConfig( files := make([]file.File, 0, len(fileBytes)) for fp, bytes := range fileBytes { - files = append(files, file.File{ - Path: fp, - Content: bytes, - Type: file.TypeRegular, - }) + files = append( + files, file.File{ + Path: fp, + Content: bytes, + Type: file.TypeRegular, + }, + ) } return files @@ -168,6 +136,7 @@ func (g GeneratorImpl) generateHTTPConfig( func (g GeneratorImpl) getExecuteFuncs(generator policies.Generator) []executeFunc { return []executeFunc{ + executeMainConfig, executeBaseHTTPConfig, g.newExecuteServersFunc(generator), g.executeUpstreams, @@ -177,29 +146,35 @@ func (g GeneratorImpl) getExecuteFuncs(generator policies.Generator) []executeFu g.executeStreamServers, g.executeStreamUpstreams, executeStreamMaps, + executeVersion, } } -// generateConfigVersion writes the config version file. -func generateConfigVersion(configVersion int) file.File { - c := executeVersion(configVersion) +func generatePEM(id dataplane.SSLKeyPairID, cert []byte, key []byte) file.File { + c := make([]byte, 0, len(cert)+len(key)+1) + c = append(c, cert...) + c = append(c, '\n') + c = append(c, key...) return file.File{ Content: c, - Path: configVersionFile, - Type: file.TypeRegular, + Path: generatePEMFileName(id), + Type: file.TypeSecret, } } -func generateLoadModulesConf(conf dataplane.Configuration) file.File { - var c []byte - if conf.Telemetry.Endpoint != "" { - c = []byte("load_module modules/ngx_otel_module.so;") - } +func generatePEMFileName(id dataplane.SSLKeyPairID) string { + return filepath.Join(secretsFolder, string(id)+".pem") +} +func generateCertBundle(id dataplane.CertBundleID, cert []byte) file.File { return file.File{ - Content: c, - Path: loadModulesFile, + Content: cert, + Path: generateCertBundleFileName(id), Type: file.TypeRegular, } } + +func generateCertBundleFileName(id dataplane.CertBundleID) string { + return filepath.Join(secretsFolder, string(id)+".crt") +} diff --git a/internal/mode/static/nginx/config/generator_test.go b/internal/mode/static/nginx/config/generator_test.go index 4940d72452..1e4eae8375 100644 --- a/internal/mode/static/nginx/config/generator_test.go +++ b/internal/mode/static/nginx/config/generator_test.go @@ -92,6 +92,26 @@ func TestGenerate(t *testing.T) { }, BaseHTTPConfig: dataplane.BaseHTTPConfig{ HTTP2: true, + Snippets: []dataplane.Snippet{ + { + Name: "http_snippet1", + Contents: "http snippet 1 contents", + }, + { + Name: "http_snippet2", + Contents: "http 2 contents", + }, + }, + }, + MainSnippets: []dataplane.Snippet{ + { + Name: "main_snippet1", + Contents: "main snippet 1 contents", + }, + { + Name: "main_snippet2", + Contents: "main 2 contents", + }, }, } g := NewWithT(t) @@ -101,12 +121,27 @@ func TestGenerate(t *testing.T) { files := generator.Generate(conf) - g.Expect(files).To(HaveLen(7)) + g.Expect(files).To(HaveLen(11)) arrange := func(i, j int) bool { return files[i].Path < files[j].Path } sort.Slice(files, arrange) + /* + Order of files: + /etc/nginx/conf.d/config-version.conf + /etc/nginx/conf.d/http.conf + /etc/nginx/conf.d/matches.json + /etc/nginx/includes/http_snippet1.conf + /etc/nginx/includes/http_snippet2.conf + /etc/nginx/includes/main_snippet1.conf + /etc/nginx/includes/main_snippet2.conf + /etc/nginx/main-includes/main.conf + /etc/nginx/secrets/test-certbundle.crt + /etc/nginx/secrets/test-keypair.pem + /etc/nginx/stream-conf.d/stream.conf + */ + g.Expect(files[0].Type).To(Equal(file.TypeRegular)) g.Expect(files[0].Path).To(Equal("/etc/nginx/conf.d/config-version.conf")) configVersion := string(files[0].Content) @@ -128,6 +163,8 @@ func TestGenerate(t *testing.T) { g.Expect(httpCfg).To(ContainSubstring("batch_count 4;")) g.Expect(httpCfg).To(ContainSubstring("otel_service_name ngf:gw-ns:gw-name:my-name;")) g.Expect(httpCfg).To(ContainSubstring("http2 on;")) + g.Expect(httpCfg).To(ContainSubstring("include /etc/nginx/includes/http_snippet1.conf;")) + g.Expect(httpCfg).To(ContainSubstring("include /etc/nginx/includes/http_snippet2.conf;")) g.Expect(files[2].Path).To(Equal("/etc/nginx/conf.d/matches.json")) @@ -135,22 +172,36 @@ func TestGenerate(t *testing.T) { expString := "{}" g.Expect(string(files[2].Content)).To(Equal(expString)) - g.Expect(files[3].Path).To(Equal("/etc/nginx/module-includes/load-modules.conf")) - g.Expect(files[3].Content).To(Equal([]byte("load_module modules/ngx_otel_module.so;"))) - - g.Expect(files[4].Path).To(Equal("/etc/nginx/secrets/test-certbundle.crt")) - certBundle := string(files[4].Content) + // snippet include files + // content is not checked in this test. + g.Expect(files[3].Path).To(Equal("/etc/nginx/includes/http_snippet1.conf")) + g.Expect(files[4].Path).To(Equal("/etc/nginx/includes/http_snippet2.conf")) + g.Expect(files[5].Path).To(Equal("/etc/nginx/includes/main_snippet1.conf")) + g.Expect(files[6].Path).To(Equal("/etc/nginx/includes/main_snippet2.conf")) + + g.Expect(files[7].Path).To(Equal("/etc/nginx/main-includes/main.conf")) + mainConfStr := string(files[7].Content) + g.Expect(mainConfStr).To(ContainSubstring("load_module modules/ngx_otel_module.so;")) + g.Expect(mainConfStr).To(ContainSubstring("include /etc/nginx/includes/main_snippet1.conf;")) + g.Expect(mainConfStr).To(ContainSubstring("include /etc/nginx/includes/main_snippet2.conf;")) + + g.Expect(files[8].Path).To(Equal("/etc/nginx/secrets/test-certbundle.crt")) + certBundle := string(files[8].Content) g.Expect(certBundle).To(Equal("test-cert")) - g.Expect(files[5]).To(Equal(file.File{ - Type: file.TypeSecret, - Path: "/etc/nginx/secrets/test-keypair.pem", - Content: []byte("test-cert\ntest-key"), - })) + g.Expect(files[9]).To( + Equal( + file.File{ + Type: file.TypeSecret, + Path: "/etc/nginx/secrets/test-keypair.pem", + Content: []byte("test-cert\ntest-key"), + }, + ), + ) - g.Expect(files[6].Path).To(Equal("/etc/nginx/stream-conf.d/stream.conf")) - g.Expect(files[6].Type).To(Equal(file.TypeRegular)) - streamCfg := string(files[6].Content) + g.Expect(files[10].Path).To(Equal("/etc/nginx/stream-conf.d/stream.conf")) + g.Expect(files[10].Type).To(Equal(file.TypeRegular)) + streamCfg := string(files[10].Content) g.Expect(streamCfg).To(ContainSubstring("listen unix:/var/run/nginx/app.example.com-443.sock")) g.Expect(streamCfg).To(ContainSubstring("listen 443")) g.Expect(streamCfg).To(ContainSubstring("app.example.com unix:/var/run/nginx/app.example.com-443.sock")) diff --git a/internal/mode/static/nginx/config/http/config.go b/internal/mode/static/nginx/config/http/config.go index f17d51e5d5..24aecaa3e4 100644 --- a/internal/mode/static/nginx/config/http/config.go +++ b/internal/mode/static/nginx/config/http/config.go @@ -13,7 +13,7 @@ type Server struct { ServerName string Listen string Locations []Location - Includes []Include + Includes []shared.Include IsDefaultHTTP bool IsDefaultSSL bool GRPC bool @@ -39,7 +39,7 @@ type Location struct { Return *Return ResponseHeaders ResponseHeaders Rewrites []string - Includes []Include + Includes []shared.Include GRPC bool } @@ -117,9 +117,3 @@ type ServerConfig struct { IPFamily shared.IPFamily Plus bool } - -// Include defines a file that's included via the include directive. -type Include struct { - Name string - Content []byte -} diff --git a/internal/mode/static/nginx/config/includes.go b/internal/mode/static/nginx/config/includes.go new file mode 100644 index 0000000000..9fd69d5639 --- /dev/null +++ b/internal/mode/static/nginx/config/includes.go @@ -0,0 +1,148 @@ +package config + +import ( + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/http" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/policies" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/shared" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/dataplane" +) + +func createIncludeExecuteResultsFromServers(servers []http.Server) []executeResult { + uniqueIncludes := make(map[string][]byte) + + // deduplicate include files across servers and location + for _, server := range servers { + for _, include := range server.Includes { + uniqueIncludes[include.Name] = include.Content + } + + for _, loc := range server.Locations { + for _, include := range loc.Includes { + uniqueIncludes[include.Name] = include.Content + } + } + } + + results := make([]executeResult, 0, len(uniqueIncludes)) + + for filename, contents := range uniqueIncludes { + results = append( + results, executeResult{ + dest: filename, + data: contents, + }, + ) + } + + return results +} + +func createIncludesFromPolicyGenerateResult(resFiles []policies.File) []shared.Include { + if len(resFiles) == 0 { + return nil + } + + includes := make([]shared.Include, 0, len(resFiles)) + for _, file := range resFiles { + includes = append( + includes, shared.Include{ + Name: includesFolder + "/" + file.Name, + Content: file.Content, + }, + ) + } + + return includes +} + +func createIncludeFromSnippet(snippet dataplane.Snippet) shared.Include { + return shared.Include{ + Name: includesFolder + "/" + snippet.Name + ".conf", + Content: []byte(snippet.Contents), + } +} + +func deduplicateIncludes(includes []shared.Include) []shared.Include { + uniqueIncludes := make(map[string]shared.Include) + for _, i := range includes { + if _, ok := uniqueIncludes[i.Name]; !ok { + uniqueIncludes[i.Name] = i + } + } + + results := make([]shared.Include, 0, len(uniqueIncludes)) + for _, i := range uniqueIncludes { + results = append(results, i) + } + + return results +} + +func createIncludesFromLocationSnippetsFilters(filters []dataplane.SnippetsFilter) []shared.Include { + if len(filters) == 0 { + return nil + } + + includes := make([]shared.Include, 0) + + if len(filters) > 0 { + for _, f := range filters { + if f.LocationSnippet != nil { + includes = append(includes, createIncludeFromSnippet(*f.LocationSnippet)) + } + } + } + + return deduplicateIncludes(includes) +} + +func createIncludesFromServerSnippetsFilters(server dataplane.VirtualServer) []shared.Include { + if len(server.PathRules) == 0 { + return nil + } + + includes := make([]shared.Include, 0) + + for _, pr := range server.PathRules { + for _, mr := range pr.MatchRules { + if len(mr.Filters.SnippetsFilters) > 0 { + for _, sf := range mr.Filters.SnippetsFilters { + if sf.ServerSnippet != nil { + includes = append(includes, createIncludeFromSnippet(*sf.ServerSnippet)) + } + } + } + } + } + + return deduplicateIncludes(includes) +} + +func createIncludesFromSnippets(snippets []dataplane.Snippet) []shared.Include { + if len(snippets) == 0 { + return nil + } + + includes := make([]shared.Include, 0) + + for _, s := range snippets { + includes = append(includes, createIncludeFromSnippet(s)) + } + + return deduplicateIncludes(includes) +} + +func createIncludeExecuteResults(includes []shared.Include) []executeResult { + results := make([]executeResult, 0, len(includes)) + + for _, inc := range includes { + results = append( + results, executeResult{ + dest: inc.Name, + data: inc.Content, + }, + ) + } + + return results +} diff --git a/internal/mode/static/nginx/config/includes_test.go b/internal/mode/static/nginx/config/includes_test.go new file mode 100644 index 0000000000..59ae51dbe4 --- /dev/null +++ b/internal/mode/static/nginx/config/includes_test.go @@ -0,0 +1,527 @@ +package config + +import ( + "testing" + + . "github.com/onsi/gomega" + + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/http" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/policies" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/shared" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/dataplane" +) + +func TestCreateIncludeExecuteResultsFromServers(t *testing.T) { + t.Parallel() + + servers := []http.Server{ + { + Includes: []shared.Include{ + { + Name: "include-1.conf", + Content: []byte("include-1"), + }, + { + Name: "include-2.conf", + Content: []byte("include-2"), + }, + }, + Locations: []http.Location{ + { + Includes: []shared.Include{ + { + Name: "include-3.conf", + Content: []byte("include-3"), + }, + { + Name: "include-4.conf", + Content: []byte("include-4"), + }, + }, + }, + }, + }, + { + Includes: []shared.Include{ + { + Name: "include-1.conf", // dupe + Content: []byte("include-1"), + }, + { + Name: "include-2.conf", // dupe + Content: []byte("include-2"), + }, + }, + Locations: []http.Location{ + { + Includes: []shared.Include{ + { + Name: "include-3.conf", // dupe + Content: []byte("include-3"), + }, + { + Name: "include-4.conf", // dupe + Content: []byte("include-4"), + }, + { + Name: "include-5.conf", + Content: []byte("include-5"), + }, + }, + }, + }, + }, + } + + results := createIncludeExecuteResultsFromServers(servers) + + expResults := []executeResult{ + { + dest: "include-1.conf", + data: []byte("include-1"), + }, + { + dest: "include-2.conf", + data: []byte("include-2"), + }, + { + dest: "include-3.conf", + data: []byte("include-3"), + }, + { + dest: "include-4.conf", + data: []byte("include-4"), + }, + { + dest: "include-5.conf", + data: []byte("include-5"), + }, + } + + g := NewWithT(t) + + g.Expect(results).To(ConsistOf(expResults)) +} + +func TestCreateIncludesFromPolicyGenerateResult(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + files []policies.File + includes []shared.Include + }{ + { + name: "no files", + files: nil, + includes: nil, + }, + { + name: "additions", + files: []policies.File{ + { + Content: []byte("one"), + Name: "one.conf", + }, + { + Content: []byte("two"), + Name: "two.conf", + }, + { + Content: []byte("three"), + Name: "three.conf", + }, + }, + includes: []shared.Include{ + { + Content: []byte("one"), + Name: includesFolder + "/one.conf", + }, + { + Content: []byte("two"), + Name: includesFolder + "/two.conf", + }, + { + Content: []byte("three"), + Name: includesFolder + "/three.conf", + }, + }, + }, + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + includes := createIncludesFromPolicyGenerateResult(test.files) + g.Expect(includes).To(Equal(test.includes)) + }, + ) + } +} + +func TestCreateIncludesFromLocationSnippetsFilter(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + filters []dataplane.SnippetsFilter + expIncludes []shared.Include + }{ + { + name: "no filters", + filters: nil, + expIncludes: nil, + }, + { + name: "filters with no location snippets", + filters: []dataplane.SnippetsFilter{ + { + LocationSnippet: nil, + ServerSnippet: &dataplane.Snippet{Name: "server1", Contents: "directive1"}, + }, + { + LocationSnippet: nil, + ServerSnippet: &dataplane.Snippet{Name: "server2", Contents: "directive2"}, + }, + }, + expIncludes: []shared.Include{}, + }, + { + name: "filters with some location snippets, duplicates should be ignored", + filters: []dataplane.SnippetsFilter{ + { + LocationSnippet: &dataplane.Snippet{Name: "location1", Contents: "location directive1"}, + ServerSnippet: &dataplane.Snippet{Name: "server1", Contents: "server directive1"}, + }, + { + LocationSnippet: nil, + ServerSnippet: &dataplane.Snippet{Name: "server2", Contents: "server directive2"}, + }, + { + LocationSnippet: &dataplane.Snippet{Name: "location2", Contents: "location directive2"}, + ServerSnippet: nil, + }, + { + LocationSnippet: &dataplane.Snippet{Name: "location2", Contents: "location directive2"}, // dupe + ServerSnippet: nil, + }, + }, + expIncludes: []shared.Include{ + { + Name: includesFolder + "/location1.conf", + Content: []byte("location directive1"), + }, + { + Name: includesFolder + "/location2.conf", + Content: []byte("location directive2"), + }, + }, + }, + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + + includes := createIncludesFromLocationSnippetsFilters(test.filters) + g.Expect(includes).To(ConsistOf(test.expIncludes)) + }, + ) + } +} + +func TestCreateIncludesFromServerSnippetsFilters(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + expIncludes []shared.Include + server dataplane.VirtualServer + }{ + { + name: "no path rules (default server) should return nil includes", + server: dataplane.VirtualServer{IsDefault: true, PathRules: nil}, + expIncludes: nil, + }, + { + name: "no snippets filters", + server: dataplane.VirtualServer{ + PathRules: []dataplane.PathRule{ + { + MatchRules: []dataplane.MatchRule{ + { + Filters: dataplane.HTTPFilters{ + RequestRedirect: &dataplane.HTTPRequestRedirectFilter{}, + SnippetsFilters: nil, + }, + }, + { + Filters: dataplane.HTTPFilters{ + RequestURLRewrite: &dataplane.HTTPURLRewriteFilter{}, + SnippetsFilters: nil, + }, + }, + }, + }, + { + MatchRules: []dataplane.MatchRule{ + { + Filters: dataplane.HTTPFilters{ + ResponseHeaderModifiers: &dataplane.HTTPHeaderFilter{}, + SnippetsFilters: nil, + }, + }, + { + Filters: dataplane.HTTPFilters{ + ResponseHeaderModifiers: &dataplane.HTTPHeaderFilter{}, + SnippetsFilters: nil, + }, + }, + }, + }, + { + MatchRules: []dataplane.MatchRule{ + { + Filters: dataplane.HTTPFilters{ + InvalidFilter: &dataplane.InvalidHTTPFilter{}, + }, + }, + }, + }, + }, + }, + expIncludes: []shared.Include{}, + }, + { + name: "some snippets filters, duplicates should be ignored", + server: dataplane.VirtualServer{ + PathRules: []dataplane.PathRule{ + { + MatchRules: []dataplane.MatchRule{ + { + Filters: dataplane.HTTPFilters{ + SnippetsFilters: []dataplane.SnippetsFilter{ + { + ServerSnippet: &dataplane.Snippet{ + Name: "server1", + Contents: "server directive1", + }, + }, + }, + }, + }, + }, + }, + { + MatchRules: []dataplane.MatchRule{ + { + Filters: dataplane.HTTPFilters{ + SnippetsFilters: []dataplane.SnippetsFilter{ + { + ServerSnippet: &dataplane.Snippet{ + Name: "server1", // dupe, should be ignored + Contents: "server directive1", + }, + }, + }, + }, + }, + { + Filters: dataplane.HTTPFilters{ + SnippetsFilters: []dataplane.SnippetsFilter{ + { + ServerSnippet: &dataplane.Snippet{ + Name: "server2", + Contents: "server directive2", + }, + }, + }, + }, + }, + }, + }, + { + MatchRules: []dataplane.MatchRule{ + { + Filters: dataplane.HTTPFilters{ + SnippetsFilters: []dataplane.SnippetsFilter{ + { + ServerSnippet: &dataplane.Snippet{ + Name: "server1", // another dupe, should be ignored + Contents: "server directive1", + }, + }, + }, + }, + }, + }, + }, + }, + }, + expIncludes: []shared.Include{ + { + Name: includesFolder + "/server1.conf", + Content: []byte("server directive1"), + }, + { + Name: includesFolder + "/server2.conf", + Content: []byte("server directive2"), + }, + }, + }, + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + includes := createIncludesFromServerSnippetsFilters(test.server) + g.Expect(includes).To(ConsistOf(test.expIncludes)) + }, + ) + } +} + +func TestCreateIncludesFromSnippets(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + snippets []dataplane.Snippet + expIncludes []shared.Include + }{ + { + name: "no snippets", + snippets: nil, + expIncludes: nil, + }, + { + name: "snippets, duplicates are ignored", + snippets: []dataplane.Snippet{ + { + Name: "snippet1", + Contents: "directive1", + }, + { + Name: "snippet2", + Contents: "directive2", + }, + { + Name: "snippet1", // duplicate + Contents: "directive1", + }, + { + Name: "snippet3", + Contents: "directive3", + }, + { + Name: "snippet3", // duplicate + Contents: "directive3", + }, + { + Name: "snippet4", + Contents: "directive4", + }, + }, + expIncludes: []shared.Include{ + { + Name: includesFolder + "/snippet1.conf", + Content: []byte("directive1"), + }, + { + Name: includesFolder + "/snippet2.conf", + Content: []byte("directive2"), + }, + { + Name: includesFolder + "/snippet3.conf", + Content: []byte("directive3"), + }, + { + Name: includesFolder + "/snippet4.conf", + Content: []byte("directive4"), + }, + }, + }, + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + + includes := createIncludesFromSnippets(test.snippets) + g.Expect(includes).To(ConsistOf(test.expIncludes)) + }, + ) + } +} + +func TestCreateIncludeExecuteResults(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + includes []shared.Include + expExecuteResults []executeResult + }{ + { + name: "no includes", + includes: nil, + expExecuteResults: []executeResult{}, + }, + { + name: "includes", + includes: []shared.Include{ + { + Name: "include1.conf", + Content: []byte("directive1"), + }, + { + Name: "include2.conf", + Content: []byte("directive2"), + }, + { + Name: "include3.conf", + Content: []byte("directive3"), + }, + }, + expExecuteResults: []executeResult{ + { + dest: "include1.conf", + data: []byte("directive1"), + }, + { + dest: "include2.conf", + data: []byte("directive2"), + }, + { + dest: "include3.conf", + data: []byte("directive3"), + }, + }, + }, + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + + results := createIncludeExecuteResults(test.includes) + g.Expect(results).To(ConsistOf(test.expExecuteResults)) + }, + ) + } +} diff --git a/internal/mode/static/nginx/config/main_config.go b/internal/mode/static/nginx/config/main_config.go new file mode 100644 index 0000000000..10529361ad --- /dev/null +++ b/internal/mode/static/nginx/config/main_config.go @@ -0,0 +1,36 @@ +package config + +import ( + gotemplate "text/template" + + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/shared" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/dataplane" +) + +var mainConfigTemplate = gotemplate.Must(gotemplate.New("main").Parse(mainConfigTemplateText)) + +type mainConfig struct { + Includes []shared.Include + TelemetryEnabled bool +} + +func executeMainConfig(conf dataplane.Configuration) []executeResult { + includes := createIncludesFromSnippets(conf.MainSnippets) + + mc := mainConfig{ + TelemetryEnabled: conf.Telemetry.Endpoint != "", + Includes: includes, + } + + results := make([]executeResult, 0, len(includes)+1) + results = append( + results, executeResult{ + dest: mainIncludeFile, + data: helpers.MustExecuteTemplate(mainConfigTemplate, mc), + }, + ) + results = append(results, createIncludeExecuteResults(includes)...) + + return results +} diff --git a/internal/mode/static/nginx/config/main_config_template.go b/internal/mode/static/nginx/config/main_config_template.go new file mode 100644 index 0000000000..dbd3dca2d6 --- /dev/null +++ b/internal/mode/static/nginx/config/main_config_template.go @@ -0,0 +1,11 @@ +package config + +const mainConfigTemplateText = ` +{{ if .TelemetryEnabled -}} +load_module modules/ngx_otel_module.so; +{{ end -}} + +{{ range $i := .Includes -}} +include {{ $i.Name }}; +{{ end -}} +` diff --git a/internal/mode/static/nginx/config/main_config_test.go b/internal/mode/static/nginx/config/main_config_test.go new file mode 100644 index 0000000000..8a453dd078 --- /dev/null +++ b/internal/mode/static/nginx/config/main_config_test.go @@ -0,0 +1,111 @@ +package config + +import ( + "sort" + "testing" + + . "github.com/onsi/gomega" + + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/dataplane" +) + +func TestExecuteMainConfig_Telemetry(t *testing.T) { + t.Parallel() + + telemetryOff := dataplane.Configuration{ + Telemetry: dataplane.Telemetry{}, + } + telemetryOn := dataplane.Configuration{ + Telemetry: dataplane.Telemetry{ + Endpoint: "endpoint", + }, + } + loadModuleDirective := "load_module modules/ngx_otel_module.so;" + + tests := []struct { + name string + conf dataplane.Configuration + expLoadModuleDirective bool + }{ + { + name: "telemetry off", + conf: telemetryOff, + expLoadModuleDirective: false, + }, + { + name: "telemetry on", + conf: telemetryOn, + expLoadModuleDirective: true, + }, + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + res := executeMainConfig(test.conf) + g.Expect(res).To(HaveLen(1)) + g.Expect(res[0].dest).To(Equal(mainIncludeFile)) + if test.expLoadModuleDirective { + g.Expect(res[0].data).To(ContainSubstring(loadModuleDirective)) + } else { + g.Expect(res[0].data).ToNot(ContainSubstring(loadModuleDirective)) + } + }, + ) + } +} + +func TestExecuteMainConfig_Snippets(t *testing.T) { + t.Parallel() + + conf := dataplane.Configuration{ + MainSnippets: []dataplane.Snippet{ + { + Name: "snippet1", + Contents: "contents1", + }, + { + Name: "snippet2", + Contents: "contents2", + }, + { + Name: "snippet3", + Contents: "contents3", + }, + }, + } + + g := NewWithT(t) + + res := executeMainConfig(conf) + g.Expect(res).To(HaveLen(4)) + + // sort results by filename + sort.Slice( + res, func(i, j int) bool { + return res[i].dest < res[j].dest + }, + ) + + /* + Order of files: + /etc/nginx/includes/snippet1.conf + /etc/nginx/includes/snippet2.conf + /etc/nginx/includes/snippet3.conf + /etc/nginx/main-includes/main.conf + */ + + g.Expect(res[0].dest).To(Equal("/etc/nginx/includes/snippet1.conf")) + g.Expect(string(res[0].data)).To(ContainSubstring("contents1")) + + g.Expect(res[1].dest).To(Equal("/etc/nginx/includes/snippet2.conf")) + g.Expect(string(res[1].data)).To(ContainSubstring("contents2")) + + g.Expect(res[2].dest).To(Equal("/etc/nginx/includes/snippet3.conf")) + g.Expect(string(res[2].data)).To(ContainSubstring("contents3")) + + g.Expect(res[3].dest).To(Equal(mainIncludeFile)) +} diff --git a/internal/mode/static/nginx/config/policies/generator.go b/internal/mode/static/nginx/config/policies/generator.go index a1428a2f9c..24ed738a69 100644 --- a/internal/mode/static/nginx/config/policies/generator.go +++ b/internal/mode/static/nginx/config/policies/generator.go @@ -10,7 +10,7 @@ import ( type Generator interface { // GenerateForServer generates policy configuration for the server block. GenerateForServer(policies []Policy, server http.Server) GenerateResultFiles - // GenerateForServer generates policy configuration for a normal location block. + // GenerateForLocation generates policy configuration for a normal location block. GenerateForLocation(policies []Policy, location http.Location) GenerateResultFiles // GenerateForInternalLocation generates policy configuration for an internal location block. GenerateForInternalLocation(policies []Policy) GenerateResultFiles diff --git a/internal/mode/static/nginx/config/servers.go b/internal/mode/static/nginx/config/servers.go index 0fd1930f5e..e840802e0a 100644 --- a/internal/mode/static/nginx/config/servers.go +++ b/internal/mode/static/nginx/config/servers.go @@ -124,7 +124,7 @@ func (g GeneratorImpl) executeServers(conf dataplane.Configuration, generator po data: httpMatchConf, } - includeFileResults := createIncludeFileResults(servers) + includeFileResults := createIncludeExecuteResultsFromServers(servers) allResults := make([]executeResult, 0, len(includeFileResults)+2) allResults = append(allResults, includeFileResults...) @@ -145,33 +145,6 @@ func getIPFamily(baseHTTPConfig dataplane.BaseHTTPConfig) shared.IPFamily { return shared.IPFamily{IPv4: true, IPv6: true} } -func createIncludeFileResults(servers []http.Server) []executeResult { - uniqueIncludes := make(map[string][]byte) - - for _, server := range servers { - for _, include := range server.Includes { - uniqueIncludes[include.Name] = include.Content - } - - for _, loc := range server.Locations { - for _, include := range loc.Includes { - uniqueIncludes[include.Name] = include.Content - } - } - } - - results := make([]executeResult, 0, len(uniqueIncludes)) - - for filename, contents := range uniqueIncludes { - results = append(results, executeResult{ - dest: filename, - data: contents, - }) - } - - return results -} - func createServers(conf dataplane.Configuration, generator policies.Generator) ([]http.Server, httpMatchPairs) { servers := make([]http.Server, 0, len(conf.HTTPServers)+len(conf.SSLServers)) finalMatchPairs := make(httpMatchPairs) @@ -229,9 +202,15 @@ func createSSLServer( Listen: listen, } - server.Includes = createIncludesFromPolicyGenerateResult( + policyIncludes := createIncludesFromPolicyGenerateResult( generator.GenerateForServer(virtualServer.Policies, server), ) + snippetIncludes := createIncludesFromServerSnippetsFilters(virtualServer) + + server.Includes = make([]shared.Include, 0, len(policyIncludes)+len(snippetIncludes)) + server.Includes = append(server.Includes, policyIncludes...) + server.Includes = append(server.Includes, snippetIncludes...) + return server, matchPairs } @@ -258,9 +237,14 @@ func createServer( GRPC: grpc, } - server.Includes = createIncludesFromPolicyGenerateResult( + policyIncludes := createIncludesFromPolicyGenerateResult( generator.GenerateForServer(virtualServer.Policies, server), ) + snippetIncludes := createIncludesFromServerSnippetsFilters(virtualServer) + + server.Includes = make([]shared.Include, 0, len(policyIncludes)+len(snippetIncludes)) + server.Includes = append(server.Includes, policyIncludes...) + server.Includes = append(server.Includes, snippetIncludes...) return server, matchPairs } @@ -363,22 +347,6 @@ func needsInternalLocations(rule dataplane.PathRule) bool { return len(rule.MatchRules) == 1 && !isPathOnlyMatch(rule.MatchRules[0].Match) } -func createIncludesFromPolicyGenerateResult(resFiles []policies.File) []http.Include { - if len(resFiles) == 0 { - return nil - } - - includes := make([]http.Include, 0, len(resFiles)) - for _, file := range resFiles { - includes = append(includes, http.Include{ - Name: includesFolder + "/" + file.Name, - Content: file.Content, - }) - } - - return includes -} - // pathAndTypeMap contains a map of paths and any path types defined for that path // for example, {/foo: {exact: {}, prefix: {}}}. type pathAndTypeMap map[string]map[dataplane.PathType]struct{} @@ -488,6 +456,8 @@ func updateLocation( return location } + location.Includes = append(location.Includes, createIncludesFromLocationSnippetsFilters(filters.SnippetsFilters)...) + if filters.RequestRedirect != nil { ret := createReturnValForRedirectFilter(filters.RequestRedirect, listenerPort) location.Return = ret @@ -827,10 +797,12 @@ func generateProxySetHeaders(filters *dataplane.HTTPFilters, grpc bool) []http.H } // If the value of a header field is an empty string then this field will not be passed to a proxied server for _, h := range headerFilter.Remove { - proxySetHeaders = append(proxySetHeaders, http.Header{ - Name: h, - Value: "", - }) + proxySetHeaders = append( + proxySetHeaders, http.Header{ + Name: h, + Value: "", + }, + ) } return append(proxySetHeaders, headers...) @@ -858,10 +830,12 @@ func createHeadersWithVarName(headers []dataplane.HTTPHeader) []http.Header { locHeaders := make([]http.Header, 0, len(headers)) for _, h := range headers { mapVarName := "${" + generateAddHeaderMapVariableName(h.Name) + "}" - locHeaders = append(locHeaders, http.Header{ - Name: h.Name, - Value: mapVarName + h.Value, - }) + locHeaders = append( + locHeaders, http.Header{ + Name: h.Name, + Value: mapVarName + h.Value, + }, + ) } return locHeaders } @@ -869,10 +843,12 @@ func createHeadersWithVarName(headers []dataplane.HTTPHeader) []http.Header { func createHeaders(headers []dataplane.HTTPHeader) []http.Header { locHeaders := make([]http.Header, 0, len(headers)) for _, h := range headers { - locHeaders = append(locHeaders, http.Header{ - Name: h.Name, - Value: h.Value, - }) + locHeaders = append( + locHeaders, http.Header{ + Name: h.Name, + Value: h.Value, + }, + ) } return locHeaders } diff --git a/internal/mode/static/nginx/config/servers_test.go b/internal/mode/static/nginx/config/servers_test.go index 515908c76f..38362b528e 100644 --- a/internal/mode/static/nginx/config/servers_test.go +++ b/internal/mode/static/nginx/config/servers_test.go @@ -19,6 +19,7 @@ import ( func TestExecuteServers(t *testing.T) { t.Parallel() + conf := dataplane.Configuration{ HTTPServers: []dataplane.VirtualServer{ { @@ -35,6 +36,42 @@ func TestExecuteServers(t *testing.T) { Policies: []policies.Policy{ &policiesfakes.FakePolicy{}, }, + PathRules: []dataplane.PathRule{ + { + Path: "/", + PathType: dataplane.PathTypePrefix, + MatchRules: []dataplane.MatchRule{ + { + Filters: dataplane.HTTPFilters{ + SnippetsFilters: []dataplane.SnippetsFilter{ + { + LocationSnippet: &dataplane.Snippet{ + Name: "location-snippet", + Contents: "location snippet contents", + }, + ServerSnippet: &dataplane.Snippet{ + Name: "server-snippet", + Contents: "server snippet contents", + }, + }, + }, + }, + Match: dataplane.Match{}, + BackendGroup: dataplane.BackendGroup{ + Source: types.NamespacedName{Namespace: "test", Name: "route1"}, + RuleIdx: 0, + Backends: []dataplane.Backend{ + { + UpstreamName: "test_foo_443", + Valid: true, + Weight: 1, + }, + }, + }, + }, + }, + }, + }, }, }, SSLServers: []dataplane.VirtualServer{ @@ -99,6 +136,8 @@ func TestExecuteServers(t *testing.T) { "ssl_certificate_key /etc/nginx/secrets/test-keypair.pem;": 2, "proxy_ssl_server_name on;": 1, "status_zone": 0, + "include /etc/nginx/includes/location-snippet.conf": 1, + "include /etc/nginx/includes/server-snippet.conf": 1, } type assertion func(g *WithT, data string) @@ -118,20 +157,29 @@ func TestExecuteServers(t *testing.T) { includesFolder + "/include-2.conf": func(g *WithT, data string) { g.Expect(data).To(Equal("include-2")) }, + includesFolder + "/location-snippet.conf": func(g *WithT, data string) { + g.Expect(data).To(Equal("location snippet contents")) + }, + includesFolder + "/server-snippet.conf": func(g *WithT, data string) { + g.Expect(data).To(Equal("server snippet contents")) + }, } + g := NewWithT(t) fakeGenerator := &policiesfakes.FakeGenerator{} - fakeGenerator.GenerateForServerReturns(policies.GenerateResultFiles{ - { - Name: "include-1.conf", - Content: []byte("include-1"), - }, - { - Name: "include-2.conf", - Content: []byte("include-2"), + fakeGenerator.GenerateForServerReturns( + policies.GenerateResultFiles{ + { + Name: "include-1.conf", + Content: []byte("include-1"), + }, + { + Name: "include-2.conf", + Content: []byte("include-2"), + }, }, - }) + ) gen := GeneratorImpl{} results := gen.executeServers(conf, fakeGenerator) @@ -268,21 +316,23 @@ func TestExecuteServers_IPFamily(t *testing.T) { } for _, test := range tests { - t.Run(test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - gen := GeneratorImpl{} - results := gen.executeServers(test.config, &policiesfakes.FakeGenerator{}) - g.Expect(results).To(HaveLen(2)) - serverConf := string(results[0].data) - httpMatchConf := string(results[1].data) - g.Expect(httpMatchConf).To(Equal("{}")) - - for expSubStr, expCount := range test.expectedHTTPConfig { - g.Expect(strings.Count(serverConf, expSubStr)).To(Equal(expCount)) - } - }) + t.Run( + test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + gen := GeneratorImpl{} + results := gen.executeServers(test.config, &policiesfakes.FakeGenerator{}) + g.Expect(results).To(HaveLen(2)) + serverConf := string(results[0].data) + httpMatchConf := string(results[1].data) + g.Expect(httpMatchConf).To(Equal("{}")) + + for expSubStr, expCount := range test.expectedHTTPConfig { + g.Expect(strings.Count(serverConf, expSubStr)).To(Equal(expCount)) + } + }, + ) } } @@ -386,21 +436,23 @@ func TestExecuteServers_RewriteClientIP(t *testing.T) { } for _, test := range tests { - t.Run(test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - gen := GeneratorImpl{} - results := gen.executeServers(test.config, &policiesfakes.FakeGenerator{}) - g.Expect(results).To(HaveLen(2)) - serverConf := string(results[0].data) - httpMatchConf := string(results[1].data) - g.Expect(httpMatchConf).To(Equal("{}")) - - for expSubStr, expCount := range test.expectedHTTPConfig { - g.Expect(strings.Count(serverConf, expSubStr)).To(Equal(expCount)) - } - }) + t.Run( + test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + gen := GeneratorImpl{} + results := gen.executeServers(test.config, &policiesfakes.FakeGenerator{}) + g.Expect(results).To(HaveLen(2)) + serverConf := string(results[0].data) + httpMatchConf := string(results[1].data) + g.Expect(httpMatchConf).To(Equal("{}")) + + for expSubStr, expCount := range test.expectedHTTPConfig { + g.Expect(strings.Count(serverConf, expSubStr)).To(Equal(expCount)) + } + }, + ) } } @@ -512,25 +564,27 @@ func TestExecuteForDefaultServers(t *testing.T) { httpDefaultFmt := "listen %d default_server" for _, tc := range testcases { - t.Run(tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - gen := GeneratorImpl{} - serverResults := gen.executeServers(tc.conf, &policiesfakes.FakeGenerator{}) - g.Expect(serverResults).To(HaveLen(2)) - serverConf := string(serverResults[0].data) - httpMatchConf := string(serverResults[1].data) - g.Expect(httpMatchConf).To(Equal("{}")) - - for _, expPort := range tc.httpPorts { - g.Expect(serverConf).To(ContainSubstring(fmt.Sprintf(httpDefaultFmt, expPort))) - } + t.Run( + tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - for _, expPort := range tc.sslPorts { - g.Expect(serverConf).To(ContainSubstring(fmt.Sprintf(sslDefaultFmt, expPort))) - } - }) + gen := GeneratorImpl{} + serverResults := gen.executeServers(tc.conf, &policiesfakes.FakeGenerator{}) + g.Expect(serverResults).To(HaveLen(2)) + serverConf := string(serverResults[0].data) + httpMatchConf := string(serverResults[1].data) + g.Expect(httpMatchConf).To(Equal("{}")) + + for _, expPort := range tc.httpPorts { + g.Expect(serverConf).To(ContainSubstring(fmt.Sprintf(httpDefaultFmt, expPort))) + } + + for _, expPort := range tc.sslPorts { + g.Expect(serverConf).To(ContainSubstring(fmt.Sprintf(sslDefaultFmt, expPort))) + } + }, + ) } } @@ -1050,10 +1104,11 @@ func TestCreateServers(t *testing.T) { }, } - externalIncludes := []http.Include{ + externalIncludes := []shared.Include{ {Name: "/etc/nginx/includes/include-1.conf", Content: []byte("include-1")}, } - internalIncludes := []http.Include{ + + internalIncludes := []shared.Include{ {Name: "/etc/nginx/includes/internal-include-1.conf", Content: []byte("include-1")}, } @@ -1453,6 +1508,7 @@ func TestCreateServers(t *testing.T) { { ServerName: "cafe.example.com", Locations: getExpectedLocations(false), + Includes: []shared.Include{}, Listen: "8080", GRPC: true, }, @@ -1468,6 +1524,7 @@ func TestCreateServers(t *testing.T) { CertificateKey: expectedPEMPath, }, Locations: getExpectedLocations(true), + Includes: []shared.Include{}, Listen: getSocketNameHTTPS(8443), IsSocket: true, GRPC: true, @@ -1477,18 +1534,22 @@ func TestCreateServers(t *testing.T) { g := NewWithT(t) fakeGenerator := &policiesfakes.FakeGenerator{} - fakeGenerator.GenerateForLocationReturns(policies.GenerateResultFiles{ - { - Name: "include-1.conf", - Content: []byte("include-1"), + fakeGenerator.GenerateForLocationReturns( + policies.GenerateResultFiles{ + { + Name: "include-1.conf", + Content: []byte("include-1"), + }, }, - }) - fakeGenerator.GenerateForInternalLocationReturns(policies.GenerateResultFiles{ - { - Name: "internal-include-1.conf", - Content: []byte("include-1"), + ) + fakeGenerator.GenerateForInternalLocationReturns( + policies.GenerateResultFiles{ + { + Name: "internal-include-1.conf", + Content: []byte("include-1"), + }, }, - }) + ) result, httpMatchPair := createServers(conf, fakeGenerator) @@ -1680,36 +1741,367 @@ func TestCreateServersConflicts(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - httpServers := []dataplane.VirtualServer{ + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + httpServers := []dataplane.VirtualServer{ + { + IsDefault: true, + Port: 8080, + }, + { + Hostname: "cafe.example.com", + PathRules: test.rules, + Port: 8080, + }, + } + expectedServers := []http.Server{ + { + IsDefaultHTTP: true, + Listen: "8080", + }, + { + ServerName: "cafe.example.com", + Locations: test.expLocs, + Listen: "8080", + Includes: []shared.Include{}, + }, + } + + g := NewWithT(t) + + result, _ := createServers( + dataplane.Configuration{HTTPServers: httpServers}, + &policiesfakes.FakeGenerator{}, + ) + g.Expect(helpers.Diff(expectedServers, result)).To(BeEmpty()) + }, + ) + } +} + +func TestCreateServers_Includes(t *testing.T) { + t.Parallel() + + pathRules := []dataplane.PathRule{ + { + Path: "/", + PathType: dataplane.PathTypeExact, + MatchRules: []dataplane.MatchRule{ { - IsDefault: true, - Port: 8080, + Filters: dataplane.HTTPFilters{ + SnippetsFilters: []dataplane.SnippetsFilter{ + { + LocationSnippet: &dataplane.Snippet{ + Name: "location-snippet", + Contents: "location snippet contents", + }, + ServerSnippet: &dataplane.Snippet{ + Name: "server-snippet", + Contents: "server snippet contents", + }, + }, + }, + }, }, + }, + }, + } + + httpServers := []dataplane.VirtualServer{ + { + IsDefault: true, + Port: 8080, + }, + { + Hostname: "http.example.com", + PathRules: pathRules, + Port: 8080, + Policies: []policies.Policy{ + &policiesfakes.FakePolicy{}, + }, + }, + } + + sslServers := []dataplane.VirtualServer{ + { + IsDefault: true, + Port: 8443, + }, + { + Hostname: "ssl.example.com", + SSL: &dataplane.SSL{KeyPairID: "test-keypair"}, + PathRules: pathRules, + Port: 8443, + Policies: []policies.Policy{ + &policiesfakes.FakePolicy{}, + }, + }, + } + + fakeGenerator := &policiesfakes.FakeGenerator{} + fakeGenerator.GenerateForLocationReturns( + policies.GenerateResultFiles{ + { + Name: "ext-policy.conf", + Content: []byte("external policy conf"), + }, + }, + ) + fakeGenerator.GenerateForServerReturns( + policies.GenerateResultFiles{ + { + Name: "server-policy.conf", + Content: []byte("server policy conf"), + }, + }, + ) + + expServers := []http.Server{ + { + IsDefaultHTTP: true, + }, + { + ServerName: "http.example.com", + Locations: []http.Location{ { - Hostname: "cafe.example.com", - PathRules: test.rules, - Port: 8080, + Path: "= /", + Includes: []shared.Include{ + { + Name: includesFolder + "/location-snippet.conf", + Content: []byte("location snippet contents"), + }, + { + Name: includesFolder + "/ext-policy.conf", + Content: []byte("external policy conf"), + }, + }, }, - } - expectedServers := []http.Server{ + }, + Includes: []shared.Include{ { - IsDefaultHTTP: true, - Listen: "8080", + Name: includesFolder + "/server-policy.conf", + Content: []byte("server policy conf"), }, { - ServerName: "cafe.example.com", - Locations: test.expLocs, - Listen: "8080", + Name: includesFolder + "/server-snippet.conf", + Content: []byte("server snippet contents"), }, - } + }, + Listen: "8080", + GRPC: true, + }, + { + IsDefaultSSL: true, + }, + { + ServerName: "ssl.example.com", + Locations: []http.Location{ + { + Path: "= /", + Includes: []shared.Include{ + { + Name: includesFolder + "/location-snippet.conf", + Content: []byte("location snippet contents"), + }, + { + Name: includesFolder + "/ext-policy.conf", + Content: []byte("external policy conf"), + }, + }, + }, + }, + Includes: []shared.Include{ + { + Name: includesFolder + "/server-policy.conf", + Content: []byte("server policy conf"), + }, + { + Name: includesFolder + "/server-snippet.conf", + Content: []byte("server snippet contents"), + }, + }, + }, + } - g := NewWithT(t) + g := NewWithT(t) + + conf := dataplane.Configuration{HTTPServers: httpServers, SSLServers: sslServers} + + servers, matchPairs := createServers(conf, fakeGenerator) + g.Expect(matchPairs).To(BeEmpty()) + g.Expect(servers).To(HaveLen(len(expServers))) - result, _ := createServers(dataplane.Configuration{HTTPServers: httpServers}, &policiesfakes.FakeGenerator{}) - g.Expect(helpers.Diff(expectedServers, result)).To(BeEmpty()) - }) + for i, server := range expServers { + g.Expect(server.ServerName).To(Equal(servers[i].ServerName)) + + if servers[i].IsDefaultHTTP || servers[i].IsDefaultSSL { + g.Expect(servers[i].Includes).To(BeEmpty()) + } else { + g.Expect(server.Includes).To(ConsistOf(servers[i].Includes)) + g.Expect(server.Locations).To(HaveLen(1)) + g.Expect(server.Locations[0].Path).To(Equal(servers[i].Locations[0].Path)) + g.Expect(server.Locations[0].Includes).To(ConsistOf(servers[i].Locations[0].Includes)) + } + } +} + +func TestCreateLocations_Includes(t *testing.T) { + t.Parallel() + + httpServer := dataplane.VirtualServer{ + Hostname: "example.com", + PathRules: []dataplane.PathRule{ + { + Path: "/", + PathType: dataplane.PathTypeExact, + MatchRules: []dataplane.MatchRule{ + { + Filters: dataplane.HTTPFilters{ + SnippetsFilters: []dataplane.SnippetsFilter{ + { + LocationSnippet: &dataplane.Snippet{ + Name: "location-snippet", + Contents: "location snippet contents", + }, + ServerSnippet: &dataplane.Snippet{ + Name: "server-snippet", + Contents: "server snippet 2 contents", + }, + }, + }, + }, + }, + }, + }, + { + Path: "/snippets-prefix-path", + PathType: dataplane.PathTypePrefix, + MatchRules: []dataplane.MatchRule{ + { + Filters: dataplane.HTTPFilters{ + SnippetsFilters: []dataplane.SnippetsFilter{ + { + LocationSnippet: &dataplane.Snippet{ + Name: "prefix-path-location-snippet", + Contents: "prefix path location snippet contents", + }, + }, + }, + }, + }, + }, + }, + { + Path: "/snippets-with-method-match", + PathType: dataplane.PathTypeExact, + MatchRules: []dataplane.MatchRule{ + { + Match: dataplane.Match{ + Method: helpers.GetPointer("GET"), + }, + Filters: dataplane.HTTPFilters{ + SnippetsFilters: []dataplane.SnippetsFilter{ + { + LocationSnippet: &dataplane.Snippet{ + Name: "method-match-location-snippet", + Contents: "method match location snippet contents", + }, + }, + }, + }, + }, + }, + }, + }, + Port: 80, + } + + externalPolicyInclude := shared.Include{ + Name: includesFolder + "/ext-policy.conf", + Content: []byte("external policy conf"), + } + + internalPolicyInclude := shared.Include{ + Name: includesFolder + "/int-policy.conf", + Content: []byte("internal policy conf"), + } + + // this test only covers the includes generated for locations, it does not test other location fields. + expLocations := []http.Location{ + { + Path: "= /", + Includes: []shared.Include{ + { + Name: includesFolder + "/location-snippet.conf", + Content: []byte("location snippet contents"), + }, + externalPolicyInclude, + }, + }, + { + Path: "/snippets-prefix-path/", + Includes: []shared.Include{ + { + Name: includesFolder + "/prefix-path-location-snippet.conf", + Content: []byte("prefix path location snippet contents"), + }, + externalPolicyInclude, + }, + }, + { + Path: "= /snippets-prefix-path", + Includes: []shared.Include{ + { + Name: includesFolder + "/prefix-path-location-snippet.conf", + Content: []byte("prefix path location snippet contents"), + }, + externalPolicyInclude, + }, + }, + { + Path: "= /snippets-with-method-match", + Includes: []shared.Include{externalPolicyInclude}, + }, + { + Path: "/_ngf-internal-rule2-route0", + Includes: []shared.Include{ + { + Name: includesFolder + "/method-match-location-snippet.conf", + Content: []byte("method match location snippet contents"), + }, + internalPolicyInclude, + }, + }, + } + + fakeGenerator := &policiesfakes.FakeGenerator{} + fakeGenerator.GenerateForLocationReturns( + policies.GenerateResultFiles{ + { + Name: "ext-policy.conf", + Content: []byte("external policy conf"), + }, + }, + ) + fakeGenerator.GenerateForInternalLocationReturns( + policies.GenerateResultFiles{ + { + Name: "int-policy.conf", + Content: []byte("internal policy conf"), + }, + }, + ) + + locations, matches, grpc := createLocations(&httpServer, "1", fakeGenerator) + + g := NewWithT(t) + g.Expect(grpc).To(BeFalse()) + g.Expect(matches).To(HaveLen(1)) + g.Expect(locations).To(HaveLen(len(expLocations))) + for i, location := range locations { + g.Expect(location.Path).To(Equal(expLocations[i].Path)) + g.Expect(location.Includes).To(ConsistOf(expLocations[i].Includes)) } } @@ -1752,28 +2144,32 @@ func TestCreateLocationsRootPath(t *testing.T) { } if rootPath { - rules = append(rules, dataplane.PathRule{ - Path: "/", - MatchRules: []dataplane.MatchRule{ - { - Match: dataplane.Match{}, - BackendGroup: fooGroup, + rules = append( + rules, dataplane.PathRule{ + Path: "/", + MatchRules: []dataplane.MatchRule{ + { + Match: dataplane.Match{}, + BackendGroup: fooGroup, + }, }, }, - }) + ) } if grpc { - rules = append(rules, dataplane.PathRule{ - Path: "/grpc", - GRPC: true, - MatchRules: []dataplane.MatchRule{ - { - Match: dataplane.Match{}, - BackendGroup: fooGroup, + rules = append( + rules, dataplane.PathRule{ + Path: "/grpc", + GRPC: true, + MatchRules: []dataplane.MatchRule{ + { + Match: dataplane.Match{}, + BackendGroup: fooGroup, + }, }, }, - }) + ) } return rules @@ -1880,18 +2276,22 @@ func TestCreateLocationsRootPath(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - locs, httpMatchPair, grpc := createLocations(&dataplane.VirtualServer{ - PathRules: test.pathRules, - Port: 80, - }, "1", &policiesfakes.FakeGenerator{}) - g.Expect(locs).To(Equal(test.expLocations)) - g.Expect(httpMatchPair).To(BeEmpty()) - g.Expect(grpc).To(Equal(test.grpc)) - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + locs, httpMatchPair, grpc := createLocations( + &dataplane.VirtualServer{ + PathRules: test.pathRules, + Port: 80, + }, "1", &policiesfakes.FakeGenerator{}, + ) + g.Expect(locs).To(Equal(test.expLocations)) + g.Expect(httpMatchPair).To(BeEmpty()) + g.Expect(grpc).To(Equal(test.grpc)) + }, + ) } } @@ -2018,13 +2418,15 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { } for _, test := range tests { - t.Run(test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - result := createReturnValForRedirectFilter(test.filter, test.listenerPort) - g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) - }) + result := createReturnValForRedirectFilter(test.filter, test.listenerPort) + g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) + }, + ) } } @@ -2146,13 +2548,15 @@ func TestCreateRewritesValForRewriteFilter(t *testing.T) { } for _, test := range tests { - t.Run(test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - result := createRewritesValForRewriteFilter(test.filter, test.path) - g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) - }) + result := createRewritesValForRewriteFilter(test.filter, test.path) + g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) + }, + ) } } @@ -2307,13 +2711,15 @@ func TestCreateRouteMatch(t *testing.T) { }, } for _, tc := range tests { - t.Run(tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - result := createRouteMatch(tc.match, testPath) - g.Expect(helpers.Diff(result, tc.expected)).To(BeEmpty()) - }) + result := createRouteMatch(tc.match, testPath) + g.Expect(helpers.Diff(result, tc.expected)).To(BeEmpty()) + }, + ) } } @@ -2406,13 +2812,15 @@ func TestIsPathOnlyMatch(t *testing.T) { } for _, tc := range tests { - t.Run(tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - result := isPathOnlyMatch(tc.match) - g.Expect(result).To(Equal(tc.expected)) - }) + result := isPathOnlyMatch(tc.match) + g.Expect(result).To(Equal(tc.expected)) + }, + ) } } @@ -2486,12 +2894,14 @@ func TestCreateProxyPass(t *testing.T) { } for _, tc := range tests { - t.Run(tc.expected, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := createProxyPass(tc.grp, tc.rewrite, generateProtocolString(nil, tc.GRPC), tc.GRPC) - g.Expect(result).To(Equal(tc.expected)) - }) + t.Run( + tc.expected, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + result := createProxyPass(tc.grp, tc.rewrite, generateProtocolString(nil, tc.GRPC), tc.GRPC) + g.Expect(result).To(Equal(tc.expected)) + }, + ) } } @@ -2713,13 +3123,15 @@ func TestGenerateProxySetHeaders(t *testing.T) { } for _, tc := range tests { - t.Run(tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - headers := generateProxySetHeaders(tc.filters, tc.GRPC) - g.Expect(headers).To(Equal(tc.expectedHeaders)) - }) + headers := generateProxySetHeaders(tc.filters, tc.GRPC) + g.Expect(headers).To(Equal(tc.expectedHeaders)) + }, + ) } } @@ -2805,13 +3217,15 @@ func TestConvertBackendTLSFromGroup(t *testing.T) { } for _, tc := range tests { - t.Run(tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - result := createProxyTLSFromBackends(tc.grp) - g.Expect(result).To(Equal(tc.expected)) - }) + result := createProxyTLSFromBackends(tc.grp) + g.Expect(result).To(Equal(tc.expected)) + }, + ) } } @@ -2875,163 +3289,18 @@ func TestGenerateResponseHeaders(t *testing.T) { } for _, tc := range tests { - t.Run(tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - headers := generateResponseHeaders(tc.filters) - g.Expect(headers).To(Equal(tc.expectedHeaders)) - }) - } -} + t.Run( + tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) -func TestCreateIncludesFromPolicyGenerateResult(t *testing.T) { - t.Parallel() - tests := []struct { - name string - files []policies.File - includes []http.Include - }{ - { - name: "no files", - files: nil, - includes: nil, - }, - { - name: "additions", - files: []policies.File{ - { - Content: []byte("one"), - Name: "one.conf", - }, - { - Content: []byte("two"), - Name: "two.conf", - }, - { - Content: []byte("three"), - Name: "three.conf", - }, - }, - includes: []http.Include{ - { - Content: []byte("one"), - Name: includesFolder + "/one.conf", - }, - { - Content: []byte("two"), - Name: includesFolder + "/two.conf", - }, - { - Content: []byte("three"), - Name: includesFolder + "/three.conf", - }, + headers := generateResponseHeaders(tc.filters) + g.Expect(headers).To(Equal(tc.expectedHeaders)) }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - includes := createIncludesFromPolicyGenerateResult(test.files) - g.Expect(includes).To(Equal(test.includes)) - }) + ) } } -func TestCreateIncludeFileResults(t *testing.T) { - t.Parallel() - servers := []http.Server{ - { - Includes: []http.Include{ - { - Name: "include-1.conf", - Content: []byte("include-1"), - }, - { - Name: "include-2.conf", - Content: []byte("include-2"), - }, - }, - Locations: []http.Location{ - { - Includes: []http.Include{ - { - Name: "include-3.conf", - Content: []byte("include-3"), - }, - { - Name: "include-4.conf", - Content: []byte("include-4"), - }, - }, - }, - }, - }, - { - Includes: []http.Include{ - { - Name: "include-1.conf", // dupe - Content: []byte("include-1"), - }, - { - Name: "include-2.conf", // dupe - Content: []byte("include-2"), - }, - }, - Locations: []http.Location{ - { - Includes: []http.Include{ - { - Name: "include-3.conf", // dupe - Content: []byte("include-3"), - }, - { - Name: "include-4.conf", // dupe - Content: []byte("include-4"), - }, - { - Name: "include-5.conf", - Content: []byte("include-5"), - }, - }, - }, - }, - }, - } - - results := createIncludeFileResults(servers) - - expResults := []executeResult{ - { - dest: "include-1.conf", - data: []byte("include-1"), - }, - { - dest: "include-2.conf", - data: []byte("include-2"), - }, - { - dest: "include-3.conf", - data: []byte("include-3"), - }, - { - dest: "include-4.conf", - data: []byte("include-4"), - }, - { - dest: "include-5.conf", - data: []byte("include-5"), - }, - } - - g := NewWithT(t) - - g.Expect(results).To(ConsistOf(expResults)) -} - func TestGetIPFamily(t *testing.T) { t.Parallel() test := []struct { @@ -3057,12 +3326,14 @@ func TestGetIPFamily(t *testing.T) { } for _, tc := range test { - t.Run(tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - result := getIPFamily(tc.baseHTTPConfig) - g.Expect(result).To(Equal(tc.expected)) - }) + result := getIPFamily(tc.baseHTTPConfig) + g.Expect(result).To(Equal(tc.expected)) + }, + ) } } diff --git a/internal/mode/static/nginx/config/shared/config.go b/internal/mode/static/nginx/config/shared/config.go index 62ea4bec82..c38acb7e75 100644 --- a/internal/mode/static/nginx/config/shared/config.go +++ b/internal/mode/static/nginx/config/shared/config.go @@ -20,7 +20,7 @@ type IPFamily struct { IPv6 bool } -// RewriteClientIP holds the configuration for the rewrite client IP settings. +// RewriteClientIPSettings holds the configuration for the rewrite client IP settings. type RewriteClientIPSettings struct { RealIPHeader string ProxyProtocol string @@ -31,3 +31,9 @@ type RewriteClientIPSettings struct { const ( ProxyProtocolDirective = " proxy_protocol" ) + +// Include defines a file that's included via the include directive. +type Include struct { + Name string + Content []byte +} diff --git a/internal/mode/static/nginx/config/version.go b/internal/mode/static/nginx/config/version.go index 5baa7f24b8..6e29f36056 100644 --- a/internal/mode/static/nginx/config/version.go +++ b/internal/mode/static/nginx/config/version.go @@ -4,10 +4,16 @@ import ( gotemplate "text/template" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/dataplane" ) var versionTemplate = gotemplate.Must(gotemplate.New("version").Parse(versionTemplateText)) -func executeVersion(version int) []byte { - return helpers.MustExecuteTemplate(versionTemplate, version) +func executeVersion(conf dataplane.Configuration) []executeResult { + result := executeResult{ + dest: configVersionFile, + data: helpers.MustExecuteTemplate(versionTemplate, conf.Version), + } + + return []executeResult{result} } diff --git a/internal/mode/static/nginx/config/version_test.go b/internal/mode/static/nginx/config/version_test.go index 176db3dfec..3d30c6c3b2 100644 --- a/internal/mode/static/nginx/config/version_test.go +++ b/internal/mode/static/nginx/config/version_test.go @@ -1,21 +1,20 @@ package config import ( - "strings" "testing" . "github.com/onsi/gomega" + + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/dataplane" ) func TestExecuteVersion(t *testing.T) { t.Parallel() g := NewWithT(t) - expSubStrings := map[string]int{ - "return 200 42;": 1, - } - maps := string(executeVersion(42)) - for expSubStr, expCount := range expSubStrings { - g.Expect(expCount).To(Equal(strings.Count(maps, expSubStr))) - } + conf := dataplane.Configuration{Version: 42} + res := executeVersion(conf) + g.Expect(res).To(HaveLen(1)) + g.Expect(res[0].dest).To(Equal(configVersionFile)) + g.Expect(string(res[0].data)).To(ContainSubstring("return 200 42;")) } diff --git a/internal/mode/static/state/change_processor.go b/internal/mode/static/state/change_processor.go index 8e73e82df4..9da779c7da 100644 --- a/internal/mode/static/state/change_processor.go +++ b/internal/mode/static/state/change_processor.go @@ -222,7 +222,7 @@ func NewChangeProcessorImpl(cfg ChangeProcessorConfig) *ChangeProcessorImpl { { gvk: cfg.MustExtractGVK(&ngfAPI.SnippetsFilter{}), store: newObjectStoreMapAdapter(clusterStore.SnippetsFilters), - predicate: nil, /*TODO(kate-osborn): will add predicate in next PR*/ + predicate: nil, // we always want to write status to SnippetsFilters so we don't filter them out }, }, ) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index fe790d6087..d060153e25 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -330,2393 +330,2983 @@ cpLlHMAqbLJ8WYGJCkhiWxyal6hYTyWY4cVkC0xtTl/hUE9IeNKo -----END RSA PRIVATE KEY-----`) ) -var _ = Describe("ChangeProcessor", func() { - // graph outputs are large, so allow gomega to print everything on test failure - format.MaxLength = 0 - Describe("Normal cases of processing changes", func() { - var ( - gc = &v1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: gcName, - Generation: 1, - }, - Spec: v1.GatewayClassSpec{ - ControllerName: controllerName, - }, - } - processor state.ChangeProcessor - ) - - BeforeEach(OncePerOrdered, func() { - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }) - }) - - Describe("Process gateway resources", Ordered, func() { - var ( - gcUpdated *v1.GatewayClass - diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret - hr1, hr1Updated, hr2 *v1.HTTPRoute - tr1, tr1Updated, tr2 *v1alpha2.TLSRoute - gw1, gw1Updated, gw2 *v1.Gateway - secretRefGrant, hrServiceRefGrant, trServiceRefGrant *v1beta1.ReferenceGrant - expGraph *graph.Graph - expRouteHR1, expRouteHR2 *graph.L7Route - expRouteTR1, expRouteTR2 *graph.L4Route - gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata - routeKey1, routeKey2 graph.RouteKey - trKey1, trKey2 graph.L4RouteKey - ) - BeforeAll(func() { - gcUpdated = gc.DeepCopy() - gcUpdated.Generation++ - - crossNsBackendRef := v1.HTTPBackendRef{ - BackendRef: v1.BackendRef{ - BackendObjectReference: v1.BackendObjectReference{ - Kind: helpers.GetPointer[v1.Kind]("Service"), - Name: "service", - Namespace: helpers.GetPointer[v1.Namespace]("service-ns"), - Port: helpers.GetPointer[v1.PortNumber](80), +var _ = Describe( + "ChangeProcessor", func() { + // graph outputs are large, so allow gomega to print everything on test failure + format.MaxLength = 0 + Describe( + "Normal cases of processing changes", func() { + var ( + gc = &v1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: gcName, + Generation: 1, }, - }, - } + Spec: v1.GatewayClassSpec{ + ControllerName: controllerName, + }, + } + processor state.ChangeProcessor + ) - hr1 = createRoute("hr-1", "gateway-1", "foo.example.com", crossNsBackendRef) + BeforeEach( + OncePerOrdered, func() { + processor = state.NewChangeProcessorImpl( + state.ChangeProcessorConfig{ + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }, + ) + }, + ) - routeKey1 = graph.CreateRouteKey(hr1) + Describe( + "Process gateway resources", Ordered, func() { + var ( + gcUpdated *v1.GatewayClass + diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret + hr1, hr1Updated, hr2 *v1.HTTPRoute + tr1, tr1Updated, tr2 *v1alpha2.TLSRoute + gw1, gw1Updated, gw2 *v1.Gateway + secretRefGrant, hrServiceRefGrant, trServiceRefGrant *v1beta1.ReferenceGrant + expGraph *graph.Graph + expRouteHR1, expRouteHR2 *graph.L7Route + expRouteTR1, expRouteTR2 *graph.L4Route + gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata + routeKey1, routeKey2 graph.RouteKey + trKey1, trKey2 graph.L4RouteKey + ) + BeforeAll( + func() { + gcUpdated = gc.DeepCopy() + gcUpdated.Generation++ + + crossNsBackendRef := v1.HTTPBackendRef{ + BackendRef: v1.BackendRef{ + BackendObjectReference: v1.BackendObjectReference{ + Kind: helpers.GetPointer[v1.Kind]("Service"), + Name: "service", + Namespace: helpers.GetPointer[v1.Namespace]("service-ns"), + Port: helpers.GetPointer[v1.PortNumber](80), + }, + }, + } - hr1Updated = hr1.DeepCopy() - hr1Updated.Generation++ + hr1 = createRoute("hr-1", "gateway-1", "foo.example.com", crossNsBackendRef) - hr2 = createRoute("hr-2", "gateway-2", "bar.example.com") + routeKey1 = graph.CreateRouteKey(hr1) - routeKey2 = graph.CreateRouteKey(hr2) + hr1Updated = hr1.DeepCopy() + hr1Updated.Generation++ - tlsBackendRef := createTLSBackendRef("tls-service", "tls-service-ns") + hr2 = createRoute("hr-2", "gateway-2", "bar.example.com") - tr1 = createTLSRoute("tr-1", "gateway-1", "foo.tls.com", tlsBackendRef) + routeKey2 = graph.CreateRouteKey(hr2) - trKey1 = graph.CreateRouteKeyL4(tr1) + tlsBackendRef := createTLSBackendRef("tls-service", "tls-service-ns") - tr1Updated = tr1.DeepCopy() - tr1Updated.Generation++ + tr1 = createTLSRoute("tr-1", "gateway-1", "foo.tls.com", tlsBackendRef) - tr2 = createTLSRoute("tr-2", "gateway-2", "bar.tls.com", tlsBackendRef) + trKey1 = graph.CreateRouteKeyL4(tr1) - trKey2 = graph.CreateRouteKeyL4(tr2) + tr1Updated = tr1.DeepCopy() + tr1Updated.Generation++ - secretRefGrant = &v1beta1.ReferenceGrant{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "cert-ns", - Name: "ref-grant", - }, - Spec: v1beta1.ReferenceGrantSpec{ - From: []v1beta1.ReferenceGrantFrom{ - { - Group: v1.GroupName, - Kind: kinds.Gateway, - Namespace: "test", - }, - }, - To: []v1beta1.ReferenceGrantTo{ - { - Kind: "Secret", - }, - }, - }, - } + tr2 = createTLSRoute("tr-2", "gateway-2", "bar.tls.com", tlsBackendRef) - hrServiceRefGrant = &v1beta1.ReferenceGrant{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "service-ns", - Name: "ref-grant", - }, - Spec: v1beta1.ReferenceGrantSpec{ - From: []v1beta1.ReferenceGrantFrom{ - { - Group: v1.GroupName, - Kind: kinds.HTTPRoute, - Namespace: "test", - }, - }, - To: []v1beta1.ReferenceGrantTo{ - { - Kind: "Service", - }, - }, - }, - } + trKey2 = graph.CreateRouteKeyL4(tr2) - trServiceRefGrant = &v1beta1.ReferenceGrant{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "tls-service-ns", - Name: "ref-grant", - }, - Spec: v1beta1.ReferenceGrantSpec{ - From: []v1beta1.ReferenceGrantFrom{ - { - Group: v1.GroupName, - Kind: kinds.TLSRoute, - Namespace: "test", - }, - }, - To: []v1beta1.ReferenceGrantTo{ - { - Kind: "Service", - }, - }, - }, - } - - sameNsTLSSecret = &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tls-secret", - Namespace: "test", - }, - Type: apiv1.SecretTypeTLS, - Data: map[string][]byte{ - apiv1.TLSCertKey: cert, - apiv1.TLSPrivateKeyKey: key, - }, - } + secretRefGrant = &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "cert-ns", + Name: "ref-grant", + }, + Spec: v1beta1.ReferenceGrantSpec{ + From: []v1beta1.ReferenceGrantFrom{ + { + Group: v1.GroupName, + Kind: kinds.Gateway, + Namespace: "test", + }, + }, + To: []v1beta1.ReferenceGrantTo{ + { + Kind: "Secret", + }, + }, + }, + } - diffNsTLSSecret = &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "different-ns-tls-secret", - Namespace: "cert-ns", - }, - Type: apiv1.SecretTypeTLS, - Data: map[string][]byte{ - apiv1.TLSCertKey: cert, - apiv1.TLSPrivateKeyKey: key, - }, - } + hrServiceRefGrant = &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "service-ns", + Name: "ref-grant", + }, + Spec: v1beta1.ReferenceGrantSpec{ + From: []v1beta1.ReferenceGrantFrom{ + { + Group: v1.GroupName, + Kind: kinds.HTTPRoute, + Namespace: "test", + }, + }, + To: []v1beta1.ReferenceGrantTo{ + { + Kind: "Service", + }, + }, + }, + } - gw1 = createGateway( - "gateway-1", - createHTTPListener(), - createHTTPSListener(httpsListenerName, diffNsTLSSecret), // cert in diff namespace than gw - createTLSListener(tlsListenerName), - ) + trServiceRefGrant = &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "tls-service-ns", + Name: "ref-grant", + }, + Spec: v1beta1.ReferenceGrantSpec{ + From: []v1beta1.ReferenceGrantFrom{ + { + Group: v1.GroupName, + Kind: kinds.TLSRoute, + Namespace: "test", + }, + }, + To: []v1beta1.ReferenceGrantTo{ + { + Kind: "Service", + }, + }, + }, + } - gw1Updated = gw1.DeepCopy() - gw1Updated.Generation++ + sameNsTLSSecret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tls-secret", + Namespace: "test", + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } - gw2 = createGateway( - "gateway-2", - createHTTPListener(), - createHTTPSListener(httpsListenerName, sameNsTLSSecret), - createTLSListener(tlsListenerName), - ) + diffNsTLSSecret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "different-ns-tls-secret", + Namespace: "cert-ns", + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } + + gw1 = createGateway( + "gateway-1", + createHTTPListener(), + createHTTPSListener( + httpsListenerName, + diffNsTLSSecret, + ), // cert in diff namespace than gw + createTLSListener(tlsListenerName), + ) + + gw1Updated = gw1.DeepCopy() + gw1Updated.Generation++ + + gw2 = createGateway( + "gateway-2", + createHTTPListener(), + createHTTPSListener(httpsListenerName, sameNsTLSSecret), + createTLSListener(tlsListenerName), + ) + + gatewayAPICRD = &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + Kind: "CustomResourceDefinition", + APIVersion: "apiextensions.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewayclasses.gateway.networking.k8s.io", + Annotations: map[string]string{ + gatewayclass.BundleVersionAnnotation: gatewayclass.SupportedVersion, + }, + }, + } - gatewayAPICRD = &metav1.PartialObjectMetadata{ - TypeMeta: metav1.TypeMeta{ - Kind: "CustomResourceDefinition", - APIVersion: "apiextensions.k8s.io/v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gatewayclasses.gateway.networking.k8s.io", - Annotations: map[string]string{ - gatewayclass.BundleVersionAnnotation: gatewayclass.SupportedVersion, - }, - }, - } - - gatewayAPICRDUpdated = gatewayAPICRD.DeepCopy() - gatewayAPICRDUpdated.Annotations[gatewayclass.BundleVersionAnnotation] = "v1.99.0" - }) - BeforeEach(func() { - expRouteHR1 = &graph.L7Route{ - Source: hr1, - RouteType: graph.RouteTypeHTTP, - ParentRefs: []graph.ParentRef{ - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpListenerName: {"foo.example.com"}}, - Attached: true, - ListenerPort: 80, + gatewayAPICRDUpdated = gatewayAPICRD.DeepCopy() + gatewayAPICRDUpdated.Annotations[gatewayclass.BundleVersionAnnotation] = "v1.99.0" }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - SectionName: hr1.Spec.ParentRefs[0].SectionName, - }, - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpsListenerName: {"foo.example.com"}}, - Attached: true, - ListenerPort: 443, - }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - Idx: 1, - SectionName: hr1.Spec.ParentRefs[1].SectionName, - }, - }, - Spec: graph.L7RouteSpec{ - Hostnames: hr1.Spec.Hostnames, - Rules: []graph.RouteRule{ - { - BackendRefs: []graph.BackendRef{ - { - SvcNsName: types.NamespacedName{Namespace: "service-ns", Name: "service"}, - Weight: 1, + ) + BeforeEach( + func() { + expRouteHR1 = &graph.L7Route{ + Source: hr1, + RouteType: graph.RouteTypeHTTP, + ParentRefs: []graph.ParentRef{ + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{httpListenerName: {"foo.example.com"}}, + Attached: true, + ListenerPort: 80, + }, + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, + SectionName: hr1.Spec.ParentRefs[0].SectionName, + }, + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{httpsListenerName: {"foo.example.com"}}, + Attached: true, + ListenerPort: 443, + }, + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, + Idx: 1, + SectionName: hr1.Spec.ParentRefs[1].SectionName, + }, }, - }, - ValidMatches: true, - ValidFilters: true, - Matches: hr1.Spec.Rules[0].Matches, - RouteBackendRefs: createRouteBackendRefs(hr1.Spec.Rules[0].BackendRefs), + Spec: graph.L7RouteSpec{ + Hostnames: hr1.Spec.Hostnames, + Rules: []graph.RouteRule{ + { + BackendRefs: []graph.BackendRef{ + { + SvcNsName: types.NamespacedName{ + Namespace: "service-ns", + Name: "service", + }, + Weight: 1, + }, + }, + ValidMatches: true, + Filters: graph.RouteRuleFilters{ + Filters: []graph.Filter{}, + Valid: true, + }, + Matches: hr1.Spec.Rules[0].Matches, + RouteBackendRefs: createRouteBackendRefs(hr1.Spec.Rules[0].BackendRefs), + }, + }, + }, + Valid: true, + Attachable: true, + Conditions: []conditions.Condition{ + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"service\"", + ), + }, + } + + expRouteHR2 = &graph.L7Route{ + Source: hr2, + RouteType: graph.RouteTypeHTTP, + ParentRefs: []graph.ParentRef{ + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, + Attached: true, + ListenerPort: 80, + }, + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + SectionName: hr2.Spec.ParentRefs[0].SectionName, + }, + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{httpsListenerName: {"bar.example.com"}}, + Attached: true, + ListenerPort: 443, + }, + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + Idx: 1, + SectionName: hr2.Spec.ParentRefs[1].SectionName, + }, + }, + Spec: graph.L7RouteSpec{ + Hostnames: hr2.Spec.Hostnames, + Rules: []graph.RouteRule{ + { + ValidMatches: true, + Filters: graph.RouteRuleFilters{ + Valid: true, + Filters: []graph.Filter{}, + }, + Matches: hr2.Spec.Rules[0].Matches, + RouteBackendRefs: []graph.RouteBackendRef{}, + }, + }, + }, + Valid: true, + Attachable: true, + } + + expRouteTR1 = &graph.L4Route{ + Source: tr1, + ParentRefs: []graph.ParentRef{ + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{tlsListenerName: {"foo.tls.com"}}, + Attached: true, + }, + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, + SectionName: tr1.Spec.ParentRefs[0].SectionName, + }, + }, + Spec: graph.L4RouteSpec{ + Hostnames: tr1.Spec.Hostnames, + BackendRef: graph.BackendRef{ + SvcNsName: types.NamespacedName{ + Namespace: "tls-service-ns", + Name: "tls-service", + }, + Valid: false, + }, + }, + Valid: true, + Attachable: true, + Conditions: []conditions.Condition{ + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"tls-service\"", + ), + }, + } + + expRouteTR2 = &graph.L4Route{ + Source: tr2, + ParentRefs: []graph.ParentRef{ + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{tlsListenerName: {"bar.tls.com"}}, + Attached: true, + }, + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + SectionName: tr2.Spec.ParentRefs[0].SectionName, + }, + }, + Spec: graph.L4RouteSpec{ + Hostnames: tr2.Spec.Hostnames, + BackendRef: graph.BackendRef{ + SvcNsName: types.NamespacedName{ + Namespace: "tls-service-ns", + Name: "tls-service", + }, + Valid: false, + }, + }, + Valid: true, + Attachable: true, + Conditions: []conditions.Condition{ + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"tls-service\"", + ), + }, + } + + // This is the base case expected graph. Tests will manipulate this to add or remove elements + // to fit the expected output of the input under test. + expGraph = &graph.Graph{ + GatewayClass: &graph.GatewayClass{ + Source: gc, + Valid: true, + }, + Gateway: &graph.Gateway{ + Source: gw1, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + Source: gw1.Spec.Listeners[0], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + { + Kind: v1.Kind(kinds.HTTPRoute), + Group: helpers.GetPointer[v1.Group](v1.GroupName), + }, + { + Kind: v1.Kind(kinds.GRPCRoute), + Group: helpers.GetPointer[v1.Group](v1.GroupName), + }, + }, + }, + { + Name: httpsListenerName, + Source: gw1.Spec.Listeners[1], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(diffNsTLSSecret)), + SupportedKinds: []v1.RouteGroupKind{ + { + Kind: v1.Kind(kinds.HTTPRoute), + Group: helpers.GetPointer[v1.Group](v1.GroupName), + }, + { + Kind: v1.Kind(kinds.GRPCRoute), + Group: helpers.GetPointer[v1.Group](v1.GroupName), + }, + }, + }, + { + Name: tlsListenerName, + Source: gw1.Spec.Listeners[2], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, + SupportedKinds: []v1.RouteGroupKind{ + { + Kind: v1.Kind(kinds.TLSRoute), + Group: helpers.GetPointer[v1.Group](v1.GroupName), + }, + }, + }, + }, + Valid: true, + }, + IgnoredGateways: map[types.NamespacedName]*v1.Gateway{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, + Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, + ReferencedSecrets: map[types.NamespacedName]*graph.Secret{}, + ReferencedServices: map[types.NamespacedName]struct{}{ + { + Namespace: "service-ns", + Name: "service", + }: {}, + { + Namespace: "tls-service-ns", + Name: "tls-service", + }: {}, + }, + } }, - }, - }, - Valid: true, - Attachable: true, - Conditions: []conditions.Condition{ - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"service\"", - ), - }, - } - - expRouteHR2 = &graph.L7Route{ - Source: hr2, - RouteType: graph.RouteTypeHTTP, - ParentRefs: []graph.ParentRef{ - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, - Attached: true, - ListenerPort: 80, + ) + When( + "no upsert has occurred", func() { + It( + "returns nil graph", func() { + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + Expect(graphCfg).To(BeNil()) + Expect(processor.GetLatestGraph()).To(BeNil()) + }, + ) }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - SectionName: hr2.Spec.ParentRefs[0].SectionName, - }, - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpsListenerName: {"bar.example.com"}}, - Attached: true, - ListenerPort: 443, - }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - Idx: 1, - SectionName: hr2.Spec.ParentRefs[1].SectionName, - }, - }, - Spec: graph.L7RouteSpec{ - Hostnames: hr2.Spec.Hostnames, - Rules: []graph.RouteRule{ - { - ValidMatches: true, - ValidFilters: true, - Matches: hr2.Spec.Rules[0].Matches, - RouteBackendRefs: []graph.RouteBackendRef{}, + ) + When( + "GatewayClass doesn't exist", func() { + When( + "Gateway API CRD is added", func() { + It( + "returns empty graph", func() { + processor.CaptureUpsertChange(gatewayAPICRD) + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) + Expect( + helpers.Diff( + &graph.Graph{}, + processor.GetLatestGraph(), + ), + ).To(BeEmpty()) + }, + ) + }, + ) + When( + "Gateways don't exist", func() { + When( + "the first HTTPRoute is upserted", func() { + It( + "returns empty graph", func() { + processor.CaptureUpsertChange(hr1) + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) + Expect( + helpers.Diff( + &graph.Graph{}, + processor.GetLatestGraph(), + ), + ).To(BeEmpty()) + }, + ) + }, + ) + When( + "the first TLSRoute is upserted", func() { + It( + "returns empty graph", func() { + processor.CaptureUpsertChange(tr1) + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) + Expect( + helpers.Diff( + &graph.Graph{}, + processor.GetLatestGraph(), + ), + ).To(BeEmpty()) + }, + ) + }, + ) + When( + "the different namespace TLS Secret is upserted", func() { + It( + "returns nil graph", func() { + processor.CaptureUpsertChange(diffNsTLSSecret) + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + Expect(graphCfg).To(BeNil()) + Expect( + helpers.Diff( + &graph.Graph{}, + processor.GetLatestGraph(), + ), + ).To(BeEmpty()) + }, + ) + }, + ) + When( + "the first Gateway is upserted", func() { + It( + "returns populated graph", func() { + processor.CaptureUpsertChange(gw1) + + expGraph.GatewayClass = nil + + expGraph.Gateway.Conditions = staticConds.NewGatewayInvalid("GatewayClass doesn't exist") + expGraph.Gateway.Valid = false + expGraph.Gateway.Listeners = nil + + // no ref grant exists yet for hr1 or tr1 + expGraph.Routes[routeKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", + ), + } + + expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", + ), + } + + // gateway class does not exist so routes cannot attach + expGraph.Routes[routeKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNoMatchingParent(), + } + expGraph.Routes[routeKey1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNoMatchingParent(), + } + expGraph.L4Routes[trKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNoMatchingParent(), + } + + expGraph.ReferencedSecrets = nil + expGraph.ReferencedServices = nil + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect( + helpers.Diff( + expGraph, + processor.GetLatestGraph(), + ), + ).To(BeEmpty()) + }, + ) + }, + ) + }, + ) }, - }, - }, - Valid: true, - Attachable: true, - } + ) + When( + "the GatewayClass is upserted", func() { + It( + "returns updated graph", func() { + processor.CaptureUpsertChange(gc) + + // No ref grant exists yet for gw1 + // so the listener is not valid, but still attachable + listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + listener443.Valid = false + listener443.ResolvedSecret = nil + listener443.Conditions = staticConds.NewListenerRefNotPermitted( + "Certificate ref to secret cert-ns/different-ns-tls-secret not permitted by any ReferenceGrant", + ) + + expAttachment80 := &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + httpListenerName: {"foo.example.com"}, + }, + Attached: true, + ListenerPort: 80, + } - expRouteTR1 = &graph.L4Route{ - Source: tr1, - ParentRefs: []graph.ParentRef{ - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{tlsListenerName: {"foo.tls.com"}}, - Attached: true, + expAttachment443 := &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + httpsListenerName: {"foo.example.com"}, + }, + Attached: true, + ListenerPort: 443, + } + + listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener80.Routes[routeKey1].ParentRefs[0].Attachment = expAttachment80 + listener443.Routes[routeKey1].ParentRefs[1].Attachment = expAttachment443 + + // no ref grant exists yet for hr1 + expGraph.Routes[routeKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteInvalidListener(), + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", + ), + } + expGraph.Routes[routeKey1].ParentRefs[0].Attachment = expAttachment80 + expGraph.Routes[routeKey1].ParentRefs[1].Attachment = expAttachment443 + + // no ref grant exists yet for tr1 + expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", + ), + } + + expGraph.ReferencedSecrets = nil + expGraph.ReferencedServices = nil + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - SectionName: tr1.Spec.ParentRefs[0].SectionName, - }, - }, - Spec: graph.L4RouteSpec{ - Hostnames: tr1.Spec.Hostnames, - BackendRef: graph.BackendRef{ - SvcNsName: types.NamespacedName{Namespace: "tls-service-ns", Name: "tls-service"}, - Valid: false, - }, - }, - Valid: true, - Attachable: true, - Conditions: []conditions.Condition{ - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"tls-service\"", - ), - }, - } - - expRouteTR2 = &graph.L4Route{ - Source: tr2, - ParentRefs: []graph.ParentRef{ - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{tlsListenerName: {"bar.tls.com"}}, - Attached: true, + ) + When( + "the ReferenceGrant allowing the Gateway to reference its Secret is upserted", func() { + It( + "returns updated graph", func() { + processor.CaptureUpsertChange(secretRefGrant) + + // no ref grant exists yet for hr1 + expGraph.Routes[routeKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", + ), + } + + // no ref grant exists yet for tr1 + expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", + ), + } + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + expGraph.ReferencedServices = nil + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - SectionName: tr2.Spec.ParentRefs[0].SectionName, - }, - }, - Spec: graph.L4RouteSpec{ - Hostnames: tr2.Spec.Hostnames, - BackendRef: graph.BackendRef{ - SvcNsName: types.NamespacedName{Namespace: "tls-service-ns", Name: "tls-service"}, - Valid: false, - }, - }, - Valid: true, - Attachable: true, - Conditions: []conditions.Condition{ - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"tls-service\"", - ), - }, - } - - // This is the base case expected graph. Tests will manipulate this to add or remove elements - // to fit the expected output of the input under test. - expGraph = &graph.Graph{ - GatewayClass: &graph.GatewayClass{ - Source: gc, - Valid: true, - }, - Gateway: &graph.Gateway{ - Source: gw1, - Listeners: []*graph.Listener{ - { - Name: httpListenerName, - Source: gw1.Spec.Listeners[0], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, - SupportedKinds: []v1.RouteGroupKind{ - {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, - {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, - }, + ) + When( + "the ReferenceGrant allowing the hr1 to reference the Service in different ns is upserted", + func() { + It( + "returns updated graph", func() { + processor.CaptureUpsertChange(hrServiceRefGrant) + + // no ref grant exists yet for tr1 + expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", + ), + } + delete( + expGraph.ReferencedServices, + types.NamespacedName{Namespace: "tls-service-ns", Name: "tls-service"}, + ) + expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) }, - { - Name: httpsListenerName, - Source: gw1.Spec.Listeners[1], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(diffNsTLSSecret)), - SupportedKinds: []v1.RouteGroupKind{ - {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, - {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, - }, + ) + When( + "the ReferenceGrant allowing the tr1 to reference the Service in different ns is upserted", + func() { + It( + "returns updated graph", func() { + processor.CaptureUpsertChange(trServiceRefGrant) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) }, - { - Name: tlsListenerName, - Source: gw1.Spec.Listeners[2], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, - SupportedKinds: []v1.RouteGroupKind{ - {Kind: v1.Kind(kinds.TLSRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, - }, + ) + When( + "the Gateway API CRD with bundle version annotation change is processed", func() { + It( + "returns updated graph", func() { + processor.CaptureUpsertChange(gatewayAPICRDUpdated) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + expGraph.GatewayClass.Conditions = conditions.NewGatewayClassSupportedVersionBestEffort( + gatewayclass.SupportedVersion, + ) + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) }, - }, - Valid: true, - }, - IgnoredGateways: map[types.NamespacedName]*v1.Gateway{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, - Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, - ReferencedSecrets: map[types.NamespacedName]*graph.Secret{}, - ReferencedServices: map[types.NamespacedName]struct{}{ - { - Namespace: "service-ns", - Name: "service", - }: {}, - { - Namespace: "tls-service-ns", - Name: "tls-service", - }: {}, + ) + When( + "the Gateway API CRD without bundle version annotation change is processed", func() { + It( + "returns nil graph", func() { + gatewayAPICRDSameVersion := gatewayAPICRDUpdated.DeepCopy() + + processor.CaptureUpsertChange(gatewayAPICRDSameVersion) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + expGraph.GatewayClass.Conditions = conditions.NewGatewayClassSupportedVersionBestEffort( + gatewayclass.SupportedVersion, + ) + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + Expect(graphCfg).To(BeNil()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the Gateway API CRD with bundle version annotation change is processed", func() { + It( + "returns updated graph", func() { + // change back to supported version + processor.CaptureUpsertChange(gatewayAPICRD) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the first HTTPRoute update with a generation changed is processed", func() { + It( + "returns populated graph", func() { + processor.CaptureUpsertChange(hr1Updated) + + listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + listener443.Routes[routeKey1].Source.SetGeneration(hr1Updated.Generation) + + listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener80.Routes[routeKey1].Source.SetGeneration(hr1Updated.Generation) + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the first TLSRoute update with a generation changed is processed", func() { + It( + "returns populated graph", func() { + processor.CaptureUpsertChange(tr1Updated) + + tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) + tlsListener.L4Routes[trKey1].Source.SetGeneration(tr1Updated.Generation) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the first Gateway update with a generation changed is processed", func() { + It( + "returns populated graph", func() { + processor.CaptureUpsertChange(gw1Updated) + + expGraph.Gateway.Source.Generation = gw1Updated.Generation + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the GatewayClass update with generation change is processed", func() { + It( + "returns populated graph", func() { + processor.CaptureUpsertChange(gcUpdated) + + expGraph.GatewayClass.Source.Generation = gcUpdated.Generation + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the different namespace TLS secret is upserted again", func() { + It( + "returns populated graph", func() { + processor.CaptureUpsertChange(diffNsTLSSecret) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "no changes are captured", func() { + It( + "returns nil graph", func() { + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + Expect(graphCfg).To(BeNil()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the same namespace TLS Secret is upserted", func() { + It( + "returns nil graph", func() { + processor.CaptureUpsertChange(sameNsTLSSecret) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + Expect(graphCfg).To(BeNil()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the second Gateway is upserted", func() { + It( + "returns populated graph using first gateway", func() { + processor.CaptureUpsertChange(gw2) + + expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ + {Namespace: "test", Name: "gateway-2"}: gw2, + } + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the second HTTPRoute is upserted", func() { + It( + "returns populated graph", func() { + processor.CaptureUpsertChange(hr2) + + expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ + {Namespace: "test", Name: "gateway-2"}: gw2, + } + expGraph.Routes[routeKey2] = expRouteHR2 + expGraph.Routes[routeKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + } + expGraph.Routes[routeKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + } + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the second TLSRoute is upserted", func() { + It( + "returns populated graph", func() { + processor.CaptureUpsertChange(tr2) + + expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ + {Namespace: "test", Name: "gateway-2"}: gw2, + } + expGraph.Routes[routeKey2] = expRouteHR2 + expGraph.Routes[routeKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + } + expGraph.Routes[routeKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + } + + expGraph.L4Routes[trKey2] = expRouteTR2 + expGraph.L4Routes[trKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + } + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the first Gateway is deleted", func() { + It( + "returns updated graph", func() { + processor.CaptureDeleteChange( + &v1.Gateway{}, + types.NamespacedName{Namespace: "test", Name: "gateway-1"}, + ) + + // gateway 2 takes over; + // route 1 has been replaced by route 2 + listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) + + expGraph.Gateway.Source = gw2 + listener80.Source = gw2.Spec.Listeners[0] + listener443.Source = gw2.Spec.Listeners[1] + tlsListener.Source = gw2.Spec.Listeners[2] + + delete(listener80.Routes, routeKey1) + delete(listener443.Routes, routeKey1) + delete(tlsListener.L4Routes, trKey1) + + listener80.Routes[routeKey2] = expRouteHR2 + listener443.Routes[routeKey2] = expRouteHR2 + tlsListener.L4Routes[trKey2] = expRouteTR2 + + delete(expGraph.Routes, routeKey1) + delete(expGraph.L4Routes, trKey1) + + expGraph.Routes[routeKey2] = expRouteHR2 + expGraph.L4Routes[trKey2] = expRouteTR2 + + sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) + listener443.ResolvedSecret = sameNsTLSSecretRef + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ + Source: sameNsTLSSecret, + } + + delete( + expGraph.ReferencedServices, + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName, + ) + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the second HTTPRoute is deleted", func() { + It( + "returns updated graph", func() { + processor.CaptureDeleteChange( + &v1.HTTPRoute{}, + types.NamespacedName{Namespace: "test", Name: "hr-2"}, + ) + + // gateway 2 still in charge; + // no HTTP routes remain + // TLSRoute 2 still exists + listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) + + expGraph.Gateway.Source = gw2 + listener80.Source = gw2.Spec.Listeners[0] + listener443.Source = gw2.Spec.Listeners[1] + tlsListener.Source = gw2.Spec.Listeners[2] + + delete(listener80.Routes, routeKey1) + delete(listener443.Routes, routeKey1) + delete(tlsListener.L4Routes, trKey1) + + tlsListener.L4Routes[trKey2] = expRouteTR2 + + expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} + delete(expGraph.L4Routes, trKey1) + expGraph.L4Routes[trKey2] = expRouteTR2 + + sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) + listener443.ResolvedSecret = sameNsTLSSecretRef + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ + Source: sameNsTLSSecret, + } + + delete( + expGraph.ReferencedServices, + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName, + ) + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the second TLSRoute is deleted", func() { + It( + "returns updated graph", func() { + processor.CaptureDeleteChange( + &v1alpha2.TLSRoute{}, + types.NamespacedName{Namespace: "test", Name: "tr-2"}, + ) + + // gateway 2 still in charge; + // no HTTP or TLS routes remain + listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) + + expGraph.Gateway.Source = gw2 + listener80.Source = gw2.Spec.Listeners[0] + listener443.Source = gw2.Spec.Listeners[1] + tlsListener.Source = gw2.Spec.Listeners[2] + + delete(listener80.Routes, routeKey1) + delete(listener443.Routes, routeKey1) + delete(tlsListener.L4Routes, trKey1) + + expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} + expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} + + sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) + listener443.ResolvedSecret = sameNsTLSSecretRef + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ + Source: sameNsTLSSecret, + } + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServices = nil + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the GatewayClass is deleted", func() { + It( + "returns updated graph", func() { + processor.CaptureDeleteChange( + &v1.GatewayClass{}, + types.NamespacedName{Name: gcName}, + ) + + expGraph.GatewayClass = nil + expGraph.Gateway = &graph.Gateway{ + Source: gw2, + Conditions: staticConds.NewGatewayInvalid("GatewayClass doesn't exist"), + } + expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} + expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} + expGraph.ReferencedSecrets = nil + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServices = nil + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the second Gateway is deleted", func() { + It( + "returns empty graph", func() { + processor.CaptureDeleteChange( + &v1.Gateway{}, + types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + ) + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServices = nil + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) + When( + "the first HTTPRoute is deleted", func() { + It( + "returns empty graph", func() { + processor.CaptureDeleteChange( + &v1.HTTPRoute{}, + types.NamespacedName{Namespace: "test", Name: "hr-1"}, + ) + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServices = nil + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) + }, + ) + }, + ) }, - } - }) - When("no upsert has occurred", func() { - It("returns nil graph", func() { - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - Expect(graphCfg).To(BeNil()) - Expect(processor.GetLatestGraph()).To(BeNil()) - }) - }) - When("GatewayClass doesn't exist", func() { - When("Gateway API CRD is added", func() { - It("returns empty graph", func() { - processor.CaptureUpsertChange(gatewayAPICRD) - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("Gateways don't exist", func() { - When("the first HTTPRoute is upserted", func() { - It("returns empty graph", func() { - processor.CaptureUpsertChange(hr1) - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the first TLSRoute is upserted", func() { - It("returns empty graph", func() { - processor.CaptureUpsertChange(tr1) - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the different namespace TLS Secret is upserted", func() { - It("returns nil graph", func() { - processor.CaptureUpsertChange(diffNsTLSSecret) - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - Expect(graphCfg).To(BeNil()) - Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the first Gateway is upserted", func() { - It("returns populated graph", func() { - processor.CaptureUpsertChange(gw1) - - expGraph.GatewayClass = nil - - expGraph.Gateway.Conditions = staticConds.NewGatewayInvalid("GatewayClass doesn't exist") - expGraph.Gateway.Valid = false - expGraph.Gateway.Listeners = nil - - // no ref grant exists yet for hr1 or tr1 - expGraph.Routes[routeKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", - ), - } + ) - expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", - ), - } + Describe( + "Process services and endpoints", Ordered, func() { + var ( + hr1, hr2, hr3, hrInvalidBackendRef, hrMultipleRules *v1.HTTPRoute + hr1svc, sharedSvc, bazSvc1, bazSvc2, bazSvc3, invalidSvc, notRefSvc *apiv1.Service + hr1slice1, hr1slice2, noRefSlice, missingSvcNameSlice *discoveryV1.EndpointSlice + gw *v1.Gateway + btls *v1alpha3.BackendTLSPolicy + ) - // gateway class does not exist so routes cannot attach - expGraph.Routes[routeKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), - } - expGraph.Routes[routeKey1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), - } - expGraph.L4Routes[trKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), + createSvc := func(name string) *apiv1.Service { + return &apiv1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: name, + }, } + } - expGraph.ReferencedSecrets = nil - expGraph.ReferencedServices = nil - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - }) - }) - When("the GatewayClass is upserted", func() { - It("returns updated graph", func() { - processor.CaptureUpsertChange(gc) - - // No ref grant exists yet for gw1 - // so the listener is not valid, but still attachable - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - listener443.Valid = false - listener443.ResolvedSecret = nil - listener443.Conditions = staticConds.NewListenerRefNotPermitted( - "Certificate ref to secret cert-ns/different-ns-tls-secret not permitted by any ReferenceGrant", - ) - - expAttachment80 := &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{ - httpListenerName: {"foo.example.com"}, - }, - Attached: true, - ListenerPort: 80, - } - - expAttachment443 := &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{ - httpsListenerName: {"foo.example.com"}, - }, - Attached: true, - ListenerPort: 443, - } - - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener80.Routes[routeKey1].ParentRefs[0].Attachment = expAttachment80 - listener443.Routes[routeKey1].ParentRefs[1].Attachment = expAttachment443 - - // no ref grant exists yet for hr1 - expGraph.Routes[routeKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteInvalidListener(), - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", - ), - } - expGraph.Routes[routeKey1].ParentRefs[0].Attachment = expAttachment80 - expGraph.Routes[routeKey1].ParentRefs[1].Attachment = expAttachment443 - - // no ref grant exists yet for tr1 - expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", - ), - } - - expGraph.ReferencedSecrets = nil - expGraph.ReferencedServices = nil - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the ReferenceGrant allowing the Gateway to reference its Secret is upserted", func() { - It("returns updated graph", func() { - processor.CaptureUpsertChange(secretRefGrant) - - // no ref grant exists yet for hr1 - expGraph.Routes[routeKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", - ), - } - - // no ref grant exists yet for tr1 - expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", - ), - } - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - expGraph.ReferencedServices = nil - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the ReferenceGrant allowing the hr1 to reference the Service in different ns is upserted", func() { - It("returns updated graph", func() { - processor.CaptureUpsertChange(hrServiceRefGrant) - - // no ref grant exists yet for tr1 - expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", - ), - } - delete(expGraph.ReferencedServices, types.NamespacedName{Namespace: "tls-service-ns", Name: "tls-service"}) - expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the ReferenceGrant allowing the tr1 to reference the Service in different ns is upserted", func() { - It("returns updated graph", func() { - processor.CaptureUpsertChange(trServiceRefGrant) - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the Gateway API CRD with bundle version annotation change is processed", func() { - It("returns updated graph", func() { - processor.CaptureUpsertChange(gatewayAPICRDUpdated) - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - expGraph.GatewayClass.Conditions = conditions.NewGatewayClassSupportedVersionBestEffort( - gatewayclass.SupportedVersion, - ) + 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}, + }, + } + } - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the Gateway API CRD without bundle version annotation change is processed", func() { - It("returns nil graph", func() { - gatewayAPICRDSameVersion := gatewayAPICRDUpdated.DeepCopy() + createBackendTLSPolicy := func(name string, svcName string) *v1alpha3.BackendTLSPolicy { + return &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: name, + }, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: v1.Kind("Service"), + Name: v1.ObjectName(svcName), + }, + }, + }, + }, + } + } - processor.CaptureUpsertChange(gatewayAPICRDSameVersion) + BeforeAll( + func() { + testNamespace := v1.Namespace("test") + kindService := v1.Kind("Service") + kindInvalid := v1.Kind("Invalid") + + // backend Refs + fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) + baz1NilNamespace := createHTTPBackendRef(&kindService, "baz-svc-v1", &testNamespace) + barRef := createHTTPBackendRef(&kindService, "bar-svc", nil) + baz2Ref := createHTTPBackendRef(&kindService, "baz-svc-v2", &testNamespace) + baz3Ref := createHTTPBackendRef(&kindService, "baz-svc-v3", &testNamespace) + invalidKindRef := createHTTPBackendRef(&kindInvalid, "bar-svc", &testNamespace) + + // httproutes + hr1 = 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", "") + + // backendTLSPolicy + btls = createBackendTLSPolicy("btls", "foo-svc") + + gw = createGateway("gw", createHTTPListener()) + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } + testProcessChangedVal := func(expChanged state.ChangeType) { + changed, _ := processor.Process() + Expect(changed).To(Equal(expChanged)) + } - expGraph.GatewayClass.Conditions = conditions.NewGatewayClassSupportedVersionBestEffort( - gatewayclass.SupportedVersion, - ) - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - Expect(graphCfg).To(BeNil()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the Gateway API CRD with bundle version annotation change is processed", func() { - It("returns updated graph", func() { - // change back to supported version - processor.CaptureUpsertChange(gatewayAPICRD) - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } + testUpsertTriggersChange := func(obj client.Object, expChanged state.ChangeType) { + processor.CaptureUpsertChange(obj) + testProcessChangedVal(expChanged) + } - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the first HTTPRoute update with a generation changed is processed", func() { - It("returns populated graph", func() { - processor.CaptureUpsertChange(hr1Updated) - - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - listener443.Routes[routeKey1].Source.SetGeneration(hr1Updated.Generation) - - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener80.Routes[routeKey1].Source.SetGeneration(hr1Updated.Generation) - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } + testDeleteTriggersChange := func( + obj client.Object, + nsname types.NamespacedName, + expChanged state.ChangeType, + ) { + processor.CaptureDeleteChange(obj, nsname) + testProcessChangedVal(expChanged) + } - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, + When( + "hr1 is added", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(hr1, state.ClusterStateChange) + }, + ) + }, + ) + When( + "a hr1 service is added", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(hr1svc, state.ClusterStateChange) + }, + ) + }, + ) + When( + "a backendTLSPolicy is added for referenced service", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(btls, state.ClusterStateChange) + }, + ) + }, + ) + When( + "an hr1 endpoint slice is added", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(hr1slice1, state.EndpointsOnlyChange) + }, + ) + }, + ) + When( + "an hr1 service is updated", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(hr1svc, state.ClusterStateChange) + }, + ) + }, + ) + When( + "another hr1 endpoint slice is added", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) + }, + ) + }, + ) + When( + "an endpoint slice with a missing svc name label is added", func() { + It( + "should not trigger a change", func() { + testUpsertTriggersChange(missingSvcNameSlice, state.NoChange) + }, + ) + }, + ) + When( + "an hr1 endpoint slice is deleted", func() { + It( + "should trigger a change", func() { + testDeleteTriggersChange( + hr1slice1, + types.NamespacedName{Namespace: hr1slice1.Namespace, Name: hr1slice1.Name}, + state.EndpointsOnlyChange, + ) + }, + ) + }, + ) + When( + "the second hr1 endpoint slice is deleted", func() { + It( + "should trigger a change", func() { + testDeleteTriggersChange( + hr1slice2, + types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, + state.EndpointsOnlyChange, + ) + }, + ) + }, + ) + When( + "the second hr1 endpoint slice is recreated", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) + }, + ) + }, + ) + When( + "hr1 is deleted", func() { + It( + "should trigger a change", func() { + testDeleteTriggersChange( + hr1, + types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name}, + state.ClusterStateChange, + ) + }, + ) + }, + ) + When( + "hr1 service is deleted", func() { + It( + "should not trigger a change", func() { + testDeleteTriggersChange( + hr1svc, + types.NamespacedName{Namespace: hr1svc.Namespace, Name: hr1svc.Name}, + state.NoChange, + ) + }, + ) + }, + ) + When( + "the second hr1 endpoint slice is deleted", func() { + It( + "should not trigger a change", func() { + testDeleteTriggersChange( + hr1slice2, + types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, + state.NoChange, + ) + }, + ) + }, + ) + When( + "hr2 is added", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(hr2, state.ClusterStateChange) + }, + ) + }, + ) + When( + "a hr3, that shares a backend service with hr2, is added", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(hr3, state.ClusterStateChange) + }, + ) + }, + ) + When( + "sharedSvc, a service referenced by both hr2 and hr3, is added", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) + }, + ) + }, + ) + When( + "hr2 is deleted", func() { + It( + "should trigger a change", func() { + testDeleteTriggersChange( + hr2, + types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name}, + state.ClusterStateChange, + ) + }, + ) + }, + ) + When( + "sharedSvc is deleted", func() { + It( + "should trigger a change", func() { + testDeleteTriggersChange( + sharedSvc, + types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, + state.ClusterStateChange, + ) + }, + ) + }, + ) + When( + "sharedSvc is recreated", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) + }, + ) + }, + ) + When( + "hr3 is deleted", func() { + It( + "should trigger a change", func() { + testDeleteTriggersChange( + hr3, + types.NamespacedName{Namespace: hr3.Namespace, Name: hr3.Name}, + state.ClusterStateChange, + ) + }, + ) + }, + ) + When( + "sharedSvc is deleted", func() { + It( + "should not trigger a change", func() { + testDeleteTriggersChange( + sharedSvc, + types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, + state.NoChange, + ) + }, + ) + }, + ) + When( + "a service that is not referenced by any route is added", func() { + It( + "should not trigger a change", func() { + testUpsertTriggersChange(notRefSvc, state.NoChange) + }, + ) + }, + ) + When( + "a route with an invalid backend ref type is added", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(hrInvalidBackendRef, state.ClusterStateChange) + }, + ) + }, + ) + When( + "a service with a namespace name that matches invalid backend ref is added", func() { + It( + "should not trigger a change", func() { + testUpsertTriggersChange(invalidSvc, state.NoChange) + }, + ) + }, + ) + When( + "an endpoint slice that is not owned by a referenced service is added", func() { + It( + "should not trigger a change", func() { + testUpsertTriggersChange(noRefSlice, state.NoChange) + }, + ) + }, + ) + When( + "an endpoint slice that is not owned by a referenced service is deleted", func() { + It( + "should not trigger a change", func() { + testDeleteTriggersChange( + noRefSlice, + types.NamespacedName{ + Namespace: noRefSlice.Namespace, + Name: noRefSlice.Name, + }, + state.NoChange, + ) + }, + ) + }, + ) + Context( + "processing a route with multiple rules and three unique backend services", func() { + When( + "route is added", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(hrMultipleRules, state.ClusterStateChange) + }, + ) + }, + ) + When( + "first referenced service is added", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) + }, + ) + }, + ) + When( + "second referenced service is added", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(bazSvc2, state.ClusterStateChange) + }, + ) + }, + ) + When( + "first referenced service is deleted", func() { + It( + "should trigger a change", func() { + testDeleteTriggersChange( + bazSvc1, + types.NamespacedName{ + Namespace: bazSvc1.Namespace, + Name: bazSvc1.Name, + }, + state.ClusterStateChange, + ) + }, + ) + }, + ) + When( + "first referenced service is recreated", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) + }, + ) + }, + ) + When( + "third referenced service is added", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) + }, + ) + }, + ) + When( + "third referenced service is updated", func() { + It( + "should trigger a change", func() { + testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) + }, + ) + }, + ) + When( + "route is deleted", func() { + It( + "should trigger a change", func() { + testDeleteTriggersChange( + hrMultipleRules, + types.NamespacedName{ + Namespace: hrMultipleRules.Namespace, + Name: hrMultipleRules.Name, + }, + state.ClusterStateChange, + ) + }, + ) + }, + ) + When( + "first referenced service is deleted", func() { + It( + "should not trigger a change", func() { + testDeleteTriggersChange( + bazSvc1, + types.NamespacedName{ + Namespace: bazSvc1.Namespace, + Name: bazSvc1.Name, + }, + state.NoChange, + ) + }, + ) + }, + ) + When( + "second referenced service is deleted", func() { + It( + "should not trigger a change", func() { + testDeleteTriggersChange( + bazSvc2, + types.NamespacedName{ + Namespace: bazSvc2.Namespace, + Name: bazSvc2.Name, + }, + state.NoChange, + ) + }, + ) + }, + ) + When( + "final referenced service is deleted", func() { + It( + "should not trigger a change", func() { + testDeleteTriggersChange( + bazSvc3, + types.NamespacedName{ + Namespace: bazSvc3.Namespace, + Name: bazSvc3.Name, + }, + state.NoChange, + ) + }, + ) + }, + ) + }, + ) + }, ) - }) - When("the first TLSRoute update with a generation changed is processed", func() { - It("returns populated graph", func() { - processor.CaptureUpsertChange(tr1Updated) - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) - tlsListener.L4Routes[trKey1].Source.SetGeneration(tr1Updated.Generation) + Describe( + "namespace changes", Ordered, func() { + var ( + ns, nsDifferentLabels, nsNoLabels *apiv1.Namespace + gw *v1.Gateway + ) - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } + BeforeAll( + func() { + ns = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns", + Labels: map[string]string{ + "app": "allowed", + }, + }, + } + nsDifferentLabels = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns-different-labels", + Labels: map[string]string{ + "oranges": "bananas", + }, + }, + } + nsNoLabels = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "no-labels", + }, + } + gw = &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + }, + Spec: v1.GatewaySpec{ + GatewayClassName: gcName, + Listeners: []v1.Listener{ + { + Port: 80, + Protocol: v1.HTTPProtocolType, + AllowedRoutes: &v1.AllowedRoutes{ + Namespaces: &v1.RouteNamespaces{ + From: helpers.GetPointer(v1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "allowed", + }, + }, + }, + }, + }, + }, + }, + } + processor = state.NewChangeProcessorImpl( + state.ChangeProcessorConfig{ + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }, + ) + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw) + processor.Process() + }, + ) - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, + When( + "a namespace is created that is not linked to a listener", func() { + It( + "does not trigger an update", func() { + processor.CaptureUpsertChange(nsNoLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }, + ) + }, + ) + When( + "a namespace is created that is linked to a listener", func() { + It( + "triggers an update", func() { + processor.CaptureUpsertChange(ns) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + }, + ) + When( + "a namespace is deleted that is not linked to a listener", func() { + It( + "does not trigger an update", func() { + processor.CaptureDeleteChange( + nsNoLabels, + types.NamespacedName{Name: "no-labels"}, + ) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }, + ) + }, + ) + When( + "a namespace is deleted that is linked to a listener", func() { + It( + "triggers an update", func() { + processor.CaptureDeleteChange(ns, types.NamespacedName{Name: "ns"}) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + }, + ) + When( + "a namespace that is not linked to a listener has its labels changed to match a listener", + func() { + It( + "triggers an update", func() { + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + + nsDifferentLabels.Labels = map[string]string{ + "app": "allowed", + } + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ = processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + }, + ) + When( + "a namespace that is linked to a listener has its labels changed to no longer match a listener", + func() { + It( + "triggers an update", func() { + nsDifferentLabels.Labels = map[string]string{ + "oranges": "bananas", + } + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + }, + ) + When( + "a gateway changes its listener's labels", func() { + It( + "triggers an update when a namespace that matches the new labels is created", + func() { + gwChangedLabel := gw.DeepCopy() + gwChangedLabel.Spec.Listeners[0].AllowedRoutes.Namespaces.Selector.MatchLabels = map[string]string{ + "oranges": "bananas", + } + gwChangedLabel.Generation++ + processor.CaptureUpsertChange(gwChangedLabel) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + + // After changing the gateway's labels and generation, the processor should be marked to update + // the nginx configuration and build a new graph. When processor.Process() gets called, + // the nginx configuration gets updated and a new graph is built with an updated + // referencedNamespaces. Thus, when the namespace "ns" is upserted with labels that no longer match + // the new labels on the gateway, it would not trigger a change as the namespace would no longer + // be in the updated referencedNamespaces and the labels no longer match the new labels on the + // gateway. + processor.CaptureUpsertChange(ns) + changed, _ = processor.Process() + Expect(changed).To(Equal(state.NoChange)) + + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ = processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + }, + ) + When( + "a namespace that is not linked to a listener has its labels removed", func() { + It( + "does not trigger an update", func() { + ns.Labels = nil + processor.CaptureUpsertChange(ns) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }, + ) + }, + ) + When( + "a namespace that is linked to a listener has its labels removed", func() { + It( + "triggers an update when labels are removed", func() { + nsDifferentLabels.Labels = nil + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + }, + ) + }, ) - }) - When("the first Gateway update with a generation changed is processed", func() { - It("returns populated graph", func() { - processor.CaptureUpsertChange(gw1Updated) - - expGraph.Gateway.Source.Generation = gw1Updated.Generation - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the GatewayClass update with generation change is processed", func() { - It("returns populated graph", func() { - processor.CaptureUpsertChange(gcUpdated) - - expGraph.GatewayClass.Source.Generation = gcUpdated.Generation - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the different namespace TLS secret is upserted again", func() { - It("returns populated graph", func() { - processor.CaptureUpsertChange(diffNsTLSSecret) - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } + Describe( + "NginxProxy resource changes", Ordered, func() { + paramGC := gc.DeepCopy() + paramGC.Spec.ParametersRef = &v1beta1.ParametersReference{ + Group: ngfAPI.GroupName, + Kind: kinds.NginxProxy, + Name: "np", + } - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("no changes are captured", func() { - It("returns nil graph", func() { - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } + np := &ngfAPI.NginxProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "np", + }, + } - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - Expect(graphCfg).To(BeNil()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the same namespace TLS Secret is upserted", func() { - It("returns nil graph", func() { - processor.CaptureUpsertChange(sameNsTLSSecret) - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } + npUpdated := &ngfAPI.NginxProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "np", + }, + Spec: ngfAPI.NginxProxySpec{ + Telemetry: &ngfAPI.Telemetry{ + Exporter: &ngfAPI.TelemetryExporter{ + Endpoint: "my-svc:123", + BatchSize: helpers.GetPointer(int32(512)), + BatchCount: helpers.GetPointer(int32(4)), + Interval: helpers.GetPointer(ngfAPI.Duration("5s")), + }, + }, + }, + } + It( + "handles upserts for an NginxProxy", func() { + processor.CaptureUpsertChange(np) + processor.CaptureUpsertChange(paramGC) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NginxProxy.Source).To(Equal(np)) + }, + ) + It( + "captures changes for an NginxProxy", func() { + processor.CaptureUpsertChange(npUpdated) + processor.CaptureUpsertChange(paramGC) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NginxProxy.Source).To(Equal(npUpdated)) + }, + ) + It( + "handles deletes for an NginxProxy", func() { + processor.CaptureDeleteChange(np, client.ObjectKeyFromObject(np)) - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - Expect(graphCfg).To(BeNil()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the second Gateway is upserted", func() { - It("returns populated graph using first gateway", func() { - processor.CaptureUpsertChange(gw2) - - expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-2"}: gw2, - } - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NginxProxy).To(BeNil()) + }, + ) + }, + ) - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the second HTTPRoute is upserted", func() { - It("returns populated graph", func() { - processor.CaptureUpsertChange(hr2) - - expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-2"}: gw2, - } - expGraph.Routes[routeKey2] = expRouteHR2 - expGraph.Routes[routeKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - expGraph.Routes[routeKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } + Describe( + "NGF Policy resource changes", Ordered, func() { + var ( + gw *v1.Gateway + route *v1.HTTPRoute + csp, cspUpdated *ngfAPI.ClientSettingsPolicy + obs, obsUpdated *ngfAPI.ObservabilityPolicy + cspKey, obsKey graph.PolicyKey + ) - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the second TLSRoute is upserted", func() { - It("returns populated graph", func() { - processor.CaptureUpsertChange(tr2) - - expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-2"}: gw2, - } - expGraph.Routes[routeKey2] = expRouteHR2 - expGraph.Routes[routeKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - expGraph.Routes[routeKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } + BeforeAll( + func() { + processor.CaptureUpsertChange(gc) + changed, newGraph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(newGraph.GatewayClass.Source).To(Equal(gc)) + Expect(newGraph.NGFPolicies).To(BeEmpty()) + + gw = createGateway("gw", createHTTPListener()) + route = createRoute("hr-1", "gw", "foo.example.com", v1.HTTPBackendRef{}) + + csp = &ngfAPI.ClientSettingsPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csp", + Namespace: "test", + }, + Spec: ngfAPI.ClientSettingsPolicySpec{ + TargetRef: v1alpha2.LocalPolicyTargetReference{ + Group: v1.GroupName, + Kind: kinds.Gateway, + Name: "gw", + }, + Body: &ngfAPI.ClientBody{ + MaxSize: helpers.GetPointer[ngfAPI.Size]("10m"), + }, + }, + } - expGraph.L4Routes[trKey2] = expRouteTR2 - expGraph.L4Routes[trKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } + cspUpdated = csp.DeepCopy() + cspUpdated.Spec.Body.MaxSize = helpers.GetPointer[ngfAPI.Size]("20m") - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } + cspKey = graph.PolicyKey{ + NsName: types.NamespacedName{Name: "csp", Namespace: "test"}, + GVK: schema.GroupVersionKind{ + Group: ngfAPI.GroupName, + Kind: kinds.ClientSettingsPolicy, + Version: "v1alpha1", + }, + } - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the first Gateway is deleted", func() { - It("returns updated graph", func() { - processor.CaptureDeleteChange( - &v1.Gateway{}, - types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - ) - - // gateway 2 takes over; - // route 1 has been replaced by route 2 - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) - - expGraph.Gateway.Source = gw2 - listener80.Source = gw2.Spec.Listeners[0] - listener443.Source = gw2.Spec.Listeners[1] - tlsListener.Source = gw2.Spec.Listeners[2] - - delete(listener80.Routes, routeKey1) - delete(listener443.Routes, routeKey1) - delete(tlsListener.L4Routes, trKey1) - - listener80.Routes[routeKey2] = expRouteHR2 - listener443.Routes[routeKey2] = expRouteHR2 - tlsListener.L4Routes[trKey2] = expRouteTR2 - - delete(expGraph.Routes, routeKey1) - delete(expGraph.L4Routes, trKey1) - - expGraph.Routes[routeKey2] = expRouteHR2 - expGraph.L4Routes[trKey2] = expRouteTR2 - - sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) - listener443.ResolvedSecret = sameNsTLSSecretRef - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, - } + obs = &ngfAPI.ObservabilityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "obs", + Namespace: "test", + }, + Spec: ngfAPI.ObservabilityPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReference{ + { + Group: v1.GroupName, + Kind: kinds.HTTPRoute, + Name: "hr-1", + }, + }, + Tracing: &ngfAPI.Tracing{ + Strategy: ngfAPI.TraceStrategyRatio, + }, + }, + } - delete(expGraph.ReferencedServices, expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName) - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the second HTTPRoute is deleted", func() { - It("returns updated graph", func() { - processor.CaptureDeleteChange( - &v1.HTTPRoute{}, - types.NamespacedName{Namespace: "test", Name: "hr-2"}, - ) - - // gateway 2 still in charge; - // no HTTP routes remain - // TLSRoute 2 still exists - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) - - expGraph.Gateway.Source = gw2 - listener80.Source = gw2.Spec.Listeners[0] - listener443.Source = gw2.Spec.Listeners[1] - tlsListener.Source = gw2.Spec.Listeners[2] - - delete(listener80.Routes, routeKey1) - delete(listener443.Routes, routeKey1) - delete(tlsListener.L4Routes, trKey1) - - tlsListener.L4Routes[trKey2] = expRouteTR2 - - expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - delete(expGraph.L4Routes, trKey1) - expGraph.L4Routes[trKey2] = expRouteTR2 - - sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) - listener443.ResolvedSecret = sameNsTLSSecretRef - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, - } + obsUpdated = obs.DeepCopy() + obsUpdated.Spec.Tracing.Strategy = ngfAPI.TraceStrategyParent - delete(expGraph.ReferencedServices, expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName) - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the second TLSRoute is deleted", func() { - It("returns updated graph", func() { - processor.CaptureDeleteChange( - &v1alpha2.TLSRoute{}, - types.NamespacedName{Namespace: "test", Name: "tr-2"}, - ) - - // gateway 2 still in charge; - // no HTTP or TLS routes remain - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) - - expGraph.Gateway.Source = gw2 - listener80.Source = gw2.Spec.Listeners[0] - listener443.Source = gw2.Spec.Listeners[1] - tlsListener.Source = gw2.Spec.Listeners[2] - - delete(listener80.Routes, routeKey1) - delete(listener443.Routes, routeKey1) - delete(tlsListener.L4Routes, trKey1) - - expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} - - sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) - listener443.ResolvedSecret = sameNsTLSSecretRef - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, - } + obsKey = graph.PolicyKey{ + NsName: types.NamespacedName{Name: "obs", Namespace: "test"}, + GVK: schema.GroupVersionKind{ + Group: ngfAPI.GroupName, + Kind: kinds.ObservabilityPolicy, + Version: "v1alpha1", + }, + } + }, + ) - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the GatewayClass is deleted", func() { - It("returns updated graph", func() { - processor.CaptureDeleteChange( - &v1.GatewayClass{}, - types.NamespacedName{Name: gcName}, - ) - - expGraph.GatewayClass = nil - expGraph.Gateway = &graph.Gateway{ - Source: gw2, - Conditions: staticConds.NewGatewayInvalid("GatewayClass doesn't exist"), - } - expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} - expGraph.ReferencedSecrets = nil - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the second Gateway is deleted", func() { - It("returns empty graph", func() { - processor.CaptureDeleteChange( - &v1.Gateway{}, - types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - ) - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) - }) - }) - When("the first HTTPRoute is deleted", func() { - It("returns empty graph", func() { - processor.CaptureDeleteChange( - &v1.HTTPRoute{}, - types.NamespacedName{Namespace: "test", Name: "hr-1"}, - ) - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).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 - gw *v1.Gateway - btls *v1alpha3.BackendTLSPolicy - ) - - createSvc := func(name string) *apiv1.Service { - return &apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: name, - }, - } - } - - createEndpointSlice := func(name string, svcName string) *discoveryV1.EndpointSlice { - return &discoveryV1.EndpointSlice{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: name, - Labels: map[string]string{index.KubernetesServiceNameLabel: svcName}, - }, - } - } - - createBackendTLSPolicy := func(name string, svcName string) *v1alpha3.BackendTLSPolicy { - return &v1alpha3.BackendTLSPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: name, - }, - Spec: v1alpha3.BackendTLSPolicySpec{ - TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ - { - LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ - Kind: v1.Kind("Service"), - Name: v1.ObjectName(svcName), - }, + /* + NOTE: When adding a new NGF policy to the change processor, + update the following tests to make sure that the change processor can track changes for multiple NGF + policies. + */ + + When( + "a policy is created that references a resource that is not in the last graph", func() { + It( + "reports no changes", func() { + processor.CaptureUpsertChange(csp) + processor.CaptureUpsertChange(obs) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }, + ) }, - }, - }, - } - } - - BeforeAll(func() { - testNamespace := v1.Namespace("test") - kindService := v1.Kind("Service") - kindInvalid := v1.Kind("Invalid") - - // backend Refs - fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) - baz1NilNamespace := createHTTPBackendRef(&kindService, "baz-svc-v1", &testNamespace) - barRef := createHTTPBackendRef(&kindService, "bar-svc", nil) - baz2Ref := createHTTPBackendRef(&kindService, "baz-svc-v2", &testNamespace) - baz3Ref := createHTTPBackendRef(&kindService, "baz-svc-v3", &testNamespace) - invalidKindRef := createHTTPBackendRef(&kindInvalid, "bar-svc", &testNamespace) - - // httproutes - hr1 = 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), + ) + When( + "the resource the policy references is created", func() { + It( + "populates the graph with the policy", func() { + processor.CaptureUpsertChange(gw) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(HaveKey(cspKey)) + Expect(graph.NGFPolicies[cspKey].Source).To(Equal(csp)) + Expect(graph.NGFPolicies).ToNot(HaveKey(obsKey)) + + processor.CaptureUpsertChange(route) + changed, graph = processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(HaveKey(obsKey)) + Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obs)) + }, + ) + }, + ) + When( + "the policy is updated", func() { + It( + "captures changes for a policy", func() { + processor.CaptureUpsertChange(cspUpdated) + processor.CaptureUpsertChange(obsUpdated) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(HaveKey(cspKey)) + Expect(graph.NGFPolicies[cspKey].Source).To(Equal(cspUpdated)) + Expect(graph.NGFPolicies).To(HaveKey(obsKey)) + Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obsUpdated)) + }, + ) + }, + ) + When( + "the policy is deleted", func() { + It( + "removes the policy from the graph", func() { + processor.CaptureDeleteChange( + &ngfAPI.ClientSettingsPolicy{}, + client.ObjectKeyFromObject(csp), + ) + processor.CaptureDeleteChange( + &ngfAPI.ObservabilityPolicy{}, + client.ObjectKeyFromObject(obs), + ) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(BeEmpty()) + }, + ) + }, + ) }, ) - // services - hr1svc = createSvc("foo-svc") - sharedSvc = createSvc("bar-svc") // shared between hr2 and hr3 - invalidSvc = createSvc("invalid") // nsname matches invalid BackendRef - notRefSvc = createSvc("not-ref") - bazSvc1 = createSvc("baz-svc-v1") - bazSvc2 = createSvc("baz-svc-v2") - bazSvc3 = createSvc("baz-svc-v3") - - // endpoint slices - hr1slice1 = createEndpointSlice("hr1-1", "foo-svc") - hr1slice2 = createEndpointSlice("hr1-2", "foo-svc") - noRefSlice = createEndpointSlice("no-ref", "no-ref") - missingSvcNameSlice = createEndpointSlice("missing-svc-name", "") - - // backendTLSPolicy - btls = createBackendTLSPolicy("btls", "foo-svc") - - gw = createGateway("gw", createHTTPListener()) - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - - testProcessChangedVal := func(expChanged state.ChangeType) { - changed, _ := processor.Process() - Expect(changed).To(Equal(expChanged)) - } - - testUpsertTriggersChange := func(obj client.Object, expChanged state.ChangeType) { - processor.CaptureUpsertChange(obj) - testProcessChangedVal(expChanged) - } - - testDeleteTriggersChange := func(obj client.Object, nsname types.NamespacedName, expChanged state.ChangeType) { - processor.CaptureDeleteChange(obj, nsname) - testProcessChangedVal(expChanged) - } - - When("hr1 is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1, state.ClusterStateChange) - }) - }) - When("a hr1 service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1svc, state.ClusterStateChange) - }) - }) - When("a backendTLSPolicy is added for referenced service", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(btls, state.ClusterStateChange) - }) - }) - When("an hr1 endpoint slice is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1slice1, state.EndpointsOnlyChange) - }) - }) - When("an hr1 service is updated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1svc, state.ClusterStateChange) - }) - }) - When("another hr1 endpoint slice is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) - }) - }) - When("an endpoint slice with a missing svc name label is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(missingSvcNameSlice, state.NoChange) - }) - }) - When("an hr1 endpoint slice is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr1slice1, - types.NamespacedName{Namespace: hr1slice1.Namespace, Name: hr1slice1.Name}, - state.EndpointsOnlyChange, - ) - }) - }) - When("the second hr1 endpoint slice is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr1slice2, - types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, - state.EndpointsOnlyChange, - ) - }) - }) - When("the second hr1 endpoint slice is recreated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) - }) - }) - When("hr1 is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr1, - types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name}, - state.ClusterStateChange, - ) - }) - }) - When("hr1 service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - hr1svc, - types.NamespacedName{Namespace: hr1svc.Namespace, Name: hr1svc.Name}, - state.NoChange, - ) - }) - }) - When("the second hr1 endpoint slice is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - hr1slice2, - types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, - state.NoChange, - ) - }) - }) - When("hr2 is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr2, state.ClusterStateChange) - }) - }) - When("a hr3, that shares a backend service with hr2, is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hr3, state.ClusterStateChange) - }) - }) - When("sharedSvc, a service referenced by both hr2 and hr3, is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) - }) - }) - When("hr2 is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr2, - types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name}, - state.ClusterStateChange, - ) - }) - }) - When("sharedSvc is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - sharedSvc, - types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, - state.ClusterStateChange, - ) - }) - }) - When("sharedSvc is recreated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) - }) - }) - When("hr3 is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hr3, - types.NamespacedName{Namespace: hr3.Namespace, Name: hr3.Name}, - state.ClusterStateChange, - ) - }) - }) - When("sharedSvc is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - sharedSvc, - types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, - state.NoChange, - ) - }) - }) - When("a service that is not referenced by any route is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(notRefSvc, state.NoChange) - }) - }) - When("a route with an invalid backend ref type is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hrInvalidBackendRef, state.ClusterStateChange) - }) - }) - When("a service with a namespace name that matches invalid backend ref is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(invalidSvc, state.NoChange) - }) - }) - When("an endpoint slice that is not owned by a referenced service is added", func() { - It("should not trigger a change", func() { - testUpsertTriggersChange(noRefSlice, state.NoChange) - }) - }) - When("an endpoint slice that is not owned by a referenced service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - noRefSlice, - types.NamespacedName{Namespace: noRefSlice.Namespace, Name: noRefSlice.Name}, - state.NoChange, - ) - }) - }) - Context("processing a route with multiple rules and three unique backend services", func() { - When("route is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(hrMultipleRules, state.ClusterStateChange) - }) - }) - When("first referenced service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) - }) - }) - When("second referenced service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc2, state.ClusterStateChange) - }) - }) - When("first referenced service is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - bazSvc1, - types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, - state.ClusterStateChange, - ) - }) - }) - When("first referenced service is recreated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) - }) - }) - When("third referenced service is added", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) - }) - }) - When("third referenced service is updated", func() { - It("should trigger a change", func() { - testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) - }) - }) - When("route is deleted", func() { - It("should trigger a change", func() { - testDeleteTriggersChange( - hrMultipleRules, - types.NamespacedName{ - Namespace: hrMultipleRules.Namespace, - Name: hrMultipleRules.Name, - }, - state.ClusterStateChange, - ) - }) - }) - When("first referenced service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc1, - types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, - state.NoChange, - ) - }) - }) - When("second referenced service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc2, - types.NamespacedName{Namespace: bazSvc2.Namespace, Name: bazSvc2.Name}, - state.NoChange, - ) - }) - }) - When("final referenced service is deleted", func() { - It("should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc3, - types.NamespacedName{Namespace: bazSvc3.Namespace, Name: bazSvc3.Name}, - state.NoChange, - ) - }) - }) - }) - }) - - Describe("namespace changes", Ordered, func() { - var ( - ns, nsDifferentLabels, nsNoLabels *apiv1.Namespace - gw *v1.Gateway - ) - - BeforeAll(func() { - ns = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ns", - Labels: map[string]string{ - "app": "allowed", - }, - }, - } - nsDifferentLabels = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ns-different-labels", - Labels: map[string]string{ - "oranges": "bananas", - }, - }, - } - nsNoLabels = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "no-labels", - }, - } - gw = &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: v1.GatewaySpec{ - GatewayClassName: gcName, - Listeners: []v1.Listener{ - { - Port: 80, - Protocol: v1.HTTPProtocolType, - AllowedRoutes: &v1.AllowedRoutes{ - Namespaces: &v1.RouteNamespaces{ - From: helpers.GetPointer(v1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "allowed", - }, - }, + Describe( + "SnippetsFilter resource changed", Ordered, func() { + sfNsName := types.NamespacedName{ + Name: "sf", + Namespace: "test", + } + + sf := &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: sfNsName.Name, + Namespace: sfNsName.Namespace, + }, + Spec: ngfAPI.SnippetsFilterSpec{ + Snippets: []ngfAPI.Snippet{ + { + Context: ngfAPI.NginxContextMain, + Value: "main snippet", }, }, }, - }, - }, - } - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }) - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw) - processor.Process() - }) - - When("a namespace is created that is not linked to a listener", func() { - It("does not trigger an update", func() { - processor.CaptureUpsertChange(nsNoLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - }) - When("a namespace is created that is linked to a listener", func() { - It("triggers an update", func() { - processor.CaptureUpsertChange(ns) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - When("a namespace is deleted that is not linked to a listener", func() { - It("does not trigger an update", func() { - processor.CaptureDeleteChange(nsNoLabels, types.NamespacedName{Name: "no-labels"}) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - }) - When("a namespace is deleted that is linked to a listener", func() { - It("triggers an update", func() { - processor.CaptureDeleteChange(ns, types.NamespacedName{Name: "ns"}) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - When("a namespace that is not linked to a listener has its labels changed to match a listener", func() { - It("triggers an update", func() { - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - - nsDifferentLabels.Labels = map[string]string{ - "app": "allowed", - } - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ = processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - When( - "a namespace that is linked to a listener has its labels changed to no longer match a listener", - func() { - It("triggers an update", func() { - nsDifferentLabels.Labels = map[string]string{ - "oranges": "bananas", } - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }, - ) - When("a gateway changes its listener's labels", func() { - It("triggers an update when a namespace that matches the new labels is created", func() { - gwChangedLabel := gw.DeepCopy() - gwChangedLabel.Spec.Listeners[0].AllowedRoutes.Namespaces.Selector.MatchLabels = map[string]string{ - "oranges": "bananas", - } - gwChangedLabel.Generation++ - processor.CaptureUpsertChange(gwChangedLabel) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - - // After changing the gateway's labels and generation, the processor should be marked to update - // the nginx configuration and build a new graph. When processor.Process() gets called, - // the nginx configuration gets updated and a new graph is built with an updated - // referencedNamespaces. Thus, when the namespace "ns" is upserted with labels that no longer match - // the new labels on the gateway, it would not trigger a change as the namespace would no longer - // be in the updated referencedNamespaces and the labels no longer match the new labels on the - // gateway. - processor.CaptureUpsertChange(ns) - changed, _ = processor.Process() - Expect(changed).To(Equal(state.NoChange)) - - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ = processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - When("a namespace that is not linked to a listener has its labels removed", func() { - It("does not trigger an update", func() { - ns.Labels = nil - processor.CaptureUpsertChange(ns) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - }) - When("a namespace that is linked to a listener has its labels removed", func() { - It("triggers an update when labels are removed", func() { - nsDifferentLabels.Labels = nil - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - }) - - Describe("NginxProxy resource changes", Ordered, func() { - paramGC := gc.DeepCopy() - paramGC.Spec.ParametersRef = &v1beta1.ParametersReference{ - Group: ngfAPI.GroupName, - Kind: v1beta1.Kind(kinds.NginxProxy), - Name: "np", - } - - np := &ngfAPI.NginxProxy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "np", - }, - } - - npUpdated := &ngfAPI.NginxProxy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "np", - }, - Spec: ngfAPI.NginxProxySpec{ - Telemetry: &ngfAPI.Telemetry{ - Exporter: &ngfAPI.TelemetryExporter{ - Endpoint: "my-svc:123", - BatchSize: helpers.GetPointer(int32(512)), - BatchCount: helpers.GetPointer(int32(4)), - Interval: helpers.GetPointer(ngfAPI.Duration("5s")), - }, - }, - }, - } - It("handles upserts for an NginxProxy", func() { - processor.CaptureUpsertChange(np) - processor.CaptureUpsertChange(paramGC) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NginxProxy.Source).To(Equal(np)) - }) - It("captures changes for an NginxProxy", func() { - processor.CaptureUpsertChange(npUpdated) - processor.CaptureUpsertChange(paramGC) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NginxProxy.Source).To(Equal(npUpdated)) - }) - It("handles deletes for an NginxProxy", func() { - processor.CaptureDeleteChange(np, client.ObjectKeyFromObject(np)) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NginxProxy).To(BeNil()) - }) - }) - - Describe("NGF Policy resource changes", Ordered, func() { - var ( - gw *v1.Gateway - route *v1.HTTPRoute - csp, cspUpdated *ngfAPI.ClientSettingsPolicy - obs, obsUpdated *ngfAPI.ObservabilityPolicy - cspKey, obsKey graph.PolicyKey - ) - - BeforeAll(func() { - processor.CaptureUpsertChange(gc) - changed, newGraph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(newGraph.GatewayClass.Source).To(Equal(gc)) - Expect(newGraph.NGFPolicies).To(BeEmpty()) - - gw = createGateway("gw", createHTTPListener()) - route = createRoute("hr-1", "gw", "foo.example.com", v1.HTTPBackendRef{}) - - csp = &ngfAPI.ClientSettingsPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "csp", - Namespace: "test", - }, - Spec: ngfAPI.ClientSettingsPolicySpec{ - TargetRef: v1alpha2.LocalPolicyTargetReference{ - Group: v1.GroupName, - Kind: kinds.Gateway, - Name: "gw", - }, - Body: &ngfAPI.ClientBody{ - MaxSize: helpers.GetPointer[ngfAPI.Size]("10m"), - }, - }, - } - cspUpdated = csp.DeepCopy() - cspUpdated.Spec.Body.MaxSize = helpers.GetPointer[ngfAPI.Size]("20m") + sfUpdated := &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: sfNsName.Name, + Namespace: sfNsName.Namespace, + }, + Spec: ngfAPI.SnippetsFilterSpec{ + Snippets: []ngfAPI.Snippet{ + { + Context: ngfAPI.NginxContextMain, + Value: "main snippet", + }, + { + Context: ngfAPI.NginxContextHTTP, + Value: "http snippet", + }, + }, + }, + } + It( + "handles upserts for a SnippetsFilter", func() { + processor.CaptureUpsertChange(sf) - cspKey = graph.PolicyKey{ - NsName: types.NamespacedName{Name: "csp", Namespace: "test"}, - GVK: schema.GroupVersionKind{ - Group: ngfAPI.GroupName, - Kind: kinds.ClientSettingsPolicy, - Version: "v1alpha1", - }, - } + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) - obs = &ngfAPI.ObservabilityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "obs", - Namespace: "test", - }, - Spec: ngfAPI.ObservabilityPolicySpec{ - TargetRefs: []v1alpha2.LocalPolicyTargetReference{ - { - Group: v1.GroupName, - Kind: kinds.HTTPRoute, - Name: "hr-1", + processedSf, exists := graph.SnippetsFilters[sfNsName] + Expect(exists).To(BeTrue()) + Expect(processedSf.Source).To(Equal(sf)) + Expect(processedSf.Valid).To(BeTrue()) }, - }, - Tracing: &ngfAPI.Tracing{ - Strategy: ngfAPI.TraceStrategyRatio, - }, - }, - } + ) + It( + "captures changes for a SnippetsFilter", func() { + processor.CaptureUpsertChange(sfUpdated) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) - obsUpdated = obs.DeepCopy() - obsUpdated.Spec.Tracing.Strategy = ngfAPI.TraceStrategyParent + processedSf, exists := graph.SnippetsFilters[sfNsName] + Expect(exists).To(BeTrue()) + Expect(processedSf.Source).To(Equal(sfUpdated)) + Expect(processedSf.Valid).To(BeTrue()) + }, + ) + It( + "handles deletes for a SnippetsFilter", func() { + processor.CaptureDeleteChange(sfUpdated, sfNsName) - obsKey = graph.PolicyKey{ - NsName: types.NamespacedName{Name: "obs", Namespace: "test"}, - GVK: schema.GroupVersionKind{ - Group: ngfAPI.GroupName, - Kind: kinds.ObservabilityPolicy, - Version: "v1alpha1", + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.SnippetsFilters).To(BeEmpty()) + }, + ) }, - } - }) - - /* - NOTE: When adding a new NGF policy to the change processor, - update the following tests to make sure that the change processor can track changes for multiple NGF - policies. - */ - - When("a policy is created that references a resource that is not in the last graph", func() { - It("reports no changes", func() { - processor.CaptureUpsertChange(csp) - processor.CaptureUpsertChange(obs) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - }) - When("the resource the policy references is created", func() { - It("populates the graph with the policy", func() { - processor.CaptureUpsertChange(gw) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(HaveKey(cspKey)) - Expect(graph.NGFPolicies[cspKey].Source).To(Equal(csp)) - Expect(graph.NGFPolicies).ToNot(HaveKey(obsKey)) - - processor.CaptureUpsertChange(route) - changed, graph = processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(HaveKey(obsKey)) - Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obs)) - }) - }) - When("the policy is updated", func() { - It("captures changes for a policy", func() { - processor.CaptureUpsertChange(cspUpdated) - processor.CaptureUpsertChange(obsUpdated) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(HaveKey(cspKey)) - Expect(graph.NGFPolicies[cspKey].Source).To(Equal(cspUpdated)) - Expect(graph.NGFPolicies).To(HaveKey(obsKey)) - Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obsUpdated)) - }) - }) - When("the policy is deleted", func() { - It("removes the policy from the graph", func() { - processor.CaptureDeleteChange(&ngfAPI.ClientSettingsPolicy{}, client.ObjectKeyFromObject(csp)) - processor.CaptureDeleteChange(&ngfAPI.ObservabilityPolicy{}, client.ObjectKeyFromObject(obs)) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(BeEmpty()) - }) - }) - }) - }) - Describe("Ensuring non-changing changes don't override previously changing changes", func() { - // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses - // -- this is done in 'Normal cases of processing changes' - - //nolint:lll - var ( - processor *state.ChangeProcessorImpl - gcNsName, gwNsName, hrNsName, hr2NsName, rgNsName, svcNsName, sliceNsName, secretNsName, cmNsName, btlsNsName, npNsName 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 - cm, cmUpdated, unrelatedCM *apiv1.ConfigMap - btls, btlsUpdated *v1alpha3.BackendTLSPolicy - np, npUpdated *ngfAPI.NginxProxy + ) + }, ) + Describe( + "Ensuring non-changing changes don't override previously changing changes", func() { + // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses + // -- this is done in 'Normal cases of processing changes' + + //nolint:lll + var ( + processor *state.ChangeProcessorImpl + gcNsName, gwNsName, hrNsName, hr2NsName, rgNsName, svcNsName, sliceNsName, secretNsName, cmNsName, btlsNsName, npNsName 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 + cm, cmUpdated, unrelatedCM *apiv1.ConfigMap + btls, btlsUpdated *v1alpha3.BackendTLSPolicy + np, npUpdated *ngfAPI.NginxProxy + ) - BeforeEach(OncePerOrdered, func() { - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: "test.controller", - GatewayClassName: "test-class", - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }) - - secretNsName = types.NamespacedName{Namespace: "test", Name: "tls-secret"} - secret = &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretNsName.Name, - Namespace: secretNsName.Namespace, - Generation: 1, - }, - Type: apiv1.SecretTypeTLS, - Data: map[string][]byte{ - apiv1.TLSCertKey: cert, - apiv1.TLSPrivateKeyKey: key, - }, - } - secretUpdated = secret.DeepCopy() - secretUpdated.Generation++ - barSecret = &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "bar-secret", - Namespace: "test", - Generation: 1, - }, - Type: apiv1.SecretTypeTLS, - Data: map[string][]byte{ - apiv1.TLSCertKey: cert, - apiv1.TLSPrivateKeyKey: key, - }, - } - barSecretUpdated = barSecret.DeepCopy() - barSecretUpdated.Generation++ - unrelatedSecret = &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "unrelated-tls-secret", - Namespace: "unrelated-ns", - Generation: 1, - }, - Type: apiv1.SecretTypeTLS, - Data: map[string][]byte{ - apiv1.TLSCertKey: cert, - apiv1.TLSPrivateKeyKey: key, - }, - } + BeforeEach( + OncePerOrdered, func() { + processor = state.NewChangeProcessorImpl( + state.ChangeProcessorConfig{ + GatewayCtlrName: "test.controller", + GatewayClassName: "test-class", + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }, + ) + + secretNsName = types.NamespacedName{Namespace: "test", Name: "tls-secret"} + secret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretNsName.Name, + Namespace: secretNsName.Namespace, + Generation: 1, + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } + secretUpdated = secret.DeepCopy() + secretUpdated.Generation++ + barSecret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar-secret", + Namespace: "test", + Generation: 1, + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } + barSecretUpdated = barSecret.DeepCopy() + barSecretUpdated.Generation++ + unrelatedSecret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unrelated-tls-secret", + Namespace: "unrelated-ns", + Generation: 1, + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } - gcNsName = types.NamespacedName{Name: "test-class"} + gcNsName = types.NamespacedName{Name: "test-class"} - gc = &v1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: gcNsName.Name, - }, - Spec: v1.GatewayClassSpec{ - ControllerName: "test.controller", - }, - } + gc = &v1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: gcNsName.Name, + }, + Spec: v1.GatewayClassSpec{ + ControllerName: "test.controller", + }, + } - gcUpdated = gc.DeepCopy() - gcUpdated.Generation++ + gcUpdated = gc.DeepCopy() + gcUpdated.Generation++ - gwNsName = types.NamespacedName{Namespace: "test", Name: "gw-1"} + gwNsName = types.NamespacedName{Namespace: "test", Name: "gw-1"} - gw1 = &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw-1", - Namespace: "test", - Generation: 1, - }, - Spec: v1.GatewaySpec{ - GatewayClassName: gcName, - Listeners: []v1.Listener{ - { - Name: httpListenerName, - Hostname: nil, - Port: 80, - Protocol: v1.HTTPProtocolType, - AllowedRoutes: &v1.AllowedRoutes{ - Namespaces: &v1.RouteNamespaces{ - From: helpers.GetPointer(v1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "test": "namespace", + gw1 = &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-1", + Namespace: "test", + Generation: 1, + }, + Spec: v1.GatewaySpec{ + GatewayClassName: gcName, + Listeners: []v1.Listener{ + { + Name: httpListenerName, + Hostname: nil, + Port: 80, + Protocol: v1.HTTPProtocolType, + AllowedRoutes: &v1.AllowedRoutes{ + Namespaces: &v1.RouteNamespaces{ + From: helpers.GetPointer(v1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "namespace", + }, + }, + }, }, }, - }, - }, - }, - { - Name: httpsListenerName, - Hostname: nil, - Port: 443, - Protocol: v1.HTTPSProtocolType, - TLS: &v1.GatewayTLSConfig{ - Mode: helpers.GetPointer(v1.TLSModeTerminate), - CertificateRefs: []v1.SecretObjectReference{ { - Kind: (*v1.Kind)(helpers.GetPointer("Secret")), - Name: v1.ObjectName(secret.Name), - Namespace: (*v1.Namespace)(&secret.Namespace), + Name: httpsListenerName, + Hostname: nil, + Port: 443, + Protocol: v1.HTTPSProtocolType, + TLS: &v1.GatewayTLSConfig{ + Mode: helpers.GetPointer(v1.TLSModeTerminate), + CertificateRefs: []v1.SecretObjectReference{ + { + Kind: (*v1.Kind)(helpers.GetPointer("Secret")), + Name: v1.ObjectName(secret.Name), + Namespace: (*v1.Namespace)(&secret.Namespace), + }, + }, + }, }, - }, - }, - }, - { - Name: "listener-500-1", - Hostname: nil, - Port: 500, - Protocol: v1.HTTPSProtocolType, - TLS: &v1.GatewayTLSConfig{ - Mode: helpers.GetPointer(v1.TLSModeTerminate), - CertificateRefs: []v1.SecretObjectReference{ { - Kind: (*v1.Kind)(helpers.GetPointer("Secret")), - Name: v1.ObjectName(barSecret.Name), - Namespace: (*v1.Namespace)(&barSecret.Namespace), + Name: "listener-500-1", + Hostname: nil, + Port: 500, + Protocol: v1.HTTPSProtocolType, + TLS: &v1.GatewayTLSConfig{ + Mode: helpers.GetPointer(v1.TLSModeTerminate), + CertificateRefs: []v1.SecretObjectReference{ + { + Kind: (*v1.Kind)(helpers.GetPointer("Secret")), + Name: v1.ObjectName(barSecret.Name), + Namespace: (*v1.Namespace)(&barSecret.Namespace), + }, + }, + }, }, }, }, - }, - }, - }, - } + } - gw1Updated = gw1.DeepCopy() - gw1Updated.Generation++ + gw1Updated = gw1.DeepCopy() + gw1Updated.Generation++ - gw2 = gw1.DeepCopy() - gw2.Name = "gw-2" + gw2 = gw1.DeepCopy() + gw2.Name = "gw-2" - testNamespace := v1.Namespace("test") - kindService := v1.Kind("Service") - fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) - barRef := createHTTPBackendRef(&kindService, "bar-svc", &testNamespace) + testNamespace := v1.Namespace("test") + kindService := v1.Kind("Service") + fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) + barRef := createHTTPBackendRef(&kindService, "bar-svc", &testNamespace) - hrNsName = types.NamespacedName{Namespace: "test", Name: "hr-1"} + hrNsName = types.NamespacedName{Namespace: "test", Name: "hr-1"} - hr1 = createRoute("hr-1", "gw-1", "foo.example.com", fooRef, barRef) + hr1 = createRoute("hr-1", "gw-1", "foo.example.com", fooRef, barRef) - hr1Updated = hr1.DeepCopy() - hr1Updated.Generation++ + hr1Updated = hr1.DeepCopy() + hr1Updated.Generation++ - hr2NsName = types.NamespacedName{Namespace: "test", Name: "hr-2"} + hr2NsName = types.NamespacedName{Namespace: "test", Name: "hr-2"} - hr2 = hr1.DeepCopy() - hr2.Name = hr2NsName.Name + 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"}, - }, - } + 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", + }, + } - 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", - }, - }, - } + sliceNsName = types.NamespacedName{Namespace: "test", Name: "slice"} + slice = &discoveryV1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: sliceNsName.Namespace, + Name: sliceNsName.Name, + Labels: map[string]string{index.KubernetesServiceNameLabel: svc.Name}, + }, + } + barSlice = &discoveryV1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "bar-slice", + Labels: map[string]string{index.KubernetesServiceNameLabel: "bar-svc"}, + }, + } + unrelatedSlice = &discoveryV1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "unrelated-slice", + Labels: map[string]string{index.KubernetesServiceNameLabel: "unrelated-svc"}, + }, + } + + testNs = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Labels: map[string]string{ + "test": "namespace", + }, + }, + } + ns = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns", + Labels: map[string]string{ + "test": "namespace", + }, + }, + } + barNs = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar-ns", + Labels: map[string]string{ + "test": "namespace", + }, + }, + } + unrelatedNS = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unrelated-ns", + Labels: map[string]string{ + "oranges": "bananas", + }, + }, + } - rgNsName = types.NamespacedName{Namespace: "test", Name: "rg-1"} + rgNsName = types.NamespacedName{Namespace: "test", Name: "rg-1"} - rg1 = &v1beta1.ReferenceGrant{ - ObjectMeta: metav1.ObjectMeta{ - Name: rgNsName.Name, - Namespace: rgNsName.Namespace, - }, - } + rg1 = &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Name: rgNsName.Name, + Namespace: rgNsName.Namespace, + }, + } - rg1Updated = rg1.DeepCopy() - rg1Updated.Generation++ + rg1Updated = rg1.DeepCopy() + rg1Updated.Generation++ - rg2 = rg1.DeepCopy() - rg2.Name = "rg-2" + rg2 = rg1.DeepCopy() + rg2.Name = "rg-2" - cmNsName = types.NamespacedName{Namespace: "test", Name: "cm-1"} - cm = &apiv1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: cmNsName.Name, - Namespace: cmNsName.Namespace, - }, - Data: map[string]string{ - "ca.crt": "value", - }, - } - cmUpdated = cm.DeepCopy() - cmUpdated.Data["ca.crt"] = "updated-value" - - unrelatedCM = &apiv1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "unrelated-cm", - Namespace: "unrelated-ns", - }, - Data: map[string]string{ - "ca.crt": "value", - }, - } - - btlsNsName = types.NamespacedName{Namespace: "test", Name: "btls-1"} - btls = &v1alpha3.BackendTLSPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: btlsNsName.Name, - Namespace: btlsNsName.Namespace, - Generation: 1, - }, - Spec: v1alpha3.BackendTLSPolicySpec{ - TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ - { - LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ - Kind: "Service", - Name: v1.ObjectName(svc.Name), + cmNsName = types.NamespacedName{Namespace: "test", Name: "cm-1"} + cm = &apiv1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: cmNsName.Name, + Namespace: cmNsName.Namespace, }, - }, + Data: map[string]string{ + "ca.crt": "value", + }, + } + cmUpdated = cm.DeepCopy() + cmUpdated.Data["ca.crt"] = "updated-value" + + unrelatedCM = &apiv1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unrelated-cm", + Namespace: "unrelated-ns", + }, + Data: map[string]string{ + "ca.crt": "value", + }, + } + + btlsNsName = types.NamespacedName{Namespace: "test", Name: "btls-1"} + btls = &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: btlsNsName.Name, + Namespace: btlsNsName.Namespace, + Generation: 1, + }, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: "Service", + Name: v1.ObjectName(svc.Name), + }, + }, + }, + Validation: v1alpha3.BackendTLSPolicyValidation{ + CACertificateRefs: []v1.LocalObjectReference{ + { + Name: v1.ObjectName(cm.Name), + }, + }, + }, + }, + } + btlsUpdated = btls.DeepCopy() + + npNsName = types.NamespacedName{Name: "np-1"} + np = &ngfAPI.NginxProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: npNsName.Name, + }, + Spec: ngfAPI.NginxProxySpec{ + Telemetry: &ngfAPI.Telemetry{ + ServiceName: helpers.GetPointer("my-svc"), + }, + }, + } + npUpdated = np.DeepCopy() }, - Validation: v1alpha3.BackendTLSPolicyValidation{ - CACertificateRefs: []v1.LocalObjectReference{ - { - Name: v1.ObjectName(cm.Name), + ) + // Changing change - a change that makes processor.Process() report changed + // Non-changing change - a change that doesn't do that + // Related resource - a K8s resource that is related to a configured Gateway API resource + // Unrelated resource - a K8s resource that is not related to a configured Gateway API resource + + // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses + // -- this is done in 'Normal cases of processing changes' + Describe( + "Multiple Gateway API resource changes", Ordered, func() { + It( + "should report changed after multiple Upserts", func() { + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(testNs) + processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(rg1) + processor.CaptureUpsertChange(btls) + processor.CaptureUpsertChange(cm) + processor.CaptureUpsertChange(np) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) }, - }, + ) + When( + "a upsert of updated resources is followed by an upsert of the same generation", func() { + It( + "should report changed", func() { + // these are changing changes + processor.CaptureUpsertChange(gcUpdated) + processor.CaptureUpsertChange(gw1Updated) + processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(rg1Updated) + processor.CaptureUpsertChange(btlsUpdated) + processor.CaptureUpsertChange(cmUpdated) + processor.CaptureUpsertChange(npUpdated) + + // there are non-changing changes + processor.CaptureUpsertChange(gcUpdated) + processor.CaptureUpsertChange(gw1Updated) + processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(rg1Updated) + processor.CaptureUpsertChange(btlsUpdated) + processor.CaptureUpsertChange(cmUpdated) + processor.CaptureUpsertChange(npUpdated) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + }, + ) + It( + "should report changed after upserting new resources", func() { + // we can't have a second GatewayClass, so we don't add it + processor.CaptureUpsertChange(gw2) + processor.CaptureUpsertChange(hr2) + processor.CaptureUpsertChange(rg2) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + When( + "resources are deleted followed by upserts with the same generations", func() { + It( + "should report changed", func() { + // these are changing changes + processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) + processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) + processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) + processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) + processor.CaptureDeleteChange(&v1alpha3.BackendTLSPolicy{}, btlsNsName) + processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) + processor.CaptureDeleteChange(&ngfAPI.NginxProxy{}, npNsName) + + // these are non-changing changes + processor.CaptureUpsertChange(gw2) + processor.CaptureUpsertChange(hr2) + processor.CaptureUpsertChange(rg2) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + }, + ) + It( + "should report changed after deleting resources", func() { + processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) }, - }, - } - btlsUpdated = btls.DeepCopy() + ) + Describe( + "Deleting non-existing Gateway API resource", func() { + It( + "should not report changed after deleting non-existing", func() { + processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) + processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) + processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) + processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) + processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }, + ) + }, + ) + Describe( + "Multiple Kubernetes API resource changes", Ordered, func() { + BeforeAll( + func() { + // Set up graph + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(testNs) + processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(secret) + processor.CaptureUpsertChange(barSecret) + processor.CaptureUpsertChange(cm) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) - npNsName = types.NamespacedName{Name: "np-1"} - np = &ngfAPI.NginxProxy{ - ObjectMeta: metav1.ObjectMeta{ - Name: npNsName.Name, - }, - Spec: ngfAPI.NginxProxySpec{ - Telemetry: &ngfAPI.Telemetry{ - ServiceName: helpers.GetPointer("my-svc"), + It( + "should report changed after multiple Upserts of related resources", func() { + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(ns) + processor.CaptureUpsertChange(secretUpdated) + processor.CaptureUpsertChange(cmUpdated) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + It( + "should report not changed after multiple Upserts of unrelated resources", func() { + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }, + ) + When( + "upserts of related resources are followed by upserts of unrelated resources", func() { + It( + "should report changed", func() { + // these are changing changes + processor.CaptureUpsertChange(barSvc) + processor.CaptureUpsertChange(barSlice) + processor.CaptureUpsertChange(barNs) + processor.CaptureUpsertChange(barSecretUpdated) + processor.CaptureUpsertChange(cmUpdated) + + // there are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + }, + ) + When( + "deletes of related resources are followed by upserts of unrelated resources", func() { + It( + "should report changed", func() { + // these are changing changes + processor.CaptureDeleteChange(&apiv1.Service{}, svcNsName) + processor.CaptureDeleteChange(&discoveryV1.EndpointSlice{}, sliceNsName) + processor.CaptureDeleteChange( + &apiv1.Namespace{}, + types.NamespacedName{Name: "ns"}, + ) + processor.CaptureDeleteChange(&apiv1.Secret{}, secretNsName) + processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) + + // these are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + }, + ) }, - }, - } - npUpdated = np.DeepCopy() - }) - // Changing change - a change that makes processor.Process() report changed - // Non-changing change - a change that doesn't do that - // Related resource - a K8s resource that is related to a configured Gateway API resource - // Unrelated resource - a K8s resource that is not related to a configured Gateway API resource - - // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses - // -- this is done in 'Normal cases of processing changes' - Describe("Multiple Gateway API resource changes", Ordered, func() { - It("should report changed after multiple Upserts", func() { - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(testNs) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(rg1) - processor.CaptureUpsertChange(btls) - processor.CaptureUpsertChange(cm) - processor.CaptureUpsertChange(np) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - When("a upsert of updated resources is followed by an upsert of the same generation", func() { - It("should report changed", func() { - // these are changing changes - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(rg1Updated) - processor.CaptureUpsertChange(btlsUpdated) - processor.CaptureUpsertChange(cmUpdated) - processor.CaptureUpsertChange(npUpdated) - - // there are non-changing changes - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(rg1Updated) - processor.CaptureUpsertChange(btlsUpdated) - processor.CaptureUpsertChange(cmUpdated) - processor.CaptureUpsertChange(npUpdated) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - It("should report changed after upserting new resources", func() { - // we can't have a second GatewayClass, so we don't add it - processor.CaptureUpsertChange(gw2) - processor.CaptureUpsertChange(hr2) - processor.CaptureUpsertChange(rg2) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - When("resources are deleted followed by upserts with the same generations", func() { - It("should report changed", func() { - // these are changing changes - processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) - processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) - processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) - processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) - processor.CaptureDeleteChange(&v1alpha3.BackendTLSPolicy{}, btlsNsName) - processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) - processor.CaptureDeleteChange(&ngfAPI.NginxProxy{}, npNsName) - - // these are non-changing changes - processor.CaptureUpsertChange(gw2) - processor.CaptureUpsertChange(hr2) - processor.CaptureUpsertChange(rg2) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - It("should report changed after deleting resources", func() { - processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - Describe("Deleting non-existing Gateway API resource", func() { - It("should not report changed after deleting non-existing", func() { - processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) - processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) - processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) - processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) - processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - }) - Describe("Multiple Kubernetes API resource changes", Ordered, func() { - BeforeAll(func() { - // Set up graph - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(testNs) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(secret) - processor.CaptureUpsertChange(barSecret) - processor.CaptureUpsertChange(cm) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - - It("should report changed after multiple Upserts of related resources", func() { - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(ns) - processor.CaptureUpsertChange(secretUpdated) - processor.CaptureUpsertChange(cmUpdated) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - It("should report not changed after multiple Upserts of unrelated resources", func() { - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - When("upserts of related resources are followed by upserts of unrelated resources", func() { - It("should report changed", func() { - // these are changing changes - processor.CaptureUpsertChange(barSvc) - processor.CaptureUpsertChange(barSlice) - processor.CaptureUpsertChange(barNs) - processor.CaptureUpsertChange(barSecretUpdated) - processor.CaptureUpsertChange(cmUpdated) - - // there are non-changing changes - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - When("deletes of related resources are followed by upserts of unrelated resources", func() { - It("should report changed", func() { - // these are changing changes - processor.CaptureDeleteChange(&apiv1.Service{}, svcNsName) - processor.CaptureDeleteChange(&discoveryV1.EndpointSlice{}, sliceNsName) - processor.CaptureDeleteChange(&apiv1.Namespace{}, types.NamespacedName{Name: "ns"}) - processor.CaptureDeleteChange(&apiv1.Secret{}, secretNsName) - processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) - - // these are non-changing changes - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) - }) - Describe("Multiple Kubernetes API and Gateway API resource changes", Ordered, func() { - It("should report changed after multiple Upserts of new and related resources", func() { - // new Gateway API resources - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(testNs) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(rg1) - processor.CaptureUpsertChange(btls) - - // related Kubernetes API resources - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(ns) - processor.CaptureUpsertChange(secret) - processor.CaptureUpsertChange(cm) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - It("should report not changed after multiple Upserts of unrelated resources", func() { - // unrelated Kubernetes API resources - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }) - It("should report changed after upserting changed resources followed by upserting unrelated resources", - func() { - // these are changing changes - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(rg1Updated) - processor.CaptureUpsertChange(btlsUpdated) - - // these are non-changing changes - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - }) - }) - Describe("Edge cases with panic", func() { - var processor state.ChangeProcessor - - BeforeEach(func() { - processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ - GatewayCtlrName: "test.controller", - GatewayClassName: "my-class", - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }) - }) - - DescribeTable("CaptureUpsertChange must panic", - func(obj client.Object) { - process := func() { - processor.CaptureUpsertChange(obj) - } - Expect(process).Should(Panic()) + ) + Describe( + "Multiple Kubernetes API and Gateway API resource changes", Ordered, func() { + It( + "should report changed after multiple Upserts of new and related resources", func() { + // new Gateway API resources + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(testNs) + processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(rg1) + processor.CaptureUpsertChange(btls) + + // related Kubernetes API resources + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(ns) + processor.CaptureUpsertChange(secret) + processor.CaptureUpsertChange(cm) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + It( + "should report not changed after multiple Upserts of unrelated resources", func() { + // unrelated Kubernetes API resources + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }, + ) + It( + "should report changed after upserting changed resources followed by upserting unrelated resources", + func() { + // these are changing changes + processor.CaptureUpsertChange(gcUpdated) + processor.CaptureUpsertChange(gw1Updated) + processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(rg1Updated) + processor.CaptureUpsertChange(btlsUpdated) + + // these are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) + }, + ) }, - Entry( - "an unsupported resource", - &v1alpha2.TCPRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "tcp"}}, - ), - Entry( - "nil resource", - nil, - ), ) + Describe( + "Edge cases with panic", func() { + var processor state.ChangeProcessor + + BeforeEach( + func() { + processor = state.NewChangeProcessorImpl( + state.ChangeProcessorConfig{ + GatewayCtlrName: "test.controller", + GatewayClassName: "my-class", + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }, + ) + }, + ) - DescribeTable( - "CaptureDeleteChange must panic", - func(resourceType ngftypes.ObjectType, nsname types.NamespacedName) { - process := func() { - processor.CaptureDeleteChange(resourceType, nsname) - } - Expect(process).Should(Panic()) + DescribeTable( + "CaptureUpsertChange must panic", + func(obj client.Object) { + process := func() { + processor.CaptureUpsertChange(obj) + } + Expect(process).Should(Panic()) + }, + Entry( + "an unsupported resource", + &v1alpha2.TCPRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "tcp"}}, + ), + Entry( + "nil resource", + nil, + ), + ) + + DescribeTable( + "CaptureDeleteChange must panic", + func(resourceType ngftypes.ObjectType, nsname types.NamespacedName) { + process := func() { + processor.CaptureDeleteChange(resourceType, nsname) + } + Expect(process).Should(Panic()) + }, + Entry( + "an unsupported resource", + &v1alpha2.TCPRoute{}, + types.NamespacedName{Namespace: "test", Name: "tcp"}, + ), + Entry( + "nil resource type", + nil, + types.NamespacedName{Namespace: "test", Name: "resource"}, + ), + ) }, - 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/conditions/conditions.go b/internal/mode/static/state/conditions/conditions.go index 0cb959db6a..c97f9efa2d 100644 --- a/internal/mode/static/state/conditions/conditions.go +++ b/internal/mode/static/state/conditions/conditions.go @@ -49,6 +49,10 @@ const ( // Used with ResolvedRefs (false). RouteReasonInvalidIPFamily v1.RouteConditionReason = "InvalidServiceIPFamily" + // RouteReasonInvalidFilter is used when an extension ref filter referenced by a Route cannot be resolved, or is + // invalid. Used with ResolvedRefs (false). + RouteReasonInvalidFilter v1.RouteConditionReason = "InvalidFilter" + // GatewayReasonGatewayConflict indicates there are multiple Gateway resources to choose from, // and we ignored the resource in question and picked another Gateway as the winner. // This reason is used with GatewayConditionAccepted (false). @@ -311,6 +315,17 @@ func NewRouteInvalidIPFamily(msg string) conditions.Condition { } } +// NewRouteResolvedRefsInvalidFilter returns a Condition that indicates that the Route has a filter that +// cannot be resolved or is invalid. +func NewRouteResolvedRefsInvalidFilter(msg string) conditions.Condition { + return conditions.Condition{ + Type: string(v1.RouteConditionResolvedRefs), + Status: metav1.ConditionFalse, + Reason: string(RouteReasonInvalidFilter), + Message: msg, + } +} + // NewDefaultListenerConditions returns the default Conditions that must be present in the status of a Listener. func NewDefaultListenerConditions() []conditions.Condition { return []conditions.Condition{ diff --git a/internal/mode/static/state/dataplane/configuration.go b/internal/mode/static/state/dataplane/configuration.go index 5c6721f6ed..60b97bf8d8 100644 --- a/internal/mode/static/state/dataplane/configuration.go +++ b/internal/mode/static/state/dataplane/configuration.go @@ -15,7 +15,7 @@ import ( ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" - policies "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/policies" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/policies" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/graph" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/resolver" ) @@ -63,6 +63,7 @@ func BuildConfiguration( CertBundles: certBundles, Telemetry: telemetry, BaseHTTPConfig: baseHTTPConfig, + MainSnippets: buildSnippetsForContext(g.SnippetsFilters, ngfAPI.NginxContextMain), } return config @@ -104,25 +105,31 @@ func buildPassthroughServers(g *graph.Graph) []Layer4VirtualServer { if l.Source.Hostname != nil && h == string(*l.Source.Hostname) { foundRouteMatchingListenerHostname = true } - passthroughServersMap[key] = append(passthroughServersMap[key], Layer4VirtualServer{ - Hostname: h, - UpstreamName: r.Spec.BackendRef.ServicePortReference(), - Port: int32(l.Source.Port), - }) + passthroughServersMap[key] = append( + passthroughServersMap[key], Layer4VirtualServer{ + Hostname: h, + UpstreamName: r.Spec.BackendRef.ServicePortReference(), + Port: int32(l.Source.Port), + }, + ) } } if !foundRouteMatchingListenerHostname { if l.Source.Hostname != nil { - listenerPassthroughServers = append(listenerPassthroughServers, Layer4VirtualServer{ - Hostname: string(*l.Source.Hostname), - IsDefault: true, - Port: int32(l.Source.Port), - }) + listenerPassthroughServers = append( + listenerPassthroughServers, Layer4VirtualServer{ + Hostname: string(*l.Source.Hostname), + IsDefault: true, + Port: int32(l.Source.Port), + }, + ) } else { - listenerPassthroughServers = append(listenerPassthroughServers, Layer4VirtualServer{ - Hostname: "", - Port: int32(l.Source.Port), - }) + listenerPassthroughServers = append( + listenerPassthroughServers, Layer4VirtualServer{ + Hostname: "", + Port: int32(l.Source.Port), + }, + ) } } } @@ -256,7 +263,7 @@ func buildCertBundles( if err != nil { data = cm.CACert } - bundles[id] = CertBundle(data) + bundles[id] = data } } } @@ -310,12 +317,14 @@ func newBackendGroup(refs []graph.BackendRef, sourceNsName types.NamespacedName, } for _, ref := range refs { - backends = append(backends, Backend{ - UpstreamName: ref.ServicePortReference(), - Weight: ref.Weight, - Valid: ref.Valid, - VerifyTLS: convertBackendTLS(ref.BackendTLSPolicy), - }) + backends = append( + backends, Backend{ + UpstreamName: ref.ServicePortReference(), + Weight: ref.Weight, + Valid: ref.Valid, + VerifyTLS: convertBackendTLS(ref.BackendTLSPolicy), + }, + ) } return BackendGroup{ @@ -477,8 +486,8 @@ func (hpr *hostPathRules) upsertRoute( } var filters HTTPFilters - if rule.ValidFilters { - filters = createHTTPFilters(rule.Filters) + if rule.Filters.Valid { + filters = createHTTPFilters(rule.Filters.Filters) } else { filters = HTTPFilters{ InvalidFilter: &InvalidHTTPFilter{}, @@ -507,12 +516,14 @@ func (hpr *hostPathRules) upsertRoute( hostRule.GRPC = GRPC hostRule.Policies = append(hostRule.Policies, pols...) - hostRule.MatchRules = append(hostRule.MatchRules, MatchRule{ - Source: objectSrc, - BackendGroup: newBackendGroup(rule.BackendRefs, routeNsName, i), - Filters: filters, - Match: convertMatch(m), - }) + hostRule.MatchRules = append( + hostRule.MatchRules, MatchRule{ + Source: objectSrc, + BackendGroup: newBackendGroup(rule.BackendRefs, routeNsName, i), + Filters: filters, + Match: convertMatch(m), + }, + ) hpr.rulesPerHost[h][key] = hostRule } @@ -548,13 +559,15 @@ func (hpr *hostPathRules) buildServers() []VirtualServer { } // We sort the path rules so the order is preserved after reconfiguration. - sort.Slice(s.PathRules, func(i, j int) bool { - if s.PathRules[i].Path != s.PathRules[j].Path { - return s.PathRules[i].Path < s.PathRules[j].Path - } + sort.Slice( + s.PathRules, func(i, j int) bool { + if s.PathRules[i].Path != s.PathRules[j].Path { + return s.PathRules[i].Path < s.PathRules[j].Path + } - return s.PathRules[i].PathType < s.PathRules[j].PathType - }) + return s.PathRules[i].PathType < s.PathRules[j].PathType + }, + ) servers = append(servers, s) } @@ -581,16 +594,20 @@ func (hpr *hostPathRules) buildServers() []VirtualServer { // if any listeners exist, we need to generate a default server block. if hpr.listenersExist { - servers = append(servers, VirtualServer{ - IsDefault: true, - Port: hpr.port, - }) + servers = append( + servers, VirtualServer{ + IsDefault: true, + Port: hpr.port, + }, + ) } // We sort the servers so the order is preserved after reconfiguration. - sort.Slice(servers, func(i, j int) bool { - return servers[i].Hostname < servers[j].Hostname - }) + sort.Slice( + servers, func(i, j int) bool { + return servers[i].Hostname < servers[j].Hostname + }, + ) return servers } @@ -628,7 +645,7 @@ func buildUpstreams( } for _, rule := range route.Spec.Rules { - if !rule.ValidMatches || !rule.ValidFilters { + if !rule.ValidMatches || !rule.Filters.Valid { // don't generate upstreams for rules that have invalid matches or filters continue } @@ -699,33 +716,64 @@ func getPath(path *v1.HTTPPathMatch) string { return *path.Value } -func createHTTPFilters(filters []v1.HTTPRouteFilter) HTTPFilters { +func createHTTPFilters(filters []graph.Filter) HTTPFilters { var result HTTPFilters for _, f := range filters { - switch f.Type { - case v1.HTTPRouteFilterRequestRedirect: + switch f.FilterType { + case graph.FilterRequestRedirect: if result.RequestRedirect == nil { // using the first filter result.RequestRedirect = convertHTTPRequestRedirectFilter(f.RequestRedirect) } - case v1.HTTPRouteFilterURLRewrite: + case graph.FilterURLRewrite: if result.RequestURLRewrite == nil { // using the first filter result.RequestURLRewrite = convertHTTPURLRewriteFilter(f.URLRewrite) } - case v1.HTTPRouteFilterRequestHeaderModifier: + case graph.FilterRequestHeaderModifier: if result.RequestHeaderModifiers == nil { // using the first filter result.RequestHeaderModifiers = convertHTTPHeaderFilter(f.RequestHeaderModifier) } - case v1.HTTPRouteFilterResponseHeaderModifier: + case graph.FilterResponseHeaderModifier: if result.ResponseHeaderModifiers == nil { // using the first filter result.ResponseHeaderModifiers = convertHTTPHeaderFilter(f.ResponseHeaderModifier) } + case graph.FilterExtensionRef: + if f.ResolvedExtensionRef != nil && f.ResolvedExtensionRef.SnippetsFilter != nil { + result.SnippetsFilters = append( + result.SnippetsFilters, + convertSnippetsFilter(f.ResolvedExtensionRef.SnippetsFilter), + ) + } } } + + return result +} + +func convertSnippetsFilter(filter *graph.SnippetsFilter) SnippetsFilter { + result := SnippetsFilter{} + + if snippet, ok := filter.Snippets[ngfAPI.NginxContextHTTPServer]; ok { + result.ServerSnippet = &Snippet{ + Name: createSnippetName(ngfAPI.NginxContextHTTPServer, client.ObjectKeyFromObject(filter.Source)), + Contents: snippet, + } + } + + if snippet, ok := filter.Snippets[ngfAPI.NginxContextHTTPServerLocation]; ok { + result.LocationSnippet = &Snippet{ + Name: createSnippetName( + ngfAPI.NginxContextHTTPServerLocation, + client.ObjectKeyFromObject(filter.Source), + ), + Contents: snippet, + } + } + return result } @@ -834,6 +882,7 @@ func buildBaseHTTPConfig(g *graph.Graph) BaseHTTPConfig { // HTTP2 should be enabled by default HTTP2: true, IPFamily: Dual, + Snippets: buildSnippetsForContext(g.SnippetsFilters, ngfAPI.NginxContextHTTP), } if g.NginxProxy == nil || !g.NginxProxy.Valid { return baseConfig @@ -876,6 +925,48 @@ func buildBaseHTTPConfig(g *graph.Graph) BaseHTTPConfig { return baseConfig } +func createSnippetName(nc ngfAPI.NginxContext, nsname types.NamespacedName) string { + return fmt.Sprintf( + "SnippetsFilter_%s_%s_%s", + nc, + nsname.Namespace, + nsname.Name, + ) +} + +func buildSnippetsForContext( + snippetFilters map[types.NamespacedName]*graph.SnippetsFilter, + nc ngfAPI.NginxContext, +) []Snippet { + if len(snippetFilters) == 0 { + return nil + } + + snippetsForContext := make([]Snippet, 0) + + for _, filter := range snippetFilters { + if !filter.Valid || !filter.Referenced { + continue + } + + snippetValue, ok := filter.Snippets[nc] + + if !ok { + continue + } + + snippetsForContext = append( + snippetsForContext, + Snippet{ + Name: createSnippetName(nc, client.ObjectKeyFromObject(filter.Source)), + Contents: snippetValue, + }, + ) + } + + return snippetsForContext +} + func buildPolicies(graphPolicies []*graph.Policy) []policies.Policy { if len(graphPolicies) == 0 { return nil diff --git a/internal/mode/static/state/dataplane/configuration_test.go b/internal/mode/static/state/dataplane/configuration_test.go index 07b6d40946..cbc6b9e5d2 100644 --- a/internal/mode/static/state/dataplane/configuration_test.go +++ b/internal/mode/static/state/dataplane/configuration_test.go @@ -21,8 +21,9 @@ import ( ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/kinds" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/policies" - policiesfakes "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/policies/policiesfakes" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/policies/policiesfakes" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/graph" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/resolver" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/resolver/resolverfakes" @@ -160,9 +161,12 @@ func TestBuildConfiguration(t *testing.T) { } } - addFilters := func(hr *graph.L7Route, filters []v1.HTTPRouteFilter) { + addFilters := func(hr *graph.L7Route, filters []graph.Filter) { for i := range hr.Spec.Rules { - hr.Spec.Rules[i].Filters = filters + hr.Spec.Rules[i].Filters = graph.RouteRuleFilters{ + Filters: filters, + Valid: *hr.Spec.Rules[i].Matches[0].Path.Value != invalidFiltersPath, + } } } @@ -217,10 +221,12 @@ func TestBuildConfiguration(t *testing.T) { } rules[i] = graph.RouteRule{ - ValidMatches: validMatches, - ValidFilters: validFilters, + Matches: m, + Filters: graph.RouteRuleFilters{ + Valid: validFilters, + }, BackendRefs: createBackendRefs(validRule), - Matches: m, + ValidMatches: validMatches, } } @@ -259,15 +265,17 @@ func TestBuildConfiguration(t *testing.T) { for idx, r := range route.Spec.Rules { var backends []Backend - if r.ValidFilters && r.ValidMatches { + if r.Filters.Valid && r.ValidMatches { backends = []Backend{expValidBackend} } - groups = append(groups, BackendGroup{ - Backends: backends, - Source: client.ObjectKeyFromObject(route.Source), - RuleIdx: idx, - }) + groups = append( + groups, BackendGroup{ + Backends: backends, + Source: client.ObjectKeyFromObject(route.Source), + RuleIdx: idx, + }, + ) } return groups @@ -326,16 +334,76 @@ func TestBuildConfiguration(t *testing.T) { pathAndType{path: "/", pathType: prefix}, pathAndType{path: invalidFiltersPath, pathType: prefix}, ) - redirect := v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterRequestRedirect, + sf1 := &graph.SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sf", + Namespace: "test", + }, + }, + Valid: true, + Referenced: true, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTPServerLocation: "location snippet", + ngfAPI.NginxContextHTTPServer: "server snippet", + ngfAPI.NginxContextMain: "main snippet", + ngfAPI.NginxContextHTTP: "http snippet", + }, + } + + sfNotReferenced := &graph.SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sf-not-referenced", + Namespace: "test", + }, + }, + Valid: true, + Referenced: false, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextMain: "main snippet no ref", + ngfAPI.NginxContextHTTP: "http snippet no ref", + }, + } + + redirect := graph.Filter{ + FilterType: graph.FilterRequestRedirect, RequestRedirect: &v1.HTTPRequestRedirectFilter{ Hostname: (*v1.PreciseHostname)(helpers.GetPointer("foo.example.com")), }, } - addFilters(routeHR5, []v1.HTTPRouteFilter{redirect}) + extRefFilter := graph.Filter{ + FilterType: graph.FilterExtensionRef, + ExtensionRef: &v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: "sf", + }, + ResolvedExtensionRef: &graph.ExtensionRefFilter{ + Valid: true, + SnippetsFilter: sf1, + }, + } + addFilters(routeHR5, []graph.Filter{redirect, extRefFilter}) expRedirect := HTTPRequestRedirectFilter{ Hostname: helpers.GetPointer("foo.example.com"), } + expExtRefFilter := SnippetsFilter{ + LocationSnippet: &Snippet{ + Name: createSnippetName( + ngfAPI.NginxContextHTTPServerLocation, + client.ObjectKeyFromObject(extRefFilter.ResolvedExtensionRef.SnippetsFilter.Source), + ), + Contents: "location snippet", + }, + ServerSnippet: &Snippet{ + Name: createSnippetName( + ngfAPI.NginxContextHTTPServer, + client.ObjectKeyFromObject(extRefFilter.ResolvedExtensionRef.SnippetsFilter.Source), + ), + Contents: "server snippet", + }, + } hr6, expHR6Groups, routeHR6 := createTestResources( "hr-6", @@ -457,15 +525,19 @@ func TestBuildConfiguration(t *testing.T) { Valid: true, } - TR1Key := graph.L4RouteKey{NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "secure-app", - }} + TR1Key := graph.L4RouteKey{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "secure-app", + }, + } - TR2Key := graph.L4RouteKey{NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "secure-app2", - }} + TR2Key := graph.L4RouteKey{ + NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "secure-app2", + }, + } httpsHR7, expHTTPSHR7Groups, httpsRouteHR7 := createTestResources( "https-hr-7", @@ -801,1458 +873,1676 @@ func TestBuildConfiguration(t *testing.T) { }{ { graph: getNormalGraph(), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = []VirtualServer{} - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.HTTPServers = []VirtualServer{} + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }, + ), msg: "no listeners and routes", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - }) - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }), + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + }, + ) + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }, + ), msg: "http listener with no routes", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1Invalid): routeHR1Invalid, + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1Invalid): routeHR1Invalid, + }, + }, + { + Name: "listener-443-1", + Source: listener443, // nil hostname + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR1Invalid): httpsRouteHR1Invalid, + }, + ResolvedSecret: &secret1NsName, + }, + }..., + ) + g.Routes[graph.CreateRouteKey(hr1Invalid)] = routeHR1Invalid + g.ReferencedSecrets[secret1NsName] = secret1 + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.HTTPServers = []VirtualServer{ + { + IsDefault: true, + Port: 80, }, - }, - { - Name: "listener-443-1", - Source: listener443, // nil hostname - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR1Invalid): httpsRouteHR1Invalid, + } + conf.SSLServers = append( + conf.SSLServers, VirtualServer{ + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, }, - ResolvedSecret: &secret1NsName, - }, - }...) - g.Routes[graph.CreateRouteKey(hr1Invalid)] = routeHR1Invalid - g.ReferencedSecrets[secret1NsName] = secret1 - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = []VirtualServer{{ - IsDefault: true, - Port: 80, - }} - conf.SSLServers = append(conf.SSLServers, VirtualServer{ - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }) - return conf - }), + ) + return conf + }, + ), msg: "http and https listeners with no valid routes", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-443-1", - Source: listener443, // nil hostname - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - ResolvedSecret: &secret1NsName, - }, - { - Name: "listener-443-with-hostname", - Source: listener443WithHostname, // non-nil hostname - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - ResolvedSecret: &secret2NsName, - }, - }...) - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - secret2NsName: secret2, - } - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = []VirtualServer{} - conf.SSLServers = append(conf.SSLServers, []VirtualServer{ - { - Hostname: string(hostname), - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }...) - conf.SSLKeyPairs["ssl_keypair_test_secret-2"] = SSLKeyPair{ - Cert: []byte("cert-2"), - Key: []byte("privateKey-2"), - } - return conf - }), + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-443-1", + Source: listener443, // nil hostname + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-with-hostname", + Source: listener443WithHostname, // non-nil hostname + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + ResolvedSecret: &secret2NsName, + }, + }..., + ) + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + secret2NsName: secret2, + } + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.HTTPServers = []VirtualServer{} + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ + { + Hostname: string(hostname), + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }..., + ) + conf.SSLKeyPairs["ssl_keypair_test_secret-2"] = SSLKeyPair{ + Cert: []byte("cert-2"), + Key: []byte("privateKey-2"), + } + return conf + }, + ), msg: "https listeners with no routes", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "invalid-listener", - Source: invalidListener, - Valid: false, - ResolvedSecret: &secret1NsName, - }) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR1): httpsRouteHR1, - graph.CreateRouteKey(httpsHR2): httpsRouteHR2, - } - g.ReferencedSecrets[secret1NsName] = secret1 - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = []VirtualServer{} - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }), + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "invalid-listener", + Source: invalidListener, + Valid: false, + ResolvedSecret: &secret1NsName, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR1): httpsRouteHR1, + graph.CreateRouteKey(httpsHR2): httpsRouteHR2, + } + g.ReferencedSecrets[secret1NsName] = secret1 + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.HTTPServers = []VirtualServer{} + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }, + ), msg: "invalid https listener with resolved secret", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): routeHR1, + graph.CreateRouteKey(hr2): routeHR2, + }, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr1): routeHR1, graph.CreateRouteKey(hr2): routeHR2, - }, - }) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): routeHR1, - graph.CreateRouteKey(hr2): routeHR2, - } - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append(conf.HTTPServers, []VirtualServer{ - { - Hostname: "bar.example.com", - PathRules: []PathRule{ + } + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, []VirtualServer{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Hostname: "bar.example.com", + PathRules: []PathRule{ { - BackendGroup: expHR2Groups[0], - Source: &hr2.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR2Groups[0], + Source: &hr2.ObjectMeta, + }, + }, }, }, + Port: 80, }, - }, - Port: 80, - }, - { - Hostname: "foo.example.com", - PathRules: []PathRule{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Hostname: "foo.example.com", + PathRules: []PathRule{ { - BackendGroup: expHR1Groups[0], - Source: &hr1.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR1Groups[0], + Source: &hr1.ObjectMeta, + }, + }, }, }, + Port: 80, }, - }, - Port: 80, - }, - }...) - conf.SSLServers = []VirtualServer{} - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHR1Groups[0], expHR2Groups[0]} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - - return conf - }), + }..., + ) + conf.SSLServers = []VirtualServer{} + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHR1Groups[0], expHR2Groups[0]} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + + return conf + }, + ), msg: "one http listener with two routes for different hostnames", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(gr): routeGR, - }, - }) - g.Routes[graph.CreateRouteKey(gr)] = routeGR - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append(conf.HTTPServers, VirtualServer{ - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - GRPC: true, - MatchRules: []MatchRule{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(gr): routeGR, + }, + }, + ) + g.Routes[graph.CreateRouteKey(gr)] = routeGR + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, VirtualServer{ + Hostname: "foo.example.com", + PathRules: []PathRule{ { - BackendGroup: expGRGroups[0], - Source: &gr.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + GRPC: true, + MatchRules: []MatchRule{ + { + BackendGroup: expGRGroups[0], + Source: &gr.ObjectMeta, + }, + }, }, }, + Port: 80, }, - }, - Port: 80, + ) + conf.SSLServers = []VirtualServer{} + conf.Upstreams = append(conf.Upstreams, fooUpstream) + conf.BackendGroups = []BackendGroup{expGRGroups[0]} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf }, - ) - conf.SSLServers = []VirtualServer{} - conf.Upstreams = append(conf.Upstreams, fooUpstream) - conf.BackendGroups = []BackendGroup{expGRGroups[0]} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }), + ), msg: "one http listener with one grpc route", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR1): httpsRouteHR1, - graph.CreateRouteKey(httpsHR2): httpsRouteHR2, - }, - ResolvedSecret: &secret1NsName, - }, - { - Name: "listener-443-with-hostname", - Source: listener443WithHostname, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - }, - ResolvedSecret: &secret2NsName, - }, - }...) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): httpsRouteHR1, - graph.CreateRouteKey(hr2): httpsRouteHR2, - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - secret2NsName: secret2, - } - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = []VirtualServer{} - conf.SSLServers = append(conf.SSLServers, []VirtualServer{ - { - Hostname: "bar.example.com", - PathRules: []PathRule{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR1): httpsRouteHR1, + graph.CreateRouteKey(httpsHR2): httpsRouteHR2, + }, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-with-hostname", + Source: listener443WithHostname, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, + }, + ResolvedSecret: &secret2NsName, + }, + }..., + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): httpsRouteHR1, + graph.CreateRouteKey(hr2): httpsRouteHR2, + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + secret2NsName: secret2, + } + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.HTTPServers = []VirtualServer{} + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Hostname: "bar.example.com", + PathRules: []PathRule{ { - BackendGroup: expHTTPSHR2Groups[0], - Source: &httpsHR2.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR2Groups[0], + Source: &httpsHR2.ObjectMeta, + }, + }, }, }, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, }, - }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - { - Hostname: "example.com", - PathRules: []PathRule{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Hostname: "example.com", + PathRules: []PathRule{ { - BackendGroup: expHTTPSHR5Groups[0], - Source: &httpsHR5.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR5Groups[0], + Source: &httpsHR5.ObjectMeta, + }, + }, }, }, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, + Port: 443, }, - }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, - Port: 443, - }, - { - Hostname: "foo.example.com", - PathRules: []PathRule{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Hostname: "foo.example.com", + PathRules: []PathRule{ { - BackendGroup: expHTTPSHR1Groups[0], - Source: &httpsHR1.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR1Groups[0], + Source: &httpsHR1.ObjectMeta, + }, + }, }, }, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }..., + ) + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{ + expHTTPSHR1Groups[0], + expHTTPSHR2Groups[0], + expHTTPSHR5Groups[0], + } + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{ + "ssl_keypair_test_secret-1": { + Cert: []byte("cert-1"), + Key: []byte("privateKey-1"), }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }...) - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHTTPSHR1Groups[0], expHTTPSHR2Groups[0], expHTTPSHR5Groups[0]} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{ - "ssl_keypair_test_secret-1": { - Cert: []byte("cert-1"), - Key: []byte("privateKey-1"), - }, - "ssl_keypair_test_secret-2": { - Cert: []byte("cert-2"), - Key: []byte("privateKey-2"), - }, - } - return conf - }), + "ssl_keypair_test_secret-2": { + Cert: []byte("cert-2"), + Key: []byte("privateKey-2"), + }, + } + return conf + }, + ), msg: "two https listeners each with routes for different hostnames", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr3): routeHR3, - graph.CreateRouteKey(hr4): routeHR4, - }, - }, - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR3): httpsRouteHR3, - graph.CreateRouteKey(httpsHR4): httpsRouteHR4, - }, - ResolvedSecret: &secret1NsName, - }, - }...) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr3): routeHR3, - graph.CreateRouteKey(hr4): routeHR4, - graph.CreateRouteKey(httpsHR3): httpsRouteHR3, - graph.CreateRouteKey(httpsHR4): httpsRouteHR4, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - } - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append(conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR3Groups[0], - Source: &hr3.ObjectMeta, - }, - { - BackendGroup: expHR4Groups[1], - Source: &hr4.ObjectMeta, - }, + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr3): routeHR3, + graph.CreateRouteKey(hr4): routeHR4, }, }, { - Path: "/fourth", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR4Groups[0], - Source: &hr4.ObjectMeta, - }, + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR3): httpsRouteHR3, + graph.CreateRouteKey(httpsHR4): httpsRouteHR4, }, + ResolvedSecret: &secret1NsName, }, + }..., + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr3): routeHR3, + graph.CreateRouteKey(hr4): routeHR4, + graph.CreateRouteKey(httpsHR3): httpsRouteHR3, + graph.CreateRouteKey(httpsHR4): httpsRouteHR4, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + } + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, []VirtualServer{ { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Hostname: "foo.example.com", + PathRules: []PathRule{ { - BackendGroup: expHR3Groups[1], - Source: &hr3.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR3Groups[0], + Source: &hr3.ObjectMeta, + }, + { + BackendGroup: expHR4Groups[1], + Source: &hr4.ObjectMeta, + }, + }, }, - }, - }, - }, - Port: 80, - }, - }...) - conf.SSLServers = append(conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ { - BackendGroup: expHTTPSHR3Groups[0], - Source: &httpsHR3.ObjectMeta, + Path: "/fourth", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR4Groups[0], + Source: &hr4.ObjectMeta, + }, + }, }, { - BackendGroup: expHTTPSHR4Groups[1], - Source: &httpsHR4.ObjectMeta, + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR3Groups[1], + Source: &hr3.ObjectMeta, + }, + }, }, }, + Port: 80, }, + }..., + ) + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ { - Path: "/fourth", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Hostname: "foo.example.com", + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + PathRules: []PathRule{ { - BackendGroup: expHTTPSHR4Groups[0], - Source: &httpsHR4.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR3Groups[0], + Source: &httpsHR3.ObjectMeta, + }, + { + BackendGroup: expHTTPSHR4Groups[1], + Source: &httpsHR4.ObjectMeta, + }, + }, + }, + { + Path: "/fourth", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR4Groups[0], + Source: &httpsHR4.ObjectMeta, + }, + }, }, - }, - }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ { - BackendGroup: expHTTPSHR3Groups[1], - Source: &httpsHR3.ObjectMeta, + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR3Groups[1], + Source: &httpsHR3.ObjectMeta, + }, + }, }, }, + Port: 443, }, - }, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }...) - conf.Upstreams = append(conf.Upstreams, fooUpstream) - conf.BackendGroups = []BackendGroup{ - expHR3Groups[0], - expHR3Groups[1], - expHR4Groups[0], - expHR4Groups[1], - expHTTPSHR3Groups[0], - expHTTPSHR3Groups[1], - expHTTPSHR4Groups[0], - expHTTPSHR4Groups[1], - } - return conf - }), + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }..., + ) + conf.Upstreams = append(conf.Upstreams, fooUpstream) + conf.BackendGroups = []BackendGroup{ + expHR3Groups[0], + expHR3Groups[1], + expHR4Groups[0], + expHR4Groups[1], + expHTTPSHR3Groups[0], + expHTTPSHR3Groups[1], + expHTTPSHR4Groups[0], + expHTTPSHR4Groups[1], + } + return conf + }, + ), msg: "one http and one https listener with two routes with the same hostname with and without collisions", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr3): routeHR3, - }, - }, - { - Name: "listener-8080", - Source: listener8080, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr8): routeHR8, - }, - }, - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR3): httpsRouteHR3, - }, - ResolvedSecret: &secret1NsName, - }, - { - Name: "listener-8443", - Source: listener8443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR7): httpsRouteHR7, - }, - ResolvedSecret: &secret1NsName, - }, - }...) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr3): routeHR3, - graph.CreateRouteKey(hr8): routeHR8, - graph.CreateRouteKey(httpsHR3): httpsRouteHR3, - graph.CreateRouteKey(httpsHR7): httpsRouteHR7, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - } - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append(conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR3Groups[0], - Source: &hr3.ObjectMeta, - }, + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr3): routeHR3, }, }, { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR3Groups[1], - Source: &hr3.ObjectMeta, - }, + Name: "listener-8080", + Source: listener8080, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr8): routeHR8, }, }, - }, - Port: 80, - }, - { - IsDefault: true, - Port: 8080, - }, - { - Hostname: "foo.example.com", - PathRules: []PathRule{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR8Groups[0], - Source: &hr8.ObjectMeta, - }, + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR3): httpsRouteHR3, }, + ResolvedSecret: &secret1NsName, }, { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR8Groups[1], - Source: &hr8.ObjectMeta, - }, + Name: "listener-8443", + Source: listener8443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR7): httpsRouteHR7, }, + ResolvedSecret: &secret1NsName, }, - }, - Port: 8080, - }, - }...) - conf.SSLServers = append(conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - PathRules: []PathRule{ + }..., + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr3): routeHR3, + graph.CreateRouteKey(hr8): routeHR8, + graph.CreateRouteKey(httpsHR3): httpsRouteHR3, + graph.CreateRouteKey(httpsHR7): httpsRouteHR7, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + } + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, []VirtualServer{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR3Groups[0], + Source: &hr3.ObjectMeta, + }, + }, + }, { - BackendGroup: expHTTPSHR3Groups[0], - Source: &httpsHR3.ObjectMeta, + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR3Groups[1], + Source: &hr3.ObjectMeta, + }, + }, }, }, + Port: 80, }, { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + IsDefault: true, + Port: 8080, + }, + { + Hostname: "foo.example.com", + PathRules: []PathRule{ { - BackendGroup: expHTTPSHR3Groups[1], - Source: &httpsHR3.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR8Groups[0], + Source: &hr8.ObjectMeta, + }, + }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR8Groups[1], + Source: &hr8.ObjectMeta, + }, + }, }, }, + Port: 8080, }, - }, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - { - IsDefault: true, - Port: 8443, - }, - { - Hostname: "foo.example.com", - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - PathRules: []PathRule{ + }..., + ) + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Hostname: "foo.example.com", + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + PathRules: []PathRule{ { - BackendGroup: expHTTPSHR7Groups[0], - Source: &httpsHR7.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR3Groups[0], + Source: &httpsHR3.ObjectMeta, + }, + }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR3Groups[1], + Source: &httpsHR3.ObjectMeta, + }, + }, }, }, + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + { + IsDefault: true, + Port: 8443, }, { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Hostname: "foo.example.com", + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + PathRules: []PathRule{ { - BackendGroup: expHTTPSHR7Groups[1], - Source: &httpsHR7.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR7Groups[0], + Source: &httpsHR7.ObjectMeta, + }, + }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR7Groups[1], + Source: &httpsHR7.ObjectMeta, + }, + }, }, }, + Port: 8443, }, - }, - Port: 8443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 8443, - }, - }...) - conf.Upstreams = append(conf.Upstreams, fooUpstream) - conf.BackendGroups = []BackendGroup{ - expHR3Groups[0], - expHR3Groups[1], - expHR8Groups[0], - expHR8Groups[1], - expHTTPSHR3Groups[0], - expHTTPSHR3Groups[1], - expHTTPSHR7Groups[0], - expHTTPSHR7Groups[1], - } - return conf - }), + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 8443, + }, + }..., + ) + conf.Upstreams = append(conf.Upstreams, fooUpstream) + conf.BackendGroups = []BackendGroup{ + expHR3Groups[0], + expHR3Groups[1], + expHR8Groups[0], + expHR8Groups[1], + expHTTPSHR3Groups[0], + expHTTPSHR3Groups[1], + expHTTPSHR7Groups[0], + expHTTPSHR7Groups[1], + } + return conf + }, + ), msg: "multiple http and https listener; different ports", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.GatewayClass.Valid = false - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.GatewayClass.Valid = false + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): routeHR1, + }, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr1): routeHR1, - }, - }) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): routeHR1, - } - return g - }), + } + return g + }, + ), expConf: Configuration{}, msg: "invalid gatewayclass", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.GatewayClass.Valid = false - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.GatewayClass.Valid = false + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): routeHR1, + }, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr1): routeHR1, - }, - }) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): routeHR1, - } - return g - }), + } + return g + }, + ), expConf: Configuration{}, msg: "missing gatewayclass", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway = nil - return g - }), + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway = nil + return g + }, + ), expConf: Configuration{}, msg: "missing gateway", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr5): routeHR5, + }, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr5): routeHR5, - }, - }) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr5): routeHR5, - } - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append(conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ + } + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, []VirtualServer{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Hostname: "foo.example.com", + PathRules: []PathRule{ { - Source: &hr5.ObjectMeta, - BackendGroup: expHR5Groups[0], - Filters: HTTPFilters{ - RequestRedirect: &expRedirect, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + Source: &hr5.ObjectMeta, + BackendGroup: expHR5Groups[0], + Filters: HTTPFilters{ + RequestRedirect: &expRedirect, + SnippetsFilters: []SnippetsFilter{expExtRefFilter}, + }, + }, }, }, - }, - }, - { - Path: invalidFiltersPath, - PathType: PathTypePrefix, - MatchRules: []MatchRule{ { - Source: &hr5.ObjectMeta, - BackendGroup: expHR5Groups[1], - Filters: HTTPFilters{ - InvalidFilter: &InvalidHTTPFilter{}, + Path: invalidFiltersPath, + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + Source: &hr5.ObjectMeta, + BackendGroup: expHR5Groups[1], + Filters: HTTPFilters{ + InvalidFilter: &InvalidHTTPFilter{}, + }, + }, }, }, }, + Port: 80, }, - }, - Port: 80, - }, - }...) - conf.SSLServers = []VirtualServer{} - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHR5Groups[0], expHR5Groups[1]} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }), + }..., + ) + conf.SSLServers = []VirtualServer{} + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHR5Groups[0], expHR5Groups[1]} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }, + ), msg: "one http listener with one route with filters", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr6): routeHR6, - }, - }, - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR6): httpsRouteHR6, - }, - ResolvedSecret: &secret1NsName, - }, - { - Name: "listener-443-2", - Source: listener443_2, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{ - TR1Key: &tlsTR1, - TR2Key: &invalidBackendRefTR2, - }, - ResolvedSecret: &secret1NsName, - }, - { - Name: "listener-444-3", - Source: listener444_3, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{ - TR1Key: &tlsTR1, - TR2Key: &invalidBackendRefTR2, - }, - ResolvedSecret: &secret1NsName, - }, - { - Name: "listener-443-4", - Source: listener443_4, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, - ResolvedSecret: &secret1NsName, - }, - }...) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr6): routeHR6, - graph.CreateRouteKey(httpsHR6): httpsRouteHR6, - } - g.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ - TR1Key: &tlsTR1, - TR2Key: &invalidBackendRefTR2, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - } - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append(conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr6): routeHR6, + }, + }, + { + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR6): httpsRouteHR6, + }, + ResolvedSecret: &secret1NsName, + }, { - Path: "/valid", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Name: "listener-443-2", + Source: listener443_2, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{ + TR1Key: &tlsTR1, + TR2Key: &invalidBackendRefTR2, + }, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-444-3", + Source: listener444_3, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{ + TR1Key: &tlsTR1, + TR2Key: &invalidBackendRefTR2, + }, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-4", + Source: listener443_4, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + ResolvedSecret: &secret1NsName, + }, + }..., + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr6): routeHR6, + graph.CreateRouteKey(httpsHR6): httpsRouteHR6, + } + g.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + TR1Key: &tlsTR1, + TR2Key: &invalidBackendRefTR2, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + } + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ { - BackendGroup: expHR6Groups[0], - Source: &hr6.ObjectMeta, + Path: "/valid", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR6Groups[0], + Source: &hr6.ObjectMeta, + }, + }, }, }, + Port: 80, }, - }, - Port: 80, - }, - }...) - conf.SSLServers = append(conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - PathRules: []PathRule{ + }..., + ) + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ { - Path: "/valid", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ + Hostname: "foo.example.com", + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + PathRules: []PathRule{ { - BackendGroup: expHTTPSHR6Groups[0], - Source: &httpsHR6.ObjectMeta, + Path: "/valid", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR6Groups[0], + Source: &httpsHR6.ObjectMeta, + }, + }, }, }, + Port: 443, }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }..., + ) + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHR6Groups[0], expHTTPSHR6Groups[0]} + conf.StreamUpstreams = []Upstream{ + { + Endpoints: fooEndpoints, + Name: "default_secure-app_8443", }, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }...) - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHR6Groups[0], expHTTPSHR6Groups[0]} - conf.StreamUpstreams = []Upstream{ - { - Endpoints: fooEndpoints, - Name: "default_secure-app_8443", - }, - } - conf.TLSPassthroughServers = []Layer4VirtualServer{ - { - Hostname: "app.example.com", - UpstreamName: "default_secure-app_8443", - Port: 443, - }, - { - Hostname: "*.example.com", - UpstreamName: "", - Port: 443, - IsDefault: true, - }, - { - Hostname: "app.example.com", - UpstreamName: "default_secure-app_8443", - Port: 444, - IsDefault: false, - }, - { - Hostname: "", - UpstreamName: "", - Port: 443, - IsDefault: false, - }, - } - return conf - }), + } + conf.TLSPassthroughServers = []Layer4VirtualServer{ + { + Hostname: "app.example.com", + UpstreamName: "default_secure-app_8443", + Port: 443, + }, + { + Hostname: "*.example.com", + UpstreamName: "", + Port: 443, + IsDefault: true, + }, + { + Hostname: "app.example.com", + UpstreamName: "default_secure-app_8443", + Port: 444, + IsDefault: false, + }, + { + Hostname: "", + UpstreamName: "", + Port: 443, + IsDefault: false, + }, + } + return conf + }, + ), msg: "one http, one https listener, and three tls listeners with routes with valid and invalid rules", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr7): routeHR7, + }, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr7): routeHR7, - }, - }) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr7): routeHR7, - } - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append(conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ + } + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, []VirtualServer{ { - Path: "/valid", - PathType: PathTypeExact, - MatchRules: []MatchRule{ + Hostname: "foo.example.com", + PathRules: []PathRule{ { - BackendGroup: expHR7Groups[1], - Source: &hr7.ObjectMeta, + Path: "/valid", + PathType: PathTypeExact, + MatchRules: []MatchRule{ + { + BackendGroup: expHR7Groups[1], + Source: &hr7.ObjectMeta, + }, + }, }, - }, - }, - { - Path: "/valid", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ { - BackendGroup: expHR7Groups[0], - Source: &hr7.ObjectMeta, + Path: "/valid", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR7Groups[0], + Source: &hr7.ObjectMeta, + }, + }, }, }, + Port: 80, }, - }, - Port: 80, - }, - }...) - conf.SSLServers = []VirtualServer{} - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHR7Groups[0], expHR7Groups[1]} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }), + }..., + ) + conf.SSLServers = []VirtualServer{} + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHR7Groups[0], expHR7Groups[1]} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }, + ), msg: "duplicate paths with different types", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-443-with-hostname", - Source: listener443WithHostname, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - }, - ResolvedSecret: &secret2NsName, - }, - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - }, - ResolvedSecret: &secret1NsName, - }, - }...) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - secret2NsName: secret2, - } - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = append(conf.SSLServers, []VirtualServer{ - { - Hostname: "example.com", - PathRules: []PathRule{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - // duplicate match rules since two listeners both match this route's hostname - { - BackendGroup: expHTTPSHR5Groups[0], - Source: &httpsHR5.ObjectMeta, - }, + Name: "listener-443-with-hostname", + Source: listener443WithHostname, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, + }, + ResolvedSecret: &secret2NsName, + }, + { + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, + }, + ResolvedSecret: &secret1NsName, + }, + }..., + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + secret2NsName: secret2, + } + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ + { + Hostname: "example.com", + PathRules: []PathRule{ { - BackendGroup: expHTTPSHR5Groups[0], - Source: &httpsHR5.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + // duplicate match rules since two listeners both match this route's hostname + { + BackendGroup: expHTTPSHR5Groups[0], + Source: &httpsHR5.ObjectMeta, + }, + { + BackendGroup: expHTTPSHR5Groups[0], + Source: &httpsHR5.ObjectMeta, + }, + }, }, }, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, }, + }..., + ) + conf.HTTPServers = []VirtualServer{} + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHTTPSHR5Groups[0]} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{ + "ssl_keypair_test_secret-1": { + Cert: []byte("cert-1"), + Key: []byte("privateKey-1"), }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }...) - conf.HTTPServers = []VirtualServer{} - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHTTPSHR5Groups[0]} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{ - "ssl_keypair_test_secret-1": { - Cert: []byte("cert-1"), - Key: []byte("privateKey-1"), - }, - "ssl_keypair_test_secret-2": { - Cert: []byte("cert-2"), - Key: []byte("privateKey-2"), - }, - } - return conf - }), + "ssl_keypair_test_secret-2": { + Cert: []byte("cert-2"), + Key: []byte("privateKey-2"), + }, + } + return conf + }, + ), msg: "two https listeners with different hostnames but same route; chooses listener with more specific hostname", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-443", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-443", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR8): httpsRouteHR8, + }, + ResolvedSecret: &secret1NsName, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR8): httpsRouteHR8, - }, - ResolvedSecret: &secret1NsName, - }) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR8): httpsRouteHR8, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - } - g.ReferencedCaCertConfigMaps = referencedConfigMaps - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = append(conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + } + g.ReferencedCaCertConfigMaps = referencedConfigMaps + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR8Groups[0], - Source: &httpsHR8.ObjectMeta, - }, + Hostname: "foo.example.com", + PathRules: []PathRule{ { - BackendGroup: expHTTPSHR8Groups[1], - Source: &httpsHR8.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR8Groups[0], + Source: &httpsHR8.ObjectMeta, + }, + { + BackendGroup: expHTTPSHR8Groups[1], + Source: &httpsHR8.ObjectMeta, + }, + }, }, }, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, }, - }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }...) - conf.HTTPServers = []VirtualServer{} - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHTTPSHR8Groups[0], expHTTPSHR8Groups[1]} - conf.CertBundles = map[CertBundleID]CertBundle{ - "cert_bundle_test_configmap-1": []byte("cert-1"), - } - return conf - }), + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }..., + ) + conf.HTTPServers = []VirtualServer{} + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHTTPSHR8Groups[0], expHTTPSHR8Groups[1]} + conf.CertBundles = map[CertBundleID]CertBundle{ + "cert_bundle_test_configmap-1": []byte("cert-1"), + } + return conf + }, + ), msg: "https listener with httproute with backend that has a backend TLS policy attached", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-443", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-443", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR9): httpsRouteHR9, + }, + ResolvedSecret: &secret1NsName, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR9): httpsRouteHR9, - }, - ResolvedSecret: &secret1NsName, - }) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR9): httpsRouteHR9, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - } - g.ReferencedCaCertConfigMaps = referencedConfigMaps - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = append(conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + } + g.ReferencedCaCertConfigMaps = referencedConfigMaps + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR9Groups[0], - Source: &httpsHR9.ObjectMeta, - }, + Hostname: "foo.example.com", + PathRules: []PathRule{ { - BackendGroup: expHTTPSHR9Groups[1], - Source: &httpsHR9.ObjectMeta, + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR9Groups[0], + Source: &httpsHR9.ObjectMeta, + }, + { + BackendGroup: expHTTPSHR9Groups[1], + Source: &httpsHR9.ObjectMeta, + }, + }, }, }, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, }, - }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }...) - conf.HTTPServers = []VirtualServer{} - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHTTPSHR9Groups[0], expHTTPSHR9Groups[1]} - conf.CertBundles = map[CertBundleID]CertBundle{ - "cert_bundle_test_configmap-2": []byte("cert-2"), - } - return conf - }), + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }..., + ) + conf.HTTPServers = []VirtualServer{} + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHTTPSHR9Groups[0], expHTTPSHR9Groups[1]} + conf.CertBundles = map[CertBundleID]CertBundle{ + "cert_bundle_test_configmap-2": []byte("cert-2"), + } + return conf + }, + ), msg: "https listener with httproute with backend that has a backend TLS policy with binaryData attached", - }, - { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", - } - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }) - g.NginxProxy = nginxProxy - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - conf.Telemetry = Telemetry{ - Endpoint: "my-otel.svc:4563", - Interval: "5s", - BatchSize: 512, - BatchCount: 4, - ServiceName: "ngf:ns:gw:my-svc", - Ratios: []Ratio{}, - SpanAttributes: []SpanAttribute{}, - } - conf.BaseHTTPConfig = BaseHTTPConfig{HTTP2: false, IPFamily: Dual} - return conf - }), + }, + { + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + } + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }, + ) + g.NginxProxy = nginxProxy + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + conf.Telemetry = Telemetry{ + Endpoint: "my-otel.svc:4563", + Interval: "5s", + BatchSize: 512, + BatchCount: 4, + ServiceName: "ngf:ns:gw:my-svc", + Ratios: []Ratio{}, + SpanAttributes: []SpanAttribute{}, + } + conf.BaseHTTPConfig = BaseHTTPConfig{HTTP2: false, IPFamily: Dual} + return conf + }, + ), msg: "NginxProxy with tracing config and http2 disabled", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", - } - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }) - g.NginxProxy = &graph.NginxProxy{ - Valid: false, - Source: &ngfAPI.NginxProxy{ - Spec: ngfAPI.NginxProxySpec{ - DisableHTTP2: true, - IPFamily: helpers.GetPointer(ngfAPI.Dual), - Telemetry: &ngfAPI.Telemetry{ - Exporter: &ngfAPI.TelemetryExporter{ - Endpoint: "some-endpoint", + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + } + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }, + ) + g.NginxProxy = &graph.NginxProxy{ + Valid: false, + Source: &ngfAPI.NginxProxy{ + Spec: ngfAPI.NginxProxySpec{ + DisableHTTP2: true, + IPFamily: helpers.GetPointer(ngfAPI.Dual), + Telemetry: &ngfAPI.Telemetry{ + Exporter: &ngfAPI.TelemetryExporter{ + Endpoint: "some-endpoint", + }, }, }, }, - }, - } - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }), + } + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }, + ), msg: "invalid NginxProxy", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, - }, - }, - { - Name: "listener-443", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHRWithPolicy): l7HTTPSRouteWithPolicy, - }, - ResolvedSecret: &secret1NsName, - }, - }...) - g.Gateway.Policies = []*graph.Policy{gwPolicy1, gwPolicy2} - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, - graph.CreateRouteKey(httpsHRWithPolicy): l7HTTPSRouteWithPolicy, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - } - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{ - { - IsDefault: true, - Port: 443, - Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, - }, - { - Hostname: "policy.com", - PathRules: []PathRule{ + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHRWithPolicyGroups[0], - Source: &httpsHRWithPolicy.ObjectMeta, + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, + }, + }, + { + Name: "listener-443", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHRWithPolicy): l7HTTPSRouteWithPolicy, + }, + ResolvedSecret: &secret1NsName, + }, + }..., + ) + g.Gateway.Policies = []*graph.Policy{gwPolicy1, gwPolicy2} + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, + graph.CreateRouteKey(httpsHRWithPolicy): l7HTTPSRouteWithPolicy, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + } + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{ + { + IsDefault: true, + Port: 443, + Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, + }, + { + Hostname: "policy.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHRWithPolicyGroups[0], + Source: &httpsHRWithPolicy.ObjectMeta, + }, }, + Policies: []policies.Policy{hrPolicy2.Source}, }, - Policies: []policies.Policy{hrPolicy2.Source}, }, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, - }, - } - conf.HTTPServers = []VirtualServer{ - { - IsDefault: true, - Port: 80, - Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, - }, - { - Hostname: "policy.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - Source: &hrWithPolicy.ObjectMeta, - BackendGroup: expHRWithPolicyGroups[0], + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, + }, + } + conf.HTTPServers = []VirtualServer{ + { + IsDefault: true, + Port: 80, + Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, + }, + { + Hostname: "policy.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + Source: &hrWithPolicy.ObjectMeta, + BackendGroup: expHRWithPolicyGroups[0], + }, }, + Policies: []policies.Policy{hrPolicy1.Source}, }, - Policies: []policies.Policy{hrPolicy1.Source}, }, + Port: 80, + Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, }, - Port: 80, - Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, - }, - } - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHRWithPolicyGroups[0], expHTTPSHRWithPolicyGroups[0]} - return conf - }), + } + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHRWithPolicyGroups[0], expHTTPSHRWithPolicyGroups[0]} + return conf + }, + ), msg: "Simple Gateway and HTTPRoute with policies attached", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", - } - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }) - g.NginxProxy = nginxProxyIPv4 - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - conf.BaseHTTPConfig = BaseHTTPConfig{HTTP2: true, IPFamily: IPv4} - return conf - }), + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + } + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }, + ) + g.NginxProxy = nginxProxyIPv4 + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + conf.BaseHTTPConfig = BaseHTTPConfig{HTTP2: true, IPFamily: IPv4} + return conf + }, + ), msg: "NginxProxy with IPv4 IPFamily and no routes", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", - } - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }) - g.NginxProxy = nginxProxyIPv6 - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - conf.BaseHTTPConfig = BaseHTTPConfig{HTTP2: true, IPFamily: IPv6} - return conf - }), + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + } + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }, + ) + g.NginxProxy = nginxProxyIPv6 + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + conf.BaseHTTPConfig = BaseHTTPConfig{HTTP2: true, IPFamily: IPv6} + return conf + }, + ), msg: "NginxProxy with IPv6 IPFamily and no routes", }, { - graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", - } - g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }) - g.NginxProxy = &graph.NginxProxy{ - Valid: true, - Source: &ngfAPI.NginxProxy{ - Spec: ngfAPI.NginxProxySpec{ - RewriteClientIP: &ngfAPI.RewriteClientIP{ - SetIPRecursively: helpers.GetPointer(true), - TrustedAddresses: []ngfAPI.Address{ - { - Type: ngfAPI.AddressTypeCIDR, - Value: "1.1.1.1/32", + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + } + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }, + ) + g.NginxProxy = &graph.NginxProxy{ + Valid: true, + Source: &ngfAPI.NginxProxy{ + Spec: ngfAPI.NginxProxySpec{ + RewriteClientIP: &ngfAPI.RewriteClientIP{ + SetIPRecursively: helpers.GetPointer(true), + TrustedAddresses: []ngfAPI.Address{ + { + Type: ngfAPI.AddressTypeCIDR, + Value: "1.1.1.1/32", + }, }, + Mode: helpers.GetPointer(ngfAPI.RewriteClientIPModeProxyProtocol), }, - Mode: helpers.GetPointer(ngfAPI.RewriteClientIPModeProxyProtocol), }, }, - }, - } - return g - }), - expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - conf.BaseHTTPConfig = BaseHTTPConfig{ - HTTP2: true, - IPFamily: Dual, - RewriteClientIPSettings: RewriteClientIPSettings{ - IPRecursive: true, - TrustedAddresses: []string{"1.1.1.1/32"}, - Mode: RewriteIPModeProxyProtocol, - }, - } - return conf - }), + } + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + conf.BaseHTTPConfig = BaseHTTPConfig{ + HTTP2: true, + IPFamily: Dual, + RewriteClientIPSettings: RewriteClientIPSettings{ + IPRecursive: true, + TrustedAddresses: []string{"1.1.1.1/32"}, + Mode: RewriteIPModeProxyProtocol, + }, + } + return conf + }, + ), msg: "NginxProxy with rewriteClientIP details set", }, + { + graph: getModifiedGraph( + func(g *graph.Graph) *graph.Graph { + g.SnippetsFilters = map[types.NamespacedName]*graph.SnippetsFilter{ + client.ObjectKeyFromObject(sf1.Source): sf1, + client.ObjectKeyFromObject(sfNotReferenced.Source): sfNotReferenced, + } + + return g + }, + ), + expConf: getModifiedExpectedConfiguration( + func(conf Configuration) Configuration { + conf.MainSnippets = []Snippet{ + { + Name: createSnippetName( + ngfAPI.NginxContextMain, + client.ObjectKeyFromObject(sf1.Source), + ), + Contents: "main snippet", + }, + } + conf.BaseHTTPConfig.Snippets = []Snippet{ + { + Name: createSnippetName( + ngfAPI.NginxContextHTTP, + client.ObjectKeyFromObject(sf1.Source), + ), + Contents: "http snippet", + }, + } + conf.HTTPServers = []VirtualServer{} + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + + return conf + }, + ), + msg: "SnippetsFilters with main and http snippet", + }, } for _, test := range tests { - t.Run(test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - result := BuildConfiguration( - context.TODO(), - test.graph, - fakeResolver, - 1, - ) + t.Run( + test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + result := BuildConfiguration( + context.TODO(), + test.graph, + fakeResolver, + 1, + ) - g.Expect(result.BackendGroups).To(ConsistOf(test.expConf.BackendGroups)) - g.Expect(result.Upstreams).To(ConsistOf(test.expConf.Upstreams)) - g.Expect(result.HTTPServers).To(ConsistOf(test.expConf.HTTPServers)) - g.Expect(result.SSLServers).To(ConsistOf(test.expConf.SSLServers)) - g.Expect(result.TLSPassthroughServers).To(ConsistOf(test.expConf.TLSPassthroughServers)) - g.Expect(result.SSLKeyPairs).To(Equal(test.expConf.SSLKeyPairs)) - g.Expect(result.Version).To(Equal(1)) - g.Expect(result.CertBundles).To(Equal(test.expConf.CertBundles)) - g.Expect(result.Telemetry).To(Equal(test.expConf.Telemetry)) - g.Expect(result.BaseHTTPConfig).To(Equal(test.expConf.BaseHTTPConfig)) - }) + g.Expect(result.BackendGroups).To(ConsistOf(test.expConf.BackendGroups)) + g.Expect(result.Upstreams).To(ConsistOf(test.expConf.Upstreams)) + g.Expect(result.HTTPServers).To(ConsistOf(test.expConf.HTTPServers)) + g.Expect(result.SSLServers).To(ConsistOf(test.expConf.SSLServers)) + g.Expect(result.TLSPassthroughServers).To(ConsistOf(test.expConf.TLSPassthroughServers)) + g.Expect(result.SSLKeyPairs).To(Equal(test.expConf.SSLKeyPairs)) + g.Expect(result.Version).To(Equal(1)) + g.Expect(result.CertBundles).To(Equal(test.expConf.CertBundles)) + g.Expect(result.Telemetry).To(Equal(test.expConf.Telemetry)) + g.Expect(result.BaseHTTPConfig).To(Equal(test.expConf.BaseHTTPConfig)) + }, + ) } } @@ -2286,43 +2576,46 @@ func TestGetPath(t *testing.T) { } for _, test := range tests { - t.Run(test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := getPath(test.path) - g.Expect(result).To(Equal(test.expected)) - }) + t.Run( + test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + result := getPath(test.path) + g.Expect(result).To(Equal(test.expected)) + }, + ) } } func TestCreateFilters(t *testing.T) { t.Parallel() - redirect1 := v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterRequestRedirect, + + redirect1 := graph.Filter{ + FilterType: graph.FilterRequestRedirect, RequestRedirect: &v1.HTTPRequestRedirectFilter{ Hostname: helpers.GetPointer[v1.PreciseHostname]("foo.example.com"), }, } - redirect2 := v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterRequestRedirect, + redirect2 := graph.Filter{ + FilterType: graph.FilterRequestRedirect, RequestRedirect: &v1.HTTPRequestRedirectFilter{ Hostname: helpers.GetPointer[v1.PreciseHostname]("bar.example.com"), }, } - rewrite1 := v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterURLRewrite, + rewrite1 := graph.Filter{ + FilterType: graph.FilterURLRewrite, URLRewrite: &v1.HTTPURLRewriteFilter{ Hostname: helpers.GetPointer[v1.PreciseHostname]("foo.example.com"), }, } - rewrite2 := v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterURLRewrite, + rewrite2 := graph.Filter{ + FilterType: graph.FilterURLRewrite, URLRewrite: &v1.HTTPURLRewriteFilter{ Hostname: helpers.GetPointer[v1.PreciseHostname]("bar.example.com"), }, } - requestHeaderModifiers1 := v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterRequestHeaderModifier, + requestHeaderModifiers1 := graph.Filter{ + FilterType: graph.FilterRequestHeaderModifier, RequestHeaderModifier: &v1.HTTPHeaderFilter{ Set: []v1.HTTPHeader{ { @@ -2332,8 +2625,8 @@ func TestCreateFilters(t *testing.T) { }, }, } - requestHeaderModifiers2 := v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterRequestHeaderModifier, + requestHeaderModifiers2 := graph.Filter{ + FilterType: graph.FilterRequestHeaderModifier, RequestHeaderModifier: &v1.HTTPHeaderFilter{ Add: []v1.HTTPHeader{ { @@ -2344,8 +2637,8 @@ func TestCreateFilters(t *testing.T) { }, } - responseHeaderModifiers1 := v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterResponseHeaderModifier, + responseHeaderModifiers1 := graph.Filter{ + FilterType: graph.FilterResponseHeaderModifier, ResponseHeaderModifier: &v1.HTTPHeaderFilter{ Add: []v1.HTTPHeader{ { @@ -2356,8 +2649,8 @@ func TestCreateFilters(t *testing.T) { }, } - responseHeaderModifiers2 := v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterResponseHeaderModifier, + responseHeaderModifiers2 := graph.Filter{ + FilterType: graph.FilterResponseHeaderModifier, ResponseHeaderModifier: &v1.HTTPHeaderFilter{ Set: []v1.HTTPHeader{ { @@ -2392,49 +2685,83 @@ func TestCreateFilters(t *testing.T) { }, } + snippetsFilter1 := graph.Filter{ + FilterType: graph.FilterExtensionRef, + ExtensionRef: &v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: "sf1", + }, + ResolvedExtensionRef: &graph.ExtensionRefFilter{ + Valid: true, + SnippetsFilter: &graph.SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sf1", + Namespace: "default", + }, + }, + Valid: true, + Referenced: true, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTPServerLocation: "location snippet 1", + ngfAPI.NginxContextMain: "main snippet 1", + ngfAPI.NginxContextHTTPServer: "server snippet 1", + ngfAPI.NginxContextHTTP: "main snippet 1", + }, + }, + }, + } + + snippetsFilter2 := graph.Filter{ + FilterType: graph.FilterExtensionRef, + ExtensionRef: &v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: "sf2", + }, + ResolvedExtensionRef: &graph.ExtensionRefFilter{ + Valid: true, + SnippetsFilter: &graph.SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sf2", + Namespace: "default", + }, + }, + Valid: true, + Referenced: true, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTPServerLocation: "location snippet 2", + ngfAPI.NginxContextMain: "main snippet 2", + ngfAPI.NginxContextHTTPServer: "server snippet 2", + ngfAPI.NginxContextHTTP: "main snippet 2", + }, + }, + }, + } + tests := []struct { expected HTTPFilters msg string - filters []v1.HTTPRouteFilter + filters []graph.Filter }{ { - filters: []v1.HTTPRouteFilter{}, + filters: []graph.Filter{}, expected: HTTPFilters{}, msg: "no filters", }, { - filters: []v1.HTTPRouteFilter{ - redirect1, - }, - expected: HTTPFilters{ - RequestRedirect: &expectedRedirect1, - }, - msg: "one filter", - }, - { - filters: []v1.HTTPRouteFilter{ + filters: []graph.Filter{ redirect1, - redirect2, }, expected: HTTPFilters{ RequestRedirect: &expectedRedirect1, }, - msg: "two filters, first wins", - }, - { - filters: []v1.HTTPRouteFilter{ - redirect1, - redirect2, - requestHeaderModifiers1, - }, - expected: HTTPFilters{ - RequestRedirect: &expectedRedirect1, - RequestHeaderModifiers: &expectedHeaderModifier1, - }, - msg: "two redirect filters, one request header modifier, first redirect wins", + msg: "one request redirect filter", }, { - filters: []v1.HTTPRouteFilter{ + filters: []graph.Filter{ redirect1, redirect2, rewrite1, @@ -2443,25 +2770,63 @@ func TestCreateFilters(t *testing.T) { requestHeaderModifiers2, responseHeaderModifiers1, responseHeaderModifiers2, + snippetsFilter1, + snippetsFilter2, }, expected: HTTPFilters{ RequestRedirect: &expectedRedirect1, RequestURLRewrite: &expectedRewrite1, RequestHeaderModifiers: &expectedHeaderModifier1, ResponseHeaderModifiers: &expectedresponseHeaderModifier, + SnippetsFilters: []SnippetsFilter{ + { + LocationSnippet: &Snippet{ + Name: createSnippetName( + ngfAPI.NginxContextHTTPServerLocation, + types.NamespacedName{Namespace: "default", Name: "sf1"}, + ), + Contents: "location snippet 1", + }, + ServerSnippet: &Snippet{ + Name: createSnippetName( + ngfAPI.NginxContextHTTPServer, + types.NamespacedName{Namespace: "default", Name: "sf1"}, + ), + Contents: "server snippet 1", + }, + }, + { + LocationSnippet: &Snippet{ + Name: createSnippetName( + ngfAPI.NginxContextHTTPServerLocation, + types.NamespacedName{Namespace: "default", Name: "sf2"}, + ), + Contents: "location snippet 2", + }, + ServerSnippet: &Snippet{ + Name: createSnippetName( + ngfAPI.NginxContextHTTPServer, + types.NamespacedName{Namespace: "default", Name: "sf2"}, + ), + Contents: "server snippet 2", + }, + }, + }, }, - msg: "two of each filter, first value for each wins", + msg: "two of each filter, first value for each standard filter wins, all ext ref filters added", }, } for _, test := range tests { - t.Run(test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := createHTTPFilters(test.filters) + t.Run( + test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + result := createHTTPFilters(test.filters) - g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) - }) + g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) + }, + ) } } @@ -2493,12 +2858,14 @@ func TestGetListenerHostname(t *testing.T) { } for _, test := range tests { - t.Run(test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := getListenerHostname(test.hostname) - g.Expect(result).To(Equal(test.expected)) - }) + t.Run( + test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + result := getListenerHostname(test.hostname) + g.Expect(result).To(Equal(test.expected)) + }, + ) } } @@ -2506,11 +2873,13 @@ func refsToValidRules(refs ...[]graph.BackendRef) []graph.RouteRule { rules := make([]graph.RouteRule, 0, len(refs)) for _, ref := range refs { - rules = append(rules, graph.RouteRule{ - ValidMatches: true, - ValidFilters: true, - BackendRefs: ref, - }) + rules = append( + rules, graph.RouteRule{ + ValidMatches: true, + Filters: graph.RouteRuleFilters{Valid: true}, + BackendRefs: ref, + }, + ) } return rules @@ -2599,11 +2968,13 @@ func TestBuildUpstreams(t *testing.T) { createBackendRefs := func(serviceNames ...string) []graph.BackendRef { var backends []graph.BackendRef for _, name := range serviceNames { - backends = append(backends, graph.BackendRef{ - SvcNsName: types.NamespacedName{Namespace: "test", Name: name}, - ServicePort: apiv1.ServicePort{Port: 80}, - Valid: name != "", - }) + backends = append( + backends, graph.BackendRef{ + SvcNsName: types.NamespacedName{Namespace: "test", Name: name}, + ServicePort: apiv1.ServicePort{Port: 80}, + Valid: name != "", + }, + ) } return backends } @@ -2750,33 +3121,35 @@ func TestBuildUpstreams(t *testing.T) { } fakeResolver := &resolverfakes.FakeServiceResolver{} - fakeResolver.ResolveCalls(func( - _ context.Context, - svcNsName types.NamespacedName, - _ apiv1.ServicePort, - _ []discoveryV1.AddressType, - ) ([]resolver.Endpoint, error) { - switch svcNsName.Name { - case "bar": - return barEndpoints, nil - case "baz": - return bazEndpoints, nil - case "baz2": - return baz2Endpoints, nil - case "empty-endpoints": - return []resolver.Endpoint{}, errors.New(emptyEndpointsErrMsg) - case "foo": - return fooEndpoints, nil - case "nil-endpoints": - return nil, errors.New(nilEndpointsErrMsg) - case "abc": - return abcEndpoints, nil - case "ipv6-endpoints": - return ipv6Endpoints, nil - default: - return nil, fmt.Errorf("unexpected service %s", svcNsName.Name) - } - }) + fakeResolver.ResolveCalls( + func( + _ context.Context, + svcNsName types.NamespacedName, + _ apiv1.ServicePort, + _ []discoveryV1.AddressType, + ) ([]resolver.Endpoint, error) { + switch svcNsName.Name { + case "bar": + return barEndpoints, nil + case "baz": + return bazEndpoints, nil + case "baz2": + return baz2Endpoints, nil + case "empty-endpoints": + return []resolver.Endpoint{}, errors.New(emptyEndpointsErrMsg) + case "foo": + return fooEndpoints, nil + case "nil-endpoints": + return nil, errors.New(nilEndpointsErrMsg) + case "abc": + return abcEndpoints, nil + case "ipv6-endpoints": + return ipv6Endpoints, nil + default: + return nil, fmt.Errorf("unexpected service %s", svcNsName.Name) + } + }, + ) g := NewWithT(t) @@ -2910,12 +3283,14 @@ func TestHostnameMoreSpecific(t *testing.T) { } for _, tc := range tests { - t.Run(tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - g.Expect(listenerHostnameMoreSpecific(tc.host1, tc.host2)).To(Equal(tc.host1Wins)) - }) + g.Expect(listenerHostnameMoreSpecific(tc.host1, tc.host2)).To(Equal(tc.host1Wins)) + }, + ) } } @@ -2988,12 +3363,13 @@ func TestConvertBackendTLS(t *testing.T) { } for _, tc := range tests { - t.Run(tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - g.Expect(convertBackendTLS(tc.btp)).To(Equal(tc.expected)) - }) + t.Run( + tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + g.Expect(convertBackendTLS(tc.btp)).To(Equal(tc.expected)) + }, + ) } } @@ -3109,12 +3485,14 @@ func TestBuildTelemetry(t *testing.T) { }, }, }, - expTelemetry: createModifiedTelemetry(func(t Telemetry) Telemetry { - t.Ratios = []Ratio{ - {Name: "$otel_ratio_25", Value: 25}, - } - return t - }), + expTelemetry: createModifiedTelemetry( + func(t Telemetry) Telemetry { + t.Ratios = []Ratio{ + {Name: "$otel_ratio_25", Value: 25}, + } + return t + }, + ), msg: "Telemetry configured with observability policy ratio", }, { @@ -3178,13 +3556,15 @@ func TestBuildTelemetry(t *testing.T) { }, }, }, - expTelemetry: createModifiedTelemetry(func(t Telemetry) Telemetry { - t.Ratios = []Ratio{ - {Name: "$otel_ratio_25", Value: 25}, - {Name: "$otel_ratio_50", Value: 50}, - } - return t - }), + expTelemetry: createModifiedTelemetry( + func(t Telemetry) Telemetry { + t.Ratios = []Ratio{ + {Name: "$otel_ratio_25", Value: 25}, + {Name: "$otel_ratio_50", Value: 50}, + } + return t + }, + ), msg: "Multiple policies exist; telemetry ratio is properly set", }, { @@ -3220,15 +3600,19 @@ func TestBuildTelemetry(t *testing.T) { } for _, tc := range tests { - t.Run(tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - tel := buildTelemetry(tc.g) - sort.Slice(tel.Ratios, func(i, j int) bool { - return tel.Ratios[i].Value < tel.Ratios[j].Value - }) - g.Expect(tel).To(Equal(tc.expTelemetry)) - }) + t.Run( + tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + tel := buildTelemetry(tc.g) + sort.Slice( + tel.Ratios, func(i, j int) bool { + return tel.Ratios[i].Value < tel.Ratios[j].Value + }, + ) + g.Expect(tel).To(Equal(tc.expTelemetry)) + }, + ) } } @@ -3297,16 +3681,18 @@ func TestBuildPolicies(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - pols := buildPolicies(test.policies) - g.Expect(pols).To(HaveLen(len(test.expPolicies))) - for _, pol := range pols { - g.Expect(test.expPolicies).To(ContainElement(pol.GetName())) - } - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + pols := buildPolicies(test.policies) + g.Expect(pols).To(HaveLen(len(test.expPolicies))) + for _, pol := range pols { + g.Expect(test.expPolicies).To(ContainElement(pol.GetName())) + } + }, + ) } } @@ -3340,11 +3726,13 @@ func TestGetAllowedAddressType(t *testing.T) { } for _, tc := range test { - t.Run(tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - g.Expect(getAllowedAddressType(tc.ipFamily)).To(Equal(tc.expected)) - }) + t.Run( + tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + g.Expect(getAllowedAddressType(tc.ipFamily)).To(Equal(tc.expected)) + }, + ) } } @@ -3736,11 +4124,186 @@ func TestBuildRewriteIPSettings(t *testing.T) { } for _, tc := range tests { - t.Run(tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - baseConfig := buildBaseHTTPConfig(tc.g) - g.Expect(baseConfig.RewriteClientIPSettings).To(Equal(tc.expRewriteIPSettings)) - }) + t.Run( + tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + baseConfig := buildBaseHTTPConfig(tc.g) + g.Expect(baseConfig.RewriteClientIPSettings).To(Equal(tc.expRewriteIPSettings)) + }, + ) + } +} + +func TestCreateSnippetName(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + + name := createSnippetName( + ngfAPI.NginxContextHTTPServerLocation, + types.NamespacedName{Namespace: "some-ns", Name: "some-name"}, + ) + g.Expect(name).To(Equal("SnippetsFilter_http.server.location_some-ns_some-name")) +} + +func TestBuildSnippetForContext(t *testing.T) { + t.Parallel() + + validUnreferenced := &graph.SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-unreferenced", + Namespace: "default", + }, + }, + Valid: true, + Referenced: false, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTPServerLocation: "valid unreferenced", + }, + } + + invalidUnreferenced := &graph.SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid-unreferenced", + Namespace: "default", + }, + }, + Valid: false, + Referenced: false, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTPServerLocation: "invalid unreferenced", + }, + } + + invalidReferenced := &graph.SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "invalid-referenced", + Namespace: "default", + }, + }, + Valid: false, + Referenced: true, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTPServerLocation: "invalid referenced", + }, + } + + validReferenced1 := &graph.SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-referenced1", + Namespace: "default", + }, + }, + Valid: true, + Referenced: true, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTP: "http valid referenced 1", + ngfAPI.NginxContextMain: "main valid referenced 1", + }, + } + + validReferenced2 := &graph.SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-referenced2", + Namespace: "other-ns", + }, + }, + Valid: true, + Referenced: true, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextMain: "main valid referenced 2", + ngfAPI.NginxContextHTTP: "http valid referenced 2", + }, + } + + validReferenced3 := &graph.SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "valid-referenced3", + Namespace: "other-ns", + }, + }, + Valid: true, + Referenced: true, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTPServerLocation: "location valid referenced 2", + }, + } + + expMainSnippets := []Snippet{ + { + Name: createSnippetName(ngfAPI.NginxContextMain, client.ObjectKeyFromObject(validReferenced1.Source)), + Contents: "main valid referenced 1", + }, + { + Name: createSnippetName(ngfAPI.NginxContextMain, client.ObjectKeyFromObject(validReferenced2.Source)), + Contents: "main valid referenced 2", + }, + } + + expHTTPSnippets := []Snippet{ + { + Name: createSnippetName(ngfAPI.NginxContextHTTP, client.ObjectKeyFromObject(validReferenced1.Source)), + Contents: "http valid referenced 1", + }, + { + Name: createSnippetName(ngfAPI.NginxContextHTTP, client.ObjectKeyFromObject(validReferenced2.Source)), + Contents: "http valid referenced 2", + }, + } + + getSnippetsFilters := func() map[types.NamespacedName]*graph.SnippetsFilter { + return map[types.NamespacedName]*graph.SnippetsFilter{ + client.ObjectKeyFromObject(validUnreferenced.Source): validUnreferenced, + client.ObjectKeyFromObject(invalidUnreferenced.Source): invalidUnreferenced, + client.ObjectKeyFromObject(invalidReferenced.Source): invalidReferenced, + client.ObjectKeyFromObject(validReferenced1.Source): validReferenced1, + client.ObjectKeyFromObject(validReferenced2.Source): validReferenced2, + client.ObjectKeyFromObject(validReferenced3.Source): validReferenced3, + } + } + + tests := []struct { + name string + snippetsFilters map[types.NamespacedName]*graph.SnippetsFilter + ctx ngfAPI.NginxContext + expSnippets []Snippet + }{ + { + name: "no snippets filters", + snippetsFilters: nil, + ctx: ngfAPI.NginxContextMain, + expSnippets: nil, + }, + { + name: "main context: mix of invalid, unreferenced, and valid, referenced snippets filters", + snippetsFilters: getSnippetsFilters(), + ctx: ngfAPI.NginxContextMain, + expSnippets: expMainSnippets, + }, + { + name: "http context: mix of invalid, unreferenced, and valid, referenced snippets filters", + snippetsFilters: getSnippetsFilters(), + ctx: ngfAPI.NginxContextHTTP, + expSnippets: expHTTPSnippets, + }, + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + snippets := buildSnippetsForContext(test.snippetsFilters, test.ctx) + g.Expect(snippets).To(ConsistOf(test.expSnippets)) + }, + ) } } diff --git a/internal/mode/static/state/dataplane/types.go b/internal/mode/static/state/dataplane/types.go index 59110f8cbb..b8c1786df5 100644 --- a/internal/mode/static/state/dataplane/types.go +++ b/internal/mode/static/state/dataplane/types.go @@ -38,6 +38,8 @@ type Configuration struct { StreamUpstreams []Upstream // BackendGroups holds all unique BackendGroups. BackendGroups []BackendGroup + // MainSnippets holds all the snippets that apply to the main context. + MainSnippets []Snippet // Telemetry holds the Otel configuration. Telemetry Telemetry // BaseHTTPConfig holds the configuration options at the http context. @@ -139,6 +141,18 @@ type HTTPFilters struct { RequestHeaderModifiers *HTTPHeaderFilter // ResponseHeaderModifiers holds the HTTPHeaderFilter. ResponseHeaderModifiers *HTTPHeaderFilter + // SnippetsFilters holds all the SnippetsFilters for the MatchRule. + // Unlike the core and extended filters, there can be more than on SnippetsFilters defined on a routing rule. + SnippetsFilters []SnippetsFilter +} + +// SnippetsFilter holds the location and server snippets in a SnippetsFilter. +// The main and http snippets are store separately in Configuration.MainSnippets and BaseHTTPConfig.Snippets. +type SnippetsFilter struct { + // LocationSnippet holds the snippet for the location context. + LocationSnippet *Snippet + // ServerSnippet holds the snippet for the location context. + ServerSnippet *Snippet } // HTTPHeader represents an HTTP header. @@ -309,13 +323,23 @@ type SpanAttribute struct { type BaseHTTPConfig struct { // IPFamily specifies the IP family for all servers. IPFamily IPFamilyType + // Snippets contain the snippets that apply to the http context. + Snippets []Snippet // RewriteIPSettings defines configuration for rewriting the client IP to the original client's IP. RewriteClientIPSettings RewriteClientIPSettings // HTTP2 specifies whether http2 should be enabled for all servers. HTTP2 bool } -// RewriteIPSettings defines configuration for rewriting the client IP to the original client's IP. +// Snippet is a snippet of configuration. +type Snippet struct { + // Name is the name of the snippet. + Name string + // Contents is the content of the snippet. + Contents string +} + +// RewriteClientIPSettings defines configuration for rewriting the client IP to the original client's IP. type RewriteClientIPSettings struct { // Mode specifies the mode for rewriting the client IP. Mode RewriteIPModeType diff --git a/internal/mode/static/state/graph/backend_refs.go b/internal/mode/static/state/graph/backend_refs.go index b65fd7cbcc..e8c5120b48 100644 --- a/internal/mode/static/state/graph/backend_refs.go +++ b/internal/mode/static/state/graph/backend_refs.go @@ -71,7 +71,7 @@ func addBackendRefsToRules( if !rule.ValidMatches { continue } - if !rule.ValidFilters { + if !rule.Filters.Valid { continue } @@ -326,7 +326,8 @@ func verifyIPFamily(npCfg *NginxProxy, svcIPFamily []v1.IPFamily) error { if *npIPFamily == ngfAPI.IPv4 { if slices.Contains(svcIPFamily, v1.IPv6Protocol) { // capitalizing error message to match the rest of the error messages associated with a condition - return errors.New( //nolint: stylecheck + //nolint: stylecheck + return errors.New( "Service configured with IPv6 family but NginxProxy is configured with IPv4", ) } @@ -334,7 +335,8 @@ func verifyIPFamily(npCfg *NginxProxy, svcIPFamily []v1.IPFamily) error { if *npIPFamily == ngfAPI.IPv6 { if slices.Contains(svcIPFamily, v1.IPv4Protocol) { // capitalizing error message to match the rest of the error messages associated with a condition - return errors.New( //nolint: stylecheck + //nolint: stylecheck + return errors.New( "Service configured with IPv4 family but NginxProxy is configured with IPv6", ) } diff --git a/internal/mode/static/state/graph/backend_refs_test.go b/internal/mode/static/state/graph/backend_refs_test.go index b4e5b2cf3c..fdd96b4d2c 100644 --- a/internal/mode/static/state/graph/backend_refs_test.go +++ b/internal/mode/static/state/graph/backend_refs_test.go @@ -72,10 +72,12 @@ func TestValidateRouteBackendRef(t *testing.T) { { name: "invalid base ref", ref: RouteBackendRef{ - BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Kind = helpers.GetPointer[gatewayv1.Kind]("NotService") - return backend - }), + BackendRef: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Kind = helpers.GetPointer[gatewayv1.Kind]("NotService") + return backend + }, + ), }, expectedValid: false, expectedCondition: staticConds.NewRouteBackendRefInvalidKind( @@ -85,16 +87,23 @@ func TestValidateRouteBackendRef(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - alwaysTrueRefGrantResolver := func(_ toResource) bool { return true } - - valid, cond := validateRouteBackendRef(test.ref, "test", alwaysTrueRefGrantResolver, field.NewPath("test")) - - g.Expect(valid).To(Equal(test.expectedValid)) - g.Expect(cond).To(Equal(test.expectedCondition)) - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + alwaysTrueRefGrantResolver := func(_ toResource) bool { return true } + + valid, cond := validateRouteBackendRef( + test.ref, + "test", + alwaysTrueRefGrantResolver, + field.NewPath("test"), + ) + + g.Expect(valid).To(Equal(test.expectedValid)) + g.Expect(cond).To(Equal(test.expectedCondition)) + }, + ) } } @@ -118,37 +127,45 @@ func TestValidateBackendRef(t *testing.T) { }, { name: "normal case with implicit namespace", - ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Namespace = nil - return backend - }), + ref: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Namespace = nil + return backend + }, + ), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: true, }, { name: "normal case with implicit kind Service", - ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Kind = nil - return backend - }), + ref: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Kind = nil + return backend + }, + ), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: true, }, { name: "normal case with backend ref allowed by reference grant", - ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Namespace = helpers.GetPointer[gatewayv1.Namespace]("cross-ns") - return backend - }), + ref: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Namespace = helpers.GetPointer[gatewayv1.Namespace]("cross-ns") + return backend + }, + ), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: true, }, { name: "invalid group", - ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Group = helpers.GetPointer[gatewayv1.Group]("invalid") - return backend - }), + ref: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Group = helpers.GetPointer[gatewayv1.Group]("invalid") + return backend + }, + ), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: false, expectedCondition: staticConds.NewRouteBackendRefInvalidKind( @@ -157,10 +174,12 @@ func TestValidateBackendRef(t *testing.T) { }, { name: "not a service kind", - ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Kind = helpers.GetPointer[gatewayv1.Kind]("NotService") - return backend - }), + ref: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Kind = helpers.GetPointer[gatewayv1.Kind]("NotService") + return backend + }, + ), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: false, expectedCondition: staticConds.NewRouteBackendRefInvalidKind( @@ -169,10 +188,12 @@ func TestValidateBackendRef(t *testing.T) { }, { name: "backend ref not allowed by reference grant", - ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Namespace = helpers.GetPointer[gatewayv1.Namespace]("invalid") - return backend - }), + ref: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Namespace = helpers.GetPointer[gatewayv1.Namespace]("invalid") + return backend + }, + ), refGrantResolver: alwaysFalseRefGrantResolver, expectedValid: false, expectedCondition: staticConds.NewRouteBackendRefRefNotPermitted( @@ -181,10 +202,12 @@ func TestValidateBackendRef(t *testing.T) { }, { name: "invalid weight", - ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Weight = helpers.GetPointer[int32](-1) - return backend - }), + ref: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Weight = helpers.GetPointer[int32](-1) + return backend + }, + ), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: false, expectedCondition: staticConds.NewRouteBackendRefUnsupportedValue( @@ -193,10 +216,12 @@ func TestValidateBackendRef(t *testing.T) { }, { name: "nil port", - ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Port = nil - return backend - }), + ref: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Port = nil + return backend + }, + ), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: false, expectedCondition: staticConds.NewRouteBackendRefUnsupportedValue( @@ -206,15 +231,17 @@ func TestValidateBackendRef(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - valid, cond := validateBackendRef(test.ref, "test", test.refGrantResolver, field.NewPath("test")) + valid, cond := validateBackendRef(test.ref, "test", test.refGrantResolver, field.NewPath("test")) - g.Expect(valid).To(Equal(test.expectedValid)) - g.Expect(cond).To(Equal(test.expectedCondition)) - }) + g.Expect(valid).To(Equal(test.expectedValid)) + g.Expect(cond).To(Equal(test.expectedCondition)) + }, + ) } } @@ -276,10 +303,12 @@ func TestGetIPFamilyAndPortFromRef(t *testing.T) { }, { name: "service does not exist", - ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Name = "does-not-exist" - return backend - }), + ref: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Name = "does-not-exist" + return backend + }, + ), expErr: true, expServicePort: v1.ServicePort{}, expSvcIPFamily: []v1.IPFamily{}, @@ -287,10 +316,12 @@ func TestGetIPFamilyAndPortFromRef(t *testing.T) { }, { 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 - }), + ref: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Port = helpers.GetPointer[gatewayv1.PortNumber](504) + return backend + }, + ), expErr: true, expServicePort: v1.ServicePort{}, expSvcIPFamily: []v1.IPFamily{}, @@ -306,16 +337,18 @@ func TestGetIPFamilyAndPortFromRef(t *testing.T) { refPath := field.NewPath("test") for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - svcIPFamily, servicePort, err := getIPFamilyAndPortFromRef(test.ref, test.svcNsName, services, refPath) + svcIPFamily, servicePort, err := getIPFamilyAndPortFromRef(test.ref, test.svcNsName, services, refPath) - g.Expect(err != nil).To(Equal(test.expErr)) - g.Expect(servicePort).To(Equal(test.expServicePort)) - g.Expect(svcIPFamily).To(Equal(test.expSvcIPFamily)) - }) + g.Expect(err != nil).To(Equal(test.expErr)) + g.Expect(servicePort).To(Equal(test.expServicePort)) + g.Expect(svcIPFamily).To(Equal(test.expSvcIPFamily)) + }, + ) } } @@ -384,16 +417,18 @@ func TestVerifyIPFamily(t *testing.T) { } for _, test := range test { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - err := verifyIPFamily(test.npCfg, test.svcIPFamily) - if test.expErr != nil { - g.Expect(err).To(Equal(test.expErr)) - } else { - g.Expect(err).ToNot(HaveOccurred()) - } - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + err := verifyIPFamily(test.npCfg, test.svcIPFamily) + if test.expErr != nil { + g.Expect(err).To(Equal(test.expErr)) + } else { + g.Expect(err).ToNot(HaveOccurred()) + } + }, + ) } } @@ -455,7 +490,10 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { hr.Spec.Rules[idx] = RouteRule{ RouteBackendRefs: refs, ValidMatches: true, - ValidFilters: true, + Filters: RouteRuleFilters{ + Filters: []Filter{}, + Valid: true, + }, } } return hr @@ -476,7 +514,7 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { hrWithOneBackendInvalidMatches.Spec.Rules[0].ValidMatches = false hrWithOneBackendInvalidFilters := createRoute("hr1", "Service", 1, "svc1") - hrWithOneBackendInvalidFilters.Spec.Rules[0].ValidFilters = false + hrWithOneBackendInvalidFilters.Spec.Rules[0].Filters = RouteRuleFilters{Valid: false} getSvc := func(name string) *v1.Service { return &v1.Service{ @@ -598,12 +636,13 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { btp1 := getBtp("btp1", "svc1", "test1") btp2 := getBtp("btp2", "svc2", "test2") btp3 := getBtp("btp1", "svc1", "test") - btp3.Conditions = append(btp3.Conditions, conditions.Condition{ - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "Policy is accepted", - }, + btp3.Conditions = append( + btp3.Conditions, conditions.Condition{ + Type: "Accepted", + Status: "True", + Reason: "Accepted", + Message: "Policy is accepted", + }, ) tests := []struct { @@ -740,19 +779,21 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - g := NewWithT(t) - resolver := newReferenceGrantResolver(nil) - addBackendRefsToRules(test.route, resolver, services, test.policies, nil) - - var actual []BackendRef - if test.route.Spec.Rules != nil { - actual = test.route.Spec.Rules[0].BackendRefs - } - - g.Expect(helpers.Diff(test.expectedBackendRefs, actual)).To(BeEmpty()) - g.Expect(test.route.Conditions).To(Equal(test.expectedConditions)) - }) + t.Run( + test.name, func(t *testing.T) { + g := NewWithT(t) + resolver := newReferenceGrantResolver(nil) + addBackendRefsToRules(test.route, resolver, services, test.policies, nil) + + var actual []BackendRef + if test.route.Spec.Rules != nil { + actual = test.route.Spec.Rules[0].BackendRefs + } + + g.Expect(helpers.Diff(test.expectedBackendRefs, actual)).To(BeEmpty()) + g.Expect(test.route.Conditions).To(Equal(test.expectedConditions)) + }, + ) } } @@ -858,10 +899,12 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Weight = nil - return backend - }), + BackendRef: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Weight = nil + return backend + }, + ), }, expectedBackend: BackendRef{ SvcNsName: svc1NamespacedName, @@ -875,10 +918,12 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Weight = helpers.GetPointer[int32](-1) - return backend - }), + BackendRef: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Weight = helpers.GetPointer[int32](-1) + return backend + }, + ), }, expectedBackend: BackendRef{ SvcNsName: types.NamespacedName{}, @@ -896,10 +941,12 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Kind = helpers.GetPointer[gatewayv1.Kind]("NotService") - return backend - }), + BackendRef: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Kind = helpers.GetPointer[gatewayv1.Kind]("NotService") + return backend + }, + ), }, expectedBackend: BackendRef{ SvcNsName: types.NamespacedName{}, @@ -917,10 +964,12 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Name = "not-exist" - return backend - }), + BackendRef: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Name = "not-exist" + return backend + }, + ), }, expectedBackend: BackendRef{ Weight: 5, @@ -938,10 +987,12 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Name = "service2" - return backend - }), + BackendRef: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Name = "service2" + return backend + }, + ), }, expectedBackend: BackendRef{ SvcNsName: svc2NamespacedName, @@ -962,10 +1013,12 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Name = "service2" - return backend - }), + BackendRef: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Name = "service2" + return backend + }, + ), }, expectedBackend: BackendRef{ SvcNsName: svc2NamespacedName, @@ -980,10 +1033,12 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Name = "service3" - return backend - }), + BackendRef: getModifiedRef( + func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Name = "service3" + return backend + }, + ), }, expectedBackend: BackendRef{ SvcNsName: svc3NamespacedName, @@ -1016,32 +1071,34 @@ func TestCreateBackend(t *testing.T) { refPath := field.NewPath("test") for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - alwaysTrueRefGrantResolver := func(_ toResource) bool { return true } - - rbr := RouteBackendRef{ - test.ref.BackendRef, - []any{}, - } - backend, cond := createBackendRef( - rbr, - sourceNamespace, - alwaysTrueRefGrantResolver, - services, - refPath, - policies, - test.nginxProxy, - ) - - g.Expect(helpers.Diff(test.expectedBackend, backend)).To(BeEmpty()) - g.Expect(cond).To(Equal(test.expectedCondition)) - - servicePortRef := backend.ServicePortReference() - g.Expect(servicePortRef).To(Equal(test.expectedServicePortReference)) - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + alwaysTrueRefGrantResolver := func(_ toResource) bool { return true } + + rbr := RouteBackendRef{ + test.ref.BackendRef, + []any{}, + } + backend, cond := createBackendRef( + rbr, + sourceNamespace, + alwaysTrueRefGrantResolver, + services, + refPath, + policies, + test.nginxProxy, + ) + + g.Expect(helpers.Diff(test.expectedBackend, backend)).To(BeEmpty()) + g.Expect(cond).To(Equal(test.expectedCondition)) + + servicePortRef := backend.ServicePortReference() + g.Expect(servicePortRef).To(Equal(test.expectedServicePortReference)) + }, + ) } } @@ -1168,14 +1225,16 @@ func TestValidateBackendTLSPolicyMatchingAllBackends(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - cond := validateBackendTLSPolicyMatchingAllBackends(test.backendRefs) + cond := validateBackendTLSPolicyMatchingAllBackends(test.backendRefs) - g.Expect(cond).To(Equal(test.expectedCondition)) - }) + g.Expect(cond).To(Equal(test.expectedCondition)) + }, + ) } } @@ -1245,15 +1304,22 @@ func TestFindBackendTLSPolicyForService(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - btp, err := findBackendTLSPolicyForService(test.backendTLSPolicies, ref.Namespace, string(ref.Name), "test") - - g.Expect(btp.Source.Name).To(Equal(test.expectedBtpName)) - g.Expect(err).ToNot(HaveOccurred()) - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + btp, err := findBackendTLSPolicyForService( + test.backendTLSPolicies, + ref.Namespace, + string(ref.Name), + "test", + ) + + g.Expect(btp.Source.Name).To(Equal(test.expectedBtpName)) + g.Expect(err).ToNot(HaveOccurred()) + }, + ) } } @@ -1280,11 +1346,13 @@ func TestGetRefGrantFromResourceForRoute(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - g.Expect(getRefGrantFromResourceForRoute(test.routeType, test.ns)).To(Equal(test.expFromResource)) - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + g.Expect(getRefGrantFromResourceForRoute(test.routeType, test.ns)).To(Equal(test.expFromResource)) + }, + ) } } diff --git a/internal/mode/static/state/graph/common_filter.go b/internal/mode/static/state/graph/common_filter.go new file mode 100644 index 0000000000..3a9b25b5d9 --- /dev/null +++ b/internal/mode/static/state/graph/common_filter.go @@ -0,0 +1,396 @@ +package graph + +import ( + "fmt" + "slices" + "strings" + + "k8s.io/apimachinery/pkg/util/validation/field" + v1 "sigs.k8s.io/gateway-api/apis/v1" + + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation" +) + +// RouteRuleFilters holds the Filters for a RouteRule. +type RouteRuleFilters struct { + // Filters are the filters in the RouteRule. + Filters []Filter + // Valid indicates if the filters are valid and accepted by the Route. + Valid bool +} + +// Filter is a filter in a Route. The Filter can belong to a GRPCRoute or an HTTPRoute. +type Filter struct { + // RequestHeaderModifier holds an HTTP Request Header Modifier filter. + // Will be non-nil if FilterType is FilterRequestHeaderModifier. + // Can be set on GRPCRoutes and HTTPRoutes. + RequestHeaderModifier *v1.HTTPHeaderFilter + // ResponseHeaderModifier holds an HTTP Response Header Modifier filter. + // Will be non-nil if FilterType is FilterResponseHeaderModifier. + // Can be set on GRPCRoutes and HTTPRoutes. + ResponseHeaderModifier *v1.HTTPHeaderFilter + // RequestRedirect holds an HTTP Request Redirect filter. + // Will be non-nil if FilterType is FilterRequestRedirect. + // Can be set on HTTPRoutes only. + RequestRedirect *v1.HTTPRequestRedirectFilter + // URLRewrite holds an HTTP URL Rewrite filter. + // Will be non-nil if FilterType is FilterURLRewrite. + // Can be set on HTTPRoutes only. + URLRewrite *v1.HTTPURLRewriteFilter + // RequestMirror holds an HTTP Request Mirror filter. + // Will be non-nil if FilterType is FilterRequestMirror. + // Can be set on GRPCRoutes and HTTPRoutes. + RequestMirror *v1.HTTPRequestMirrorFilter + // ExtensionRef holds an Extension Ref filter. + // Will be non-nil if FilterType is FilterExtensionRef. + // Can be set on GRPCRoutes and HTTPRoutes. + ExtensionRef *v1.LocalObjectReference + // ResolvedExtensionRef holds the filter that the Extension Ref points to. + // Will be non-nil if the Extension Ref is non-nil and was resolved successfully. + // Can be set on GRPCRoutes and HTTPRoutes. + ResolvedExtensionRef *ExtensionRefFilter + // RouteType is the type of Route that this filter is on. + RouteType RouteType + // FilterType is the type of filter. + FilterType FilterType +} + +// FilterType is the type of filter. +type FilterType string + +// The following FilterTypes are supported by GRPCRoutes and HTTPRoutes. +const ( + FilterRequestHeaderModifier = FilterType(v1.HTTPRouteFilterRequestHeaderModifier) + FilterResponseHeaderModifier = FilterType(v1.HTTPRouteFilterResponseHeaderModifier) + FilterExtensionRef = FilterType(v1.HTTPRouteFilterExtensionRef) + FilterRequestMirror = FilterType(v1.HTTPRouteFilterRequestMirror) +) + +// The following FilterTypes are supported by HTTPRoutes only. +const ( + FilterRequestRedirect = FilterType(v1.HTTPRouteFilterRequestRedirect) + FilterURLRewrite = FilterType(v1.HTTPRouteFilterURLRewrite) +) + +func convertHTTPRouteFilters(filters []v1.HTTPRouteFilter) []Filter { + routeFilters := make([]Filter, 0, len(filters)) + + for _, filter := range filters { + routeFilters = append( + routeFilters, Filter{ + RouteType: RouteTypeHTTP, + FilterType: FilterType(filter.Type), + RequestHeaderModifier: filter.RequestHeaderModifier, + ResponseHeaderModifier: filter.ResponseHeaderModifier, + RequestRedirect: filter.RequestRedirect, + URLRewrite: filter.URLRewrite, + RequestMirror: filter.RequestMirror, + ExtensionRef: filter.ExtensionRef, + }, + ) + } + + return routeFilters +} + +func convertGRPCRouteFilters(filters []v1.GRPCRouteFilter) []Filter { + routeFilters := make([]Filter, 0, len(filters)) + + for _, filter := range filters { + routeFilters = append( + routeFilters, Filter{ + RouteType: RouteTypeGRPC, + FilterType: FilterType(filter.Type), + RequestHeaderModifier: filter.RequestHeaderModifier, + ResponseHeaderModifier: filter.ResponseHeaderModifier, + RequestMirror: filter.RequestMirror, + ExtensionRef: filter.ExtensionRef, + }, + ) + } + + return routeFilters +} + +func processRouteRuleFilters( + filters []Filter, + path *field.Path, + validator validation.HTTPFieldsValidator, + resolveExtRefFunc resolveExtRefFilter, +) (RouteRuleFilters, routeRuleErrors) { + errors := routeRuleErrors{} + valid := true + + for i, f := range filters { + filterPath := path.Index(i) + + validateErrs := validateFilter(validator, f, filterPath) + if len(validateErrs) > 0 { + errors.invalid = append(errors.invalid, validateErrs...) + valid = false + continue + } + + if f.FilterType == FilterExtensionRef && f.ExtensionRef != nil { + resolved := resolveExtRefFunc(*f.ExtensionRef) + + if resolved == nil { + err := field.NotFound(filterPath.Child("extensionRef"), f.ExtensionRef) + errors.resolve = append(errors.resolve, err) + valid = false + + continue + } + + if !resolved.Valid { + err := field.Invalid( + filterPath.Child("extensionRef"), + f.ExtensionRef, + "referenced filter is invalid. See filter status for more details.", + ) + errors.resolve = append(errors.resolve, err) + valid = false + + continue + } + + filters[i].ResolvedExtensionRef = resolved + } + } + + return RouteRuleFilters{Valid: valid, Filters: filters}, errors +} + +var supportedGRPCFilterTypes = []FilterType{ + FilterResponseHeaderModifier, + FilterRequestHeaderModifier, + FilterExtensionRef, +} + +var supportedHTTPFilterTypes = []FilterType{ + FilterResponseHeaderModifier, + FilterRequestHeaderModifier, + FilterExtensionRef, + FilterRequestRedirect, + FilterURLRewrite, +} + +func validateFilterType(filter Filter, filterPath *field.Path) *field.Error { + if filter.RouteType == RouteTypeGRPC && !slices.Contains(supportedGRPCFilterTypes, filter.FilterType) { + return field.NotSupported(filterPath.Child("type"), filter.FilterType, supportedGRPCFilterTypes) + } + + if !slices.Contains(supportedHTTPFilterTypes, filter.FilterType) { + return field.NotSupported(filterPath.Child("type"), filter.FilterType, supportedHTTPFilterTypes) + } + + return nil +} + +func validateFilter( + validator validation.HTTPFieldsValidator, + filter Filter, + filterPath *field.Path, +) field.ErrorList { + var allErrs field.ErrorList + + if err := validateFilterType(filter, filterPath); err != nil { + allErrs = append(allErrs, err) + return allErrs + } + + switch filter.FilterType { + case FilterRequestRedirect: + return validateFilterRedirect(validator, filter.RequestRedirect, filterPath) + case FilterURLRewrite: + return validateFilterRewrite(validator, filter.URLRewrite, filterPath) + case FilterRequestHeaderModifier: + return validateFilterHeaderModifier( + validator, + filter.RequestHeaderModifier, + filterPath.Child(string(filter.FilterType)), + ) + case FilterResponseHeaderModifier: + return validateFilterResponseHeaderModifier( + validator, + filter.ResponseHeaderModifier, + filterPath.Child(string(filter.FilterType)), + ) + case FilterExtensionRef: + return validateExtensionRefFilter(filter.ExtensionRef, filterPath) + default: + panic(fmt.Sprintf("unexpected filter type %v", filter.FilterType)) + } +} + +func validateFilterHeaderModifier( + validator validation.HTTPFieldsValidator, + headerModifier *v1.HTTPHeaderFilter, + filterPath *field.Path, +) field.ErrorList { + if headerModifier == nil { + return field.ErrorList{field.Required(filterPath, "cannot be nil")} + } + + return validateFilterHeaderModifierFields(validator, headerModifier, filterPath) +} + +func validateFilterHeaderModifierFields( + validator validation.HTTPFieldsValidator, + headerModifier *v1.HTTPHeaderFilter, + headerModifierPath *field.Path, +) field.ErrorList { + var allErrs field.ErrorList + + // Ensure that the header names are case-insensitive unique + allErrs = append( + allErrs, validateRequestHeadersCaseInsensitiveUnique( + headerModifier.Add, + headerModifierPath.Child(add), + )..., + ) + allErrs = append( + allErrs, validateRequestHeadersCaseInsensitiveUnique( + headerModifier.Set, + headerModifierPath.Child(set), + )..., + ) + allErrs = append( + allErrs, validateRequestHeaderStringCaseInsensitiveUnique( + headerModifier.Remove, + headerModifierPath.Child(remove), + )..., + ) + + for _, h := range headerModifier.Add { + if err := validator.ValidateFilterHeaderName(string(h.Name)); err != nil { + valErr := field.Invalid(headerModifierPath.Child(add), h, err.Error()) + allErrs = append(allErrs, valErr) + } + if err := validator.ValidateFilterHeaderValue(h.Value); err != nil { + valErr := field.Invalid(headerModifierPath.Child(add), h, err.Error()) + allErrs = append(allErrs, valErr) + } + } + for _, h := range headerModifier.Set { + if err := validator.ValidateFilterHeaderName(string(h.Name)); err != nil { + valErr := field.Invalid(headerModifierPath.Child(set), h, err.Error()) + allErrs = append(allErrs, valErr) + } + if err := validator.ValidateFilterHeaderValue(h.Value); err != nil { + valErr := field.Invalid(headerModifierPath.Child(set), h, err.Error()) + allErrs = append(allErrs, valErr) + } + } + for _, h := range headerModifier.Remove { + if err := validator.ValidateFilterHeaderName(h); err != nil { + valErr := field.Invalid(headerModifierPath.Child(remove), h, err.Error()) + allErrs = append(allErrs, valErr) + } + } + + return allErrs +} + +func validateFilterResponseHeaderModifier( + validator validation.HTTPFieldsValidator, + responseHeaderModifier *v1.HTTPHeaderFilter, + filterPath *field.Path, +) field.ErrorList { + if errList := validateFilterHeaderModifier(validator, responseHeaderModifier, filterPath); errList != nil { + return errList + } + var allErrs field.ErrorList + + allErrs = append( + allErrs, validateResponseHeaders( + responseHeaderModifier.Add, + filterPath.Child(add), + )..., + ) + + allErrs = append( + allErrs, validateResponseHeaders( + responseHeaderModifier.Set, + filterPath.Child(set), + )..., + ) + + var removeHeaders []v1.HTTPHeader + for _, h := range responseHeaderModifier.Remove { + removeHeaders = append(removeHeaders, v1.HTTPHeader{Name: v1.HTTPHeaderName(h)}) + } + + allErrs = append( + allErrs, validateResponseHeaders( + removeHeaders, + filterPath.Child(remove), + )..., + ) + + return allErrs +} + +func validateResponseHeaders( + headers []v1.HTTPHeader, + path *field.Path, +) field.ErrorList { + var allErrs field.ErrorList + disallowedResponseHeaderSet := map[string]struct{}{ + "server": {}, + "date": {}, + "x-pad": {}, + "content-type": {}, + "content-length": {}, + "connection": {}, + } + invalidPrefix := "x-accel" + + for _, h := range headers { + valErr := field.Invalid(path, h, "header name is not allowed") + name := strings.ToLower(string(h.Name)) + if _, exists := disallowedResponseHeaderSet[name]; exists || + strings.HasPrefix(name, strings.ToLower(invalidPrefix)) { + allErrs = append(allErrs, valErr) + } + } + + return allErrs +} + +func validateRequestHeadersCaseInsensitiveUnique( + headers []v1.HTTPHeader, + path *field.Path, +) field.ErrorList { + var allErrs field.ErrorList + + seen := make(map[string]struct{}) + + for _, h := range headers { + name := strings.ToLower(string(h.Name)) + if _, exists := seen[name]; exists { + valErr := field.Invalid(path, h, "header name is not unique") + allErrs = append(allErrs, valErr) + } + seen[name] = struct{}{} + } + + return allErrs +} + +func validateRequestHeaderStringCaseInsensitiveUnique(headers []string, path *field.Path) field.ErrorList { + var allErrs field.ErrorList + + seen := make(map[string]struct{}) + + for _, h := range headers { + name := strings.ToLower(h) + if _, exists := seen[name]; exists { + valErr := field.Invalid(path, h, "header name is not unique") + allErrs = append(allErrs, valErr) + } + seen[name] = struct{}{} + } + + return allErrs +} diff --git a/internal/mode/static/state/graph/common_filter_test.go b/internal/mode/static/state/graph/common_filter_test.go new file mode 100644 index 0000000000..02412beb9b --- /dev/null +++ b/internal/mode/static/state/graph/common_filter_test.go @@ -0,0 +1,671 @@ +package graph + +import ( + "errors" + "testing" + + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/util/validation/field" + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + + ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/kinds" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation/validationfakes" +) + +func TestValidateFilter(t *testing.T) { + t.Parallel() + + tests := []struct { + filter Filter + name string + expectErrCount int + }{ + { + filter: Filter{ + RouteType: RouteTypeHTTP, + FilterType: FilterRequestRedirect, + RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{}, + }, + expectErrCount: 0, + name: "valid HTTP redirect filter", + }, + { + filter: Filter{ + RouteType: RouteTypeHTTP, + FilterType: FilterURLRewrite, + URLRewrite: &gatewayv1.HTTPURLRewriteFilter{}, + }, + expectErrCount: 0, + name: "valid HTTP rewrite filter", + }, + { + filter: Filter{ + RouteType: RouteTypeHTTP, + FilterType: FilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{}, + }, + expectErrCount: 0, + name: "valid HTTP request header modifiers filter", + }, + { + filter: Filter{ + RouteType: RouteTypeHTTP, + FilterType: FilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{}, + }, + expectErrCount: 0, + name: "valid HTTP response header modifiers filter", + }, + { + filter: Filter{ + RouteType: RouteTypeHTTP, + FilterType: FilterExtensionRef, + ExtensionRef: &gatewayv1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: "sf", + }, + }, + expectErrCount: 0, + name: "valid HTTP extension ref filter", + }, + { + filter: Filter{ + RouteType: RouteTypeHTTP, + FilterType: "RequestMirror", + }, + expectErrCount: 1, + name: "unsupported HTTP filter type", + }, + { + filter: Filter{ + RouteType: RouteTypeGRPC, + FilterType: FilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{}, + }, + expectErrCount: 0, + name: "valid GRPC request header modifiers filter", + }, + { + filter: Filter{ + RouteType: RouteTypeGRPC, + FilterType: FilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{}, + }, + expectErrCount: 0, + name: "valid GRPC response header modifiers filter", + }, + { + filter: Filter{ + RouteType: RouteTypeGRPC, + FilterType: FilterExtensionRef, + ExtensionRef: &gatewayv1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: "sf", + }, + }, + expectErrCount: 0, + name: "valid GRPC extension ref filter", + }, + { + filter: Filter{ + RouteType: RouteTypeGRPC, + FilterType: FilterURLRewrite, + }, + expectErrCount: 1, + name: "unsupported GRPC filter type", + }, + } + + filterPath := field.NewPath("test") + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + allErrs := validateFilter(&validationfakes.FakeHTTPFieldsValidator{}, test.filter, filterPath) + g.Expect(allErrs).To(HaveLen(test.expectErrCount)) + }, + ) + } +} + +func TestValidateFilterResponseHeaderModifier(t *testing.T) { + t.Parallel() + + createAllValidValidator := func() *validationfakes.FakeHTTPFieldsValidator { + v := &validationfakes.FakeHTTPFieldsValidator{} + return v + } + + tests := []struct { + filter gatewayv1.HTTPRouteFilter + validator *validationfakes.FakeHTTPFieldsValidator + name string + expectErrCount int + }{ + { + validator: createAllValidValidator(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Set: []gatewayv1.HTTPHeader{ + {Name: "MyBespokeHeader", Value: "my-value"}, + }, + Add: []gatewayv1.HTTPHeader{ + {Name: "Accept-Encoding", Value: "gzip"}, + }, + Remove: []string{"Cache-Control"}, + }, + }, + expectErrCount: 0, + name: "valid response header modifier filter", + }, + { + validator: createAllValidValidator(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: nil, + }, + expectErrCount: 1, + name: "nil response header modifier filter", + }, + { + validator: func() *validationfakes.FakeHTTPFieldsValidator { + v := createAllValidValidator() + v.ValidateFilterHeaderNameReturns(errors.New("Invalid header")) + return v + }(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Add: []gatewayv1.HTTPHeader{ + {Name: "$var_name", Value: "gzip"}, + }, + }, + }, + expectErrCount: 1, + name: "response header modifier filter with invalid add", + }, + { + validator: func() *validationfakes.FakeHTTPFieldsValidator { + v := createAllValidValidator() + v.ValidateFilterHeaderNameReturns(errors.New("Invalid header")) + return v + }(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Remove: []string{"$var-name"}, + }, + }, + expectErrCount: 1, + name: "response header modifier filter with invalid remove", + }, + { + validator: func() *validationfakes.FakeHTTPFieldsValidator { + v := createAllValidValidator() + v.ValidateFilterHeaderValueReturns(errors.New("Invalid header value")) + return v + }(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Add: []gatewayv1.HTTPHeader{ + {Name: "Accept-Encoding", Value: "yhu$"}, + }, + }, + }, + expectErrCount: 1, + name: "response header modifier filter with invalid header value", + }, + { + validator: func() *validationfakes.FakeHTTPFieldsValidator { + v := createAllValidValidator() + v.ValidateFilterHeaderValueReturns(errors.New("Invalid header value")) + v.ValidateFilterHeaderNameReturns(errors.New("Invalid header")) + return v + }(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Set: []gatewayv1.HTTPHeader{ + {Name: "Host", Value: "my_host"}, + }, + Add: []gatewayv1.HTTPHeader{ + {Name: "}90yh&$", Value: "gzip$"}, + {Name: "}67yh&$", Value: "compress$"}, + }, + Remove: []string{"Cache-Control$}"}, + }, + }, + expectErrCount: 7, + name: "response header modifier filter all fields invalid", + }, + { + validator: createAllValidValidator(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Set: []gatewayv1.HTTPHeader{ + {Name: "MyBespokeHeader", Value: "my-value"}, + {Name: "mYbespokeHEader", Value: "duplicate"}, + }, + Add: []gatewayv1.HTTPHeader{ + {Name: "Accept-Encoding", Value: "gzip"}, + {Name: "accept-encodING", Value: "gzip"}, + }, + Remove: []string{"Cache-Control", "cache-control"}, + }, + }, + expectErrCount: 3, + name: "response header modifier filter not unique names", + }, + { + validator: createAllValidValidator(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Set: []gatewayv1.HTTPHeader{ + {Name: "Content-Length", Value: "163"}, + }, + Add: []gatewayv1.HTTPHeader{ + {Name: "Content-Type", Value: "text/plain"}, + }, + Remove: []string{"X-Pad"}, + }, + }, + expectErrCount: 3, + name: "response header modifier filter with disallowed header name", + }, + { + validator: createAllValidValidator(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Set: []gatewayv1.HTTPHeader{ + {Name: "X-Accel-Redirect", Value: "/protected/iso.img"}, + }, + Add: []gatewayv1.HTTPHeader{ + {Name: "X-Accel-Limit-Rate", Value: "1024"}, + }, + Remove: []string{"X-Accel-Charset"}, + }, + }, + expectErrCount: 3, + name: "response header modifier filter with disallowed header name prefix", + }, + } + + filterPath := field.NewPath("test") + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + allErrs := validateFilterResponseHeaderModifier( + test.validator, test.filter.ResponseHeaderModifier, filterPath, + ) + g.Expect(allErrs).To(HaveLen(test.expectErrCount)) + }, + ) + } +} + +func TestValidateFilterRequestHeaderModifier(t *testing.T) { + t.Parallel() + + createAllValidValidator := func() *validationfakes.FakeHTTPFieldsValidator { + v := &validationfakes.FakeHTTPFieldsValidator{} + return v + } + + tests := []struct { + filter gatewayv1.HTTPRouteFilter + validator *validationfakes.FakeHTTPFieldsValidator + name string + expectErrCount int + }{ + { + validator: createAllValidValidator(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Set: []gatewayv1.HTTPHeader{ + {Name: "MyBespokeHeader", Value: "my-value"}, + }, + Add: []gatewayv1.HTTPHeader{ + {Name: "Accept-Encoding", Value: "gzip"}, + }, + Remove: []string{"Cache-Control"}, + }, + }, + expectErrCount: 0, + name: "valid request header modifier filter", + }, + { + validator: createAllValidValidator(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: nil, + }, + expectErrCount: 1, + name: "nil request header modifier filter", + }, + { + validator: func() *validationfakes.FakeHTTPFieldsValidator { + v := createAllValidValidator() + v.ValidateFilterHeaderNameReturns(errors.New("Invalid header")) + return v + }(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Add: []gatewayv1.HTTPHeader{ + {Name: "$var_name", Value: "gzip"}, + }, + }, + }, + expectErrCount: 1, + name: "request header modifier filter with invalid add", + }, + { + validator: func() *validationfakes.FakeHTTPFieldsValidator { + v := createAllValidValidator() + v.ValidateFilterHeaderNameReturns(errors.New("Invalid header")) + return v + }(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Remove: []string{"$var-name"}, + }, + }, + expectErrCount: 1, + name: "request header modifier filter with invalid remove", + }, + { + validator: func() *validationfakes.FakeHTTPFieldsValidator { + v := createAllValidValidator() + v.ValidateFilterHeaderValueReturns(errors.New("Invalid header value")) + return v + }(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Add: []gatewayv1.HTTPHeader{ + {Name: "Accept-Encoding", Value: "yhu$"}, + }, + }, + }, + expectErrCount: 1, + name: "request header modifier filter with invalid header value", + }, + { + validator: func() *validationfakes.FakeHTTPFieldsValidator { + v := createAllValidValidator() + v.ValidateFilterHeaderValueReturns(errors.New("Invalid header value")) + v.ValidateFilterHeaderNameReturns(errors.New("Invalid header")) + return v + }(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Set: []gatewayv1.HTTPHeader{ + {Name: "Host", Value: "my_host"}, + }, + Add: []gatewayv1.HTTPHeader{ + {Name: "}90yh&$", Value: "gzip$"}, + {Name: "}67yh&$", Value: "compress$"}, + }, + Remove: []string{"Cache-Control$}"}, + }, + }, + expectErrCount: 7, + name: "request header modifier filter all fields invalid", + }, + { + validator: createAllValidValidator(), + filter: gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{ + Set: []gatewayv1.HTTPHeader{ + {Name: "MyBespokeHeader", Value: "my-value"}, + {Name: "mYbespokeHEader", Value: "duplicate"}, + }, + Add: []gatewayv1.HTTPHeader{ + {Name: "Accept-Encoding", Value: "gzip"}, + {Name: "accept-encodING", Value: "gzip"}, + }, + Remove: []string{"Cache-Control", "cache-control"}, + }, + }, + expectErrCount: 3, + name: "request header modifier filter not unique names", + }, + } + + filterPath := field.NewPath("test") + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + allErrs := validateFilterHeaderModifier( + test.validator, test.filter.RequestHeaderModifier, filterPath, + ) + g.Expect(allErrs).To(HaveLen(test.expectErrCount)) + }, + ) + } +} + +func TestConvertGRPCFilters(t *testing.T) { + t.Parallel() + + requestHeaderFilter1 := &gatewayv1.HTTPHeaderFilter{ + Remove: []string{"request-1"}, + } + requestHeaderFilter2 := &gatewayv1.HTTPHeaderFilter{ + Remove: []string{"request-2"}, + } + + tests := []struct { + name string + grpcFilters []gatewayv1.GRPCRouteFilter + expFilters []Filter + }{ + { + name: "nil filters", + grpcFilters: nil, + expFilters: []Filter{}, + }, + { + name: "empty filters", + grpcFilters: []gatewayv1.GRPCRouteFilter{}, + expFilters: []Filter{}, + }, + { + name: "all filter types", + grpcFilters: []gatewayv1.GRPCRouteFilter{ + { + Type: gatewayv1.GRPCRouteFilterRequestHeaderModifier, + RequestHeaderModifier: requestHeaderFilter1, + }, + { + Type: gatewayv1.GRPCRouteFilterRequestHeaderModifier, + RequestHeaderModifier: requestHeaderFilter2, // duplicates are added + }, + { + Type: gatewayv1.GRPCRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{}, + }, + { + Type: gatewayv1.GRPCRouteFilterRequestMirror, + RequestMirror: &gatewayv1.HTTPRequestMirrorFilter{}, + }, + { + Type: gatewayv1.GRPCRouteFilterExtensionRef, + ExtensionRef: &gatewayv1.LocalObjectReference{}, + }, + }, + expFilters: []Filter{ + { + RouteType: RouteTypeGRPC, + FilterType: FilterRequestHeaderModifier, + RequestHeaderModifier: requestHeaderFilter1, + }, + { + RouteType: RouteTypeGRPC, + FilterType: FilterRequestHeaderModifier, + RequestHeaderModifier: requestHeaderFilter2, + }, + { + RouteType: RouteTypeGRPC, + FilterType: FilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{}, + }, + { + RouteType: RouteTypeGRPC, + FilterType: FilterRequestMirror, + RequestMirror: &gatewayv1.HTTPRequestMirrorFilter{}, + }, + { + RouteType: RouteTypeGRPC, + FilterType: FilterExtensionRef, + ExtensionRef: &gatewayv1.LocalObjectReference{}, + }, + }, + }, + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + convertedFilters := convertGRPCRouteFilters(test.grpcFilters) + g.Expect(convertedFilters).To(Equal(test.expFilters)) + }, + ) + } +} + +func TestConvertHTTPFilters(t *testing.T) { + t.Parallel() + + requestHeaderFilter1 := &gatewayv1.HTTPHeaderFilter{ + Remove: []string{"request-1"}, + } + requestHeaderFilter2 := &gatewayv1.HTTPHeaderFilter{ + Remove: []string{"request-2"}, + } + + tests := []struct { + name string + httpFilters []gatewayv1.HTTPRouteFilter + expFilters []Filter + }{ + { + name: "nil filters", + httpFilters: nil, + expFilters: []Filter{}, + }, + { + name: "empty filters", + httpFilters: []gatewayv1.HTTPRouteFilter{}, + expFilters: []Filter{}, + }, + { + name: "all filter types", + httpFilters: []gatewayv1.HTTPRouteFilter{ + { + Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: requestHeaderFilter1, + }, + { + Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, + RequestHeaderModifier: requestHeaderFilter2, // duplicates are added + }, + { + Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{}, + }, + { + Type: gatewayv1.HTTPRouteFilterRequestRedirect, + RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{}, + }, + { + Type: gatewayv1.HTTPRouteFilterURLRewrite, + URLRewrite: &gatewayv1.HTTPURLRewriteFilter{}, + }, + { + Type: gatewayv1.HTTPRouteFilterRequestMirror, + RequestMirror: &gatewayv1.HTTPRequestMirrorFilter{}, + }, + { + Type: gatewayv1.HTTPRouteFilterExtensionRef, + ExtensionRef: &gatewayv1.LocalObjectReference{}, + }, + }, + expFilters: []Filter{ + { + RouteType: RouteTypeHTTP, + FilterType: FilterRequestHeaderModifier, + RequestHeaderModifier: requestHeaderFilter1, + }, + { + RouteType: RouteTypeHTTP, + FilterType: FilterRequestHeaderModifier, + RequestHeaderModifier: requestHeaderFilter2, + }, + { + RouteType: RouteTypeHTTP, + FilterType: FilterResponseHeaderModifier, + ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{}, + }, + { + RouteType: RouteTypeHTTP, + FilterType: FilterRequestRedirect, + RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{}, + }, + { + RouteType: RouteTypeHTTP, + FilterType: FilterURLRewrite, + URLRewrite: &gatewayv1.HTTPURLRewriteFilter{}, + }, + { + RouteType: RouteTypeHTTP, + FilterType: FilterRequestMirror, + RequestMirror: &gatewayv1.HTTPRequestMirrorFilter{}, + }, + { + RouteType: RouteTypeHTTP, + FilterType: FilterExtensionRef, + ExtensionRef: &gatewayv1.LocalObjectReference{}, + }, + }, + }, + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + convertedFilters := convertHTTPRouteFilters(test.httpFilters) + g.Expect(convertedFilters).To(Equal(test.expFilters)) + }, + ) + } +} diff --git a/internal/mode/static/state/graph/extension_ref_filter.go b/internal/mode/static/state/graph/extension_ref_filter.go new file mode 100644 index 0000000000..0444e08f98 --- /dev/null +++ b/internal/mode/static/state/graph/extension_ref_filter.go @@ -0,0 +1,49 @@ +package graph + +import ( + "k8s.io/apimachinery/pkg/util/validation/field" + v1 "sigs.k8s.io/gateway-api/apis/v1" + + ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/kinds" +) + +// ExtensionRefFilter are NGF-specific extensions to the "filter" behavior. +type ExtensionRefFilter struct { + // SnippetsFilter contains the SnippetsFilter. Will be non-nil if the Ref.Kind is SnippetsFilter and the + // SnippetsFilter exists. + // Once we support more filters, we can extend this struct with more filter kinds. + SnippetsFilter *SnippetsFilter + // Valid indicates whether the filter is valid. + Valid bool +} + +// resolveExtRefFilter resolves a LocalObjectReference to an *ExtensionRefFilter. +// If it cannot be resolved, *ExtensionRefFilter will be nil. +type resolveExtRefFilter func(ref v1.LocalObjectReference) *ExtensionRefFilter + +func validateExtensionRefFilter(ref *v1.LocalObjectReference, path *field.Path) field.ErrorList { + var allErrs field.ErrorList + + extRefPath := path.Child("extensionRef") + + if ref == nil { + return field.ErrorList{field.Required(extRefPath, "extensionRef cannot be nil")} + } + + if ref.Name == "" { + allErrs = append(allErrs, field.Required(extRefPath, "name cannot be empty")) + } + + if ref.Group != ngfAPI.GroupName { + allErrs = append(allErrs, field.NotSupported(extRefPath, ref.Group, []string{ngfAPI.GroupName})) + } + + switch ref.Kind { + case kinds.SnippetsFilter: + default: + allErrs = append(allErrs, field.NotSupported(extRefPath, ref.Kind, []string{kinds.SnippetsFilter})) + } + + return allErrs +} diff --git a/internal/mode/static/state/graph/extension_ref_filter_test.go b/internal/mode/static/state/graph/extension_ref_filter_test.go new file mode 100644 index 0000000000..5f5821e147 --- /dev/null +++ b/internal/mode/static/state/graph/extension_ref_filter_test.go @@ -0,0 +1,107 @@ +package graph + +import ( + "testing" + + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/util/validation/field" + v1 "sigs.k8s.io/gateway-api/apis/v1" + + ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/kinds" +) + +func TestValidateExtensionRefFilter(t *testing.T) { + t.Parallel() + testPath := field.NewPath("test") + + tests := []struct { + ref *v1.LocalObjectReference + name string + errSubString []string + expErrCount int + }{ + { + name: "nil ref", + ref: nil, + expErrCount: 1, + errSubString: []string{ + `test.extensionRef: Required value: extensionRef cannot be nil`, + }, + }, + { + name: "empty ref", + ref: &v1.LocalObjectReference{}, + expErrCount: 3, + errSubString: []string{ + `test.extensionRef: Required value: name cannot be empty`, + `test.extensionRef: Unsupported value: "": supported values: "gateway.nginx.org"`, + `test.extensionRef: Unsupported value: "": supported values: "SnippetsFilter"`, + }, + }, + { + name: "ref missing name", + ref: &v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + }, + expErrCount: 1, + errSubString: []string{ + `test.extensionRef: Required value: name cannot be empty`, + }, + }, + { + name: "ref unsupported group", + ref: &v1.LocalObjectReference{ + Name: v1.ObjectName("filter"), + Group: "unsupported", + Kind: kinds.SnippetsFilter, + }, + expErrCount: 1, + errSubString: []string{ + `test.extensionRef: Unsupported value: "unsupported": supported values: "gateway.nginx.org"`, + }, + }, + { + name: "ref unsupported kind", + ref: &v1.LocalObjectReference{ + Name: v1.ObjectName("filter"), + Group: ngfAPI.GroupName, + Kind: "unsupported", + }, + expErrCount: 1, + errSubString: []string{ + `test.extensionRef: Unsupported value: "unsupported": supported values: "SnippetsFilter"`, + }, + }, + { + name: "valid ref", + ref: &v1.LocalObjectReference{ + Name: v1.ObjectName("filter"), + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + }, + expErrCount: 0, + }, + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + + errs := validateExtensionRefFilter(test.ref, testPath) + g.Expect(errs).To(HaveLen(test.expErrCount)) + + if len(test.errSubString) > 0 { + aggregateErrStr := errs.ToAggregate().Error() + for _, ss := range test.errSubString { + g.Expect(aggregateErrStr).To(ContainSubstring(ss)) + } + } + }, + ) + } +} diff --git a/internal/mode/static/state/graph/graph.go b/internal/mode/static/state/graph/graph.go index 20ea60153a..df39c59423 100644 --- a/internal/mode/static/state/graph/graph.go +++ b/internal/mode/static/state/graph/graph.go @@ -226,6 +226,7 @@ func BuildGraph( state.GRPCRoutes, processedGws.GetAllNsNames(), npCfg, + processedSnippetsFilters, ) l4routes := buildL4RoutesForGateways( diff --git a/internal/mode/static/state/graph/graph_test.go b/internal/mode/static/state/graph/graph_test.go index d59f96dd57..23fc00167e 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -111,6 +111,60 @@ func TestBuildGraph(t *testing.T) { }, } + refSnippetsFilterExtensionRef := &gatewayv1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: "ref-snippets-filter", + } + + unreferencedSnippetsFilter := &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unref-snippets-filter", + Namespace: testNs, + }, + Spec: ngfAPI.SnippetsFilterSpec{ + Snippets: []ngfAPI.Snippet{ + { + Context: ngfAPI.NginxContextMain, + Value: "main snippet", + }, + }, + }, + } + + referencedSnippetsFilter := &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ref-snippets-filter", + Namespace: testNs, + }, + Spec: ngfAPI.SnippetsFilterSpec{ + Snippets: []ngfAPI.Snippet{ + { + Context: ngfAPI.NginxContextHTTPServer, + Value: "server snippet", + }, + }, + }, + } + + processedUnrefSnippetsFilter := &SnippetsFilter{ + Source: unreferencedSnippetsFilter, + Valid: true, + Referenced: false, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextMain: "main snippet", + }, + } + + processedRefSnippetsFilter := &SnippetsFilter{ + Source: referencedSnippetsFilter, + Valid: true, + Referenced: true, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTPServer: "server snippet", + }, + } + createValidRuleWithBackendRefs := func(matches []gatewayv1.HTTPRouteMatch) RouteRule { refs := []BackendRef{ { @@ -127,14 +181,40 @@ func TestBuildGraph(t *testing.T) { }, } return RouteRule{ - ValidMatches: true, - ValidFilters: true, + ValidMatches: true, + Filters: RouteRuleFilters{ + Filters: []Filter{}, + Valid: true, + }, BackendRefs: refs, Matches: matches, RouteBackendRefs: rbrs, } } + createValidRuleWithBackendRefsAndFilters := func( + matches []gatewayv1.HTTPRouteMatch, + routeType RouteType, + ) RouteRule { + rule := createValidRuleWithBackendRefs(matches) + rule.Filters = RouteRuleFilters{ + Filters: []Filter{ + { + RouteType: routeType, + FilterType: FilterExtensionRef, + ExtensionRef: refSnippetsFilterExtensionRef, + ResolvedExtensionRef: &ExtensionRefFilter{ + SnippetsFilter: processedRefSnippetsFilter, + Valid: true, + }, + }, + }, + Valid: true, + } + + return rule + } + routeMatches := []gatewayv1.HTTPRouteMatch{ { Path: &gatewayv1.HTTPPathMatch{ @@ -207,6 +287,15 @@ func TestBuildGraph(t *testing.T) { } hr1 := createRoute("hr-1", "gateway-1", "listener-80-1") + addFilterToPath( + hr1, + "/", + gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterExtensionRef, + ExtensionRef: refSnippetsFilterExtensionRef, + }, + ) + 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 @@ -239,6 +328,12 @@ func TestBuildGraph(t *testing.T) { BackendRef: commonGWBackendRef, }, }, + Filters: []gatewayv1.GRPCRouteFilter{ + { + Type: gatewayv1.GRPCRouteFilterExtensionRef, + ExtensionRef: refSnippetsFilterExtensionRef, + }, + }, }, }, }, @@ -515,26 +610,6 @@ func TestBuildGraph(t *testing.T) { Valid: true, } - snippetsFilter := &ngfAPI.SnippetsFilter{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-snippet-filter", - Namespace: testNs, - }, - Spec: ngfAPI.SnippetsFilterSpec{ - Snippets: []ngfAPI.Snippet{ - { - Context: ngfAPI.NginxContextMain, - Value: "main snippet", - }, - }, - }, - } - - processedSnippetsFilter := &SnippetsFilter{ - Source: snippetsFilter, - Valid: true, - } - createStateWithGatewayClass := func(gc *gatewayv1.GatewayClass) ClusterState { return ClusterState{ GatewayClasses: map[types.NamespacedName]*gatewayv1.GatewayClass{ @@ -585,7 +660,8 @@ func TestBuildGraph(t *testing.T) { gwPolicyKey: gwPolicy, }, SnippetsFilters: map[types.NamespacedName]*ngfAPI.SnippetsFilter{ - client.ObjectKeyFromObject(snippetsFilter): snippetsFilter, + client.ObjectKeyFromObject(unreferencedSnippetsFilter): unreferencedSnippetsFilter, + client.ObjectKeyFromObject(referencedSnippetsFilter): referencedSnippetsFilter, }, } } @@ -609,7 +685,7 @@ func TestBuildGraph(t *testing.T) { }, Spec: L7RouteSpec{ Hostnames: hr1.Spec.Hostnames, - Rules: []RouteRule{createValidRuleWithBackendRefs(routeMatches)}, + Rules: []RouteRule{createValidRuleWithBackendRefsAndFilters(routeMatches, RouteTypeHTTP)}, }, Policies: []*Policy{processedRoutePolicy}, } @@ -696,7 +772,7 @@ func TestBuildGraph(t *testing.T) { Spec: L7RouteSpec{ Hostnames: gr.Spec.Hostnames, Rules: []RouteRule{ - createValidRuleWithBackendRefs(routeMatches), + createValidRuleWithBackendRefsAndFilters(routeMatches, RouteTypeGRPC), }, }, } @@ -834,7 +910,8 @@ func TestBuildGraph(t *testing.T) { TelemetryEnabled: true, }, SnippetsFilters: map[types.NamespacedName]*SnippetsFilter{ - client.ObjectKeyFromObject(snippetsFilter): processedSnippetsFilter, + client.ObjectKeyFromObject(unreferencedSnippetsFilter): processedUnrefSnippetsFilter, + client.ObjectKeyFromObject(referencedSnippetsFilter): processedRefSnippetsFilter, }, } } @@ -879,28 +956,30 @@ func TestBuildGraph(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - g := NewWithT(t) - - // The diffs get very large so the format max length will make sure the output doesn't get truncated. - format.MaxLength = 10000000 - - fakePolicyValidator := &validationfakes.FakePolicyValidator{} - - result := BuildGraph( - test.store, - controllerName, - gcName, - validation.Validators{ - HTTPFieldsValidator: &validationfakes.FakeHTTPFieldsValidator{}, - GenericValidator: &validationfakes.FakeGenericValidator{}, - PolicyValidator: fakePolicyValidator, - }, - protectedPorts, - ) + t.Run( + test.name, func(t *testing.T) { + g := NewWithT(t) + + // The diffs get very large so the format max length will make sure the output doesn't get truncated. + format.MaxLength = 10000000 + + fakePolicyValidator := &validationfakes.FakePolicyValidator{} + + result := BuildGraph( + test.store, + controllerName, + gcName, + validation.Validators{ + HTTPFieldsValidator: &validationfakes.FakeHTTPFieldsValidator{}, + GenericValidator: &validationfakes.FakeGenericValidator{}, + PolicyValidator: fakePolicyValidator, + }, + protectedPorts, + ) - g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) - }) + g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) + }, + ) } } @@ -1197,13 +1276,15 @@ func TestIsReferenced(t *testing.T) { }, } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - g := NewWithT(t) + t.Run( + test.name, func(t *testing.T) { + g := NewWithT(t) - test.graph.GatewayClass = test.gc - result := test.graph.IsReferenced(test.resource, client.ObjectKeyFromObject(test.resource)) - g.Expect(result).To(Equal(test.expected)) - }) + test.graph.GatewayClass = test.gc + result := test.graph.IsReferenced(test.resource, client.ObjectKeyFromObject(test.resource)) + g.Expect(result).To(Equal(test.expected)) + }, + ) } } @@ -1329,20 +1410,24 @@ func TestIsNGFPolicyRelevant(t *testing.T) { }, { name: "irrelevant; policy references a Gateway, but the graph's Gateway is nil", - graph: getModifiedGraph(func(g *Graph) *Graph { - g.Gateway = nil - return g - }), + graph: getModifiedGraph( + func(g *Graph) *Graph { + g.Gateway = nil + return g + }, + ), policy: getPolicy(createTestRef(kinds.Gateway, gatewayv1.GroupName, "diff")), nsname: types.NamespacedName{Namespace: "test", Name: "nil-gw"}, expRelevant: false, }, { name: "irrelevant; policy references a Gateway, but the graph's Gateway.Source is nil", - graph: getModifiedGraph(func(g *Graph) *Graph { - g.Gateway.Source = nil - return g - }), + graph: getModifiedGraph( + func(g *Graph) *Graph { + g.Gateway.Source = nil + return g + }, + ), policy: getPolicy(createTestRef(kinds.Gateway, gatewayv1.GroupName, "diff")), nsname: types.NamespacedName{Namespace: "test", Name: "nil-gw-source"}, expRelevant: false, @@ -1350,13 +1435,15 @@ func TestIsNGFPolicyRelevant(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - relevant := test.graph.IsNGFPolicyRelevant(test.policy, policyGVK, test.nsname) - g.Expect(relevant).To(Equal(test.expRelevant)) - }) + relevant := test.graph.IsNGFPolicyRelevant(test.policy, policyGVK, test.nsname) + g.Expect(relevant).To(Equal(test.expRelevant)) + }, + ) } } diff --git a/internal/mode/static/state/graph/grpcroute.go b/internal/mode/static/state/graph/grpcroute.go index ea3f576972..56aa1cdabf 100644 --- a/internal/mode/static/state/graph/grpcroute.go +++ b/internal/mode/static/state/graph/grpcroute.go @@ -5,6 +5,7 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" v1 "sigs.k8s.io/gateway-api/apis/v1" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/conditions" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" staticConds "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/conditions" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation" @@ -15,6 +16,7 @@ func buildGRPCRoute( ghr *v1.GRPCRoute, gatewayNsNames []types.NamespacedName, http2disabled bool, + snippetsFilters map[types.NamespacedName]*SnippetsFilter, ) *L7Route { r := &L7Route{ Source: ghr, @@ -52,95 +54,124 @@ func buildGRPCRoute( } r.Spec.Hostnames = ghr.Spec.Hostnames - - r.Valid = true r.Attachable = true - rules, atLeastOneValid, allRulesErrs := processGRPCRouteRules(ghr.Spec.Rules, validator) + rules, valid, conds := processGRPCRouteRules( + ghr.Spec.Rules, + validator, + getSnippetsFilterResolverForNamespace(snippetsFilters, r.Source.GetNamespace()), + ) r.Spec.Rules = rules + r.Valid = valid + r.Conditions = append(r.Conditions, conds...) - if len(allRulesErrs) > 0 { - msg := allRulesErrs.ToAggregate().Error() + return r +} - if atLeastOneValid { - r.Conditions = append(r.Conditions, staticConds.NewRoutePartiallyInvalid(msg)) - } else { - msg = "All rules are invalid: " + msg - r.Conditions = append(r.Conditions, staticConds.NewRouteUnsupportedValue(msg)) +func processGRPCRouteRule( + specRule v1.GRPCRouteRule, + rulePath *field.Path, + validator validation.HTTPFieldsValidator, + resolveExtRefFunc resolveExtRefFilter, +) (RouteRule, routeRuleErrors) { + var errors routeRuleErrors - r.Valid = false + validMatches := true + + for j, match := range specRule.Matches { + matchPath := rulePath.Child("matches").Index(j) + + matchesErrs := validateGRPCMatch(validator, match, matchPath) + if len(matchesErrs) > 0 { + validMatches = false + errors.invalid = append(errors.invalid, matchesErrs...) } } - return r + routeFilters, filterErrors := processRouteRuleFilters( + convertGRPCRouteFilters(specRule.Filters), + rulePath.Child("filters"), + validator, + resolveExtRefFunc, + ) + + errors = errors.append(filterErrors) + + backendRefs := make([]RouteBackendRef, 0, len(specRule.BackendRefs)) + + // rule.BackendRefs are validated separately because of their special requirements + for _, b := range specRule.BackendRefs { + var interfaceFilters []interface{} + if len(b.Filters) > 0 { + interfaceFilters = make([]interface{}, 0, len(b.Filters)) + for i, v := range b.Filters { + interfaceFilters[i] = v + } + } + rbr := RouteBackendRef{ + BackendRef: b.BackendRef, + Filters: interfaceFilters, + } + backendRefs = append(backendRefs, rbr) + } + + return RouteRule{ + ValidMatches: validMatches, + Matches: convertGRPCMatches(specRule.Matches), + Filters: routeFilters, + RouteBackendRefs: backendRefs, + }, errors } func processGRPCRouteRules( specRules []v1.GRPCRouteRule, validator validation.HTTPFieldsValidator, -) (rules []RouteRule, atLeastOneValid bool, allRulesErrs field.ErrorList) { + resolveExtRefFunc resolveExtRefFilter, +) (rules []RouteRule, valid bool, conds []conditions.Condition) { rules = make([]RouteRule, len(specRules)) + var ( + allRulesErrors routeRuleErrors + atLeastOneValid bool + ) + for i, rule := range specRules { rulePath := field.NewPath("spec").Child("rules").Index(i) - var allErrs field.ErrorList - var matchesErrs field.ErrorList - var filtersErrs field.ErrorList + rr, errors := processGRPCRouteRule(rule, rulePath, validator, resolveExtRefFunc) - for j, match := range rule.Matches { - matchPath := rulePath.Child("matches").Index(j) - matchesErrs = append(matchesErrs, validateGRPCMatch(validator, match, matchPath)...) + if rr.ValidMatches && rr.Filters.Valid { + atLeastOneValid = true } - for j, filter := range rule.Filters { - filterPath := rulePath.Child("filters").Index(j) - filtersErrs = append(filtersErrs, validateGRPCFilter(validator, filter, filterPath)...) - } + allRulesErrors = allRulesErrors.append(errors) - backendRefs := make([]RouteBackendRef, 0, len(rule.BackendRefs)) + rules[i] = rr + } - // rule.BackendRefs are validated separately because of their special requirements - for _, b := range rule.BackendRefs { - var interfaceFilters []interface{} - if len(b.Filters) > 0 { - interfaceFilters = make([]interface{}, 0, len(b.Filters)) - for i, v := range b.Filters { - interfaceFilters[i] = v - } - } - rbr := RouteBackendRef{ - BackendRef: b.BackendRef, - Filters: interfaceFilters, - } - backendRefs = append(backendRefs, rbr) - } + conds = make([]conditions.Condition, 0, 2) + valid = true - allErrs = append(allErrs, matchesErrs...) - allErrs = append(allErrs, filtersErrs...) - allRulesErrs = append(allRulesErrs, allErrs...) + if len(allRulesErrors.invalid) > 0 { + msg := allRulesErrors.invalid.ToAggregate().Error() - if len(allErrs) == 0 { - atLeastOneValid = true - } - - validFilters := len(filtersErrs) == 0 - - var convertedFilters []v1.HTTPRouteFilter - if validFilters { - convertedFilters = convertGRPCFilters(rule.Filters) + if atLeastOneValid { + conds = append(conds, staticConds.NewRoutePartiallyInvalid(msg)) + } else { + msg = "All rules are invalid: " + msg + conds = append(conds, staticConds.NewRouteUnsupportedValue(msg)) + valid = false } + } - rules[i] = RouteRule{ - ValidMatches: len(matchesErrs) == 0, - ValidFilters: validFilters, - Matches: convertGRPCMatches(rule.Matches), - Filters: convertedFilters, - RouteBackendRefs: backendRefs, - } + // resolve errors do not invalidate routes + if len(allRulesErrors.resolve) > 0 { + msg := allRulesErrors.resolve.ToAggregate().Error() + conds = append(conds, staticConds.NewRouteResolvedRefsInvalidFilter(msg)) } - return rules, atLeastOneValid, allRulesErrs + + return rules, valid, conds } func convertGRPCMatches(grpcMatches []v1.GRPCRouteMatch) []v1.HTTPRouteMatch { @@ -164,10 +195,12 @@ func convertGRPCMatches(grpcMatches []v1.GRPCRouteMatch) []v1.HTTPRouteMatch { var hm v1.HTTPRouteMatch hmHeaders := make([]v1.HTTPHeaderMatch, 0, len(gm.Headers)) for _, head := range gm.Headers { - hmHeaders = append(hmHeaders, v1.HTTPHeaderMatch{ - Name: v1.HTTPHeaderName(head.Name), - Value: head.Value, - }) + hmHeaders = append( + hmHeaders, v1.HTTPHeaderMatch{ + Name: v1.HTTPHeaderName(head.Name), + Value: head.Value, + }, + ) } hm.Headers = hmHeaders @@ -246,57 +279,3 @@ func validateGRPCMethodMatch( } return allErrs } - -func validateGRPCFilter( - validator validation.HTTPFieldsValidator, - filter v1.GRPCRouteFilter, - filterPath *field.Path, -) field.ErrorList { - var allErrs field.ErrorList - - switch filter.Type { - case v1.GRPCRouteFilterRequestHeaderModifier: - return validateFilterHeaderModifier(validator, filter.RequestHeaderModifier, filterPath.Child(string(filter.Type))) - case v1.GRPCRouteFilterResponseHeaderModifier: - return validateFilterHeaderModifier(validator, filter.ResponseHeaderModifier, filterPath.Child(string(filter.Type))) - default: - valErr := field.NotSupported( - filterPath.Child("type"), - filter.Type, - []string{ - string(v1.GRPCRouteFilterRequestHeaderModifier), - string(v1.GRPCRouteFilterResponseHeaderModifier), - }, - ) - allErrs = append(allErrs, valErr) - return allErrs - } -} - -// convertGRPCFilters converts GRPCRouteFilters (a subset of HTTPRouteFilter) to HTTPRouteFilters -// so we can reuse the logic from HTTPRoute filter validation and processing. -func convertGRPCFilters(filters []v1.GRPCRouteFilter) []v1.HTTPRouteFilter { - if len(filters) == 0 { - return nil - } - httpFilters := make([]v1.HTTPRouteFilter, 0, len(filters)) - for _, filter := range filters { - switch filter.Type { - case v1.GRPCRouteFilterRequestHeaderModifier: - httpRequestHeaderFilter := v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: filter.RequestHeaderModifier, - } - httpFilters = append(httpFilters, httpRequestHeaderFilter) - case v1.GRPCRouteFilterResponseHeaderModifier: - httpResponseHeaderFilter := v1.HTTPRouteFilter{ - Type: v1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: filter.ResponseHeaderModifier, - } - httpFilters = append(httpFilters, httpResponseHeaderFilter) - default: - continue - } - } - return httpFilters -} diff --git a/internal/mode/static/state/graph/grpcroute_test.go b/internal/mode/static/state/graph/grpcroute_test.go index 7e8e8b4582..1ef96287b6 100644 --- a/internal/mode/static/state/graph/grpcroute_test.go +++ b/internal/mode/static/state/graph/grpcroute_test.go @@ -13,6 +13,7 @@ import ( ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/conditions" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/kinds" staticConds "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/conditions" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation/validationfakes" ) @@ -82,7 +83,25 @@ func TestBuildGRPCRoutes(t *testing.T) { t.Parallel() gwNsName := types.NamespacedName{Namespace: "test", Name: "gateway"} - gr := createGRPCRoute("gr-1", gwNsName.Name, "example.com", []v1.GRPCRouteRule{}) + snippetsFilterRef := v1.GRPCRouteFilter{ + Type: v1.GRPCRouteFilterExtensionRef, + ExtensionRef: &v1.LocalObjectReference{ + Name: "sf", + Kind: kinds.SnippetsFilter, + Group: ngfAPI.GroupName, + }, + } + + requestHeaderFilter := v1.GRPCRouteFilter{ + Type: v1.GRPCRouteFilterRequestHeaderModifier, + RequestHeaderModifier: &v1.HTTPHeaderFilter{}, + } + + grRuleWithFilters := v1.GRPCRouteRule{ + Filters: []v1.GRPCRouteFilter{snippetsFilterRef, requestHeaderFilter}, + } + + gr := createGRPCRoute("gr-1", gwNsName.Name, "example.com", []v1.GRPCRouteRule{grRuleWithFilters}) grWrongGateway := createGRPCRoute("gr-2", "some-gateway", "example.com", []v1.GRPCRouteRule{}) @@ -91,6 +110,21 @@ func TestBuildGRPCRoutes(t *testing.T) { client.ObjectKeyFromObject(grWrongGateway): grWrongGateway, } + sf := &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "sf", + }, + Spec: ngfAPI.SnippetsFilterSpec{ + Snippets: []ngfAPI.Snippet{ + { + Context: ngfAPI.NginxContextHTTP, + Value: "http snippet", + }, + }, + }, + } + tests := []struct { expected map[RouteKey]*L7Route name string @@ -113,7 +147,39 @@ func TestBuildGRPCRoutes(t *testing.T) { Attachable: true, Spec: L7RouteSpec{ Hostnames: gr.Spec.Hostnames, - Rules: []RouteRule{}, + Rules: []RouteRule{ + { + Matches: convertGRPCMatches(gr.Spec.Rules[0].Matches), + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{ + { + ExtensionRef: snippetsFilterRef.ExtensionRef, + ResolvedExtensionRef: &ExtensionRefFilter{ + SnippetsFilter: &SnippetsFilter{ + Source: sf, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTP: "http snippet", + }, + Valid: true, + Referenced: true, + }, + Valid: true, + }, + RouteType: RouteTypeGRPC, + FilterType: FilterExtensionRef, + }, + { + RequestHeaderModifier: &v1.HTTPHeaderFilter{}, + RouteType: RouteTypeGRPC, + FilterType: FilterRequestHeaderModifier, + }, + }, + }, + ValidMatches: true, + RouteBackendRefs: []RouteBackendRef{}, + }, + }, }, }, }, @@ -137,18 +203,32 @@ func TestBuildGRPCRoutes(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - routes := buildRoutesForGateways( - validator, - map[types.NamespacedName]*v1.HTTPRoute{}, - grRoutes, - test.gwNsNames, - npCfg, - ) - g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ + client.ObjectKeyFromObject(sf): { + Source: sf, + Valid: true, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTP: "http snippet", + }, + }, + } + + routes := buildRoutesForGateways( + validator, + map[types.NamespacedName]*v1.HTTPRoute{}, + grRoutes, + test.gwNsNames, + npCfg, + snippetsFilters, + ) + g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) + }, + ) } } @@ -252,6 +332,11 @@ func TestBuildGRPCRoute(t *testing.T) { ) grValidFilterRule := createGRPCMethodMatch("myService", "myMethod", "Exact") + validSnippetsFilterRef := &v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: "sf", + } grValidFilterRule.Filters = []v1.GRPCRouteFilter{ { @@ -268,6 +353,10 @@ func TestBuildGRPCRoute(t *testing.T) { }, }, }, + { + Type: v1.GRPCRouteFilterExtensionRef, + ExtensionRef: validSnippetsFilterRef, + }, } grValidFilter := createGRPCRoute( @@ -277,22 +366,70 @@ func TestBuildGRPCRoute(t *testing.T) { []v1.GRPCRouteRule{grValidFilterRule}, ) - convertedFilters := []v1.HTTPRouteFilter{ + // route with invalid snippets filter extension ref + grInvalidSnippetsFilterRule := createGRPCMethodMatch("myService", "myMethod", "Exact") + grInvalidSnippetsFilterRule.Filters = []v1.GRPCRouteFilter{ { - Type: v1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &v1.HTTPHeaderFilter{ - Remove: []string{"header"}, + Type: v1.GRPCRouteFilterExtensionRef, + ExtensionRef: &v1.LocalObjectReference{ + Group: "wrong", + Kind: kinds.SnippetsFilter, + Name: "sf", }, }, + } + grInvalidSnippetsFilter := createGRPCRoute( + "gr", + gatewayNsName.Name, + "example.com", + []v1.GRPCRouteRule{grInvalidSnippetsFilterRule}, + ) + + // route with unresolvable snippets filter extension ref + grUnresolvableSnippetsFilterRule := createGRPCMethodMatch("myService", "myMethod", "Exact") + grUnresolvableSnippetsFilterRule.Filters = []v1.GRPCRouteFilter{ { - Type: v1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &v1.HTTPHeaderFilter{ - Add: []v1.HTTPHeader{ - {Name: "Accept-Encoding", Value: "gzip"}, - }, + Type: v1.GRPCRouteFilterExtensionRef, + ExtensionRef: &v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: "does-not-exist", }, }, } + grUnresolvableSnippetsFilter := createGRPCRoute( + "gr", + gatewayNsName.Name, + "example.com", + []v1.GRPCRouteRule{grUnresolvableSnippetsFilterRule}, + ) + + // route with two invalid snippets filter extensions refs: (1) invalid group (2) unresolvable + grInvalidAndUnresolvableSnippetsFilterRule := createGRPCMethodMatch("myService", "myMethod", "Exact") + grInvalidAndUnresolvableSnippetsFilterRule.Filters = []v1.GRPCRouteFilter{ + { + Type: v1.GRPCRouteFilterExtensionRef, + ExtensionRef: &v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: "does-not-exist", + }, + }, + { + Type: v1.GRPCRouteFilterExtensionRef, + ExtensionRef: &v1.LocalObjectReference{ + Group: "wrong", + Kind: kinds.SnippetsFilter, + Name: "sf", + }, + }, + } + grInvalidAndUnresolvableSnippetsFilter := createGRPCRoute( + "gr", + gatewayNsName.Name, + "example.com", + []v1.GRPCRouteRule{grInvalidAndUnresolvableSnippetsFilterRule}, + ) createAllValidValidator := func() *validationfakes.FakeHTTPFieldsValidator { v := &validationfakes.FakeHTTPFieldsValidator{} @@ -326,14 +463,20 @@ func TestBuildGRPCRoute(t *testing.T) { Hostnames: grBoth.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: true, - ValidFilters: true, + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: convertGRPCMatches(grBoth.Spec.Rules[0].Matches), RouteBackendRefs: []RouteBackendRef{}, }, { - ValidMatches: true, - ValidFilters: true, + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: convertGRPCMatches(grBoth.Spec.Rules[1].Matches), RouteBackendRefs: []RouteBackendRef{}, }, @@ -361,8 +504,11 @@ func TestBuildGRPCRoute(t *testing.T) { Hostnames: grEmptyMatch.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: true, - ValidFilters: true, + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: convertGRPCMatches(grEmptyMatch.Spec.Rules[0].Matches), RouteBackendRefs: []RouteBackendRef{{BackendRef: backendRef}}, }, @@ -391,10 +537,41 @@ func TestBuildGRPCRoute(t *testing.T) { Rules: []RouteRule{ { ValidMatches: true, - ValidFilters: true, Matches: convertGRPCMatches(grValidFilter.Spec.Rules[0].Matches), RouteBackendRefs: []RouteBackendRef{}, - Filters: convertedFilters, + Filters: RouteRuleFilters{ + Filters: []Filter{ + { + RouteType: RouteTypeGRPC, + FilterType: FilterRequestHeaderModifier, + RequestHeaderModifier: &v1.HTTPHeaderFilter{ + Remove: []string{"header"}, + }, + }, + { + RouteType: RouteTypeGRPC, + FilterType: FilterResponseHeaderModifier, + ResponseHeaderModifier: &v1.HTTPHeaderFilter{ + Add: []v1.HTTPHeader{ + {Name: "Accept-Encoding", Value: "gzip"}, + }, + }, + }, + { + RouteType: RouteTypeGRPC, + FilterType: FilterExtensionRef, + ExtensionRef: validSnippetsFilterRef, + ResolvedExtensionRef: &ExtensionRefFilter{ + SnippetsFilter: &SnippetsFilter{ + Valid: true, + Referenced: true, + }, + Valid: true, + }, + }, + }, + Valid: true, + }, }, }, }, @@ -428,8 +605,11 @@ func TestBuildGRPCRoute(t *testing.T) { Hostnames: grInvalidMatchesEmptyMethodFields.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: false, - ValidFilters: true, + ValidMatches: false, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: convertGRPCMatches(grInvalidMatchesEmptyMethodFields.Spec.Rules[0].Matches), RouteBackendRefs: []RouteBackendRef{}, }, @@ -468,8 +648,11 @@ func TestBuildGRPCRoute(t *testing.T) { Hostnames: grInvalidMatchesInvalidMethodFields.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: false, - ValidFilters: true, + ValidMatches: false, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: convertGRPCMatches(grInvalidMatchesInvalidMethodFields.Spec.Rules[0].Matches), RouteBackendRefs: []RouteBackendRef{}, }, @@ -533,14 +716,20 @@ func TestBuildGRPCRoute(t *testing.T) { Hostnames: grOneInvalid.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: true, - ValidFilters: true, + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: convertGRPCMatches(grOneInvalid.Spec.Rules[0].Matches), RouteBackendRefs: []RouteBackendRef{}, }, { - ValidMatches: false, - ValidFilters: true, + ValidMatches: false, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: convertGRPCMatches(grOneInvalid.Spec.Rules[1].Matches), RouteBackendRefs: []RouteBackendRef{}, }, @@ -574,8 +763,11 @@ func TestBuildGRPCRoute(t *testing.T) { Hostnames: grInvalidHeadersEmptyType.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: false, - ValidFilters: true, + ValidMatches: false, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: convertGRPCMatches(grInvalidHeadersEmptyType.Spec.Rules[0].Matches), RouteBackendRefs: []RouteBackendRef{}, }, @@ -608,8 +800,11 @@ func TestBuildGRPCRoute(t *testing.T) { Hostnames: grInvalidMatchesNilMethodType.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: false, - ValidFilters: true, + ValidMatches: false, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: convertGRPCMatches(grInvalidMatchesNilMethodType.Spec.Rules[0].Matches), RouteBackendRefs: []RouteBackendRef{}, }, @@ -635,16 +830,20 @@ func TestBuildGRPCRoute(t *testing.T) { }, Conditions: []conditions.Condition{ staticConds.NewRouteUnsupportedValue( - `All rules are invalid: spec.rules[0].filters[0].type: ` + - `Unsupported value: "RequestMirror": supported values: "RequestHeaderModifier", "ResponseHeaderModifier"`, + `All rules are invalid: spec.rules[0].filters[0].type: Unsupported value: ` + + `"RequestMirror": supported values: "ResponseHeaderModifier", ` + + `"RequestHeaderModifier", "ExtensionRef"`, ), }, Spec: L7RouteSpec{ Hostnames: grInvalidFilter.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: true, - ValidFilters: false, + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: false, + Filters: convertGRPCRouteFilters(grInvalidFilter.Spec.Rules[0].Filters), + }, Matches: convertGRPCMatches(grInvalidFilter.Spec.Rules[0].Matches), RouteBackendRefs: []RouteBackendRef{}, }, @@ -682,18 +881,147 @@ func TestBuildGRPCRoute(t *testing.T) { }, name: "invalid hostname", }, + { + validator: createAllValidValidator(), + gr: grInvalidSnippetsFilter, + expected: &L7Route{ + Source: grInvalidSnippetsFilter, + RouteType: RouteTypeGRPC, + Valid: false, + Attachable: true, + ParentRefs: []ParentRef{ + { + Idx: 0, + Gateway: gatewayNsName, + SectionName: grInvalidSnippetsFilter.Spec.ParentRefs[0].SectionName, + }, + }, + Conditions: []conditions.Condition{ + staticConds.NewRouteUnsupportedValue( + "All rules are invalid: spec.rules[0].filters[0].extensionRef: " + + "Unsupported value: \"wrong\": supported values: \"gateway.nginx.org\"", + ), + }, + Spec: L7RouteSpec{ + Hostnames: grInvalidSnippetsFilter.Spec.Hostnames, + Rules: []RouteRule{ + { + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: false, + Filters: convertGRPCRouteFilters(grInvalidSnippetsFilter.Spec.Rules[0].Filters), + }, + Matches: convertGRPCMatches(grInvalidSnippetsFilter.Spec.Rules[0].Matches), + RouteBackendRefs: []RouteBackendRef{}, + }, + }, + }, + }, + + name: "invalid snippet filter extension ref", + }, + { + validator: createAllValidValidator(), + gr: grUnresolvableSnippetsFilter, + expected: &L7Route{ + Source: grUnresolvableSnippetsFilter, + RouteType: RouteTypeGRPC, + Valid: true, + Attachable: true, + ParentRefs: []ParentRef{ + { + Idx: 0, + Gateway: gatewayNsName, + SectionName: grUnresolvableSnippetsFilter.Spec.ParentRefs[0].SectionName, + }, + }, + Conditions: []conditions.Condition{ + staticConds.NewRouteResolvedRefsInvalidFilter( + "spec.rules[0].filters[0].extensionRef: Not found: " + + "v1.LocalObjectReference{Group:\"gateway.nginx.org\", Kind:\"SnippetsFilter\", " + + "Name:\"does-not-exist\"}", + ), + }, + Spec: L7RouteSpec{ + Hostnames: grUnresolvableSnippetsFilter.Spec.Hostnames, + Rules: []RouteRule{ + { + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: false, + Filters: convertGRPCRouteFilters(grUnresolvableSnippetsFilter.Spec.Rules[0].Filters), + }, + Matches: convertGRPCMatches(grUnresolvableSnippetsFilter.Spec.Rules[0].Matches), + RouteBackendRefs: []RouteBackendRef{}, + }, + }, + }, + }, + + name: "unresolvable snippet filter extension ref", + }, + { + validator: createAllValidValidator(), + gr: grInvalidAndUnresolvableSnippetsFilter, + expected: &L7Route{ + Source: grInvalidAndUnresolvableSnippetsFilter, + RouteType: RouteTypeGRPC, + Valid: false, + Attachable: true, + ParentRefs: []ParentRef{ + { + Idx: 0, + Gateway: gatewayNsName, + SectionName: grInvalidAndUnresolvableSnippetsFilter.Spec.ParentRefs[0].SectionName, + }, + }, + Conditions: []conditions.Condition{ + staticConds.NewRouteUnsupportedValue( + "All rules are invalid: spec.rules[0].filters[1].extensionRef: " + + "Unsupported value: \"wrong\": supported values: \"gateway.nginx.org\"", + ), + staticConds.NewRouteResolvedRefsInvalidFilter( + "spec.rules[0].filters[0].extensionRef: Not found: " + + "v1.LocalObjectReference{Group:\"gateway.nginx.org\", Kind:\"SnippetsFilter\", " + + "Name:\"does-not-exist\"}", + ), + }, + Spec: L7RouteSpec{ + Hostnames: grInvalidAndUnresolvableSnippetsFilter.Spec.Hostnames, + Rules: []RouteRule{ + { + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: false, + Filters: convertGRPCRouteFilters(grInvalidAndUnresolvableSnippetsFilter.Spec.Rules[0].Filters), + }, + Matches: convertGRPCMatches(grInvalidAndUnresolvableSnippetsFilter.Spec.Rules[0].Matches), + RouteBackendRefs: []RouteBackendRef{}, + }, + }, + }, + }, + + name: "one invalid and one unresolvable snippet filter extension ref", + }, } gatewayNsNames := []types.NamespacedName{gatewayNsName} for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - route := buildGRPCRoute(test.validator, test.gr, gatewayNsNames, test.http2disabled) - g.Expect(helpers.Diff(test.expected, route)).To(BeEmpty()) - }) + snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ + {Namespace: "test", Name: "sf"}: {Valid: true}, + } + + route := buildGRPCRoute(test.validator, test.gr, gatewayNsNames, test.http2disabled, snippetsFilters) + g.Expect(helpers.Diff(test.expected, route)).To(BeEmpty()) + }, + ) } } @@ -760,51 +1088,14 @@ func TestConvertGRPCMatches(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - httpMatches := convertGRPCMatches(test.methodMatches) - g.Expect(helpers.Diff(test.expected, httpMatches)).To(BeEmpty()) - }) - } -} + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) -func TestConvertGRPCFilters(t *testing.T) { - t.Parallel() - grFilters := []v1.GRPCRouteFilter{ - { - Type: "RequestHeaderModifier", - RequestHeaderModifier: &v1.HTTPHeaderFilter{ - Remove: []string{"header"}, + httpMatches := convertGRPCMatches(test.methodMatches) + g.Expect(helpers.Diff(test.expected, httpMatches)).To(BeEmpty()) }, - }, - { - Type: "ResponseHeaderModifier", - ResponseHeaderModifier: &v1.HTTPHeaderFilter{ - Add: []v1.HTTPHeader{ - {Name: "Accept-Encoding", Value: "gzip"}, - }, - }, - }, - { - Type: "RequestMirror", - }, - } - - expectedHTTPFilters := []v1.HTTPRouteFilter{ - { - Type: v1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: grFilters[0].RequestHeaderModifier, - }, - { - Type: v1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: grFilters[1].ResponseHeaderModifier, - }, + ) } - - g := NewWithT(t) - - httpFilters := convertGRPCFilters(grFilters) - g.Expect(helpers.Diff(expectedHTTPFilters, httpFilters)).To(BeEmpty()) } diff --git a/internal/mode/static/state/graph/httproute.go b/internal/mode/static/state/graph/httproute.go index 1b5bdf3676..31f5f425d1 100644 --- a/internal/mode/static/state/graph/httproute.go +++ b/internal/mode/static/state/graph/httproute.go @@ -8,6 +8,8 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" v1 "sigs.k8s.io/gateway-api/apis/v1" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/conditions" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/nginx/config/http" staticConds "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/conditions" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation" @@ -23,6 +25,7 @@ func buildHTTPRoute( validator validation.HTTPFieldsValidator, ghr *v1.HTTPRoute, gatewayNsNames []types.NamespacedName, + snippetsFilters map[types.NamespacedName]*SnippetsFilter, ) *L7Route { r := &L7Route{ Source: ghr, @@ -52,87 +55,125 @@ func buildHTTPRoute( } r.Spec.Hostnames = ghr.Spec.Hostnames - - r.Valid = true r.Attachable = true - rules, atLeastOneValid, allRulesErrs := processHTTPRouteRules(ghr.Spec.Rules, validator) + rules, valid, conds := processHTTPRouteRules( + ghr.Spec.Rules, + validator, + getSnippetsFilterResolverForNamespace(snippetsFilters, r.Source.GetNamespace()), + ) r.Spec.Rules = rules + r.Conditions = append(r.Conditions, conds...) + r.Valid = valid - if len(allRulesErrs) > 0 { - msg := allRulesErrs.ToAggregate().Error() + return r +} - if atLeastOneValid { - r.Conditions = append(r.Conditions, staticConds.NewRoutePartiallyInvalid(msg)) - } else { - msg = "All rules are invalid: " + msg - r.Conditions = append(r.Conditions, staticConds.NewRouteUnsupportedValue(msg)) +func processHTTPRouteRule( + specRule v1.HTTPRouteRule, + rulePath *field.Path, + validator validation.HTTPFieldsValidator, + resolveExtRefFunc resolveExtRefFilter, +) (RouteRule, routeRuleErrors) { + var errors routeRuleErrors + + validMatches := true + + for j, match := range specRule.Matches { + matchPath := rulePath.Child("matches").Index(j) - r.Valid = false + matchesErrs := validateMatch(validator, match, matchPath) + if len(matchesErrs) > 0 { + validMatches = false + errors.invalid = append(errors.invalid, matchesErrs...) } } - return r + routeFilters, filterErrors := processRouteRuleFilters( + convertHTTPRouteFilters(specRule.Filters), + rulePath.Child("filters"), + validator, + resolveExtRefFunc, + ) + + errors = errors.append(filterErrors) + + backendRefs := make([]RouteBackendRef, 0, len(specRule.BackendRefs)) + + // rule.BackendRefs are validated separately because of their special requirements + for _, b := range specRule.BackendRefs { + var interfaceFilters []interface{} + if len(b.Filters) > 0 { + interfaceFilters = make([]interface{}, 0, len(b.Filters)) + for i, v := range b.Filters { + interfaceFilters[i] = v + } + } + rbr := RouteBackendRef{ + BackendRef: b.BackendRef, + Filters: interfaceFilters, + } + backendRefs = append(backendRefs, rbr) + } + + return RouteRule{ + ValidMatches: validMatches, + Matches: specRule.Matches, + Filters: routeFilters, + RouteBackendRefs: backendRefs, + }, errors } func processHTTPRouteRules( specRules []v1.HTTPRouteRule, validator validation.HTTPFieldsValidator, -) (rules []RouteRule, atLeastOneValid bool, allRulesErrs field.ErrorList) { + resolveExtRefFunc resolveExtRefFilter, +) (rules []RouteRule, valid bool, conds []conditions.Condition) { rules = make([]RouteRule, len(specRules)) + var ( + allRulesErrors routeRuleErrors + atLeastOneValid bool + ) + for i, rule := range specRules { rulePath := field.NewPath("spec").Child("rules").Index(i) - var matchesErrs field.ErrorList - for j, match := range rule.Matches { - matchPath := rulePath.Child("matches").Index(j) - matchesErrs = append(matchesErrs, validateMatch(validator, match, matchPath)...) - } + rr, errors := processHTTPRouteRule(rule, rulePath, validator, resolveExtRefFunc) - var filtersErrs field.ErrorList - for j, filter := range rule.Filters { - filterPath := rulePath.Child("filters").Index(j) - filtersErrs = append(filtersErrs, validateFilter(validator, filter, filterPath)...) + if rr.ValidMatches && rr.Filters.Valid { + atLeastOneValid = true } - var allErrs field.ErrorList - allErrs = append(allErrs, matchesErrs...) - allErrs = append(allErrs, filtersErrs...) - allRulesErrs = append(allRulesErrs, allErrs...) + allRulesErrors = allRulesErrors.append(errors) - if len(allErrs) == 0 { - atLeastOneValid = true - } + rules[i] = rr + } - backendRefs := make([]RouteBackendRef, 0, len(rule.BackendRefs)) + conds = make([]conditions.Condition, 0, 2) - // rule.BackendRefs are validated separately because of their special requirements - for _, b := range rule.BackendRefs { - var interfaceFilters []interface{} - if len(b.Filters) > 0 { - interfaceFilters = make([]interface{}, 0, len(b.Filters)) - for i, v := range b.Filters { - interfaceFilters[i] = v - } - } - rbr := RouteBackendRef{ - BackendRef: b.BackendRef, - Filters: interfaceFilters, - } - backendRefs = append(backendRefs, rbr) - } + valid = true + + if len(allRulesErrors.invalid) > 0 { + msg := allRulesErrors.invalid.ToAggregate().Error() - rules[i] = RouteRule{ - ValidMatches: len(matchesErrs) == 0, - ValidFilters: len(filtersErrs) == 0, - Matches: rule.Matches, - Filters: rule.Filters, - RouteBackendRefs: backendRefs, + if atLeastOneValid { + conds = append(conds, staticConds.NewRoutePartiallyInvalid(msg)) + } else { + msg = "All rules are invalid: " + msg + conds = append(conds, staticConds.NewRouteUnsupportedValue(msg)) + valid = false } } - return rules, atLeastOneValid, allRulesErrs + + // resolve errors do not invalidate routes + if len(allRulesErrors.resolve) > 0 { + msg := allRulesErrors.resolve.ToAggregate().Error() + conds = append(conds, staticConds.NewRouteResolvedRefsInvalidFilter(msg)) + } + + return rules, valid, conds } func validateMatch( @@ -228,14 +269,18 @@ func validatePathMatch( } if strings.HasPrefix(*path.Value, http.InternalRoutePathPrefix) { - msg := fmt.Sprintf("path cannot start with %s. This prefix is reserved for internal use", - http.InternalRoutePathPrefix) + msg := fmt.Sprintf( + "path cannot start with %s. This prefix is reserved for internal use", + http.InternalRoutePathPrefix, + ) return field.ErrorList{field.Invalid(fieldPath.Child("value"), *path.Value, msg)} } if *path.Type != v1.PathMatchPathPrefix && *path.Type != v1.PathMatchExact { - valErr := field.NotSupported(fieldPath.Child("type"), *path.Type, - []string{string(v1.PathMatchExact), string(v1.PathMatchPathPrefix)}) + valErr := field.NotSupported( + fieldPath.Child("type"), *path.Type, + []string{string(v1.PathMatchExact), string(v1.PathMatchPathPrefix)}, + ) allErrs = append(allErrs, valErr) } @@ -247,40 +292,6 @@ func validatePathMatch( return allErrs } -func validateFilter( - validator validation.HTTPFieldsValidator, - filter v1.HTTPRouteFilter, - filterPath *field.Path, -) field.ErrorList { - var allErrs field.ErrorList - - switch filter.Type { - case v1.HTTPRouteFilterRequestRedirect: - return validateFilterRedirect(validator, filter.RequestRedirect, filterPath) - case v1.HTTPRouteFilterURLRewrite: - return validateFilterRewrite(validator, filter.URLRewrite, filterPath) - case v1.HTTPRouteFilterRequestHeaderModifier: - return validateFilterHeaderModifier(validator, filter.RequestHeaderModifier, filterPath.Child(string(filter.Type))) - case v1.HTTPRouteFilterResponseHeaderModifier: - return validateFilterResponseHeaderModifier( - validator, filter.ResponseHeaderModifier, filterPath.Child(string(filter.Type)), - ) - default: - valErr := field.NotSupported( - filterPath.Child("type"), - filter.Type, - []string{ - string(v1.HTTPRouteFilterRequestRedirect), - string(v1.HTTPRouteFilterURLRewrite), - string(v1.HTTPRouteFilterRequestHeaderModifier), - string(v1.HTTPRouteFilterResponseHeaderModifier), - }, - ) - allErrs = append(allErrs, valErr) - return allErrs - } -} - func validateFilterRedirect( validator validation.HTTPFieldsValidator, redirect *v1.HTTPRequestRedirectFilter, diff --git a/internal/mode/static/state/graph/httproute_test.go b/internal/mode/static/state/graph/httproute_test.go index 667e7b8338..040ca92ba4 100644 --- a/internal/mode/static/state/graph/httproute_test.go +++ b/internal/mode/static/state/graph/httproute_test.go @@ -11,8 +11,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/conditions" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/kinds" staticConds "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/conditions" "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation/validationfakes" ) @@ -40,16 +42,18 @@ func createHTTPRoute( if path == emptyPathValue { pathValue = nil } - rules = append(rules, gatewayv1.HTTPRouteRule{ - Matches: []gatewayv1.HTTPRouteMatch{ - { - Path: &gatewayv1.HTTPPathMatch{ - Type: pathType, - Value: pathValue, + rules = append( + rules, gatewayv1.HTTPRouteRule{ + Matches: []gatewayv1.HTTPRouteMatch{ + { + Path: &gatewayv1.HTTPPathMatch{ + Type: pathType, + Value: pathValue, + }, }, }, }, - }) + ) } return &gatewayv1.HTTPRoute{ @@ -91,6 +95,21 @@ func TestBuildHTTPRoutes(t *testing.T) { gwNsName := types.NamespacedName{Namespace: "test", Name: "gateway"} hr := createHTTPRoute("hr-1", gwNsName.Name, "example.com", "/") + snippetsFilterRef := gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterExtensionRef, + ExtensionRef: &gatewayv1.LocalObjectReference{ + Name: "sf", + Kind: kinds.SnippetsFilter, + Group: ngfAPI.GroupName, + }, + } + requestRedirectFilter := gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterRequestRedirect, + RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{}, + } + + addFilterToPath(hr, "/", snippetsFilterRef) + addFilterToPath(hr, "/", requestRedirectFilter) hrWrongGateway := createHTTPRoute("hr-2", "some-gateway", "example.com", "/") @@ -99,6 +118,21 @@ func TestBuildHTTPRoutes(t *testing.T) { client.ObjectKeyFromObject(hrWrongGateway): hrWrongGateway, } + sf := &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: "sf", + }, + Spec: ngfAPI.SnippetsFilterSpec{ + Snippets: []ngfAPI.Snippet{ + { + Context: ngfAPI.NginxContextHTTP, + Value: "http snippet", + }, + }, + }, + } + tests := []struct { expected map[RouteKey]*L7Route name string @@ -123,8 +157,33 @@ func TestBuildHTTPRoutes(t *testing.T) { Hostnames: hr.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: true, - ValidFilters: true, + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{ + { + ExtensionRef: snippetsFilterRef.ExtensionRef, + ResolvedExtensionRef: &ExtensionRefFilter{ + SnippetsFilter: &SnippetsFilter{ + Source: sf, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTP: "http snippet", + }, + Valid: true, + Referenced: true, + }, + Valid: true, + }, + RouteType: RouteTypeHTTP, + FilterType: FilterExtensionRef, + }, + { + RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{}, + RouteType: RouteTypeHTTP, + FilterType: FilterRequestRedirect, + }, + }, + }, Matches: hr.Spec.Rules[0].Matches, RouteBackendRefs: []RouteBackendRef{}, }, @@ -144,18 +203,32 @@ func TestBuildHTTPRoutes(t *testing.T) { validator := &validationfakes.FakeHTTPFieldsValidator{} for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - routes := buildRoutesForGateways( - validator, - hrRoutes, - map[types.NamespacedName]*gatewayv1.GRPCRoute{}, - test.gwNsNames, - nil, - ) - g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ + client.ObjectKeyFromObject(sf): { + Source: sf, + Valid: true, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTP: "http snippet", + }, + }, + } + + routes := buildRoutesForGateways( + validator, + hrRoutes, + map[types.NamespacedName]*gatewayv1.GRPCRoute{}, + test.gwNsNames, + nil, + snippetsFilters, + ) + g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) + }, + ) } } @@ -168,49 +241,94 @@ func TestBuildHTTPRoute(t *testing.T) { gatewayNsName := types.NamespacedName{Namespace: "test", Name: "gateway"} + // route with valid filter validFilter := gatewayv1.HTTPRouteFilter{ Type: gatewayv1.HTTPRouteFilterRequestRedirect, RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{}, } - invalidFilter := gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterRequestRedirect, - RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{ - Hostname: helpers.GetPointer[gatewayv1.PreciseHostname](invalidRedirectHostname), - }, - } - hr := createHTTPRoute("hr", gatewayNsName.Name, "example.com", "/", "/filter") addFilterToPath(hr, "/filter", validFilter) + // invalid routes without filters hrInvalidHostname := createHTTPRoute("hr", gatewayNsName.Name, "", "/") hrNotNGF := createHTTPRoute("hr", "some-gateway", "example.com", "/") hrInvalidMatches := createHTTPRoute("hr", gatewayNsName.Name, "example.com", invalidPath) - hrInvalidMatchesEmptyPathType := createHTTPRoute("hr", gatewayNsName.Name, "example.com", emptyPathType) hrInvalidMatchesEmptyPathValue := createHTTPRoute("hr", gatewayNsName.Name, "example.com", emptyPathValue) + hrDroppedInvalidMatches := createHTTPRoute("hr", gatewayNsName.Name, "example.com", invalidPath, "/") + // route with invalid filter + invalidFilter := gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterRequestRedirect, + RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{ + Hostname: helpers.GetPointer[gatewayv1.PreciseHostname](invalidRedirectHostname), + }, + } hrInvalidFilters := createHTTPRoute("hr", gatewayNsName.Name, "example.com", "/filter") addFilterToPath(hrInvalidFilters, "/filter", invalidFilter) - hrDroppedInvalidMatches := createHTTPRoute("hr", gatewayNsName.Name, "example.com", invalidPath, "/") - + // route with invalid matches and filters hrDroppedInvalidMatchesAndInvalidFilters := createHTTPRoute( "hr", gatewayNsName.Name, "example.com", - invalidPath, "/filter", "/") + invalidPath, "/filter", "/", + ) addFilterToPath(hrDroppedInvalidMatchesAndInvalidFilters, "/filter", invalidFilter) + // route with both invalid and valid filters in the same rule hrDroppedInvalidFilters := createHTTPRoute("hr", gatewayNsName.Name, "example.com", "/filter", "/") addFilterToPath(hrDroppedInvalidFilters, "/filter", validFilter) addFilterToPath(hrDroppedInvalidFilters, "/", invalidFilter) + // route with duplicate section names hrDuplicateSectionName := createHTTPRoute("hr", gatewayNsName.Name, "example.com", "/") hrDuplicateSectionName.Spec.ParentRefs = append( hrDuplicateSectionName.Spec.ParentRefs, hrDuplicateSectionName.Spec.ParentRefs[0], ) + // route with valid snippets filter extension ref + hrValidSnippetsFilter := createHTTPRoute("hr", gatewayNsName.Name, "example.com", "/filter") + validSnippetsFilterExtRef := gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterExtensionRef, + ExtensionRef: &gatewayv1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: "sf", + }, + } + addFilterToPath(hrValidSnippetsFilter, "/filter", validSnippetsFilterExtRef) + + // route with invalid snippets filter extension ref + hrInvalidSnippetsFilter := createHTTPRoute("hr", gatewayNsName.Name, "example.com", "/filter") + invalidSnippetsFilterExtRef := gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterExtensionRef, + ExtensionRef: &gatewayv1.LocalObjectReference{ + Group: "wrong", + Kind: kinds.SnippetsFilter, + Name: "sf", + }, + } + addFilterToPath(hrInvalidSnippetsFilter, "/filter", invalidSnippetsFilterExtRef) + + // route with unresolvable snippets filter extension ref + hrUnresolvableSnippetsFilter := createHTTPRoute("hr", gatewayNsName.Name, "example.com", "/filter") + unresolvableSnippetsFilterExtRef := gatewayv1.HTTPRouteFilter{ + Type: gatewayv1.HTTPRouteFilterExtensionRef, + ExtensionRef: &gatewayv1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: "does-not-exist", + }, + } + addFilterToPath(hrUnresolvableSnippetsFilter, "/filter", unresolvableSnippetsFilterExtRef) + + // route with two invalid snippets filter extensions refs: (1) invalid group (2) unresolvable + hrInvalidAndUnresolvableSnippetsFilter := createHTTPRoute("hr", gatewayNsName.Name, "example.com", "/filter") + addFilterToPath(hrInvalidAndUnresolvableSnippetsFilter, "/filter", invalidSnippetsFilterExtRef) + addFilterToPath(hrInvalidAndUnresolvableSnippetsFilter, "/filter", unresolvableSnippetsFilterExtRef) + validatorInvalidFieldsInRule := &validationfakes.FakeHTTPFieldsValidator{ ValidatePathInMatchStub: func(path string) error { if path == invalidPath { @@ -251,16 +369,21 @@ func TestBuildHTTPRoute(t *testing.T) { Hostnames: hr.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: true, - ValidFilters: true, + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: hr.Spec.Rules[0].Matches, RouteBackendRefs: []RouteBackendRef{}, }, { - ValidMatches: true, - ValidFilters: true, + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: true, + Filters: convertHTTPRouteFilters(hr.Spec.Rules[1].Filters), + }, Matches: hr.Spec.Rules[1].Matches, - Filters: hr.Spec.Rules[1].Filters, RouteBackendRefs: []RouteBackendRef{}, }, }, @@ -292,8 +415,11 @@ func TestBuildHTTPRoute(t *testing.T) { Hostnames: hrInvalidMatchesEmptyPathType.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: false, - ValidFilters: true, + ValidMatches: false, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, RouteBackendRefs: []RouteBackendRef{}, Matches: hrInvalidMatchesEmptyPathType.Spec.Rules[0].Matches, }, @@ -335,8 +461,11 @@ func TestBuildHTTPRoute(t *testing.T) { Hostnames: hr.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: false, - ValidFilters: true, + ValidMatches: false, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, RouteBackendRefs: []RouteBackendRef{}, Matches: hrInvalidMatchesEmptyPathValue.Spec.Rules[0].Matches, }, @@ -398,8 +527,11 @@ func TestBuildHTTPRoute(t *testing.T) { Hostnames: hr.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: false, - ValidFilters: true, + ValidMatches: false, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: hrInvalidMatches.Spec.Rules[0].Matches, RouteBackendRefs: []RouteBackendRef{}, }, @@ -433,10 +565,12 @@ func TestBuildHTTPRoute(t *testing.T) { Hostnames: hr.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: true, - ValidFilters: false, + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: false, + Filters: convertHTTPRouteFilters(hrInvalidFilters.Spec.Rules[0].Filters), + }, Matches: hrInvalidFilters.Spec.Rules[0].Matches, - Filters: hrInvalidFilters.Spec.Rules[0].Filters, RouteBackendRefs: []RouteBackendRef{}, }, }, @@ -468,14 +602,20 @@ func TestBuildHTTPRoute(t *testing.T) { Hostnames: hr.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: false, - ValidFilters: true, + ValidMatches: false, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: hrDroppedInvalidMatches.Spec.Rules[0].Matches, RouteBackendRefs: []RouteBackendRef{}, }, { - ValidMatches: true, - ValidFilters: true, + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: hrDroppedInvalidMatches.Spec.Rules[1].Matches, RouteBackendRefs: []RouteBackendRef{}, }, @@ -511,21 +651,31 @@ func TestBuildHTTPRoute(t *testing.T) { Hostnames: hr.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: false, - ValidFilters: true, + ValidMatches: false, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: hrDroppedInvalidMatchesAndInvalidFilters.Spec.Rules[0].Matches, RouteBackendRefs: []RouteBackendRef{}, }, { - ValidMatches: true, - ValidFilters: false, - Matches: hrDroppedInvalidMatchesAndInvalidFilters.Spec.Rules[1].Matches, - Filters: hrDroppedInvalidMatchesAndInvalidFilters.Spec.Rules[1].Filters, + ValidMatches: true, + Matches: hrDroppedInvalidMatchesAndInvalidFilters.Spec.Rules[1].Matches, + Filters: RouteRuleFilters{ + Valid: false, + Filters: convertHTTPRouteFilters( + hrDroppedInvalidMatchesAndInvalidFilters.Spec.Rules[1].Filters, + ), + }, RouteBackendRefs: []RouteBackendRef{}, }, { - ValidMatches: true, - ValidFilters: true, + ValidMatches: true, + Filters: RouteRuleFilters{ + Valid: true, + Filters: []Filter{}, + }, Matches: hrDroppedInvalidMatchesAndInvalidFilters.Spec.Rules[2].Matches, RouteBackendRefs: []RouteBackendRef{}, }, @@ -559,17 +709,21 @@ func TestBuildHTTPRoute(t *testing.T) { Hostnames: hr.Spec.Hostnames, Rules: []RouteRule{ { - ValidMatches: true, - ValidFilters: true, - Matches: hrDroppedInvalidFilters.Spec.Rules[0].Matches, - Filters: hrDroppedInvalidFilters.Spec.Rules[0].Filters, + ValidMatches: true, + Matches: hrDroppedInvalidFilters.Spec.Rules[0].Matches, + Filters: RouteRuleFilters{ + Filters: convertHTTPRouteFilters(hrDroppedInvalidFilters.Spec.Rules[0].Filters), + Valid: true, + }, RouteBackendRefs: []RouteBackendRef{}, }, { - ValidMatches: true, - ValidFilters: false, - Matches: hrDroppedInvalidFilters.Spec.Rules[1].Matches, - Filters: hrDroppedInvalidFilters.Spec.Rules[1].Filters, + ValidMatches: true, + Matches: hrDroppedInvalidFilters.Spec.Rules[1].Matches, + Filters: RouteRuleFilters{ + Filters: convertHTTPRouteFilters(hrDroppedInvalidFilters.Spec.Rules[1].Filters), + Valid: false, + }, RouteBackendRefs: []RouteBackendRef{}, }, }, @@ -577,18 +731,188 @@ func TestBuildHTTPRoute(t *testing.T) { }, name: "dropped invalid rule with invalid filters", }, + { + validator: validatorInvalidFieldsInRule, + hr: hrValidSnippetsFilter, + expected: &L7Route{ + RouteType: RouteTypeHTTP, + Source: hrValidSnippetsFilter, + Valid: true, + Attachable: true, + ParentRefs: []ParentRef{ + { + Idx: 0, + Gateway: gatewayNsName, + SectionName: hrValidSnippetsFilter.Spec.ParentRefs[0].SectionName, + }, + }, + Spec: L7RouteSpec{ + Hostnames: hrValidSnippetsFilter.Spec.Hostnames, + Rules: []RouteRule{ + { + ValidMatches: true, + Matches: hrValidSnippetsFilter.Spec.Rules[0].Matches, + Filters: RouteRuleFilters{ + Filters: []Filter{ + { + RouteType: RouteTypeHTTP, + FilterType: FilterExtensionRef, + ExtensionRef: validSnippetsFilterExtRef.ExtensionRef, + ResolvedExtensionRef: &ExtensionRefFilter{ + Valid: true, + SnippetsFilter: &SnippetsFilter{Valid: true, Referenced: true}, + }, + }, + }, + Valid: true, + }, + RouteBackendRefs: []RouteBackendRef{}, + }, + }, + }, + }, + name: "rule with valid snippets filter extension ref filter", + }, + { + validator: validatorInvalidFieldsInRule, + hr: hrInvalidSnippetsFilter, + expected: &L7Route{ + RouteType: RouteTypeHTTP, + Source: hrInvalidSnippetsFilter, + Valid: false, + Attachable: true, + ParentRefs: []ParentRef{ + { + Idx: 0, + Gateway: gatewayNsName, + SectionName: hrInvalidSnippetsFilter.Spec.ParentRefs[0].SectionName, + }, + }, + Conditions: []conditions.Condition{ + staticConds.NewRouteUnsupportedValue( + "All rules are invalid: spec.rules[0].filters[0].extensionRef: " + + "Unsupported value: \"wrong\": supported values: \"gateway.nginx.org\"", + ), + }, + Spec: L7RouteSpec{ + Hostnames: hrInvalidSnippetsFilter.Spec.Hostnames, + Rules: []RouteRule{ + { + ValidMatches: true, + Matches: hrInvalidSnippetsFilter.Spec.Rules[0].Matches, + Filters: RouteRuleFilters{ + Filters: convertHTTPRouteFilters(hrInvalidSnippetsFilter.Spec.Rules[0].Filters), + Valid: false, + }, + RouteBackendRefs: []RouteBackendRef{}, + }, + }, + }, + }, + name: "rule with invalid snippets filter extension ref filter", + }, + { + validator: validatorInvalidFieldsInRule, + hr: hrUnresolvableSnippetsFilter, + expected: &L7Route{ + RouteType: RouteTypeHTTP, + Source: hrUnresolvableSnippetsFilter, + Valid: true, + Attachable: true, + ParentRefs: []ParentRef{ + { + Idx: 0, + Gateway: gatewayNsName, + SectionName: hrUnresolvableSnippetsFilter.Spec.ParentRefs[0].SectionName, + }, + }, + Conditions: []conditions.Condition{ + staticConds.NewRouteResolvedRefsInvalidFilter( + "spec.rules[0].filters[0].extensionRef: Not found: " + + "v1.LocalObjectReference{Group:\"gateway.nginx.org\", Kind:\"SnippetsFilter\", " + + "Name:\"does-not-exist\"}", + ), + }, + Spec: L7RouteSpec{ + Hostnames: hrUnresolvableSnippetsFilter.Spec.Hostnames, + Rules: []RouteRule{ + { + ValidMatches: true, + Matches: hrUnresolvableSnippetsFilter.Spec.Rules[0].Matches, + Filters: RouteRuleFilters{ + Filters: convertHTTPRouteFilters(hrUnresolvableSnippetsFilter.Spec.Rules[0].Filters), + Valid: false, + }, + RouteBackendRefs: []RouteBackendRef{}, + }, + }, + }, + }, + name: "rule with unresolvable snippets filter extension ref filter", + }, + { + validator: validatorInvalidFieldsInRule, + hr: hrInvalidAndUnresolvableSnippetsFilter, + expected: &L7Route{ + RouteType: RouteTypeHTTP, + Source: hrInvalidAndUnresolvableSnippetsFilter, + Valid: false, + Attachable: true, + ParentRefs: []ParentRef{ + { + Idx: 0, + Gateway: gatewayNsName, + SectionName: hrInvalidAndUnresolvableSnippetsFilter.Spec.ParentRefs[0].SectionName, + }, + }, + Conditions: []conditions.Condition{ + staticConds.NewRouteUnsupportedValue( + "All rules are invalid: spec.rules[0].filters[0].extensionRef: " + + "Unsupported value: \"wrong\": supported values: \"gateway.nginx.org\"", + ), + staticConds.NewRouteResolvedRefsInvalidFilter( + "spec.rules[0].filters[1].extensionRef: Not found: " + + "v1.LocalObjectReference{Group:\"gateway.nginx.org\", Kind:\"SnippetsFilter\", " + + "Name:\"does-not-exist\"}", + ), + }, + Spec: L7RouteSpec{ + Hostnames: hrInvalidAndUnresolvableSnippetsFilter.Spec.Hostnames, + Rules: []RouteRule{ + { + ValidMatches: true, + Matches: hrInvalidAndUnresolvableSnippetsFilter.Spec.Rules[0].Matches, + Filters: RouteRuleFilters{ + Filters: convertHTTPRouteFilters( + hrInvalidAndUnresolvableSnippetsFilter.Spec.Rules[0].Filters, + ), + Valid: false, + }, + RouteBackendRefs: []RouteBackendRef{}, + }, + }, + }, + }, + name: "rule with one invalid and one unresolvable snippets filter extension ref filter", + }, } gatewayNsNames := []types.NamespacedName{gatewayNsName} for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ + {Namespace: "test", Name: "sf"}: {Valid: true}, + } - route := buildHTTPRoute(test.validator, test.hr, gatewayNsNames) - g.Expect(helpers.Diff(test.expected, route)).To(BeEmpty()) - }) + route := buildHTTPRoute(test.validator, test.hr, gatewayNsNames, snippetsFilters) + g.Expect(helpers.Diff(test.expected, route)).To(BeEmpty()) + }, + ) } } @@ -848,72 +1172,14 @@ func TestValidateMatch(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - allErrs := validateMatch(test.validator, test.match, field.NewPath("test")) - g.Expect(allErrs).To(HaveLen(test.expectErrCount)) - }) - } -} - -func TestValidateFilter(t *testing.T) { - t.Parallel() - tests := []struct { - filter gatewayv1.HTTPRouteFilter - name string - expectErrCount int - }{ - { - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterRequestRedirect, - RequestRedirect: &gatewayv1.HTTPRequestRedirectFilter{}, - }, - expectErrCount: 0, - name: "valid redirect filter", - }, - { - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterURLRewrite, - URLRewrite: &gatewayv1.HTTPURLRewriteFilter{}, + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + allErrs := validateMatch(test.validator, test.match, field.NewPath("test")) + g.Expect(allErrs).To(HaveLen(test.expectErrCount)) }, - expectErrCount: 0, - name: "valid rewrite filter", - }, - { - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{}, - }, - expectErrCount: 0, - name: "valid request header modifiers filter", - }, - { - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{}, - }, - expectErrCount: 0, - name: "valid response header modifiers filter", - }, - { - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterRequestMirror, - }, - expectErrCount: 1, - name: "unsupported filter", - }, - } - - filterPath := field.NewPath("test") - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - allErrs := validateFilter(&validationfakes.FakeHTTPFieldsValidator{}, test.filter, filterPath) - g.Expect(allErrs).To(HaveLen(test.expectErrCount)) - }) + ) } } @@ -1038,13 +1304,15 @@ func TestValidateFilterRedirect(t *testing.T) { filterPath := field.NewPath("test") for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - allErrs := validateFilterRedirect(test.validator, test.requestRedirect, filterPath) - g.Expect(allErrs).To(HaveLen(test.expectErrCount)) - }) + allErrs := validateFilterRedirect(test.validator, test.requestRedirect, filterPath) + g.Expect(allErrs).To(HaveLen(test.expectErrCount)) + }, + ) } } @@ -1158,11 +1426,13 @@ func TestValidateFilterRewrite(t *testing.T) { filterPath := field.NewPath("test") for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - allErrs := validateFilterRewrite(test.validator, test.urlRewrite, filterPath) - g.Expect(allErrs).To(HaveLen(test.expectErrCount)) - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + allErrs := validateFilterRewrite(test.validator, test.urlRewrite, filterPath) + g.Expect(allErrs).To(HaveLen(test.expectErrCount)) + }, + ) } } diff --git a/internal/mode/static/state/graph/route_common.go b/internal/mode/static/state/graph/route_common.go index 4db7807d71..ef761e2b6e 100644 --- a/internal/mode/static/state/graph/route_common.go +++ b/internal/mode/static/state/graph/route_common.go @@ -127,16 +127,14 @@ type L7RouteSpec struct { type RouteRule struct { // Matches define the predicate used to match requests to a given action. Matches []v1.HTTPRouteMatch - // Filters define processing steps that must be completed during the request or response lifecycle. - Filters []v1.HTTPRouteFilter // RouteBackendRefs are a wrapper for v1.BackendRef and any BackendRef filters from the HTTPRoute or GRPCRoute. RouteBackendRefs []RouteBackendRef // BackendRefs is an internal representation of a backendRef in a Route. BackendRefs []BackendRef + // Filters define processing steps that must be completed during the request or response lifecycle. + Filters RouteRuleFilters // ValidMatches indicates if the matches are valid and accepted by the Route. ValidMatches bool - // ValidFilters indicates if the filters are valid and accepted by the Route. - ValidFilters bool } // RouteBackendRef is a wrapper for v1.BackendRef and any BackendRef filters from the HTTPRoute or GRPCRoute. @@ -173,6 +171,18 @@ func CreateRouteKeyL4(obj client.Object) L4RouteKey { } } +type routeRuleErrors struct { + invalid field.ErrorList + resolve field.ErrorList +} + +func (e routeRuleErrors) append(newErrors routeRuleErrors) routeRuleErrors { + return routeRuleErrors{ + invalid: append(e.invalid, newErrors.invalid...), + resolve: append(e.resolve, newErrors.resolve...), + } +} + func buildL4RoutesForGateways( tlsRoutes map[types.NamespacedName]*v1alpha.TLSRoute, gatewayNsNames []types.NamespacedName, @@ -207,6 +217,7 @@ func buildRoutesForGateways( grpcRoutes map[types.NamespacedName]*v1.GRPCRoute, gatewayNsNames []types.NamespacedName, npCfg *NginxProxy, + snippetsFilters map[types.NamespacedName]*SnippetsFilter, ) map[RouteKey]*L7Route { if len(gatewayNsNames) == 0 { return nil @@ -217,14 +228,14 @@ func buildRoutesForGateways( http2disabled := isHTTP2Disabled(npCfg) for _, route := range httpRoutes { - r := buildHTTPRoute(validator, route, gatewayNsNames) + r := buildHTTPRoute(validator, route, gatewayNsNames, snippetsFilters) if r != nil { routes[CreateRouteKey(route)] = r } } for _, route := range grpcRoutes { - r := buildGRPCRoute(validator, route, gatewayNsNames, http2disabled) + r := buildGRPCRoute(validator, route, gatewayNsNames, http2disabled, snippetsFilters) if r != nil { routes[CreateRouteKey(route)] = r } @@ -274,12 +285,14 @@ func buildSectionNameRefs( } uniqueSectionsPerGateway[k] = struct{}{} - sectionNameRefs = append(sectionNameRefs, ParentRef{ - Idx: i, - Gateway: gw, - SectionName: p.SectionName, - Port: p.Port, - }) + sectionNameRefs = append( + sectionNameRefs, ParentRef{ + Idx: i, + Gateway: gw, + SectionName: p.SectionName, + Port: p.Port, + }, + ) } return sectionNameRefs, nil @@ -332,9 +345,11 @@ func bindRoutesToListeners( } // Sort the slice by timestamp and name so that we process the routes in the priority order - sort.Slice(routes, func(i, j int) bool { - return ngfSort.LessClientObject(routes[i].Source, routes[j].Source) - }) + sort.Slice( + routes, func(i, j int) bool { + return ngfSort.LessClientObject(routes[i].Source, routes[j].Source) + }, + ) // portHostnamesMap exists to detect duplicate hostnames on the same port portHostnamesMap := make(map[string]struct{}) @@ -458,18 +473,20 @@ func tryToAttachL4RouteToListeners( allowed, attached, hostnamesUnique bool ) - // Sorting the listeners from most specific hostname to least specific hostname - sort.Slice(attachableListeners, func(i, j int) bool { - h1 := "" - h2 := "" - if attachableListeners[i].Source.Hostname != nil { - h1 = string(*attachableListeners[i].Source.Hostname) - } - if attachableListeners[j].Source.Hostname != nil { - h2 = string(*attachableListeners[j].Source.Hostname) - } - return h1 == GetMoreSpecificHostname(h1, h2) - }) + // Sorting the listeners from most specific hostname to the least specific hostname + sort.Slice( + attachableListeners, func(i, j int) bool { + h1 := "" + h2 := "" + if attachableListeners[i].Source.Hostname != nil { + h1 = string(*attachableListeners[i].Source.Hostname) + } + if attachableListeners[j].Source.Hostname != nil { + h2 = string(*attachableListeners[j].Source.Hostname) + } + return h1 == GetMoreSpecificHostname(h1, h2) + }, + ) for _, l := range attachableListeners { routeAllowed, routeAttached, routeHostnamesUnique := bindToListenerL4( @@ -870,166 +887,6 @@ func validateHeaderMatch( return allErrs } -func validateFilterHeaderModifier( - validator validation.HTTPFieldsValidator, - headerModifier *v1.HTTPHeaderFilter, - filterPath *field.Path, -) field.ErrorList { - if headerModifier == nil { - return field.ErrorList{field.Required(filterPath, "cannot be nil")} - } - - return validateFilterHeaderModifierFields(validator, headerModifier, filterPath) -} - -func validateFilterHeaderModifierFields( - validator validation.HTTPFieldsValidator, - headerModifier *v1.HTTPHeaderFilter, - headerModifierPath *field.Path, -) field.ErrorList { - var allErrs field.ErrorList - - // Ensure that the header names are case-insensitive unique - allErrs = append(allErrs, validateRequestHeadersCaseInsensitiveUnique( - headerModifier.Add, - headerModifierPath.Child(add))..., - ) - allErrs = append(allErrs, validateRequestHeadersCaseInsensitiveUnique( - headerModifier.Set, - headerModifierPath.Child(set))..., - ) - allErrs = append(allErrs, validateRequestHeaderStringCaseInsensitiveUnique( - headerModifier.Remove, - headerModifierPath.Child(remove))..., - ) - - for _, h := range headerModifier.Add { - if err := validator.ValidateFilterHeaderName(string(h.Name)); err != nil { - valErr := field.Invalid(headerModifierPath.Child(add), h, err.Error()) - allErrs = append(allErrs, valErr) - } - if err := validator.ValidateFilterHeaderValue(h.Value); err != nil { - valErr := field.Invalid(headerModifierPath.Child(add), h, err.Error()) - allErrs = append(allErrs, valErr) - } - } - for _, h := range headerModifier.Set { - if err := validator.ValidateFilterHeaderName(string(h.Name)); err != nil { - valErr := field.Invalid(headerModifierPath.Child(set), h, err.Error()) - allErrs = append(allErrs, valErr) - } - if err := validator.ValidateFilterHeaderValue(h.Value); err != nil { - valErr := field.Invalid(headerModifierPath.Child(set), h, err.Error()) - allErrs = append(allErrs, valErr) - } - } - for _, h := range headerModifier.Remove { - if err := validator.ValidateFilterHeaderName(h); err != nil { - valErr := field.Invalid(headerModifierPath.Child(remove), h, err.Error()) - allErrs = append(allErrs, valErr) - } - } - - return allErrs -} - -func validateFilterResponseHeaderModifier( - validator validation.HTTPFieldsValidator, - responseHeaderModifier *v1.HTTPHeaderFilter, - filterPath *field.Path, -) field.ErrorList { - if errList := validateFilterHeaderModifier(validator, responseHeaderModifier, filterPath); errList != nil { - return errList - } - var allErrs field.ErrorList - - allErrs = append(allErrs, validateResponseHeaders( - responseHeaderModifier.Add, - filterPath.Child(add))..., - ) - - allErrs = append(allErrs, validateResponseHeaders( - responseHeaderModifier.Set, - filterPath.Child(set))..., - ) - - var removeHeaders []v1.HTTPHeader - for _, h := range responseHeaderModifier.Remove { - removeHeaders = append(removeHeaders, v1.HTTPHeader{Name: v1.HTTPHeaderName(h)}) - } - - allErrs = append(allErrs, validateResponseHeaders( - removeHeaders, - filterPath.Child(remove))..., - ) - - return allErrs -} - -func validateResponseHeaders( - headers []v1.HTTPHeader, - path *field.Path, -) field.ErrorList { - var allErrs field.ErrorList - disallowedResponseHeaderSet := map[string]struct{}{ - "server": {}, - "date": {}, - "x-pad": {}, - "content-type": {}, - "content-length": {}, - "connection": {}, - } - invalidPrefix := "x-accel" - - for _, h := range headers { - valErr := field.Invalid(path, h, "header name is not allowed") - name := strings.ToLower(string(h.Name)) - if _, exists := disallowedResponseHeaderSet[name]; exists || - strings.HasPrefix(name, strings.ToLower(invalidPrefix)) { - allErrs = append(allErrs, valErr) - } - } - - return allErrs -} - -func validateRequestHeadersCaseInsensitiveUnique( - headers []v1.HTTPHeader, - path *field.Path, -) field.ErrorList { - var allErrs field.ErrorList - - seen := make(map[string]struct{}) - - for _, h := range headers { - name := strings.ToLower(string(h.Name)) - if _, exists := seen[name]; exists { - valErr := field.Invalid(path, h, "header name is not unique") - allErrs = append(allErrs, valErr) - } - seen[name] = struct{}{} - } - - return allErrs -} - -func validateRequestHeaderStringCaseInsensitiveUnique(headers []string, path *field.Path) field.ErrorList { - var allErrs field.ErrorList - - seen := make(map[string]struct{}) - - for _, h := range headers { - name := strings.ToLower(h) - if _, exists := seen[name]; exists { - valErr := field.Invalid(path, h, "header name is not unique") - allErrs = append(allErrs, valErr) - } - seen[name] = struct{}{} - } - - return allErrs -} - func routeKeyForKind(kind v1.Kind, nsname types.NamespacedName) RouteKey { key := RouteKey{NamespacedName: nsname} switch kind { diff --git a/internal/mode/static/state/graph/route_common_test.go b/internal/mode/static/state/graph/route_common_test.go index f96e820bb7..82ba92db77 100644 --- a/internal/mode/static/state/graph/route_common_test.go +++ b/internal/mode/static/state/graph/route_common_test.go @@ -18,7 +18,6 @@ import ( "github.com/nginxinc/nginx-gateway-fabric/internal/framework/helpers" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/kinds" staticConds "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/conditions" - "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/validation/validationfakes" ) func TestBuildSectionNameRefs(t *testing.T) { @@ -123,18 +122,20 @@ func TestBuildSectionNameRefs(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - result, err := buildSectionNameRefs(test.parentRefs, routeNamespace, gwNsNames) - g.Expect(result).To(Equal(test.expectedRefs)) - if test.expectedError != nil { - g.Expect(err).To(Equal(test.expectedError)) - } else { - g.Expect(err).ToNot(HaveOccurred()) - } - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + result, err := buildSectionNameRefs(test.parentRefs, routeNamespace, gwNsNames) + g.Expect(result).To(Equal(test.expectedRefs)) + if test.expectedError != nil { + g.Expect(err).To(Equal(test.expectedError)) + } else { + g.Expect(err).ToNot(HaveOccurred()) + } + }, + ) } } @@ -214,14 +215,16 @@ func TestFindGatewayForParentRef(t *testing.T) { } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - gw, found := findGatewayForParentRef(test.ref, routeNamespace, gwNsNames) - g.Expect(found).To(Equal(test.expectedFound)) - g.Expect(gw).To(Equal(test.expectedGwNsName)) - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + gw, found := findGatewayForParentRef(test.ref, routeNamespace, gwNsNames) + g.Expect(found).To(Equal(test.expectedFound)) + g.Expect(gw).To(Equal(test.expectedGwNsName)) + }, + ) } } @@ -239,8 +242,14 @@ func TestBindRouteToListeners(t *testing.T) { Attachable: true, Routes: map[RouteKey]*L7Route{}, SupportedKinds: []gatewayv1.RouteGroupKind{ - {Kind: gatewayv1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, - {Kind: gatewayv1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + { + Kind: gatewayv1.Kind(kinds.HTTPRoute), + Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName), + }, + { + Kind: gatewayv1.Kind(kinds.GRPCRoute), + Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName), + }, }, } } @@ -436,13 +445,17 @@ func TestBindRouteToListeners(t *testing.T) { }, } - invalidNotAttachableListener := createModifiedListener("listener-80-1", func(l *Listener) { - l.Valid = false - l.Attachable = false - }) - nonMatchingHostnameListener := createModifiedListener("listener-80-1", func(l *Listener) { - l.Source.Hostname = helpers.GetPointer[gatewayv1.Hostname]("bar.example.com") - }) + invalidNotAttachableListener := createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Valid = false + l.Attachable = false + }, + ) + nonMatchingHostnameListener := createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Source.Hostname = helpers.GetPointer[gatewayv1.Hostname]("bar.example.com") + }, + ) createGRPCRouteWithSectionNameAndPort := func( sectionName *gatewayv1.SectionName, @@ -531,11 +544,13 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }, + ), }, name: "normal case", }, @@ -562,11 +577,13 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): routeWithMissingSectionName, - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): routeWithMissingSectionName, + } + }, + ), }, name: "section name is nil", }, @@ -595,16 +612,20 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80", func(l *Listener) { - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): routeWithEmptySectionName, - } - }), - createModifiedListener("listener-8080", func(l *Listener) { - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): routeWithEmptySectionName, - } - }), + createModifiedListener( + "listener-80", func(l *Listener) { + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): routeWithEmptySectionName, + } + }, + ), + createModifiedListener( + "listener-8080", func(l *Listener) { + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): routeWithEmptySectionName, + } + }, + ), }, name: "section name is empty; bind to multiple listeners", }, @@ -821,9 +842,11 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Valid = false - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Valid = false + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -840,12 +863,14 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Valid = false - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Valid = false + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }, + ), }, expectedConditions: []conditions.Condition{staticConds.NewRouteInvalidListener()}, name: "invalid attachable listener", @@ -873,11 +898,13 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): invalidAttachableRoute1, - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): invalidAttachableRoute1, + } + }, + ), }, name: "invalid attachable route", }, @@ -887,9 +914,11 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Valid = false - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Valid = false + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -906,12 +935,14 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Valid = false - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): invalidAttachableRoute2, - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Valid = false + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): invalidAttachableRoute2, + } + }, + ), }, expectedConditions: []conditions.Condition{staticConds.NewRouteInvalidListener()}, name: "invalid attachable listener with invalid attachable route", @@ -922,15 +953,17 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), - }, - } - allowedLabels := map[string]string{"app": "not-allowed"} - l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), + }, + } + allowedLabels := map[string]string{"app": "not-allowed"} + l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -946,15 +979,17 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), - }, - } - allowedLabels := map[string]string{"app": "not-allowed"} - l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), + }, + } + allowedLabels := map[string]string{"app": "not-allowed"} + l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) + }, + ), }, name: "route not allowed via labels", }, @@ -964,15 +999,17 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), - }, - } - allowedLabels := map[string]string{"app": "allowed"} - l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), + }, + } + allowedLabels := map[string]string{"app": "allowed"} + l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -989,18 +1026,20 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - allowedLabels := map[string]string{"app": "allowed"} - l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), - }, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + allowedLabels := map[string]string{"app": "allowed"} + l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), + }, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }, + ), }, name: "route allowed via labels", }, @@ -1010,13 +1049,15 @@ func TestBindRouteToListeners(t *testing.T) { Source: gwDiffNamespace, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSame), - }, - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSame), + }, + } + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1032,13 +1073,15 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSame), - }, - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSame), + }, + } + }, + ), }, name: "route not allowed via same namespace", }, @@ -1048,13 +1091,15 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSame), - }, - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSame), + }, + } + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1071,16 +1116,18 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSame), - }, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSame), + }, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }, + ), }, name: "route allowed via same namespace", }, @@ -1090,13 +1137,15 @@ func TestBindRouteToListeners(t *testing.T) { Source: gwDiffNamespace, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromAll), - }, - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromAll), + }, + } + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1113,16 +1162,18 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromAll), - }, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromAll), + }, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }, + ), }, name: "route allowed via all namespaces", }, @@ -1132,14 +1183,19 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.SupportedKinds = []gatewayv1.RouteGroupKind{ - {Kind: gatewayv1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(gr): getLastNormalGRPCRoute(), - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.SupportedKinds = []gatewayv1.RouteGroupKind{ + { + Kind: gatewayv1.Kind(kinds.HTTPRoute), + Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName), + }, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(gr): getLastNormalGRPCRoute(), + } + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1155,14 +1211,19 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.SupportedKinds = []gatewayv1.RouteGroupKind{ - {Kind: gatewayv1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(gr): getLastNormalGRPCRoute(), - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.SupportedKinds = []gatewayv1.RouteGroupKind{ + { + Kind: gatewayv1.Kind(kinds.HTTPRoute), + Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName), + }, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(gr): getLastNormalGRPCRoute(), + } + }, + ), }, name: "grpc route not allowed when listener kind is HTTPRoute", }, @@ -1172,16 +1233,18 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Kinds: []gatewayv1.RouteGroupKind{ - {Kind: "HTTPRoute"}, - }, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Kinds: []gatewayv1.RouteGroupKind{ + {Kind: "HTTPRoute"}, + }, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1198,16 +1261,18 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Kinds: []gatewayv1.RouteGroupKind{ - {Kind: "HTTPRoute"}, - }, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }), + createModifiedListener( + "listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Kinds: []gatewayv1.RouteGroupKind{ + {Kind: "HTTPRoute"}, + }, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }, + ), }, name: "http route allowed when listener kind is HTTPRoute", }, @@ -1222,19 +1287,21 @@ func TestBindRouteToListeners(t *testing.T) { }, } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - g := NewWithT(t) - - bindL7RouteToListeners( - test.route, - test.gateway, - namespaces, - ) - - g.Expect(test.route.ParentRefs).To(Equal(test.expectedSectionNameRefs)) - g.Expect(helpers.Diff(test.gateway.Listeners, test.expectedGatewayListeners)).To(BeEmpty()) - g.Expect(helpers.Diff(test.route.Conditions, test.expectedConditions)).To(BeEmpty()) - }) + t.Run( + test.name, func(t *testing.T) { + g := NewWithT(t) + + bindL7RouteToListeners( + test.route, + test.gateway, + namespaces, + ) + + g.Expect(test.route.ParentRefs).To(Equal(test.expectedSectionNameRefs)) + g.Expect(helpers.Diff(test.gateway.Listeners, test.expectedGatewayListeners)).To(BeEmpty()) + g.Expect(helpers.Diff(test.route.Conditions, test.expectedConditions)).To(BeEmpty()) + }, + ) } } @@ -1314,12 +1381,14 @@ func TestFindAcceptedHostnames(t *testing.T) { } for _, test := range tests { - t.Run(test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := findAcceptedHostnames(test.listenerHostname, test.routeHostnames) - g.Expect(result).To(Equal(test.expected)) - }) + t.Run( + test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + result := findAcceptedHostnames(test.listenerHostname, test.routeHostnames) + g.Expect(result).To(Equal(test.expected)) + }, + ) } } @@ -1351,12 +1420,14 @@ func TestGetHostname(t *testing.T) { } for _, test := range tests { - t.Run(test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := getHostname(test.h) - g.Expect(result).To(Equal(test.expected)) - }) + t.Run( + test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + result := getHostname(test.h) + g.Expect(result).To(Equal(test.expected)) + }, + ) } } @@ -1391,344 +1462,20 @@ func TestValidateHostnames(t *testing.T) { path := field.NewPath("test") for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - err := validateHostnames(test.hostnames, path) - - if test.expectErr { - g.Expect(err).To(HaveOccurred()) - } else { - g.Expect(err).ToNot(HaveOccurred()) - } - }) - } -} - -func TestValidateFilterRequestHeaderModifier(t *testing.T) { - t.Parallel() - createAllValidValidator := func() *validationfakes.FakeHTTPFieldsValidator { - v := &validationfakes.FakeHTTPFieldsValidator{} - return v - } - - tests := []struct { - filter gatewayv1.HTTPRouteFilter - validator *validationfakes.FakeHTTPFieldsValidator - name string - expectErrCount int - }{ - { - validator: createAllValidValidator(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Set: []gatewayv1.HTTPHeader{ - {Name: "MyBespokeHeader", Value: "my-value"}, - }, - Add: []gatewayv1.HTTPHeader{ - {Name: "Accept-Encoding", Value: "gzip"}, - }, - Remove: []string{"Cache-Control"}, - }, - }, - expectErrCount: 0, - name: "valid request header modifier filter", - }, - { - validator: createAllValidValidator(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: nil, - }, - expectErrCount: 1, - name: "nil request header modifier filter", - }, - { - validator: func() *validationfakes.FakeHTTPFieldsValidator { - v := createAllValidValidator() - v.ValidateFilterHeaderNameReturns(errors.New("Invalid header")) - return v - }(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Add: []gatewayv1.HTTPHeader{ - {Name: "$var_name", Value: "gzip"}, - }, - }, - }, - expectErrCount: 1, - name: "request header modifier filter with invalid add", - }, - { - validator: func() *validationfakes.FakeHTTPFieldsValidator { - v := createAllValidValidator() - v.ValidateFilterHeaderNameReturns(errors.New("Invalid header")) - return v - }(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Remove: []string{"$var-name"}, - }, - }, - expectErrCount: 1, - name: "request header modifier filter with invalid remove", - }, - { - validator: func() *validationfakes.FakeHTTPFieldsValidator { - v := createAllValidValidator() - v.ValidateFilterHeaderValueReturns(errors.New("Invalid header value")) - return v - }(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Add: []gatewayv1.HTTPHeader{ - {Name: "Accept-Encoding", Value: "yhu$"}, - }, - }, - }, - expectErrCount: 1, - name: "request header modifier filter with invalid header value", - }, - { - validator: func() *validationfakes.FakeHTTPFieldsValidator { - v := createAllValidValidator() - v.ValidateFilterHeaderValueReturns(errors.New("Invalid header value")) - v.ValidateFilterHeaderNameReturns(errors.New("Invalid header")) - return v - }(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Set: []gatewayv1.HTTPHeader{ - {Name: "Host", Value: "my_host"}, - }, - Add: []gatewayv1.HTTPHeader{ - {Name: "}90yh&$", Value: "gzip$"}, - {Name: "}67yh&$", Value: "compress$"}, - }, - Remove: []string{"Cache-Control$}"}, - }, - }, - expectErrCount: 7, - name: "request header modifier filter all fields invalid", - }, - { - validator: createAllValidValidator(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterRequestHeaderModifier, - RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Set: []gatewayv1.HTTPHeader{ - {Name: "MyBespokeHeader", Value: "my-value"}, - {Name: "mYbespokeHEader", Value: "duplicate"}, - }, - Add: []gatewayv1.HTTPHeader{ - {Name: "Accept-Encoding", Value: "gzip"}, - {Name: "accept-encodING", Value: "gzip"}, - }, - Remove: []string{"Cache-Control", "cache-control"}, - }, - }, - expectErrCount: 3, - name: "request header modifier filter not unique names", - }, - } - - filterPath := field.NewPath("test") - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - allErrs := validateFilterHeaderModifier( - test.validator, test.filter.RequestHeaderModifier, filterPath, - ) - g.Expect(allErrs).To(HaveLen(test.expectErrCount)) - }) - } -} + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) -func TestValidateFilterResponseHeaderModifier(t *testing.T) { - t.Parallel() - createAllValidValidator := func() *validationfakes.FakeHTTPFieldsValidator { - v := &validationfakes.FakeHTTPFieldsValidator{} - return v - } + err := validateHostnames(test.hostnames, path) - tests := []struct { - filter gatewayv1.HTTPRouteFilter - validator *validationfakes.FakeHTTPFieldsValidator - name string - expectErrCount int - }{ - { - validator: createAllValidValidator(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Set: []gatewayv1.HTTPHeader{ - {Name: "MyBespokeHeader", Value: "my-value"}, - }, - Add: []gatewayv1.HTTPHeader{ - {Name: "Accept-Encoding", Value: "gzip"}, - }, - Remove: []string{"Cache-Control"}, - }, - }, - expectErrCount: 0, - name: "valid response header modifier filter", - }, - { - validator: createAllValidValidator(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: nil, - }, - expectErrCount: 1, - name: "nil response header modifier filter", - }, - { - validator: func() *validationfakes.FakeHTTPFieldsValidator { - v := createAllValidValidator() - v.ValidateFilterHeaderNameReturns(errors.New("Invalid header")) - return v - }(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Add: []gatewayv1.HTTPHeader{ - {Name: "$var_name", Value: "gzip"}, - }, - }, - }, - expectErrCount: 1, - name: "response header modifier filter with invalid add", - }, - { - validator: func() *validationfakes.FakeHTTPFieldsValidator { - v := createAllValidValidator() - v.ValidateFilterHeaderNameReturns(errors.New("Invalid header")) - return v - }(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Remove: []string{"$var-name"}, - }, - }, - expectErrCount: 1, - name: "response header modifier filter with invalid remove", - }, - { - validator: func() *validationfakes.FakeHTTPFieldsValidator { - v := createAllValidValidator() - v.ValidateFilterHeaderValueReturns(errors.New("Invalid header value")) - return v - }(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Add: []gatewayv1.HTTPHeader{ - {Name: "Accept-Encoding", Value: "yhu$"}, - }, - }, - }, - expectErrCount: 1, - name: "response header modifier filter with invalid header value", - }, - { - validator: func() *validationfakes.FakeHTTPFieldsValidator { - v := createAllValidValidator() - v.ValidateFilterHeaderValueReturns(errors.New("Invalid header value")) - v.ValidateFilterHeaderNameReturns(errors.New("Invalid header")) - return v - }(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Set: []gatewayv1.HTTPHeader{ - {Name: "Host", Value: "my_host"}, - }, - Add: []gatewayv1.HTTPHeader{ - {Name: "}90yh&$", Value: "gzip$"}, - {Name: "}67yh&$", Value: "compress$"}, - }, - Remove: []string{"Cache-Control$}"}, - }, - }, - expectErrCount: 7, - name: "response header modifier filter all fields invalid", - }, - { - validator: createAllValidValidator(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Set: []gatewayv1.HTTPHeader{ - {Name: "MyBespokeHeader", Value: "my-value"}, - {Name: "mYbespokeHEader", Value: "duplicate"}, - }, - Add: []gatewayv1.HTTPHeader{ - {Name: "Accept-Encoding", Value: "gzip"}, - {Name: "accept-encodING", Value: "gzip"}, - }, - Remove: []string{"Cache-Control", "cache-control"}, - }, - }, - expectErrCount: 3, - name: "response header modifier filter not unique names", - }, - { - validator: createAllValidValidator(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Set: []gatewayv1.HTTPHeader{ - {Name: "Content-Length", Value: "163"}, - }, - Add: []gatewayv1.HTTPHeader{ - {Name: "Content-Type", Value: "text/plain"}, - }, - Remove: []string{"X-Pad"}, - }, + if test.expectErr { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).ToNot(HaveOccurred()) + } }, - expectErrCount: 3, - name: "response header modifier filter with disallowed header name", - }, - { - validator: createAllValidValidator(), - filter: gatewayv1.HTTPRouteFilter{ - Type: gatewayv1.HTTPRouteFilterResponseHeaderModifier, - ResponseHeaderModifier: &gatewayv1.HTTPHeaderFilter{ - Set: []gatewayv1.HTTPHeader{ - {Name: "X-Accel-Redirect", Value: "/protected/iso.img"}, - }, - Add: []gatewayv1.HTTPHeader{ - {Name: "X-Accel-Limit-Rate", Value: "1024"}, - }, - Remove: []string{"X-Accel-Charset"}, - }, - }, - expectErrCount: 3, - name: "response header modifier filter with disallowed header name prefix", - }, - } - - filterPath := field.NewPath("test") - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - allErrs := validateFilterResponseHeaderModifier( - test.validator, test.filter.ResponseHeaderModifier, filterPath, - ) - g.Expect(allErrs).To(HaveLen(test.expectErrCount)) - }) + ) } } @@ -1803,11 +1550,18 @@ func TestAllowedRouteType(t *testing.T) { } for _, test := range test { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - g.Expect(isRouteTypeAllowedByListener(test.listener, convertRouteType(test.routeType))).To(Equal(test.expResult)) - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + g.Expect( + isRouteTypeAllowedByListener( + test.listener, + convertRouteType(test.routeType), + ), + ).To(Equal(test.expResult)) + }, + ) } } @@ -1821,9 +1575,11 @@ func TestBindL4RouteToListeners(t *testing.T) { Name: gatewayv1.SectionName(name), Hostname: (*gatewayv1.Hostname)(helpers.GetPointer("foo.example.com")), Protocol: gatewayv1.TLSProtocolType, - TLS: helpers.GetPointer(gatewayv1.GatewayTLSConfig{ - Mode: helpers.GetPointer(gatewayv1.TLSModeTerminate), - }), + TLS: helpers.GetPointer( + gatewayv1.GatewayTLSConfig{ + Mode: helpers.GetPointer(gatewayv1.TLSModeTerminate), + }, + ), }, SupportedKinds: []gatewayv1.RouteGroupKind{ {Kind: kinds.TLSRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, @@ -1994,11 +1750,13 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-443", func(l *Listener) { - l.L4Routes = map[L4RouteKey]*L4Route{ - CreateRouteKeyL4(tr): getLastNormalRoute(), - } - }), + createModifiedListener( + "listener-443", func(l *Listener) { + l.L4Routes = map[L4RouteKey]*L4Route{ + CreateRouteKeyL4(tr): getLastNormalRoute(), + } + }, + ), }, name: "normal case", }, @@ -2145,13 +1903,17 @@ func TestBindL4RouteToListeners(t *testing.T) { Source: gwWrongNamespace, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-443", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{From: helpers.GetPointer( - gatewayv1.FromNamespaces("Same"), - )}, - } - }), + createModifiedListener( + "listener-443", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer( + gatewayv1.FromNamespaces("Same"), + ), + }, + } + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -2171,13 +1933,17 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-443", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{From: helpers.GetPointer( - gatewayv1.FromNamespaces("Same"), - )}, - } - }), + createModifiedListener( + "listener-443", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer( + gatewayv1.FromNamespaces("Same"), + ), + }, + } + }, + ), }, name: "route not allowed by listener; in different namespace", }, @@ -2187,9 +1953,11 @@ func TestBindL4RouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-443", func(l *Listener) { - l.Valid = false - }), + createModifiedListener( + "listener-443", func(l *Listener) { + l.Valid = false + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -2206,27 +1974,29 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-443", func(l *Listener) { - l.Valid = false - r := createNormalRoute(gw) - r.Conditions = append(r.Conditions, staticConds.NewRouteInvalidListener()) - r.ParentRefs = []ParentRef{ - { - Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), - SectionName: tr.Spec.ParentRefs[0].SectionName, - Attachment: &ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{ - "listener-443": {"foo.example.com"}, + createModifiedListener( + "listener-443", func(l *Listener) { + l.Valid = false + r := createNormalRoute(gw) + r.Conditions = append(r.Conditions, staticConds.NewRouteInvalidListener()) + r.ParentRefs = []ParentRef{ + { + Idx: 0, + Gateway: client.ObjectKeyFromObject(gw), + SectionName: tr.Spec.ParentRefs[0].SectionName, + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + "listener-443": {"foo.example.com"}, + }, + Attached: true, }, - Attached: true, }, - }, - } - l.L4Routes = map[L4RouteKey]*L4Route{ - CreateRouteKeyL4(tr): r, - } - }), + } + l.L4Routes = map[L4RouteKey]*L4Route{ + CreateRouteKeyL4(tr): r, + } + }, + ), }, expectedConditions: []conditions.Condition{staticConds.NewRouteInvalidListener()}, name: "invalid attachable listener", @@ -2237,9 +2007,11 @@ func TestBindL4RouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-443", func(l *Listener) { - l.Source.Hostname = (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")) - }), + createModifiedListener( + "listener-443", func(l *Listener) { + l.Source.Hostname = (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")) + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -2254,16 +2026,20 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-443", func(l *Listener) { - l.Source.Hostname = (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")) - }), + createModifiedListener( + "listener-443", func(l *Listener) { + l.Source.Hostname = (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")) + }, + ), }, name: "route hostname does not match any listener", }, { - route: makeModifiedRoute(gw, func(r *L4Route) { - r.ParentRefs[0].SectionName = nil - }), + route: makeModifiedRoute( + gw, func(r *L4Route) { + r.ParentRefs[0].SectionName = nil + }, + ), gateway: &Gateway{ Source: gw, Valid: true, @@ -2284,18 +2060,22 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-443", func(l *Listener) { - l.L4Routes = map[L4RouteKey]*L4Route{ - CreateRouteKeyL4(tr): getLastNormalRoute(), - } - }), + createModifiedListener( + "listener-443", func(l *Listener) { + l.L4Routes = map[L4RouteKey]*L4Route{ + CreateRouteKeyL4(tr): getLastNormalRoute(), + } + }, + ), }, name: "nil section name", }, { - route: makeModifiedRoute(gw, func(r *L4Route) { - r.ParentRefs[0].SectionName = helpers.GetPointer[gatewayv1.SectionName]("") - }), + route: makeModifiedRoute( + gw, func(r *L4Route) { + r.ParentRefs[0].SectionName = helpers.GetPointer[gatewayv1.SectionName]("") + }, + ), gateway: &Gateway{ Source: gw, Valid: true, @@ -2317,11 +2097,13 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-443", func(l *Listener) { - l.L4Routes = map[L4RouteKey]*L4Route{ - CreateRouteKeyL4(tr): getLastNormalRoute(), - } - }), + createModifiedListener( + "listener-443", func(l *Listener) { + l.L4Routes = map[L4RouteKey]*L4Route{ + CreateRouteKeyL4(tr): getLastNormalRoute(), + } + }, + ), }, name: "empty section name", }, @@ -2344,9 +2126,11 @@ func TestBindL4RouteToListeners(t *testing.T) { name: "listener does not exist", }, { - route: makeModifiedRoute(gw, func(r *L4Route) { - r.Valid = false - }), + route: makeModifiedRoute( + gw, func(r *L4Route) { + r.Valid = false + }, + ), gateway: &Gateway{ Source: gw, Valid: true, @@ -2368,11 +2152,13 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-443", func(l *Listener) { - l.L4Routes = map[L4RouteKey]*L4Route{ - CreateRouteKeyL4(tr): getLastNormalRoute(), - } - }), + createModifiedListener( + "listener-443", func(l *Listener) { + l.L4Routes = map[L4RouteKey]*L4Route{ + CreateRouteKeyL4(tr): getLastNormalRoute(), + } + }, + ), }, name: "invalid attachable route", }, @@ -2382,9 +2168,11 @@ func TestBindL4RouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener("listener-443", func(l *Listener) { - l.SupportedKinds = nil - }), + createModifiedListener( + "listener-443", func(l *Listener) { + l.SupportedKinds = nil + }, + ), }, }, expectedSectionNameRefs: []ParentRef{ @@ -2399,9 +2187,11 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener("listener-443", func(l *Listener) { - l.SupportedKinds = nil - }), + createModifiedListener( + "listener-443", func(l *Listener) { + l.SupportedKinds = nil + }, + ), }, name: "route kind not allowed", }, @@ -2416,21 +2206,23 @@ func TestBindL4RouteToListeners(t *testing.T) { }, } for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - bindL4RouteToListeners( - test.route, - test.gateway, - namespaces, - map[string]struct{}{}, - ) - - g.Expect(test.route.ParentRefs).To(Equal(test.expectedSectionNameRefs)) - g.Expect(helpers.Diff(test.gateway.Listeners, test.expectedGatewayListeners)).To(BeEmpty()) - g.Expect(helpers.Diff(test.route.Conditions, test.expectedConditions)).To(BeEmpty()) - }) + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + bindL4RouteToListeners( + test.route, + test.gateway, + namespaces, + map[string]struct{}{}, + ) + + g.Expect(test.route.ParentRefs).To(Equal(test.expectedSectionNameRefs)) + g.Expect(helpers.Diff(test.gateway.Listeners, test.expectedGatewayListeners)).To(BeEmpty()) + g.Expect(helpers.Diff(test.route.Conditions, test.expectedConditions)).To(BeEmpty()) + }, + ) } } @@ -2458,13 +2250,15 @@ func TestBuildL4RoutesForGateways_NoGateways(t *testing.T) { refGrantResolver := newReferenceGrantResolver(nil) - g.Expect(buildL4RoutesForGateways( - tlsRoutes, - nil, - services, - nil, - refGrantResolver, - )).To(BeNil()) + g.Expect( + buildL4RoutesForGateways( + tlsRoutes, + nil, + services, + nil, + refGrantResolver, + ), + ).To(BeNil()) } func TestTryToAttachL4RouteToListeners_NoAttachableListeners(t *testing.T) { diff --git a/internal/mode/static/state/graph/snippets_filter.go b/internal/mode/static/state/graph/snippets_filter.go index f3541d7341..bc6d4a4e35 100644 --- a/internal/mode/static/state/graph/snippets_filter.go +++ b/internal/mode/static/state/graph/snippets_filter.go @@ -3,9 +3,11 @@ package graph import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" + v1 "sigs.k8s.io/gateway-api/apis/v1" ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/conditions" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/kinds" staticConds "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/conditions" ) @@ -13,10 +15,41 @@ import ( type SnippetsFilter struct { // Source is the SnippetsFilter. Source *ngfAPI.SnippetsFilter + // Snippets stored as a map of nginx context to snippet value. + Snippets map[ngfAPI.NginxContext]string // Conditions define the conditions to be reported in the status of the SnippetsFilter. Conditions []conditions.Condition // Valid indicates whether the SnippetsFilter is semantically and syntactically valid. Valid bool + // Referenced indicates whether the SnippetsFilter is referenced by a Route. + Referenced bool +} + +// getSnippetsFilterResolverForNamespace returns a resolveExtRefFilter function. +// This function resolves a LocalObjectReference to a SnippetsFilter in the given namespace. +// If the SnippetsFilter exists, it is marked as referenced and returned as an ExtensionRefFilter. +func getSnippetsFilterResolverForNamespace( + snippetsFilters map[types.NamespacedName]*SnippetsFilter, + ns string, +) resolveExtRefFilter { + return func(ref v1.LocalObjectReference) *ExtensionRefFilter { + if len(snippetsFilters) == 0 { + return nil + } + + if ref.Group != ngfAPI.GroupName || ref.Kind != kinds.SnippetsFilter { + return nil + } + + sf := snippetsFilters[types.NamespacedName{Namespace: ns, Name: string(ref.Name)}] + if sf == nil { + return nil + } + + sf.Referenced = true + + return &ExtensionRefFilter{SnippetsFilter: sf, Valid: sf.Valid} + } } func processSnippetsFilters( @@ -29,22 +62,36 @@ func processSnippetsFilters( processed := make(map[types.NamespacedName]*SnippetsFilter) for nsname, sf := range snippetsFilters { - processedSf := &SnippetsFilter{ - Source: sf, - Valid: true, - } - if cond := validateSnippetsFilter(sf); cond != nil { - processedSf.Valid = false - processedSf.Conditions = []conditions.Condition{*cond} + processed[nsname] = &SnippetsFilter{ + Source: sf, + Conditions: []conditions.Condition{*cond}, + Valid: false, + } + + continue } - processed[nsname] = processedSf + processed[nsname] = &SnippetsFilter{ + Source: sf, + Valid: true, + Snippets: createSnippetsMap(sf.Spec.Snippets), + } } return processed } +func createSnippetsMap(snippets []ngfAPI.Snippet) map[ngfAPI.NginxContext]string { + snippetsMap := make(map[ngfAPI.NginxContext]string) + + for _, snippet := range snippets { + snippetsMap[snippet.Context] = snippet.Value + } + + return snippetsMap +} + func validateSnippetsFilter(filter *ngfAPI.SnippetsFilter) *conditions.Condition { var allErrs field.ErrorList snippetsPath := field.NewPath("spec.snippets") diff --git a/internal/mode/static/state/graph/snippets_filter_test.go b/internal/mode/static/state/graph/snippets_filter_test.go index 9c3116488a..183476289d 100644 --- a/internal/mode/static/state/graph/snippets_filter_test.go +++ b/internal/mode/static/state/graph/snippets_filter_test.go @@ -4,14 +4,19 @@ 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" ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1" "github.com/nginxinc/nginx-gateway-fabric/internal/framework/conditions" + "github.com/nginxinc/nginx-gateway-fabric/internal/framework/kinds" staticConds "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/conditions" ) func TestProcessSnippetsFilters(t *testing.T) { + t.Parallel() + filter1NsName := types.NamespacedName{Namespace: "test", Name: "filter-1"} filter2NsName := types.NamespacedName{Namespace: "other", Name: "filter-2"} invalidFilterNsName := types.NamespacedName{Namespace: "default", Name: "invalid"} @@ -79,18 +84,29 @@ func TestProcessSnippetsFilters(t *testing.T) { Source: filter1, Conditions: nil, Valid: true, + Referenced: false, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextMain: "main snippet", + ngfAPI.NginxContextHTTP: "http snippet", + }, }, filter2NsName: { Source: filter2, Conditions: nil, Valid: true, + Referenced: false, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTPServerLocation: "location snippet", + }, }, invalidFilterNsName: { Source: invalidFilter, - Conditions: []conditions.Condition{staticConds.NewSnippetsFilterInvalid( - "spec.snippets[1].context: Unsupported value: \"invalid context\": " + - "supported values: \"main\", \"http\", \"http.server\", \"http.server.location\"", - )}, + Conditions: []conditions.Condition{ + staticConds.NewSnippetsFilterInvalid( + "spec.snippets[1].context: Unsupported value: \"invalid context\": " + + "supported values: \"main\", \"http\", \"http.server\", \"http.server.location\"", + ), + }, Valid: false, }, }, @@ -98,16 +114,21 @@ func TestProcessSnippetsFilters(t *testing.T) { } for _, test := range tests { - t.Run(test.msg, func(t *testing.T) { - g := NewWithT(t) + t.Run( + test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - processedSnippetsFilters := processSnippetsFilters(test.snippetsFilters) - g.Expect(processedSnippetsFilters).To(BeEquivalentTo(test.expProcessedSnippets)) - }) + processedSnippetsFilters := processSnippetsFilters(test.snippetsFilters) + g.Expect(processedSnippetsFilters).To(BeEquivalentTo(test.expProcessedSnippets)) + }, + ) } } func TestValidateSnippetsFilter(t *testing.T) { + t.Parallel() + tests := []struct { msg string filter *ngfAPI.SnippetsFilter @@ -267,16 +288,166 @@ func TestValidateSnippetsFilter(t *testing.T) { } for _, test := range tests { - t.Run(test.msg, func(t *testing.T) { - g := NewWithT(t) + t.Run( + test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + cond := validateSnippetsFilter(test.filter) + if test.expCond != (conditions.Condition{}) { + g.Expect(cond).ToNot(BeNil()) + g.Expect(*cond).To(Equal(test.expCond)) + } else { + g.Expect(cond).To(BeNil()) + } + }, + ) + } +} + +func TestGetSnippetsFilterResolverForNamespace(t *testing.T) { + t.Parallel() + + defaultSf1NsName := types.NamespacedName{Name: "sf1", Namespace: "default"} + fooSf1NsName := types.NamespacedName{Name: "sf1", Namespace: "foo"} + fooSf2InvalidNsName := types.NamespacedName{Name: "sf2-invalid", Namespace: "foo"} + + createSnippetsFilter := func(nsname types.NamespacedName, valid bool) *SnippetsFilter { + return &SnippetsFilter{ + Source: &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: nsname.Name, + Namespace: nsname.Namespace, + }, + }, + Valid: valid, + } + } + + createSnippetsFilterMap := func() map[types.NamespacedName]*SnippetsFilter { + return map[types.NamespacedName]*SnippetsFilter{ + defaultSf1NsName: createSnippetsFilter(defaultSf1NsName, true), + fooSf1NsName: createSnippetsFilter(fooSf1NsName, true), + fooSf2InvalidNsName: createSnippetsFilter(fooSf2InvalidNsName, false), + } + } + + tests := []struct { + name string + extRef v1.LocalObjectReference + snippetsFilterMap map[types.NamespacedName]*SnippetsFilter + resolveInNamespace string + expResolve bool + expValid bool + }{ + { + name: "empty ref", + extRef: v1.LocalObjectReference{}, + snippetsFilterMap: createSnippetsFilterMap(), + resolveInNamespace: "default", + expResolve: false, + }, + { + name: "no snippets filters", + extRef: v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: v1.ObjectName(fooSf1NsName.Name), + }, + snippetsFilterMap: nil, + resolveInNamespace: "default", + expResolve: false, + }, + { + name: "invalid group", + extRef: v1.LocalObjectReference{ + Group: "invalid", + Kind: kinds.SnippetsFilter, + Name: v1.ObjectName(defaultSf1NsName.Name), + }, + snippetsFilterMap: createSnippetsFilterMap(), + resolveInNamespace: "default", + expResolve: false, + }, + { + name: "invalid kind", + extRef: v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.Gateway, + Name: v1.ObjectName(defaultSf1NsName.Name), + }, + snippetsFilterMap: createSnippetsFilterMap(), + resolveInNamespace: "default", + expResolve: false, + }, + { + name: "snippets filter does not exist", + extRef: v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: v1.ObjectName("dne"), + }, + snippetsFilterMap: createSnippetsFilterMap(), + resolveInNamespace: "default", + expResolve: false, + }, + { + name: "valid snippets filter exists - namespace default", + extRef: v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: v1.ObjectName(defaultSf1NsName.Name), + }, + snippetsFilterMap: createSnippetsFilterMap(), + resolveInNamespace: "default", + expResolve: true, + expValid: true, + }, + { + name: "valid snippets filter exists - namespace foo", + extRef: v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: v1.ObjectName(fooSf1NsName.Name), + }, + snippetsFilterMap: createSnippetsFilterMap(), + resolveInNamespace: "foo", + expResolve: true, + expValid: true, + }, + { + name: "invalid snippets filter exists - namespace foo", + extRef: v1.LocalObjectReference{ + Group: ngfAPI.GroupName, + Kind: kinds.SnippetsFilter, + Name: v1.ObjectName(fooSf2InvalidNsName.Name), + }, + snippetsFilterMap: createSnippetsFilterMap(), + resolveInNamespace: "foo", + expResolve: true, + expValid: false, + }, + } + + for _, test := range tests { + t.Run( + test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - cond := validateSnippetsFilter(test.filter) - if test.expCond != (conditions.Condition{}) { - g.Expect(cond).ToNot(BeNil()) - g.Expect(*cond).To(Equal(test.expCond)) - } else { - g.Expect(cond).To(BeNil()) - } - }) + resolve := getSnippetsFilterResolverForNamespace(test.snippetsFilterMap, test.resolveInNamespace) + resolvedSf := resolve(test.extRef) + if test.expResolve { + g.Expect(resolvedSf).ToNot(BeNil()) + g.Expect(resolvedSf.SnippetsFilter).ToNot(BeNil()) + g.Expect(resolvedSf.SnippetsFilter.Referenced).To(BeTrue()) + g.Expect(resolvedSf.SnippetsFilter.Source.Name).To(BeEquivalentTo(test.extRef.Name)) + g.Expect(resolvedSf.SnippetsFilter.Source.Namespace).To(Equal(test.resolveInNamespace)) + g.Expect(resolvedSf.Valid).To(BeEquivalentTo(test.expValid)) + } else { + g.Expect(resolvedSf).To(BeNil()) + } + }, + ) } } From ad68493944e894eefc8cca0ab5a078b9ccf196f9 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 25 Sep 2024 10:25:00 -0600 Subject: [PATCH 02/16] Fix auto formatting issues --- .../nginx/config/base_http_config_test.go | 24 +- .../mode/static/nginx/config/includes_test.go | 70 +- .../static/nginx/config/main_config_test.go | 28 +- .../mode/static/nginx/config/servers_test.go | 324 +- .../static/state/change_processor_test.go | 5314 ++++++++--------- .../static/state/dataplane/configuration.go | 105 +- .../state/dataplane/configuration_test.go | 2930 +++++---- .../static/state/graph/backend_refs_test.go | 413 +- .../mode/static/state/graph/common_filter.go | 49 +- .../static/state/graph/common_filter_test.go | 78 +- .../state/graph/extension_ref_filter_test.go | 24 +- .../mode/static/state/graph/graph_test.go | 92 +- internal/mode/static/state/graph/grpcroute.go | 10 +- .../mode/static/state/graph/grpcroute_test.go | 34 +- .../mode/static/state/graph/httproute_test.go | 122 +- .../mode/static/state/graph/route_common.go | 46 +- .../static/state/graph/route_common_test.go | 779 +-- .../state/graph/snippets_filter_test.go | 72 +- 18 files changed, 4835 insertions(+), 5679 deletions(-) diff --git a/internal/mode/static/nginx/config/base_http_config_test.go b/internal/mode/static/nginx/config/base_http_config_test.go index 0f636ed9c7..ef5a2f4ad1 100644 --- a/internal/mode/static/nginx/config/base_http_config_test.go +++ b/internal/mode/static/nginx/config/base_http_config_test.go @@ -44,19 +44,17 @@ func TestExecuteBaseHttp_HTTP2(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - res := executeBaseHTTPConfig(test.conf) - g.Expect(res).To(HaveLen(1)) - g.Expect(test.expCount).To(Equal(strings.Count(string(res[0].data), expSubStr))) - g.Expect(strings.Count(string(res[0].data), "map $http_host $gw_api_compliant_host {")).To(Equal(1)) - g.Expect(strings.Count(string(res[0].data), "map $http_upgrade $connection_upgrade {")).To(Equal(1)) - g.Expect(strings.Count(string(res[0].data), "map $request_uri $request_uri_path {")).To(Equal(1)) - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + res := executeBaseHTTPConfig(test.conf) + g.Expect(res).To(HaveLen(1)) + g.Expect(test.expCount).To(Equal(strings.Count(string(res[0].data), expSubStr))) + g.Expect(strings.Count(string(res[0].data), "map $http_host $gw_api_compliant_host {")).To(Equal(1)) + g.Expect(strings.Count(string(res[0].data), "map $http_upgrade $connection_upgrade {")).To(Equal(1)) + g.Expect(strings.Count(string(res[0].data), "map $request_uri $request_uri_path {")).To(Equal(1)) + }) } } diff --git a/internal/mode/static/nginx/config/includes_test.go b/internal/mode/static/nginx/config/includes_test.go index 59ae51dbe4..b4b4133236 100644 --- a/internal/mode/static/nginx/config/includes_test.go +++ b/internal/mode/static/nginx/config/includes_test.go @@ -150,15 +150,13 @@ func TestCreateIncludesFromPolicyGenerateResult(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - includes := createIncludesFromPolicyGenerateResult(test.files) - g.Expect(includes).To(Equal(test.includes)) - }, - ) + includes := createIncludesFromPolicyGenerateResult(test.files) + g.Expect(includes).To(Equal(test.includes)) + }) } } @@ -223,16 +221,14 @@ func TestCreateIncludesFromLocationSnippetsFilter(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() + t.Run(test.name, func(t *testing.T) { + t.Parallel() - g := NewWithT(t) + g := NewWithT(t) - includes := createIncludesFromLocationSnippetsFilters(test.filters) - g.Expect(includes).To(ConsistOf(test.expIncludes)) - }, - ) + includes := createIncludesFromLocationSnippetsFilters(test.filters) + g.Expect(includes).To(ConsistOf(test.expIncludes)) + }) } } @@ -378,15 +374,13 @@ func TestCreateIncludesFromServerSnippetsFilters(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() + t.Run(test.name, func(t *testing.T) { + t.Parallel() - g := NewWithT(t) - includes := createIncludesFromServerSnippetsFilters(test.server) - g.Expect(includes).To(ConsistOf(test.expIncludes)) - }, - ) + g := NewWithT(t) + includes := createIncludesFromServerSnippetsFilters(test.server) + g.Expect(includes).To(ConsistOf(test.expIncludes)) + }) } } @@ -453,16 +447,14 @@ func TestCreateIncludesFromSnippets(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() + t.Run(test.name, func(t *testing.T) { + t.Parallel() - g := NewWithT(t) + g := NewWithT(t) - includes := createIncludesFromSnippets(test.snippets) - g.Expect(includes).To(ConsistOf(test.expIncludes)) - }, - ) + includes := createIncludesFromSnippets(test.snippets) + g.Expect(includes).To(ConsistOf(test.expIncludes)) + }) } } @@ -513,15 +505,13 @@ func TestCreateIncludeExecuteResults(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() + t.Run(test.name, func(t *testing.T) { + t.Parallel() - g := NewWithT(t) + g := NewWithT(t) - results := createIncludeExecuteResults(test.includes) - g.Expect(results).To(ConsistOf(test.expExecuteResults)) - }, - ) + results := createIncludeExecuteResults(test.includes) + g.Expect(results).To(ConsistOf(test.expExecuteResults)) + }) } } diff --git a/internal/mode/static/nginx/config/main_config_test.go b/internal/mode/static/nginx/config/main_config_test.go index 8a453dd078..251f170c0f 100644 --- a/internal/mode/static/nginx/config/main_config_test.go +++ b/internal/mode/static/nginx/config/main_config_test.go @@ -40,21 +40,19 @@ func TestExecuteMainConfig_Telemetry(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - res := executeMainConfig(test.conf) - g.Expect(res).To(HaveLen(1)) - g.Expect(res[0].dest).To(Equal(mainIncludeFile)) - if test.expLoadModuleDirective { - g.Expect(res[0].data).To(ContainSubstring(loadModuleDirective)) - } else { - g.Expect(res[0].data).ToNot(ContainSubstring(loadModuleDirective)) - } - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + res := executeMainConfig(test.conf) + g.Expect(res).To(HaveLen(1)) + g.Expect(res[0].dest).To(Equal(mainIncludeFile)) + if test.expLoadModuleDirective { + g.Expect(res[0].data).To(ContainSubstring(loadModuleDirective)) + } else { + g.Expect(res[0].data).ToNot(ContainSubstring(loadModuleDirective)) + } + }) } } diff --git a/internal/mode/static/nginx/config/servers_test.go b/internal/mode/static/nginx/config/servers_test.go index 38362b528e..32695666d2 100644 --- a/internal/mode/static/nginx/config/servers_test.go +++ b/internal/mode/static/nginx/config/servers_test.go @@ -316,23 +316,21 @@ func TestExecuteServers_IPFamily(t *testing.T) { } for _, test := range tests { - t.Run( - test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - gen := GeneratorImpl{} - results := gen.executeServers(test.config, &policiesfakes.FakeGenerator{}) - g.Expect(results).To(HaveLen(2)) - serverConf := string(results[0].data) - httpMatchConf := string(results[1].data) - g.Expect(httpMatchConf).To(Equal("{}")) - - for expSubStr, expCount := range test.expectedHTTPConfig { - g.Expect(strings.Count(serverConf, expSubStr)).To(Equal(expCount)) - } - }, - ) + t.Run(test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + gen := GeneratorImpl{} + results := gen.executeServers(test.config, &policiesfakes.FakeGenerator{}) + g.Expect(results).To(HaveLen(2)) + serverConf := string(results[0].data) + httpMatchConf := string(results[1].data) + g.Expect(httpMatchConf).To(Equal("{}")) + + for expSubStr, expCount := range test.expectedHTTPConfig { + g.Expect(strings.Count(serverConf, expSubStr)).To(Equal(expCount)) + } + }) } } @@ -436,23 +434,21 @@ func TestExecuteServers_RewriteClientIP(t *testing.T) { } for _, test := range tests { - t.Run( - test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - gen := GeneratorImpl{} - results := gen.executeServers(test.config, &policiesfakes.FakeGenerator{}) - g.Expect(results).To(HaveLen(2)) - serverConf := string(results[0].data) - httpMatchConf := string(results[1].data) - g.Expect(httpMatchConf).To(Equal("{}")) - - for expSubStr, expCount := range test.expectedHTTPConfig { - g.Expect(strings.Count(serverConf, expSubStr)).To(Equal(expCount)) - } - }, - ) + t.Run(test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + gen := GeneratorImpl{} + results := gen.executeServers(test.config, &policiesfakes.FakeGenerator{}) + g.Expect(results).To(HaveLen(2)) + serverConf := string(results[0].data) + httpMatchConf := string(results[1].data) + g.Expect(httpMatchConf).To(Equal("{}")) + + for expSubStr, expCount := range test.expectedHTTPConfig { + g.Expect(strings.Count(serverConf, expSubStr)).To(Equal(expCount)) + } + }) } } @@ -564,27 +560,25 @@ func TestExecuteForDefaultServers(t *testing.T) { httpDefaultFmt := "listen %d default_server" for _, tc := range testcases { - t.Run( - tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - gen := GeneratorImpl{} - serverResults := gen.executeServers(tc.conf, &policiesfakes.FakeGenerator{}) - g.Expect(serverResults).To(HaveLen(2)) - serverConf := string(serverResults[0].data) - httpMatchConf := string(serverResults[1].data) - g.Expect(httpMatchConf).To(Equal("{}")) - - for _, expPort := range tc.httpPorts { - g.Expect(serverConf).To(ContainSubstring(fmt.Sprintf(httpDefaultFmt, expPort))) - } + t.Run(tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + gen := GeneratorImpl{} + serverResults := gen.executeServers(tc.conf, &policiesfakes.FakeGenerator{}) + g.Expect(serverResults).To(HaveLen(2)) + serverConf := string(serverResults[0].data) + httpMatchConf := string(serverResults[1].data) + g.Expect(httpMatchConf).To(Equal("{}")) + + for _, expPort := range tc.httpPorts { + g.Expect(serverConf).To(ContainSubstring(fmt.Sprintf(httpDefaultFmt, expPort))) + } - for _, expPort := range tc.sslPorts { - g.Expect(serverConf).To(ContainSubstring(fmt.Sprintf(sslDefaultFmt, expPort))) - } - }, - ) + for _, expPort := range tc.sslPorts { + g.Expect(serverConf).To(ContainSubstring(fmt.Sprintf(sslDefaultFmt, expPort))) + } + }) } } @@ -1741,42 +1735,40 @@ func TestCreateServersConflicts(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - httpServers := []dataplane.VirtualServer{ - { - IsDefault: true, - Port: 8080, - }, - { - Hostname: "cafe.example.com", - PathRules: test.rules, - Port: 8080, - }, - } - expectedServers := []http.Server{ - { - IsDefaultHTTP: true, - Listen: "8080", - }, - { - ServerName: "cafe.example.com", - Locations: test.expLocs, - Listen: "8080", - Includes: []shared.Include{}, - }, - } + t.Run(test.name, func(t *testing.T) { + t.Parallel() + httpServers := []dataplane.VirtualServer{ + { + IsDefault: true, + Port: 8080, + }, + { + Hostname: "cafe.example.com", + PathRules: test.rules, + Port: 8080, + }, + } + expectedServers := []http.Server{ + { + IsDefaultHTTP: true, + Listen: "8080", + }, + { + ServerName: "cafe.example.com", + Locations: test.expLocs, + Listen: "8080", + Includes: []shared.Include{}, + }, + } - g := NewWithT(t) + g := NewWithT(t) - result, _ := createServers( - dataplane.Configuration{HTTPServers: httpServers}, - &policiesfakes.FakeGenerator{}, - ) - g.Expect(helpers.Diff(expectedServers, result)).To(BeEmpty()) - }, - ) + result, _ := createServers( + dataplane.Configuration{HTTPServers: httpServers}, + &policiesfakes.FakeGenerator{}, + ) + g.Expect(helpers.Diff(expectedServers, result)).To(BeEmpty()) + }) } } @@ -2276,22 +2268,20 @@ func TestCreateLocationsRootPath(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - locs, httpMatchPair, grpc := createLocations( - &dataplane.VirtualServer{ - PathRules: test.pathRules, - Port: 80, - }, "1", &policiesfakes.FakeGenerator{}, - ) - g.Expect(locs).To(Equal(test.expLocations)) - g.Expect(httpMatchPair).To(BeEmpty()) - g.Expect(grpc).To(Equal(test.grpc)) - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + locs, httpMatchPair, grpc := createLocations( + &dataplane.VirtualServer{ + PathRules: test.pathRules, + Port: 80, + }, "1", &policiesfakes.FakeGenerator{}, + ) + g.Expect(locs).To(Equal(test.expLocations)) + g.Expect(httpMatchPair).To(BeEmpty()) + g.Expect(grpc).To(Equal(test.grpc)) + }) } } @@ -2418,15 +2408,13 @@ func TestCreateReturnValForRedirectFilter(t *testing.T) { } for _, test := range tests { - t.Run( - test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - result := createReturnValForRedirectFilter(test.filter, test.listenerPort) - g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) - }, - ) + result := createReturnValForRedirectFilter(test.filter, test.listenerPort) + g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) + }) } } @@ -2548,15 +2536,13 @@ func TestCreateRewritesValForRewriteFilter(t *testing.T) { } for _, test := range tests { - t.Run( - test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - result := createRewritesValForRewriteFilter(test.filter, test.path) - g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) - }, - ) + result := createRewritesValForRewriteFilter(test.filter, test.path) + g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) + }) } } @@ -2711,15 +2697,13 @@ func TestCreateRouteMatch(t *testing.T) { }, } for _, tc := range tests { - t.Run( - tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - result := createRouteMatch(tc.match, testPath) - g.Expect(helpers.Diff(result, tc.expected)).To(BeEmpty()) - }, - ) + result := createRouteMatch(tc.match, testPath) + g.Expect(helpers.Diff(result, tc.expected)).To(BeEmpty()) + }) } } @@ -2812,15 +2796,13 @@ func TestIsPathOnlyMatch(t *testing.T) { } for _, tc := range tests { - t.Run( - tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - result := isPathOnlyMatch(tc.match) - g.Expect(result).To(Equal(tc.expected)) - }, - ) + result := isPathOnlyMatch(tc.match) + g.Expect(result).To(Equal(tc.expected)) + }) } } @@ -2894,14 +2876,12 @@ func TestCreateProxyPass(t *testing.T) { } for _, tc := range tests { - t.Run( - tc.expected, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := createProxyPass(tc.grp, tc.rewrite, generateProtocolString(nil, tc.GRPC), tc.GRPC) - g.Expect(result).To(Equal(tc.expected)) - }, - ) + t.Run(tc.expected, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + result := createProxyPass(tc.grp, tc.rewrite, generateProtocolString(nil, tc.GRPC), tc.GRPC) + g.Expect(result).To(Equal(tc.expected)) + }) } } @@ -3123,15 +3103,13 @@ func TestGenerateProxySetHeaders(t *testing.T) { } for _, tc := range tests { - t.Run( - tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - headers := generateProxySetHeaders(tc.filters, tc.GRPC) - g.Expect(headers).To(Equal(tc.expectedHeaders)) - }, - ) + headers := generateProxySetHeaders(tc.filters, tc.GRPC) + g.Expect(headers).To(Equal(tc.expectedHeaders)) + }) } } @@ -3217,15 +3195,13 @@ func TestConvertBackendTLSFromGroup(t *testing.T) { } for _, tc := range tests { - t.Run( - tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - result := createProxyTLSFromBackends(tc.grp) - g.Expect(result).To(Equal(tc.expected)) - }, - ) + result := createProxyTLSFromBackends(tc.grp) + g.Expect(result).To(Equal(tc.expected)) + }) } } @@ -3289,15 +3265,13 @@ func TestGenerateResponseHeaders(t *testing.T) { } for _, tc := range tests { - t.Run( - tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - headers := generateResponseHeaders(tc.filters) - g.Expect(headers).To(Equal(tc.expectedHeaders)) - }, - ) + headers := generateResponseHeaders(tc.filters) + g.Expect(headers).To(Equal(tc.expectedHeaders)) + }) } } @@ -3326,14 +3300,12 @@ func TestGetIPFamily(t *testing.T) { } for _, tc := range test { - t.Run( - tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - result := getIPFamily(tc.baseHTTPConfig) - g.Expect(result).To(Equal(tc.expected)) - }, - ) + result := getIPFamily(tc.baseHTTPConfig) + g.Expect(result).To(Equal(tc.expected)) + }) } } diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index d060153e25..db701e16a7 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -330,2983 +330,2565 @@ cpLlHMAqbLJ8WYGJCkhiWxyal6hYTyWY4cVkC0xtTl/hUE9IeNKo -----END RSA PRIVATE KEY-----`) ) -var _ = Describe( - "ChangeProcessor", func() { - // graph outputs are large, so allow gomega to print everything on test failure - format.MaxLength = 0 - Describe( - "Normal cases of processing changes", func() { - var ( - gc = &v1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: gcName, - Generation: 1, - }, - Spec: v1.GatewayClassSpec{ - ControllerName: controllerName, - }, - } - processor state.ChangeProcessor - ) +var _ = Describe("ChangeProcessor", func() { + // graph outputs are large, so allow gomega to print everything on test failure + format.MaxLength = 0 + Describe("Normal cases of processing changes", func() { + var ( + gc = &v1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: gcName, + Generation: 1, + }, + Spec: v1.GatewayClassSpec{ + ControllerName: controllerName, + }, + } + processor state.ChangeProcessor + ) - BeforeEach( - OncePerOrdered, func() { - processor = state.NewChangeProcessorImpl( - state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }, - ) + BeforeEach(OncePerOrdered, func() { + processor = state.NewChangeProcessorImpl( + state.ChangeProcessorConfig{ + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }, + ) + }) + + Describe("Process gateway resources", Ordered, func() { + var ( + gcUpdated *v1.GatewayClass + diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret + hr1, hr1Updated, hr2 *v1.HTTPRoute + tr1, tr1Updated, tr2 *v1alpha2.TLSRoute + gw1, gw1Updated, gw2 *v1.Gateway + secretRefGrant, hrServiceRefGrant, trServiceRefGrant *v1beta1.ReferenceGrant + expGraph *graph.Graph + expRouteHR1, expRouteHR2 *graph.L7Route + expRouteTR1, expRouteTR2 *graph.L4Route + gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata + routeKey1, routeKey2 graph.RouteKey + trKey1, trKey2 graph.L4RouteKey + ) + BeforeAll(func() { + gcUpdated = gc.DeepCopy() + gcUpdated.Generation++ + + crossNsBackendRef := v1.HTTPBackendRef{ + BackendRef: v1.BackendRef{ + BackendObjectReference: v1.BackendObjectReference{ + Kind: helpers.GetPointer[v1.Kind]("Service"), + Name: "service", + Namespace: helpers.GetPointer[v1.Namespace]("service-ns"), + Port: helpers.GetPointer[v1.PortNumber](80), + }, }, - ) + } - Describe( - "Process gateway resources", Ordered, func() { - var ( - gcUpdated *v1.GatewayClass - diffNsTLSSecret, sameNsTLSSecret *apiv1.Secret - hr1, hr1Updated, hr2 *v1.HTTPRoute - tr1, tr1Updated, tr2 *v1alpha2.TLSRoute - gw1, gw1Updated, gw2 *v1.Gateway - secretRefGrant, hrServiceRefGrant, trServiceRefGrant *v1beta1.ReferenceGrant - expGraph *graph.Graph - expRouteHR1, expRouteHR2 *graph.L7Route - expRouteTR1, expRouteTR2 *graph.L4Route - gatewayAPICRD, gatewayAPICRDUpdated *metav1.PartialObjectMetadata - routeKey1, routeKey2 graph.RouteKey - trKey1, trKey2 graph.L4RouteKey - ) - BeforeAll( - func() { - gcUpdated = gc.DeepCopy() - gcUpdated.Generation++ - - crossNsBackendRef := v1.HTTPBackendRef{ - BackendRef: v1.BackendRef{ - BackendObjectReference: v1.BackendObjectReference{ - Kind: helpers.GetPointer[v1.Kind]("Service"), - Name: "service", - Namespace: helpers.GetPointer[v1.Namespace]("service-ns"), - Port: helpers.GetPointer[v1.PortNumber](80), - }, - }, - } + hr1 = createRoute("hr-1", "gateway-1", "foo.example.com", crossNsBackendRef) - hr1 = createRoute("hr-1", "gateway-1", "foo.example.com", crossNsBackendRef) + routeKey1 = graph.CreateRouteKey(hr1) - routeKey1 = graph.CreateRouteKey(hr1) + hr1Updated = hr1.DeepCopy() + hr1Updated.Generation++ - hr1Updated = hr1.DeepCopy() - hr1Updated.Generation++ + hr2 = createRoute("hr-2", "gateway-2", "bar.example.com") - hr2 = createRoute("hr-2", "gateway-2", "bar.example.com") + routeKey2 = graph.CreateRouteKey(hr2) - routeKey2 = graph.CreateRouteKey(hr2) + tlsBackendRef := createTLSBackendRef("tls-service", "tls-service-ns") - tlsBackendRef := createTLSBackendRef("tls-service", "tls-service-ns") + tr1 = createTLSRoute("tr-1", "gateway-1", "foo.tls.com", tlsBackendRef) - tr1 = createTLSRoute("tr-1", "gateway-1", "foo.tls.com", tlsBackendRef) + trKey1 = graph.CreateRouteKeyL4(tr1) - trKey1 = graph.CreateRouteKeyL4(tr1) + tr1Updated = tr1.DeepCopy() + tr1Updated.Generation++ - tr1Updated = tr1.DeepCopy() - tr1Updated.Generation++ + tr2 = createTLSRoute("tr-2", "gateway-2", "bar.tls.com", tlsBackendRef) - tr2 = createTLSRoute("tr-2", "gateway-2", "bar.tls.com", tlsBackendRef) + trKey2 = graph.CreateRouteKeyL4(tr2) - trKey2 = graph.CreateRouteKeyL4(tr2) + secretRefGrant = &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "cert-ns", + Name: "ref-grant", + }, + Spec: v1beta1.ReferenceGrantSpec{ + From: []v1beta1.ReferenceGrantFrom{ + { + Group: v1.GroupName, + Kind: kinds.Gateway, + Namespace: "test", + }, + }, + To: []v1beta1.ReferenceGrantTo{ + { + Kind: "Secret", + }, + }, + }, + } - secretRefGrant = &v1beta1.ReferenceGrant{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "cert-ns", - Name: "ref-grant", - }, - Spec: v1beta1.ReferenceGrantSpec{ - From: []v1beta1.ReferenceGrantFrom{ - { - Group: v1.GroupName, - Kind: kinds.Gateway, - Namespace: "test", - }, - }, - To: []v1beta1.ReferenceGrantTo{ - { - Kind: "Secret", - }, - }, - }, - } + hrServiceRefGrant = &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "service-ns", + Name: "ref-grant", + }, + Spec: v1beta1.ReferenceGrantSpec{ + From: []v1beta1.ReferenceGrantFrom{ + { + Group: v1.GroupName, + Kind: kinds.HTTPRoute, + Namespace: "test", + }, + }, + To: []v1beta1.ReferenceGrantTo{ + { + Kind: "Service", + }, + }, + }, + } - hrServiceRefGrant = &v1beta1.ReferenceGrant{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "service-ns", - Name: "ref-grant", - }, - Spec: v1beta1.ReferenceGrantSpec{ - From: []v1beta1.ReferenceGrantFrom{ - { - Group: v1.GroupName, - Kind: kinds.HTTPRoute, - Namespace: "test", - }, - }, - To: []v1beta1.ReferenceGrantTo{ - { - Kind: "Service", - }, - }, - }, - } + trServiceRefGrant = &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "tls-service-ns", + Name: "ref-grant", + }, + Spec: v1beta1.ReferenceGrantSpec{ + From: []v1beta1.ReferenceGrantFrom{ + { + Group: v1.GroupName, + Kind: kinds.TLSRoute, + Namespace: "test", + }, + }, + To: []v1beta1.ReferenceGrantTo{ + { + Kind: "Service", + }, + }, + }, + } - trServiceRefGrant = &v1beta1.ReferenceGrant{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "tls-service-ns", - Name: "ref-grant", - }, - Spec: v1beta1.ReferenceGrantSpec{ - From: []v1beta1.ReferenceGrantFrom{ - { - Group: v1.GroupName, - Kind: kinds.TLSRoute, - Namespace: "test", - }, - }, - To: []v1beta1.ReferenceGrantTo{ - { - Kind: "Service", - }, - }, - }, - } + sameNsTLSSecret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tls-secret", + Namespace: "test", + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } - sameNsTLSSecret = &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "tls-secret", - Namespace: "test", - }, - Type: apiv1.SecretTypeTLS, - Data: map[string][]byte{ - apiv1.TLSCertKey: cert, - apiv1.TLSPrivateKeyKey: key, - }, - } + diffNsTLSSecret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "different-ns-tls-secret", + Namespace: "cert-ns", + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } + + gw1 = createGateway( + "gateway-1", + createHTTPListener(), + createHTTPSListener( + httpsListenerName, + diffNsTLSSecret, + ), // cert in diff namespace than gw + createTLSListener(tlsListenerName), + ) - diffNsTLSSecret = &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "different-ns-tls-secret", - Namespace: "cert-ns", - }, - Type: apiv1.SecretTypeTLS, - Data: map[string][]byte{ - apiv1.TLSCertKey: cert, - apiv1.TLSPrivateKeyKey: key, - }, - } - - gw1 = createGateway( - "gateway-1", - createHTTPListener(), - createHTTPSListener( - httpsListenerName, - diffNsTLSSecret, - ), // cert in diff namespace than gw - createTLSListener(tlsListenerName), - ) - - gw1Updated = gw1.DeepCopy() - gw1Updated.Generation++ - - gw2 = createGateway( - "gateway-2", - createHTTPListener(), - createHTTPSListener(httpsListenerName, sameNsTLSSecret), - createTLSListener(tlsListenerName), - ) - - gatewayAPICRD = &metav1.PartialObjectMetadata{ - TypeMeta: metav1.TypeMeta{ - Kind: "CustomResourceDefinition", - APIVersion: "apiextensions.k8s.io/v1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "gatewayclasses.gateway.networking.k8s.io", - Annotations: map[string]string{ - gatewayclass.BundleVersionAnnotation: gatewayclass.SupportedVersion, - }, - }, - } + gw1Updated = gw1.DeepCopy() + gw1Updated.Generation++ + + gw2 = createGateway( + "gateway-2", + createHTTPListener(), + createHTTPSListener(httpsListenerName, sameNsTLSSecret), + createTLSListener(tlsListenerName), + ) - gatewayAPICRDUpdated = gatewayAPICRD.DeepCopy() - gatewayAPICRDUpdated.Annotations[gatewayclass.BundleVersionAnnotation] = "v1.99.0" + gatewayAPICRD = &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + Kind: "CustomResourceDefinition", + APIVersion: "apiextensions.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "gatewayclasses.gateway.networking.k8s.io", + Annotations: map[string]string{ + gatewayclass.BundleVersionAnnotation: gatewayclass.SupportedVersion, + }, + }, + } + + gatewayAPICRDUpdated = gatewayAPICRD.DeepCopy() + gatewayAPICRDUpdated.Annotations[gatewayclass.BundleVersionAnnotation] = "v1.99.0" + }) + BeforeEach(func() { + expRouteHR1 = &graph.L7Route{ + Source: hr1, + RouteType: graph.RouteTypeHTTP, + ParentRefs: []graph.ParentRef{ + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{httpListenerName: {"foo.example.com"}}, + Attached: true, + ListenerPort: 80, }, - ) - BeforeEach( - func() { - expRouteHR1 = &graph.L7Route{ - Source: hr1, - RouteType: graph.RouteTypeHTTP, - ParentRefs: []graph.ParentRef{ - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpListenerName: {"foo.example.com"}}, - Attached: true, - ListenerPort: 80, - }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - SectionName: hr1.Spec.ParentRefs[0].SectionName, - }, - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpsListenerName: {"foo.example.com"}}, - Attached: true, - ListenerPort: 443, - }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - Idx: 1, - SectionName: hr1.Spec.ParentRefs[1].SectionName, - }, - }, - Spec: graph.L7RouteSpec{ - Hostnames: hr1.Spec.Hostnames, - Rules: []graph.RouteRule{ - { - BackendRefs: []graph.BackendRef{ - { - SvcNsName: types.NamespacedName{ - Namespace: "service-ns", - Name: "service", - }, - Weight: 1, - }, - }, - ValidMatches: true, - Filters: graph.RouteRuleFilters{ - Filters: []graph.Filter{}, - Valid: true, - }, - Matches: hr1.Spec.Rules[0].Matches, - RouteBackendRefs: createRouteBackendRefs(hr1.Spec.Rules[0].BackendRefs), - }, - }, - }, - Valid: true, - Attachable: true, - Conditions: []conditions.Condition{ - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"service\"", - ), - }, - } - - expRouteHR2 = &graph.L7Route{ - Source: hr2, - RouteType: graph.RouteTypeHTTP, - ParentRefs: []graph.ParentRef{ - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, - Attached: true, - ListenerPort: 80, - }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - SectionName: hr2.Spec.ParentRefs[0].SectionName, - }, - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{httpsListenerName: {"bar.example.com"}}, - Attached: true, - ListenerPort: 443, - }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - Idx: 1, - SectionName: hr2.Spec.ParentRefs[1].SectionName, - }, - }, - Spec: graph.L7RouteSpec{ - Hostnames: hr2.Spec.Hostnames, - Rules: []graph.RouteRule{ - { - ValidMatches: true, - Filters: graph.RouteRuleFilters{ - Valid: true, - Filters: []graph.Filter{}, - }, - Matches: hr2.Spec.Rules[0].Matches, - RouteBackendRefs: []graph.RouteBackendRef{}, - }, - }, - }, - Valid: true, - Attachable: true, - } - - expRouteTR1 = &graph.L4Route{ - Source: tr1, - ParentRefs: []graph.ParentRef{ - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{tlsListenerName: {"foo.tls.com"}}, - Attached: true, - }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - SectionName: tr1.Spec.ParentRefs[0].SectionName, - }, - }, - Spec: graph.L4RouteSpec{ - Hostnames: tr1.Spec.Hostnames, - BackendRef: graph.BackendRef{ - SvcNsName: types.NamespacedName{ - Namespace: "tls-service-ns", - Name: "tls-service", - }, - Valid: false, - }, - }, - Valid: true, - Attachable: true, - Conditions: []conditions.Condition{ - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"tls-service\"", - ), - }, - } - - expRouteTR2 = &graph.L4Route{ - Source: tr2, - ParentRefs: []graph.ParentRef{ - { - Attachment: &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{tlsListenerName: {"bar.tls.com"}}, - Attached: true, - }, - Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - SectionName: tr2.Spec.ParentRefs[0].SectionName, - }, - }, - Spec: graph.L4RouteSpec{ - Hostnames: tr2.Spec.Hostnames, - BackendRef: graph.BackendRef{ - SvcNsName: types.NamespacedName{ - Namespace: "tls-service-ns", - Name: "tls-service", - }, - Valid: false, - }, - }, - Valid: true, - Attachable: true, - Conditions: []conditions.Condition{ - staticConds.NewRouteBackendRefRefBackendNotFound( - "spec.rules[0].backendRefs[0].name: Not found: \"tls-service\"", - ), - }, - } - - // This is the base case expected graph. Tests will manipulate this to add or remove elements - // to fit the expected output of the input under test. - expGraph = &graph.Graph{ - GatewayClass: &graph.GatewayClass{ - Source: gc, - Valid: true, - }, - Gateway: &graph.Gateway{ - Source: gw1, - Listeners: []*graph.Listener{ - { - Name: httpListenerName, - Source: gw1.Spec.Listeners[0], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, - SupportedKinds: []v1.RouteGroupKind{ - { - Kind: v1.Kind(kinds.HTTPRoute), - Group: helpers.GetPointer[v1.Group](v1.GroupName), - }, - { - Kind: v1.Kind(kinds.GRPCRoute), - Group: helpers.GetPointer[v1.Group](v1.GroupName), - }, - }, - }, - { - Name: httpsListenerName, - Source: gw1.Spec.Listeners[1], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, - ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(diffNsTLSSecret)), - SupportedKinds: []v1.RouteGroupKind{ - { - Kind: v1.Kind(kinds.HTTPRoute), - Group: helpers.GetPointer[v1.Group](v1.GroupName), - }, - { - Kind: v1.Kind(kinds.GRPCRoute), - Group: helpers.GetPointer[v1.Group](v1.GroupName), - }, - }, - }, - { - Name: tlsListenerName, - Source: gw1.Spec.Listeners[2], - Valid: true, - Attachable: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, - SupportedKinds: []v1.RouteGroupKind{ - { - Kind: v1.Kind(kinds.TLSRoute), - Group: helpers.GetPointer[v1.Group](v1.GroupName), - }, - }, - }, - }, - Valid: true, - }, - IgnoredGateways: map[types.NamespacedName]*v1.Gateway{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, - Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, - ReferencedSecrets: map[types.NamespacedName]*graph.Secret{}, - ReferencedServices: map[types.NamespacedName]struct{}{ - { + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, + SectionName: hr1.Spec.ParentRefs[0].SectionName, + }, + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{httpsListenerName: {"foo.example.com"}}, + Attached: true, + ListenerPort: 443, + }, + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, + Idx: 1, + SectionName: hr1.Spec.ParentRefs[1].SectionName, + }, + }, + Spec: graph.L7RouteSpec{ + Hostnames: hr1.Spec.Hostnames, + Rules: []graph.RouteRule{ + { + BackendRefs: []graph.BackendRef{ + { + SvcNsName: types.NamespacedName{ Namespace: "service-ns", Name: "service", - }: {}, - { - Namespace: "tls-service-ns", - Name: "tls-service", - }: {}, - }, - } - }, - ) - When( - "no upsert has occurred", func() { - It( - "returns nil graph", func() { - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - Expect(graphCfg).To(BeNil()) - Expect(processor.GetLatestGraph()).To(BeNil()) - }, - ) - }, - ) - When( - "GatewayClass doesn't exist", func() { - When( - "Gateway API CRD is added", func() { - It( - "returns empty graph", func() { - processor.CaptureUpsertChange(gatewayAPICRD) - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect( - helpers.Diff( - &graph.Graph{}, - processor.GetLatestGraph(), - ), - ).To(BeEmpty()) - }, - ) - }, - ) - When( - "Gateways don't exist", func() { - When( - "the first HTTPRoute is upserted", func() { - It( - "returns empty graph", func() { - processor.CaptureUpsertChange(hr1) - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect( - helpers.Diff( - &graph.Graph{}, - processor.GetLatestGraph(), - ), - ).To(BeEmpty()) - }, - ) - }, - ) - When( - "the first TLSRoute is upserted", func() { - It( - "returns empty graph", func() { - processor.CaptureUpsertChange(tr1) - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect( - helpers.Diff( - &graph.Graph{}, - processor.GetLatestGraph(), - ), - ).To(BeEmpty()) - }, - ) - }, - ) - When( - "the different namespace TLS Secret is upserted", func() { - It( - "returns nil graph", func() { - processor.CaptureUpsertChange(diffNsTLSSecret) - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - Expect(graphCfg).To(BeNil()) - Expect( - helpers.Diff( - &graph.Graph{}, - processor.GetLatestGraph(), - ), - ).To(BeEmpty()) - }, - ) - }, - ) - When( - "the first Gateway is upserted", func() { - It( - "returns populated graph", func() { - processor.CaptureUpsertChange(gw1) - - expGraph.GatewayClass = nil - - expGraph.Gateway.Conditions = staticConds.NewGatewayInvalid("GatewayClass doesn't exist") - expGraph.Gateway.Valid = false - expGraph.Gateway.Listeners = nil - - // no ref grant exists yet for hr1 or tr1 - expGraph.Routes[routeKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", - ), - } - - expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", - ), - } - - // gateway class does not exist so routes cannot attach - expGraph.Routes[routeKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), - } - expGraph.Routes[routeKey1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), - } - expGraph.L4Routes[trKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNoMatchingParent(), - } - - expGraph.ReferencedSecrets = nil - expGraph.ReferencedServices = nil - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect( - helpers.Diff( - expGraph, - processor.GetLatestGraph(), - ), - ).To(BeEmpty()) - }, - ) - }, - ) + }, + Weight: 1, }, - ) + }, + ValidMatches: true, + Filters: graph.RouteRuleFilters{ + Filters: []graph.Filter{}, + Valid: true, + }, + Matches: hr1.Spec.Rules[0].Matches, + RouteBackendRefs: createRouteBackendRefs(hr1.Spec.Rules[0].BackendRefs), }, - ) - When( - "the GatewayClass is upserted", func() { - It( - "returns updated graph", func() { - processor.CaptureUpsertChange(gc) - - // No ref grant exists yet for gw1 - // so the listener is not valid, but still attachable - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - listener443.Valid = false - listener443.ResolvedSecret = nil - listener443.Conditions = staticConds.NewListenerRefNotPermitted( - "Certificate ref to secret cert-ns/different-ns-tls-secret not permitted by any ReferenceGrant", - ) - - expAttachment80 := &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{ - httpListenerName: {"foo.example.com"}, - }, - Attached: true, - ListenerPort: 80, - } + }, + }, + Valid: true, + Attachable: true, + Conditions: []conditions.Condition{ + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"service\"", + ), + }, + } - expAttachment443 := &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{ - httpsListenerName: {"foo.example.com"}, - }, - Attached: true, - ListenerPort: 443, - } - - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener80.Routes[routeKey1].ParentRefs[0].Attachment = expAttachment80 - listener443.Routes[routeKey1].ParentRefs[1].Attachment = expAttachment443 - - // no ref grant exists yet for hr1 - expGraph.Routes[routeKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteInvalidListener(), - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", - ), - } - expGraph.Routes[routeKey1].ParentRefs[0].Attachment = expAttachment80 - expGraph.Routes[routeKey1].ParentRefs[1].Attachment = expAttachment443 - - // no ref grant exists yet for tr1 - expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", - ), - } - - expGraph.ReferencedSecrets = nil - expGraph.ReferencedServices = nil - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the ReferenceGrant allowing the Gateway to reference its Secret is upserted", func() { - It( - "returns updated graph", func() { - processor.CaptureUpsertChange(secretRefGrant) - - // no ref grant exists yet for hr1 - expGraph.Routes[routeKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", - ), - } - - // no ref grant exists yet for tr1 - expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", - ), - } - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - expGraph.ReferencedServices = nil - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the ReferenceGrant allowing the hr1 to reference the Service in different ns is upserted", - func() { - It( - "returns updated graph", func() { - processor.CaptureUpsertChange(hrServiceRefGrant) - - // no ref grant exists yet for tr1 - expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ - staticConds.NewRouteBackendRefRefNotPermitted( - "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", - ), - } - delete( - expGraph.ReferencedServices, - types.NamespacedName{Namespace: "tls-service-ns", Name: "tls-service"}, - ) - expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the ReferenceGrant allowing the tr1 to reference the Service in different ns is upserted", - func() { - It( - "returns updated graph", func() { - processor.CaptureUpsertChange(trServiceRefGrant) - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the Gateway API CRD with bundle version annotation change is processed", func() { - It( - "returns updated graph", func() { - processor.CaptureUpsertChange(gatewayAPICRDUpdated) - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - expGraph.GatewayClass.Conditions = conditions.NewGatewayClassSupportedVersionBestEffort( - gatewayclass.SupportedVersion, - ) - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the Gateway API CRD without bundle version annotation change is processed", func() { - It( - "returns nil graph", func() { - gatewayAPICRDSameVersion := gatewayAPICRDUpdated.DeepCopy() - - processor.CaptureUpsertChange(gatewayAPICRDSameVersion) - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - expGraph.GatewayClass.Conditions = conditions.NewGatewayClassSupportedVersionBestEffort( - gatewayclass.SupportedVersion, - ) - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - Expect(graphCfg).To(BeNil()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the Gateway API CRD with bundle version annotation change is processed", func() { - It( - "returns updated graph", func() { - // change back to supported version - processor.CaptureUpsertChange(gatewayAPICRD) - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the first HTTPRoute update with a generation changed is processed", func() { - It( - "returns populated graph", func() { - processor.CaptureUpsertChange(hr1Updated) - - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - listener443.Routes[routeKey1].Source.SetGeneration(hr1Updated.Generation) - - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener80.Routes[routeKey1].Source.SetGeneration(hr1Updated.Generation) - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the first TLSRoute update with a generation changed is processed", func() { - It( - "returns populated graph", func() { - processor.CaptureUpsertChange(tr1Updated) - - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) - tlsListener.L4Routes[trKey1].Source.SetGeneration(tr1Updated.Generation) - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the first Gateway update with a generation changed is processed", func() { - It( - "returns populated graph", func() { - processor.CaptureUpsertChange(gw1Updated) - - expGraph.Gateway.Source.Generation = gw1Updated.Generation - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the GatewayClass update with generation change is processed", func() { - It( - "returns populated graph", func() { - processor.CaptureUpsertChange(gcUpdated) - - expGraph.GatewayClass.Source.Generation = gcUpdated.Generation - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the different namespace TLS secret is upserted again", func() { - It( - "returns populated graph", func() { - processor.CaptureUpsertChange(diffNsTLSSecret) - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "no changes are captured", func() { - It( - "returns nil graph", func() { - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - Expect(graphCfg).To(BeNil()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the same namespace TLS Secret is upserted", func() { - It( - "returns nil graph", func() { - processor.CaptureUpsertChange(sameNsTLSSecret) - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - Expect(graphCfg).To(BeNil()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) - }, - ) - When( - "the second Gateway is upserted", func() { - It( - "returns populated graph using first gateway", func() { - processor.CaptureUpsertChange(gw2) - - expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-2"}: gw2, - } - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) + expRouteHR2 = &graph.L7Route{ + Source: hr2, + RouteType: graph.RouteTypeHTTP, + ParentRefs: []graph.ParentRef{ + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{httpListenerName: {"bar.example.com"}}, + Attached: true, + ListenerPort: 80, }, - ) - When( - "the second HTTPRoute is upserted", func() { - It( - "returns populated graph", func() { - processor.CaptureUpsertChange(hr2) - - expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-2"}: gw2, - } - expGraph.Routes[routeKey2] = expRouteHR2 - expGraph.Routes[routeKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - expGraph.Routes[routeKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + SectionName: hr2.Spec.ParentRefs[0].SectionName, + }, + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{httpsListenerName: {"bar.example.com"}}, + Attached: true, + ListenerPort: 443, + }, + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + Idx: 1, + SectionName: hr2.Spec.ParentRefs[1].SectionName, + }, + }, + Spec: graph.L7RouteSpec{ + Hostnames: hr2.Spec.Hostnames, + Rules: []graph.RouteRule{ + { + ValidMatches: true, + Filters: graph.RouteRuleFilters{ + Valid: true, + Filters: []graph.Filter{}, + }, + Matches: hr2.Spec.Rules[0].Matches, + RouteBackendRefs: []graph.RouteBackendRef{}, }, - ) - When( - "the second TLSRoute is upserted", func() { - It( - "returns populated graph", func() { - processor.CaptureUpsertChange(tr2) - - expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ - {Namespace: "test", Name: "gateway-2"}: gw2, - } - expGraph.Routes[routeKey2] = expRouteHR2 - expGraph.Routes[routeKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - expGraph.Routes[routeKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - - expGraph.L4Routes[trKey2] = expRouteTR2 - expGraph.L4Routes[trKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{}, - FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), - } - - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ - Source: diffNsTLSSecret, - } - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) + }, + }, + Valid: true, + Attachable: true, + } + + expRouteTR1 = &graph.L4Route{ + Source: tr1, + ParentRefs: []graph.ParentRef{ + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{tlsListenerName: {"foo.tls.com"}}, + Attached: true, }, - ) - When( - "the first Gateway is deleted", func() { - It( - "returns updated graph", func() { - processor.CaptureDeleteChange( - &v1.Gateway{}, - types.NamespacedName{Namespace: "test", Name: "gateway-1"}, - ) - - // gateway 2 takes over; - // route 1 has been replaced by route 2 - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) - - expGraph.Gateway.Source = gw2 - listener80.Source = gw2.Spec.Listeners[0] - listener443.Source = gw2.Spec.Listeners[1] - tlsListener.Source = gw2.Spec.Listeners[2] - - delete(listener80.Routes, routeKey1) - delete(listener443.Routes, routeKey1) - delete(tlsListener.L4Routes, trKey1) - - listener80.Routes[routeKey2] = expRouteHR2 - listener443.Routes[routeKey2] = expRouteHR2 - tlsListener.L4Routes[trKey2] = expRouteTR2 - - delete(expGraph.Routes, routeKey1) - delete(expGraph.L4Routes, trKey1) - - expGraph.Routes[routeKey2] = expRouteHR2 - expGraph.L4Routes[trKey2] = expRouteTR2 - - sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) - listener443.ResolvedSecret = sameNsTLSSecretRef - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, - } - - delete( - expGraph.ReferencedServices, - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName, - ) - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }, - ) + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-1"}, + SectionName: tr1.Spec.ParentRefs[0].SectionName, + }, + }, + Spec: graph.L4RouteSpec{ + Hostnames: tr1.Spec.Hostnames, + BackendRef: graph.BackendRef{ + SvcNsName: types.NamespacedName{ + Namespace: "tls-service-ns", + Name: "tls-service", + }, + Valid: false, + }, + }, + Valid: true, + Attachable: true, + Conditions: []conditions.Condition{ + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"tls-service\"", + ), + }, + } + + expRouteTR2 = &graph.L4Route{ + Source: tr2, + ParentRefs: []graph.ParentRef{ + { + Attachment: &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{tlsListenerName: {"bar.tls.com"}}, + Attached: true, }, - ) - When( - "the second HTTPRoute is deleted", func() { - It( - "returns updated graph", func() { - processor.CaptureDeleteChange( - &v1.HTTPRoute{}, - types.NamespacedName{Namespace: "test", Name: "hr-2"}, - ) - - // gateway 2 still in charge; - // no HTTP routes remain - // TLSRoute 2 still exists - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) - - expGraph.Gateway.Source = gw2 - listener80.Source = gw2.Spec.Listeners[0] - listener443.Source = gw2.Spec.Listeners[1] - tlsListener.Source = gw2.Spec.Listeners[2] - - delete(listener80.Routes, routeKey1) - delete(listener443.Routes, routeKey1) - delete(tlsListener.L4Routes, trKey1) - - tlsListener.L4Routes[trKey2] = expRouteTR2 - - expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - delete(expGraph.L4Routes, trKey1) - expGraph.L4Routes[trKey2] = expRouteTR2 - - sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) - listener443.ResolvedSecret = sameNsTLSSecretRef - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, - } - - delete( - expGraph.ReferencedServices, - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName, - ) - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + Gateway: types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + SectionName: tr2.Spec.ParentRefs[0].SectionName, + }, + }, + Spec: graph.L4RouteSpec{ + Hostnames: tr2.Spec.Hostnames, + BackendRef: graph.BackendRef{ + SvcNsName: types.NamespacedName{ + Namespace: "tls-service-ns", + Name: "tls-service", + }, + Valid: false, + }, + }, + Valid: true, + Attachable: true, + Conditions: []conditions.Condition{ + staticConds.NewRouteBackendRefRefBackendNotFound( + "spec.rules[0].backendRefs[0].name: Not found: \"tls-service\"", + ), + }, + } + + // This is the base case expected graph. Tests will manipulate this to add or remove elements + // to fit the expected output of the input under test. + expGraph = &graph.Graph{ + GatewayClass: &graph.GatewayClass{ + Source: gc, + Valid: true, + }, + Gateway: &graph.Gateway{ + Source: gw1, + Listeners: []*graph.Listener{ + { + Name: httpListenerName, + Source: gw1.Spec.Listeners[0], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + SupportedKinds: []v1.RouteGroupKind{ + { + Kind: v1.Kind(kinds.HTTPRoute), + Group: helpers.GetPointer[v1.Group](v1.GroupName), }, - ) - }, - ) - When( - "the second TLSRoute is deleted", func() { - It( - "returns updated graph", func() { - processor.CaptureDeleteChange( - &v1alpha2.TLSRoute{}, - types.NamespacedName{Namespace: "test", Name: "tr-2"}, - ) - - // gateway 2 still in charge; - // no HTTP or TLS routes remain - listener80 := getListenerByName(expGraph.Gateway, httpListenerName) - listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) - tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) - - expGraph.Gateway.Source = gw2 - listener80.Source = gw2.Spec.Listeners[0] - listener443.Source = gw2.Spec.Listeners[1] - tlsListener.Source = gw2.Spec.Listeners[2] - - delete(listener80.Routes, routeKey1) - delete(listener443.Routes, routeKey1) - delete(tlsListener.L4Routes, trKey1) - - expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} - - sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) - listener443.ResolvedSecret = sameNsTLSSecretRef - expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ - Source: sameNsTLSSecret, - } - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + { + Kind: v1.Kind(kinds.GRPCRoute), + Group: helpers.GetPointer[v1.Group](v1.GroupName), }, - ) + }, }, - ) - When( - "the GatewayClass is deleted", func() { - It( - "returns updated graph", func() { - processor.CaptureDeleteChange( - &v1.GatewayClass{}, - types.NamespacedName{Name: gcName}, - ) - - expGraph.GatewayClass = nil - expGraph.Gateway = &graph.Gateway{ - Source: gw2, - Conditions: staticConds.NewGatewayInvalid("GatewayClass doesn't exist"), - } - expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} - expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} - expGraph.ReferencedSecrets = nil - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + { + Name: httpsListenerName, + Source: gw1.Spec.Listeners[1], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(diffNsTLSSecret)), + SupportedKinds: []v1.RouteGroupKind{ + { + Kind: v1.Kind(kinds.HTTPRoute), + Group: helpers.GetPointer[v1.Group](v1.GroupName), }, - ) - }, - ) - When( - "the second Gateway is deleted", func() { - It( - "returns empty graph", func() { - processor.CaptureDeleteChange( - &v1.Gateway{}, - types.NamespacedName{Namespace: "test", Name: "gateway-2"}, - ) - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) + { + Kind: v1.Kind(kinds.GRPCRoute), + Group: helpers.GetPointer[v1.Group](v1.GroupName), }, - ) + }, }, - ) - When( - "the first HTTPRoute is deleted", func() { - It( - "returns empty graph", func() { - processor.CaptureDeleteChange( - &v1.HTTPRoute{}, - types.NamespacedName{Namespace: "test", Name: "hr-1"}, - ) - - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} - expGraph.ReferencedServices = nil - - changed, graphCfg := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) + { + Name: tlsListenerName, + Source: gw1.Spec.Listeners[2], + Valid: true, + Attachable: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, + SupportedKinds: []v1.RouteGroupKind{ + { + Kind: v1.Kind(kinds.TLSRoute), + Group: helpers.GetPointer[v1.Group](v1.GroupName), }, - ) + }, }, - ) + }, + Valid: true, }, - ) + IgnoredGateways: map[types.NamespacedName]*v1.Gateway{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, + Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, + ReferencedSecrets: map[types.NamespacedName]*graph.Secret{}, + ReferencedServices: map[types.NamespacedName]struct{}{ + { + Namespace: "service-ns", + Name: "service", + }: {}, + { + Namespace: "tls-service-ns", + Name: "tls-service", + }: {}, + }, + } + }) + When("no upsert has occurred", func() { + It("returns nil graph", func() { + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + Expect(graphCfg).To(BeNil()) + Expect(processor.GetLatestGraph()).To(BeNil()) + }) + }) + When("GatewayClass doesn't exist", func() { + When("Gateway API CRD is added", func() { + It("returns empty graph", func() { + processor.CaptureUpsertChange(gatewayAPICRD) + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) + Expect( + helpers.Diff( + &graph.Graph{}, + processor.GetLatestGraph(), + ), + ).To(BeEmpty()) + }) + }) + When("Gateways don't exist", func() { + When("the first HTTPRoute is upserted", func() { + It("returns empty graph", func() { + processor.CaptureUpsertChange(hr1) + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) + Expect( + helpers.Diff( + &graph.Graph{}, + processor.GetLatestGraph(), + ), + ).To(BeEmpty()) + }) + }) + When("the first TLSRoute is upserted", func() { + It("returns empty graph", func() { + processor.CaptureUpsertChange(tr1) + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) + Expect( + helpers.Diff( + &graph.Graph{}, + processor.GetLatestGraph(), + ), + ).To(BeEmpty()) + }) + }) + When("the different namespace TLS Secret is upserted", func() { + It("returns nil graph", func() { + processor.CaptureUpsertChange(diffNsTLSSecret) + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + Expect(graphCfg).To(BeNil()) + Expect( + helpers.Diff( + &graph.Graph{}, + processor.GetLatestGraph(), + ), + ).To(BeEmpty()) + }) + }) + When("the first Gateway is upserted", func() { + It("returns populated graph", func() { + processor.CaptureUpsertChange(gw1) + + expGraph.GatewayClass = nil + + expGraph.Gateway.Conditions = staticConds.NewGatewayInvalid("GatewayClass doesn't exist") + expGraph.Gateway.Valid = false + expGraph.Gateway.Listeners = nil + + // no ref grant exists yet for hr1 or tr1 + expGraph.Routes[routeKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", + ), + } - Describe( - "Process services and endpoints", Ordered, func() { - var ( - hr1, hr2, hr3, hrInvalidBackendRef, hrMultipleRules *v1.HTTPRoute - hr1svc, sharedSvc, bazSvc1, bazSvc2, bazSvc3, invalidSvc, notRefSvc *apiv1.Service - hr1slice1, hr1slice2, noRefSlice, missingSvcNameSlice *discoveryV1.EndpointSlice - gw *v1.Gateway - btls *v1alpha3.BackendTLSPolicy - ) + expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", + ), + } - createSvc := func(name string) *apiv1.Service { - return &apiv1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: name, - }, + // gateway class does not exist so routes cannot attach + expGraph.Routes[routeKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNoMatchingParent(), } - } - - 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}, - }, + expGraph.Routes[routeKey1].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNoMatchingParent(), } - } - - createBackendTLSPolicy := func(name string, svcName string) *v1alpha3.BackendTLSPolicy { - return &v1alpha3.BackendTLSPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "test", - Name: name, - }, - Spec: v1alpha3.BackendTLSPolicySpec{ - TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ - { - LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ - Kind: v1.Kind("Service"), - Name: v1.ObjectName(svcName), - }, - }, - }, - }, + expGraph.L4Routes[trKey1].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNoMatchingParent(), } - } - - BeforeAll( - func() { - testNamespace := v1.Namespace("test") - kindService := v1.Kind("Service") - kindInvalid := v1.Kind("Invalid") - - // backend Refs - fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) - baz1NilNamespace := createHTTPBackendRef(&kindService, "baz-svc-v1", &testNamespace) - barRef := createHTTPBackendRef(&kindService, "bar-svc", nil) - baz2Ref := createHTTPBackendRef(&kindService, "baz-svc-v2", &testNamespace) - baz3Ref := createHTTPBackendRef(&kindService, "baz-svc-v3", &testNamespace) - invalidKindRef := createHTTPBackendRef(&kindInvalid, "bar-svc", &testNamespace) - - // httproutes - hr1 = 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", "") - - // backendTLSPolicy - btls = createBackendTLSPolicy("btls", "foo-svc") - - gw = createGateway("gw", createHTTPListener()) - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - testProcessChangedVal := func(expChanged state.ChangeType) { - changed, _ := processor.Process() - Expect(changed).To(Equal(expChanged)) - } - - testUpsertTriggersChange := func(obj client.Object, expChanged state.ChangeType) { - processor.CaptureUpsertChange(obj) - testProcessChangedVal(expChanged) - } - - testDeleteTriggersChange := func( - obj client.Object, - nsname types.NamespacedName, - expChanged state.ChangeType, - ) { - processor.CaptureDeleteChange(obj, nsname) - testProcessChangedVal(expChanged) - } - - When( - "hr1 is added", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(hr1, state.ClusterStateChange) - }, - ) - }, - ) - When( - "a hr1 service is added", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(hr1svc, state.ClusterStateChange) - }, - ) - }, - ) - When( - "a backendTLSPolicy is added for referenced service", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(btls, state.ClusterStateChange) - }, - ) - }, - ) - When( - "an hr1 endpoint slice is added", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(hr1slice1, state.EndpointsOnlyChange) - }, - ) - }, - ) - When( - "an hr1 service is updated", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(hr1svc, state.ClusterStateChange) - }, - ) - }, - ) - When( - "another hr1 endpoint slice is added", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) - }, - ) - }, - ) - When( - "an endpoint slice with a missing svc name label is added", func() { - It( - "should not trigger a change", func() { - testUpsertTriggersChange(missingSvcNameSlice, state.NoChange) - }, - ) - }, - ) - When( - "an hr1 endpoint slice is deleted", func() { - It( - "should trigger a change", func() { - testDeleteTriggersChange( - hr1slice1, - types.NamespacedName{Namespace: hr1slice1.Namespace, Name: hr1slice1.Name}, - state.EndpointsOnlyChange, - ) - }, - ) - }, - ) - When( - "the second hr1 endpoint slice is deleted", func() { - It( - "should trigger a change", func() { - testDeleteTriggersChange( - hr1slice2, - types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, - state.EndpointsOnlyChange, - ) - }, - ) - }, - ) - When( - "the second hr1 endpoint slice is recreated", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) - }, - ) - }, - ) - When( - "hr1 is deleted", func() { - It( - "should trigger a change", func() { - testDeleteTriggersChange( - hr1, - types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name}, - state.ClusterStateChange, - ) - }, - ) - }, - ) - When( - "hr1 service is deleted", func() { - It( - "should not trigger a change", func() { - testDeleteTriggersChange( - hr1svc, - types.NamespacedName{Namespace: hr1svc.Namespace, Name: hr1svc.Name}, - state.NoChange, - ) - }, - ) - }, - ) - When( - "the second hr1 endpoint slice is deleted", func() { - It( - "should not trigger a change", func() { - testDeleteTriggersChange( - hr1slice2, - types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, - state.NoChange, - ) - }, - ) - }, - ) - When( - "hr2 is added", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(hr2, state.ClusterStateChange) - }, - ) - }, - ) - When( - "a hr3, that shares a backend service with hr2, is added", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(hr3, state.ClusterStateChange) - }, - ) - }, - ) - When( - "sharedSvc, a service referenced by both hr2 and hr3, is added", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) - }, - ) - }, - ) - When( - "hr2 is deleted", func() { - It( - "should trigger a change", func() { - testDeleteTriggersChange( - hr2, - types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name}, - state.ClusterStateChange, - ) - }, - ) - }, - ) - When( - "sharedSvc is deleted", func() { - It( - "should trigger a change", func() { - testDeleteTriggersChange( - sharedSvc, - types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, - state.ClusterStateChange, - ) - }, - ) - }, - ) - When( - "sharedSvc is recreated", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) - }, - ) - }, - ) - When( - "hr3 is deleted", func() { - It( - "should trigger a change", func() { - testDeleteTriggersChange( - hr3, - types.NamespacedName{Namespace: hr3.Namespace, Name: hr3.Name}, - state.ClusterStateChange, - ) - }, - ) - }, - ) - When( - "sharedSvc is deleted", func() { - It( - "should not trigger a change", func() { - testDeleteTriggersChange( - sharedSvc, - types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, - state.NoChange, - ) - }, - ) - }, - ) - When( - "a service that is not referenced by any route is added", func() { - It( - "should not trigger a change", func() { - testUpsertTriggersChange(notRefSvc, state.NoChange) - }, - ) - }, - ) - When( - "a route with an invalid backend ref type is added", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(hrInvalidBackendRef, state.ClusterStateChange) - }, - ) - }, - ) - When( - "a service with a namespace name that matches invalid backend ref is added", func() { - It( - "should not trigger a change", func() { - testUpsertTriggersChange(invalidSvc, state.NoChange) - }, - ) - }, - ) - When( - "an endpoint slice that is not owned by a referenced service is added", func() { - It( - "should not trigger a change", func() { - testUpsertTriggersChange(noRefSlice, state.NoChange) - }, - ) - }, - ) - When( - "an endpoint slice that is not owned by a referenced service is deleted", func() { - It( - "should not trigger a change", func() { - testDeleteTriggersChange( - noRefSlice, - types.NamespacedName{ - Namespace: noRefSlice.Namespace, - Name: noRefSlice.Name, - }, - state.NoChange, - ) - }, - ) - }, - ) - Context( - "processing a route with multiple rules and three unique backend services", func() { - When( - "route is added", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(hrMultipleRules, state.ClusterStateChange) - }, - ) - }, - ) - When( - "first referenced service is added", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) - }, - ) - }, - ) - When( - "second referenced service is added", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(bazSvc2, state.ClusterStateChange) - }, - ) - }, - ) - When( - "first referenced service is deleted", func() { - It( - "should trigger a change", func() { - testDeleteTriggersChange( - bazSvc1, - types.NamespacedName{ - Namespace: bazSvc1.Namespace, - Name: bazSvc1.Name, - }, - state.ClusterStateChange, - ) - }, - ) - }, - ) - When( - "first referenced service is recreated", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) - }, - ) - }, - ) - When( - "third referenced service is added", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) - }, - ) - }, - ) - When( - "third referenced service is updated", func() { - It( - "should trigger a change", func() { - testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) - }, - ) - }, - ) - When( - "route is deleted", func() { - It( - "should trigger a change", func() { - testDeleteTriggersChange( - hrMultipleRules, - types.NamespacedName{ - Namespace: hrMultipleRules.Namespace, - Name: hrMultipleRules.Name, - }, - state.ClusterStateChange, - ) - }, - ) - }, - ) - When( - "first referenced service is deleted", func() { - It( - "should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc1, - types.NamespacedName{ - Namespace: bazSvc1.Namespace, - Name: bazSvc1.Name, - }, - state.NoChange, - ) - }, - ) - }, - ) - When( - "second referenced service is deleted", func() { - It( - "should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc2, - types.NamespacedName{ - Namespace: bazSvc2.Namespace, - Name: bazSvc2.Name, - }, - state.NoChange, - ) - }, - ) - }, - ) - When( - "final referenced service is deleted", func() { - It( - "should not trigger a change", func() { - testDeleteTriggersChange( - bazSvc3, - types.NamespacedName{ - Namespace: bazSvc3.Namespace, - Name: bazSvc3.Name, - }, - state.NoChange, - ) - }, - ) - }, - ) - }, - ) - }, - ) + expGraph.ReferencedSecrets = nil + expGraph.ReferencedServices = nil + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect( + helpers.Diff( + expGraph, + processor.GetLatestGraph(), + ), + ).To(BeEmpty()) + }) + }) + }) + }) + When("the GatewayClass is upserted", func() { + It("returns updated graph", func() { + processor.CaptureUpsertChange(gc) + + // No ref grant exists yet for gw1 + // so the listener is not valid, but still attachable + listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + listener443.Valid = false + listener443.ResolvedSecret = nil + listener443.Conditions = staticConds.NewListenerRefNotPermitted( + "Certificate ref to secret cert-ns/different-ns-tls-secret not permitted by any ReferenceGrant", + ) + + expAttachment80 := &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + httpListenerName: {"foo.example.com"}, + }, + Attached: true, + ListenerPort: 80, + } - Describe( - "namespace changes", Ordered, func() { - var ( - ns, nsDifferentLabels, nsNoLabels *apiv1.Namespace - gw *v1.Gateway - ) + expAttachment443 := &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + httpsListenerName: {"foo.example.com"}, + }, + Attached: true, + ListenerPort: 443, + } - BeforeAll( - func() { - ns = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ns", - Labels: map[string]string{ - "app": "allowed", - }, - }, - } - nsDifferentLabels = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ns-different-labels", - Labels: map[string]string{ - "oranges": "bananas", - }, - }, - } - nsNoLabels = &apiv1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: "no-labels", - }, - } - gw = &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw", - }, - Spec: v1.GatewaySpec{ - GatewayClassName: gcName, - Listeners: []v1.Listener{ - { - Port: 80, - Protocol: v1.HTTPProtocolType, - AllowedRoutes: &v1.AllowedRoutes{ - Namespaces: &v1.RouteNamespaces{ - From: helpers.GetPointer(v1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "allowed", - }, - }, - }, - }, - }, - }, - }, - } - processor = state.NewChangeProcessorImpl( - state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }, - ) - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw) - processor.Process() - }, - ) + listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener80.Routes[routeKey1].ParentRefs[0].Attachment = expAttachment80 + listener443.Routes[routeKey1].ParentRefs[1].Attachment = expAttachment443 - When( - "a namespace is created that is not linked to a listener", func() { - It( - "does not trigger an update", func() { - processor.CaptureUpsertChange(nsNoLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }, - ) - }, - ) - When( - "a namespace is created that is linked to a listener", func() { - It( - "triggers an update", func() { - processor.CaptureUpsertChange(ns) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - }, - ) - When( - "a namespace is deleted that is not linked to a listener", func() { - It( - "does not trigger an update", func() { - processor.CaptureDeleteChange( - nsNoLabels, - types.NamespacedName{Name: "no-labels"}, - ) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }, - ) - }, - ) - When( - "a namespace is deleted that is linked to a listener", func() { - It( - "triggers an update", func() { - processor.CaptureDeleteChange(ns, types.NamespacedName{Name: "ns"}) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - }, - ) - When( - "a namespace that is not linked to a listener has its labels changed to match a listener", - func() { - It( - "triggers an update", func() { - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - - nsDifferentLabels.Labels = map[string]string{ - "app": "allowed", - } - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ = processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - }, - ) - When( - "a namespace that is linked to a listener has its labels changed to no longer match a listener", - func() { - It( - "triggers an update", func() { - nsDifferentLabels.Labels = map[string]string{ - "oranges": "bananas", - } - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - }, - ) - When( - "a gateway changes its listener's labels", func() { - It( - "triggers an update when a namespace that matches the new labels is created", - func() { - gwChangedLabel := gw.DeepCopy() - gwChangedLabel.Spec.Listeners[0].AllowedRoutes.Namespaces.Selector.MatchLabels = map[string]string{ - "oranges": "bananas", - } - gwChangedLabel.Generation++ - processor.CaptureUpsertChange(gwChangedLabel) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - - // After changing the gateway's labels and generation, the processor should be marked to update - // the nginx configuration and build a new graph. When processor.Process() gets called, - // the nginx configuration gets updated and a new graph is built with an updated - // referencedNamespaces. Thus, when the namespace "ns" is upserted with labels that no longer match - // the new labels on the gateway, it would not trigger a change as the namespace would no longer - // be in the updated referencedNamespaces and the labels no longer match the new labels on the - // gateway. - processor.CaptureUpsertChange(ns) - changed, _ = processor.Process() - Expect(changed).To(Equal(state.NoChange)) - - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ = processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - }, - ) - When( - "a namespace that is not linked to a listener has its labels removed", func() { - It( - "does not trigger an update", func() { - ns.Labels = nil - processor.CaptureUpsertChange(ns) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }, - ) - }, - ) - When( - "a namespace that is linked to a listener has its labels removed", func() { - It( - "triggers an update when labels are removed", func() { - nsDifferentLabels.Labels = nil - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - }, - ) - }, - ) + // no ref grant exists yet for hr1 + expGraph.Routes[routeKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteInvalidListener(), + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", + ), + } + expGraph.Routes[routeKey1].ParentRefs[0].Attachment = expAttachment80 + expGraph.Routes[routeKey1].ParentRefs[1].Attachment = expAttachment443 - Describe( - "NginxProxy resource changes", Ordered, func() { - paramGC := gc.DeepCopy() - paramGC.Spec.ParametersRef = &v1beta1.ParametersReference{ - Group: ngfAPI.GroupName, - Kind: kinds.NginxProxy, - Name: "np", - } - - np := &ngfAPI.NginxProxy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "np", - }, - } + // no ref grant exists yet for tr1 + expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", + ), + } - npUpdated := &ngfAPI.NginxProxy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "np", - }, - Spec: ngfAPI.NginxProxySpec{ - Telemetry: &ngfAPI.Telemetry{ - Exporter: &ngfAPI.TelemetryExporter{ - Endpoint: "my-svc:123", - BatchSize: helpers.GetPointer(int32(512)), - BatchCount: helpers.GetPointer(int32(4)), - Interval: helpers.GetPointer(ngfAPI.Duration("5s")), - }, - }, - }, - } - It( - "handles upserts for an NginxProxy", func() { - processor.CaptureUpsertChange(np) - processor.CaptureUpsertChange(paramGC) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NginxProxy.Source).To(Equal(np)) - }, - ) - It( - "captures changes for an NginxProxy", func() { - processor.CaptureUpsertChange(npUpdated) - processor.CaptureUpsertChange(paramGC) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NginxProxy.Source).To(Equal(npUpdated)) - }, - ) - It( - "handles deletes for an NginxProxy", func() { - processor.CaptureDeleteChange(np, client.ObjectKeyFromObject(np)) + expGraph.ReferencedSecrets = nil + expGraph.ReferencedServices = nil + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the ReferenceGrant allowing the Gateway to reference its Secret is upserted", func() { + It("returns updated graph", func() { + processor.CaptureUpsertChange(secretRefGrant) + + // no ref grant exists yet for hr1 + expGraph.Routes[routeKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service service-ns/service not permitted by any ReferenceGrant", + ), + } - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NginxProxy).To(BeNil()) - }, - ) - }, - ) + // no ref grant exists yet for tr1 + expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", + ), + } + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + expGraph.ReferencedServices = nil + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the ReferenceGrant allowing the hr1 to reference the Service in different ns is upserted", func() { + It("returns updated graph", func() { + processor.CaptureUpsertChange(hrServiceRefGrant) + + // no ref grant exists yet for tr1 + expGraph.L4Routes[trKey1].Conditions = []conditions.Condition{ + staticConds.NewRouteBackendRefRefNotPermitted( + "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", + ), + } + delete( + expGraph.ReferencedServices, + types.NamespacedName{Namespace: "tls-service-ns", Name: "tls-service"}, + ) + expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the ReferenceGrant allowing the tr1 to reference the Service in different ns is upserted", func() { + It("returns updated graph", func() { + processor.CaptureUpsertChange(trServiceRefGrant) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the Gateway API CRD with bundle version annotation change is processed", func() { + It("returns updated graph", func() { + processor.CaptureUpsertChange(gatewayAPICRDUpdated) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + expGraph.GatewayClass.Conditions = conditions.NewGatewayClassSupportedVersionBestEffort( + gatewayclass.SupportedVersion, + ) + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the Gateway API CRD without bundle version annotation change is processed", func() { + It("returns nil graph", func() { + gatewayAPICRDSameVersion := gatewayAPICRDUpdated.DeepCopy() + + processor.CaptureUpsertChange(gatewayAPICRDSameVersion) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + expGraph.GatewayClass.Conditions = conditions.NewGatewayClassSupportedVersionBestEffort( + gatewayclass.SupportedVersion, + ) + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + Expect(graphCfg).To(BeNil()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the Gateway API CRD with bundle version annotation change is processed", func() { + It("returns updated graph", func() { + // change back to supported version + processor.CaptureUpsertChange(gatewayAPICRD) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the first HTTPRoute update with a generation changed is processed", func() { + It("returns populated graph", func() { + processor.CaptureUpsertChange(hr1Updated) + + listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + listener443.Routes[routeKey1].Source.SetGeneration(hr1Updated.Generation) + + listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener80.Routes[routeKey1].Source.SetGeneration(hr1Updated.Generation) + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the first TLSRoute update with a generation changed is processed", func() { + It("returns populated graph", func() { + processor.CaptureUpsertChange(tr1Updated) + + tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) + tlsListener.L4Routes[trKey1].Source.SetGeneration(tr1Updated.Generation) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the first Gateway update with a generation changed is processed", func() { + It("returns populated graph", func() { + processor.CaptureUpsertChange(gw1Updated) + + expGraph.Gateway.Source.Generation = gw1Updated.Generation + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the GatewayClass update with generation change is processed", func() { + It("returns populated graph", func() { + processor.CaptureUpsertChange(gcUpdated) + + expGraph.GatewayClass.Source.Generation = gcUpdated.Generation + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the different namespace TLS secret is upserted again", func() { + It("returns populated graph", func() { + processor.CaptureUpsertChange(diffNsTLSSecret) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("no changes are captured", func() { + It("returns nil graph", func() { + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + Expect(graphCfg).To(BeNil()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the same namespace TLS Secret is upserted", func() { + It("returns nil graph", func() { + processor.CaptureUpsertChange(sameNsTLSSecret) + + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + Expect(graphCfg).To(BeNil()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the second Gateway is upserted", func() { + It("returns populated graph using first gateway", func() { + processor.CaptureUpsertChange(gw2) + + expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ + {Namespace: "test", Name: "gateway-2"}: gw2, + } + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the second HTTPRoute is upserted", func() { + It("returns populated graph", func() { + processor.CaptureUpsertChange(hr2) + + expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ + {Namespace: "test", Name: "gateway-2"}: gw2, + } + expGraph.Routes[routeKey2] = expRouteHR2 + expGraph.Routes[routeKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + } + expGraph.Routes[routeKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + } + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } - Describe( - "NGF Policy resource changes", Ordered, func() { - var ( - gw *v1.Gateway - route *v1.HTTPRoute - csp, cspUpdated *ngfAPI.ClientSettingsPolicy - obs, obsUpdated *ngfAPI.ObservabilityPolicy - cspKey, obsKey graph.PolicyKey - ) + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the second TLSRoute is upserted", func() { + It("returns populated graph", func() { + processor.CaptureUpsertChange(tr2) + + expGraph.IgnoredGateways = map[types.NamespacedName]*v1.Gateway{ + {Namespace: "test", Name: "gateway-2"}: gw2, + } + expGraph.Routes[routeKey2] = expRouteHR2 + expGraph.Routes[routeKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + } + expGraph.Routes[routeKey2].ParentRefs[1].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + } - BeforeAll( - func() { - processor.CaptureUpsertChange(gc) - changed, newGraph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(newGraph.GatewayClass.Source).To(Equal(gc)) - Expect(newGraph.NGFPolicies).To(BeEmpty()) - - gw = createGateway("gw", createHTTPListener()) - route = createRoute("hr-1", "gw", "foo.example.com", v1.HTTPBackendRef{}) - - csp = &ngfAPI.ClientSettingsPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "csp", - Namespace: "test", - }, - Spec: ngfAPI.ClientSettingsPolicySpec{ - TargetRef: v1alpha2.LocalPolicyTargetReference{ - Group: v1.GroupName, - Kind: kinds.Gateway, - Name: "gw", - }, - Body: &ngfAPI.ClientBody{ - MaxSize: helpers.GetPointer[ngfAPI.Size]("10m"), - }, - }, - } + expGraph.L4Routes[trKey2] = expRouteTR2 + expGraph.L4Routes[trKey2].ParentRefs[0].Attachment = &graph.ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{}, + FailedCondition: staticConds.NewRouteNotAcceptedGatewayIgnored(), + } - cspUpdated = csp.DeepCopy() - cspUpdated.Spec.Body.MaxSize = helpers.GetPointer[ngfAPI.Size]("20m") + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ + Source: diffNsTLSSecret, + } - cspKey = graph.PolicyKey{ - NsName: types.NamespacedName{Name: "csp", Namespace: "test"}, - GVK: schema.GroupVersionKind{ - Group: ngfAPI.GroupName, - Kind: kinds.ClientSettingsPolicy, - Version: "v1alpha1", - }, - } + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the first Gateway is deleted", func() { + It("returns updated graph", func() { + processor.CaptureDeleteChange( + &v1.Gateway{}, + types.NamespacedName{Namespace: "test", Name: "gateway-1"}, + ) + + // gateway 2 takes over; + // route 1 has been replaced by route 2 + listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) + + expGraph.Gateway.Source = gw2 + listener80.Source = gw2.Spec.Listeners[0] + listener443.Source = gw2.Spec.Listeners[1] + tlsListener.Source = gw2.Spec.Listeners[2] + + delete(listener80.Routes, routeKey1) + delete(listener443.Routes, routeKey1) + delete(tlsListener.L4Routes, trKey1) + + listener80.Routes[routeKey2] = expRouteHR2 + listener443.Routes[routeKey2] = expRouteHR2 + tlsListener.L4Routes[trKey2] = expRouteTR2 + + delete(expGraph.Routes, routeKey1) + delete(expGraph.L4Routes, trKey1) + + expGraph.Routes[routeKey2] = expRouteHR2 + expGraph.L4Routes[trKey2] = expRouteTR2 + + sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) + listener443.ResolvedSecret = sameNsTLSSecretRef + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ + Source: sameNsTLSSecret, + } - obs = &ngfAPI.ObservabilityPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: "obs", - Namespace: "test", - }, - Spec: ngfAPI.ObservabilityPolicySpec{ - TargetRefs: []v1alpha2.LocalPolicyTargetReference{ - { - Group: v1.GroupName, - Kind: kinds.HTTPRoute, - Name: "hr-1", - }, - }, - Tracing: &ngfAPI.Tracing{ - Strategy: ngfAPI.TraceStrategyRatio, - }, - }, - } + delete( + expGraph.ReferencedServices, + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName, + ) + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the second HTTPRoute is deleted", func() { + It("returns updated graph", func() { + processor.CaptureDeleteChange( + &v1.HTTPRoute{}, + types.NamespacedName{Namespace: "test", Name: "hr-2"}, + ) + + // gateway 2 still in charge; + // no HTTP routes remain + // TLSRoute 2 still exists + listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) + + expGraph.Gateway.Source = gw2 + listener80.Source = gw2.Spec.Listeners[0] + listener443.Source = gw2.Spec.Listeners[1] + tlsListener.Source = gw2.Spec.Listeners[2] + + delete(listener80.Routes, routeKey1) + delete(listener443.Routes, routeKey1) + delete(tlsListener.L4Routes, trKey1) + + tlsListener.L4Routes[trKey2] = expRouteTR2 + + expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} + delete(expGraph.L4Routes, trKey1) + expGraph.L4Routes[trKey2] = expRouteTR2 + + sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) + listener443.ResolvedSecret = sameNsTLSSecretRef + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ + Source: sameNsTLSSecret, + } - obsUpdated = obs.DeepCopy() - obsUpdated.Spec.Tracing.Strategy = ngfAPI.TraceStrategyParent + delete( + expGraph.ReferencedServices, + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName, + ) + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the second TLSRoute is deleted", func() { + It("returns updated graph", func() { + processor.CaptureDeleteChange( + &v1alpha2.TLSRoute{}, + types.NamespacedName{Namespace: "test", Name: "tr-2"}, + ) + + // gateway 2 still in charge; + // no HTTP or TLS routes remain + listener80 := getListenerByName(expGraph.Gateway, httpListenerName) + listener443 := getListenerByName(expGraph.Gateway, httpsListenerName) + tlsListener := getListenerByName(expGraph.Gateway, tlsListenerName) + + expGraph.Gateway.Source = gw2 + listener80.Source = gw2.Spec.Listeners[0] + listener443.Source = gw2.Spec.Listeners[1] + tlsListener.Source = gw2.Spec.Listeners[2] + + delete(listener80.Routes, routeKey1) + delete(listener443.Routes, routeKey1) + delete(tlsListener.L4Routes, trKey1) + + expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} + expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} + + sameNsTLSSecretRef := helpers.GetPointer(client.ObjectKeyFromObject(sameNsTLSSecret)) + listener443.ResolvedSecret = sameNsTLSSecretRef + expGraph.ReferencedSecrets[client.ObjectKeyFromObject(sameNsTLSSecret)] = &graph.Secret{ + Source: sameNsTLSSecret, + } - obsKey = graph.PolicyKey{ - NsName: types.NamespacedName{Name: "obs", Namespace: "test"}, - GVK: schema.GroupVersionKind{ - Group: ngfAPI.GroupName, - Kind: kinds.ObservabilityPolicy, - Version: "v1alpha1", - }, - } + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServices = nil + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the GatewayClass is deleted", func() { + It("returns updated graph", func() { + processor.CaptureDeleteChange( + &v1.GatewayClass{}, + types.NamespacedName{Name: gcName}, + ) + + expGraph.GatewayClass = nil + expGraph.Gateway = &graph.Gateway{ + Source: gw2, + Conditions: staticConds.NewGatewayInvalid("GatewayClass doesn't exist"), + } + expGraph.Routes = map[graph.RouteKey]*graph.L7Route{} + expGraph.L4Routes = map[graph.L4RouteKey]*graph.L4Route{} + expGraph.ReferencedSecrets = nil + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServices = nil + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the second Gateway is deleted", func() { + It("returns empty graph", func() { + processor.CaptureDeleteChange( + &v1.Gateway{}, + types.NamespacedName{Namespace: "test", Name: "gateway-2"}, + ) + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServices = nil + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) + }) + }) + When("the first HTTPRoute is deleted", func() { + It("returns empty graph", func() { + processor.CaptureDeleteChange( + &v1.HTTPRoute{}, + types.NamespacedName{Namespace: "test", Name: "hr-1"}, + ) + + expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} + expGraph.ReferencedServices = nil + + changed, graphCfg := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) + Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).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 + gw *v1.Gateway + btls *v1alpha3.BackendTLSPolicy + ) + + createSvc := func(name string) *apiv1.Service { + return &apiv1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: name, + }, + } + } + + createEndpointSlice := func(name string, svcName string) *discoveryV1.EndpointSlice { + return &discoveryV1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: name, + Labels: map[string]string{index.KubernetesServiceNameLabel: svcName}, + }, + } + } + + createBackendTLSPolicy := func(name string, svcName string) *v1alpha3.BackendTLSPolicy { + return &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "test", + Name: name, + }, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: v1.Kind("Service"), + Name: v1.ObjectName(svcName), + }, }, - ) + }, + }, + } + } + + BeforeAll(func() { + testNamespace := v1.Namespace("test") + kindService := v1.Kind("Service") + kindInvalid := v1.Kind("Invalid") + + // backend Refs + fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) + baz1NilNamespace := createHTTPBackendRef(&kindService, "baz-svc-v1", &testNamespace) + barRef := createHTTPBackendRef(&kindService, "bar-svc", nil) + baz2Ref := createHTTPBackendRef(&kindService, "baz-svc-v2", &testNamespace) + baz3Ref := createHTTPBackendRef(&kindService, "baz-svc-v3", &testNamespace) + invalidKindRef := createHTTPBackendRef(&kindInvalid, "bar-svc", &testNamespace) + + // httproutes + hr1 = 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), + }, + ) - /* - NOTE: When adding a new NGF policy to the change processor, - update the following tests to make sure that the change processor can track changes for multiple NGF - policies. - */ - - When( - "a policy is created that references a resource that is not in the last graph", func() { - It( - "reports no changes", func() { - processor.CaptureUpsertChange(csp) - processor.CaptureUpsertChange(obs) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }, - ) - }, - ) - When( - "the resource the policy references is created", func() { - It( - "populates the graph with the policy", func() { - processor.CaptureUpsertChange(gw) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(HaveKey(cspKey)) - Expect(graph.NGFPolicies[cspKey].Source).To(Equal(csp)) - Expect(graph.NGFPolicies).ToNot(HaveKey(obsKey)) - - processor.CaptureUpsertChange(route) - changed, graph = processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(HaveKey(obsKey)) - Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obs)) - }, - ) - }, + // services + hr1svc = createSvc("foo-svc") + sharedSvc = createSvc("bar-svc") // shared between hr2 and hr3 + invalidSvc = createSvc("invalid") // nsname matches invalid BackendRef + notRefSvc = createSvc("not-ref") + bazSvc1 = createSvc("baz-svc-v1") + bazSvc2 = createSvc("baz-svc-v2") + bazSvc3 = createSvc("baz-svc-v3") + + // endpoint slices + hr1slice1 = createEndpointSlice("hr1-1", "foo-svc") + hr1slice2 = createEndpointSlice("hr1-2", "foo-svc") + noRefSlice = createEndpointSlice("no-ref", "no-ref") + missingSvcNameSlice = createEndpointSlice("missing-svc-name", "") + + // backendTLSPolicy + btls = createBackendTLSPolicy("btls", "foo-svc") + + gw = createGateway("gw", createHTTPListener()) + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + + testProcessChangedVal := func(expChanged state.ChangeType) { + changed, _ := processor.Process() + Expect(changed).To(Equal(expChanged)) + } + + testUpsertTriggersChange := func(obj client.Object, expChanged state.ChangeType) { + processor.CaptureUpsertChange(obj) + testProcessChangedVal(expChanged) + } + + testDeleteTriggersChange := func( + obj client.Object, + nsname types.NamespacedName, + expChanged state.ChangeType, + ) { + processor.CaptureDeleteChange(obj, nsname) + testProcessChangedVal(expChanged) + } + + When("hr1 is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1, state.ClusterStateChange) + }) + }) + When("a hr1 service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1svc, state.ClusterStateChange) + }) + }) + When("a backendTLSPolicy is added for referenced service", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(btls, state.ClusterStateChange) + }) + }) + When("an hr1 endpoint slice is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1slice1, state.EndpointsOnlyChange) + }) + }) + When("an hr1 service is updated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1svc, state.ClusterStateChange) + }) + }) + When("another hr1 endpoint slice is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) + }) + }) + When("an endpoint slice with a missing svc name label is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(missingSvcNameSlice, state.NoChange) + }) + }) + When("an hr1 endpoint slice is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr1slice1, + types.NamespacedName{Namespace: hr1slice1.Namespace, Name: hr1slice1.Name}, + state.EndpointsOnlyChange, + ) + }) + }) + When("the second hr1 endpoint slice is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr1slice2, + types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, + state.EndpointsOnlyChange, + ) + }) + }) + When("the second hr1 endpoint slice is recreated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr1slice2, state.EndpointsOnlyChange) + }) + }) + When("hr1 is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr1, + types.NamespacedName{Namespace: hr1.Namespace, Name: hr1.Name}, + state.ClusterStateChange, + ) + }) + }) + When("hr1 service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + hr1svc, + types.NamespacedName{Namespace: hr1svc.Namespace, Name: hr1svc.Name}, + state.NoChange, + ) + }) + }) + When("the second hr1 endpoint slice is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + hr1slice2, + types.NamespacedName{Namespace: hr1slice2.Namespace, Name: hr1slice2.Name}, + state.NoChange, + ) + }) + }) + When("hr2 is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr2, state.ClusterStateChange) + }) + }) + When("a hr3, that shares a backend service with hr2, is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hr3, state.ClusterStateChange) + }) + }) + When("sharedSvc, a service referenced by both hr2 and hr3, is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) + }) + }) + When("hr2 is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr2, + types.NamespacedName{Namespace: hr2.Namespace, Name: hr2.Name}, + state.ClusterStateChange, + ) + }) + }) + When("sharedSvc is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + sharedSvc, + types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, + state.ClusterStateChange, + ) + }) + }) + When("sharedSvc is recreated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(sharedSvc, state.ClusterStateChange) + }) + }) + When("hr3 is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hr3, + types.NamespacedName{Namespace: hr3.Namespace, Name: hr3.Name}, + state.ClusterStateChange, + ) + }) + }) + When("sharedSvc is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + sharedSvc, + types.NamespacedName{Namespace: sharedSvc.Namespace, Name: sharedSvc.Name}, + state.NoChange, + ) + }) + }) + When("a service that is not referenced by any route is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(notRefSvc, state.NoChange) + }) + }) + When("a route with an invalid backend ref type is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hrInvalidBackendRef, state.ClusterStateChange) + }) + }) + When("a service with a namespace name that matches invalid backend ref is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(invalidSvc, state.NoChange) + }) + }) + When("an endpoint slice that is not owned by a referenced service is added", func() { + It("should not trigger a change", func() { + testUpsertTriggersChange(noRefSlice, state.NoChange) + }) + }) + When("an endpoint slice that is not owned by a referenced service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + noRefSlice, + types.NamespacedName{ + Namespace: noRefSlice.Namespace, + Name: noRefSlice.Name, + }, + state.NoChange, + ) + }) + }) + Context("processing a route with multiple rules and three unique backend services", func() { + When("route is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(hrMultipleRules, state.ClusterStateChange) + }) + }) + When("first referenced service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) + }) + }) + When("second referenced service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc2, state.ClusterStateChange) + }) + }) + When("first referenced service is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + bazSvc1, + types.NamespacedName{ + Namespace: bazSvc1.Namespace, + Name: bazSvc1.Name, + }, + state.ClusterStateChange, + ) + }) + }) + When("first referenced service is recreated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc1, state.ClusterStateChange) + }) + }) + When("third referenced service is added", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) + }) + }) + When("third referenced service is updated", func() { + It("should trigger a change", func() { + testUpsertTriggersChange(bazSvc3, state.ClusterStateChange) + }) + }) + When("route is deleted", func() { + It("should trigger a change", func() { + testDeleteTriggersChange( + hrMultipleRules, + types.NamespacedName{ + Namespace: hrMultipleRules.Namespace, + Name: hrMultipleRules.Name, + }, + state.ClusterStateChange, + ) + }) + }) + When("first referenced service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + bazSvc1, + types.NamespacedName{ + Namespace: bazSvc1.Namespace, + Name: bazSvc1.Name, + }, + state.NoChange, ) - When( - "the policy is updated", func() { - It( - "captures changes for a policy", func() { - processor.CaptureUpsertChange(cspUpdated) - processor.CaptureUpsertChange(obsUpdated) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(HaveKey(cspKey)) - Expect(graph.NGFPolicies[cspKey].Source).To(Equal(cspUpdated)) - Expect(graph.NGFPolicies).To(HaveKey(obsKey)) - Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obsUpdated)) - }, - ) - }, + }, + ) + }) + When("second referenced service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + bazSvc2, + types.NamespacedName{ + Namespace: bazSvc2.Namespace, + Name: bazSvc2.Name, + }, + state.NoChange, ) - When( - "the policy is deleted", func() { - It( - "removes the policy from the graph", func() { - processor.CaptureDeleteChange( - &ngfAPI.ClientSettingsPolicy{}, - client.ObjectKeyFromObject(csp), - ) - processor.CaptureDeleteChange( - &ngfAPI.ObservabilityPolicy{}, - client.ObjectKeyFromObject(obs), - ) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.NGFPolicies).To(BeEmpty()) + }, + ) + }) + When("final referenced service is deleted", func() { + It("should not trigger a change", func() { + testDeleteTriggersChange( + bazSvc3, + types.NamespacedName{ + Namespace: bazSvc3.Namespace, + Name: bazSvc3.Name, + }, + state.NoChange, + ) + }) + }) + }) + }) + + Describe("namespace changes", Ordered, func() { + var ( + ns, nsDifferentLabels, nsNoLabels *apiv1.Namespace + gw *v1.Gateway + ) + + BeforeAll(func() { + ns = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns", + Labels: map[string]string{ + "app": "allowed", + }, + }, + } + nsDifferentLabels = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ns-different-labels", + Labels: map[string]string{ + "oranges": "bananas", + }, + }, + } + nsNoLabels = &apiv1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "no-labels", + }, + } + gw = &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw", + }, + Spec: v1.GatewaySpec{ + GatewayClassName: gcName, + Listeners: []v1.Listener{ + { + Port: 80, + Protocol: v1.HTTPProtocolType, + AllowedRoutes: &v1.AllowedRoutes{ + Namespaces: &v1.RouteNamespaces{ + From: helpers.GetPointer(v1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "allowed", + }, + }, }, - ) + }, }, - ) + }, + }, + } + processor = state.NewChangeProcessorImpl( + state.ChangeProcessorConfig{ + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), }, ) + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw) + processor.Process() + }) + + When("a namespace is created that is not linked to a listener", func() { + It("does not trigger an update", func() { + processor.CaptureUpsertChange(nsNoLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + }) + When("a namespace is created that is linked to a listener", func() { + It("triggers an update", func() { + processor.CaptureUpsertChange(ns) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + When("a namespace is deleted that is not linked to a listener", func() { + It("does not trigger an update", func() { + processor.CaptureDeleteChange( + nsNoLabels, + types.NamespacedName{Name: "no-labels"}, + ) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + }) + When("a namespace is deleted that is linked to a listener", func() { + It("triggers an update", func() { + processor.CaptureDeleteChange(ns, types.NamespacedName{Name: "ns"}) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + When("a namespace that is not linked to a listener has its labels changed to match a listener", func() { + It("triggers an update", func() { + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + + nsDifferentLabels.Labels = map[string]string{ + "app": "allowed", + } + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ = processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + When("a namespace that is linked to a listener has its labels changed to no longer match a listener", func() { + It("triggers an update", func() { + nsDifferentLabels.Labels = map[string]string{ + "oranges": "bananas", + } + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + When("a gateway changes its listener's labels", func() { + It("triggers an update when a namespace that matches the new labels is created", func() { + gwChangedLabel := gw.DeepCopy() + gwChangedLabel.Spec.Listeners[0].AllowedRoutes.Namespaces.Selector.MatchLabels = map[string]string{ + "oranges": "bananas", + } + gwChangedLabel.Generation++ + processor.CaptureUpsertChange(gwChangedLabel) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + + // After changing the gateway's labels and generation, the processor should be marked to update + // the nginx configuration and build a new graph. When processor.Process() gets called, + // the nginx configuration gets updated and a new graph is built with an updated + // referencedNamespaces. Thus, when the namespace "ns" is upserted with labels that no longer match + // the new labels on the gateway, it would not trigger a change as the namespace would no longer + // be in the updated referencedNamespaces and the labels no longer match the new labels on the + // gateway. + processor.CaptureUpsertChange(ns) + changed, _ = processor.Process() + Expect(changed).To(Equal(state.NoChange)) + + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ = processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + When("a namespace that is not linked to a listener has its labels removed", func() { + It("does not trigger an update", func() { + ns.Labels = nil + processor.CaptureUpsertChange(ns) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + }) + When("a namespace that is linked to a listener has its labels removed", func() { + It("triggers an update when labels are removed", func() { + nsDifferentLabels.Labels = nil + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + }) + + Describe("NginxProxy resource changes", Ordered, func() { + paramGC := gc.DeepCopy() + paramGC.Spec.ParametersRef = &v1beta1.ParametersReference{ + Group: ngfAPI.GroupName, + Kind: kinds.NginxProxy, + Name: "np", + } + + np := &ngfAPI.NginxProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "np", + }, + } - Describe( - "SnippetsFilter resource changed", Ordered, func() { - sfNsName := types.NamespacedName{ - Name: "sf", - Namespace: "test", - } - - sf := &ngfAPI.SnippetsFilter{ - ObjectMeta: metav1.ObjectMeta{ - Name: sfNsName.Name, - Namespace: sfNsName.Namespace, - }, - Spec: ngfAPI.SnippetsFilterSpec{ - Snippets: []ngfAPI.Snippet{ - { - Context: ngfAPI.NginxContextMain, - Value: "main snippet", - }, - }, - }, - } + npUpdated := &ngfAPI.NginxProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "np", + }, + Spec: ngfAPI.NginxProxySpec{ + Telemetry: &ngfAPI.Telemetry{ + Exporter: &ngfAPI.TelemetryExporter{ + Endpoint: "my-svc:123", + BatchSize: helpers.GetPointer(int32(512)), + BatchCount: helpers.GetPointer(int32(4)), + Interval: helpers.GetPointer(ngfAPI.Duration("5s")), + }, + }, + }, + } + It("handles upserts for an NginxProxy", func() { + processor.CaptureUpsertChange(np) + processor.CaptureUpsertChange(paramGC) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NginxProxy.Source).To(Equal(np)) + }) + It("captures changes for an NginxProxy", func() { + processor.CaptureUpsertChange(npUpdated) + processor.CaptureUpsertChange(paramGC) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NginxProxy.Source).To(Equal(npUpdated)) + }) + It("handles deletes for an NginxProxy", func() { + processor.CaptureDeleteChange(np, client.ObjectKeyFromObject(np)) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NginxProxy).To(BeNil()) + }) + }) + + Describe("NGF Policy resource changes", Ordered, func() { + var ( + gw *v1.Gateway + route *v1.HTTPRoute + csp, cspUpdated *ngfAPI.ClientSettingsPolicy + obs, obsUpdated *ngfAPI.ObservabilityPolicy + cspKey, obsKey graph.PolicyKey + ) + + BeforeAll(func() { + processor.CaptureUpsertChange(gc) + changed, newGraph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(newGraph.GatewayClass.Source).To(Equal(gc)) + Expect(newGraph.NGFPolicies).To(BeEmpty()) + + gw = createGateway("gw", createHTTPListener()) + route = createRoute("hr-1", "gw", "foo.example.com", v1.HTTPBackendRef{}) + + csp = &ngfAPI.ClientSettingsPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "csp", + Namespace: "test", + }, + Spec: ngfAPI.ClientSettingsPolicySpec{ + TargetRef: v1alpha2.LocalPolicyTargetReference{ + Group: v1.GroupName, + Kind: kinds.Gateway, + Name: "gw", + }, + Body: &ngfAPI.ClientBody{ + MaxSize: helpers.GetPointer[ngfAPI.Size]("10m"), + }, + }, + } - sfUpdated := &ngfAPI.SnippetsFilter{ - ObjectMeta: metav1.ObjectMeta{ - Name: sfNsName.Name, - Namespace: sfNsName.Namespace, - }, - Spec: ngfAPI.SnippetsFilterSpec{ - Snippets: []ngfAPI.Snippet{ - { - Context: ngfAPI.NginxContextMain, - Value: "main snippet", - }, - { - Context: ngfAPI.NginxContextHTTP, - Value: "http snippet", - }, - }, - }, - } - It( - "handles upserts for a SnippetsFilter", func() { - processor.CaptureUpsertChange(sf) - - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - - processedSf, exists := graph.SnippetsFilters[sfNsName] - Expect(exists).To(BeTrue()) - Expect(processedSf.Source).To(Equal(sf)) - Expect(processedSf.Valid).To(BeTrue()) - }, - ) - It( - "captures changes for a SnippetsFilter", func() { - processor.CaptureUpsertChange(sfUpdated) + cspUpdated = csp.DeepCopy() + cspUpdated.Spec.Body.MaxSize = helpers.GetPointer[ngfAPI.Size]("20m") - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) + cspKey = graph.PolicyKey{ + NsName: types.NamespacedName{Name: "csp", Namespace: "test"}, + GVK: schema.GroupVersionKind{ + Group: ngfAPI.GroupName, + Kind: kinds.ClientSettingsPolicy, + Version: "v1alpha1", + }, + } - processedSf, exists := graph.SnippetsFilters[sfNsName] - Expect(exists).To(BeTrue()) - Expect(processedSf.Source).To(Equal(sfUpdated)) - Expect(processedSf.Valid).To(BeTrue()) + obs = &ngfAPI.ObservabilityPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: "obs", + Namespace: "test", + }, + Spec: ngfAPI.ObservabilityPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReference{ + { + Group: v1.GroupName, + Kind: kinds.HTTPRoute, + Name: "hr-1", }, - ) - It( - "handles deletes for a SnippetsFilter", func() { - processor.CaptureDeleteChange(sfUpdated, sfNsName) + }, + Tracing: &ngfAPI.Tracing{ + Strategy: ngfAPI.TraceStrategyRatio, + }, + }, + } - changed, graph := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - Expect(graph.SnippetsFilters).To(BeEmpty()) - }, - ) + obsUpdated = obs.DeepCopy() + obsUpdated.Spec.Tracing.Strategy = ngfAPI.TraceStrategyParent + + obsKey = graph.PolicyKey{ + NsName: types.NamespacedName{Name: "obs", Namespace: "test"}, + GVK: schema.GroupVersionKind{ + Group: ngfAPI.GroupName, + Kind: kinds.ObservabilityPolicy, + Version: "v1alpha1", }, - ) - }, - ) - Describe( - "Ensuring non-changing changes don't override previously changing changes", func() { - // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses - // -- this is done in 'Normal cases of processing changes' - - //nolint:lll - var ( - processor *state.ChangeProcessorImpl - gcNsName, gwNsName, hrNsName, hr2NsName, rgNsName, svcNsName, sliceNsName, secretNsName, cmNsName, btlsNsName, npNsName 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 - cm, cmUpdated, unrelatedCM *apiv1.ConfigMap - btls, btlsUpdated *v1alpha3.BackendTLSPolicy - np, npUpdated *ngfAPI.NginxProxy - ) + } + }) + + /* + NOTE: When adding a new NGF policy to the change processor, + update the following tests to make sure that the change processor can track changes for multiple NGF + policies. + */ + + When("a policy is created that references a resource that is not in the last graph", func() { + It("reports no changes", func() { + processor.CaptureUpsertChange(csp) + processor.CaptureUpsertChange(obs) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + }) + When("the resource the policy references is created", func() { + It("populates the graph with the policy", func() { + processor.CaptureUpsertChange(gw) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(HaveKey(cspKey)) + Expect(graph.NGFPolicies[cspKey].Source).To(Equal(csp)) + Expect(graph.NGFPolicies).ToNot(HaveKey(obsKey)) + + processor.CaptureUpsertChange(route) + changed, graph = processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(HaveKey(obsKey)) + Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obs)) + }) + }) + When("the policy is updated", func() { + It("captures changes for a policy", func() { + processor.CaptureUpsertChange(cspUpdated) + processor.CaptureUpsertChange(obsUpdated) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(HaveKey(cspKey)) + Expect(graph.NGFPolicies[cspKey].Source).To(Equal(cspUpdated)) + Expect(graph.NGFPolicies).To(HaveKey(obsKey)) + Expect(graph.NGFPolicies[obsKey].Source).To(Equal(obsUpdated)) + }) + }) + When("the policy is deleted", func() { + It("removes the policy from the graph", func() { + processor.CaptureDeleteChange( + &ngfAPI.ClientSettingsPolicy{}, + client.ObjectKeyFromObject(csp), + ) + processor.CaptureDeleteChange( + &ngfAPI.ObservabilityPolicy{}, + client.ObjectKeyFromObject(obs), + ) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.NGFPolicies).To(BeEmpty()) + }) + }) + }) + + Describe("SnippetsFilter resource changed", Ordered, func() { + sfNsName := types.NamespacedName{ + Name: "sf", + Namespace: "test", + } + + sf := &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: sfNsName.Name, + Namespace: sfNsName.Namespace, + }, + Spec: ngfAPI.SnippetsFilterSpec{ + Snippets: []ngfAPI.Snippet{ + { + Context: ngfAPI.NginxContextMain, + Value: "main snippet", + }, + }, + }, + } - BeforeEach( - OncePerOrdered, func() { - processor = state.NewChangeProcessorImpl( - state.ChangeProcessorConfig{ - GatewayCtlrName: "test.controller", - GatewayClassName: "test-class", - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }, - ) + sfUpdated := &ngfAPI.SnippetsFilter{ + ObjectMeta: metav1.ObjectMeta{ + Name: sfNsName.Name, + Namespace: sfNsName.Namespace, + }, + Spec: ngfAPI.SnippetsFilterSpec{ + Snippets: []ngfAPI.Snippet{ + { + Context: ngfAPI.NginxContextMain, + Value: "main snippet", + }, + { + Context: ngfAPI.NginxContextHTTP, + Value: "http snippet", + }, + }, + }, + } + It("handles upserts for a SnippetsFilter", func() { + processor.CaptureUpsertChange(sf) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + + processedSf, exists := graph.SnippetsFilters[sfNsName] + Expect(exists).To(BeTrue()) + Expect(processedSf.Source).To(Equal(sf)) + Expect(processedSf.Valid).To(BeTrue()) + }) + It("captures changes for a SnippetsFilter", func() { + processor.CaptureUpsertChange(sfUpdated) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + + processedSf, exists := graph.SnippetsFilters[sfNsName] + Expect(exists).To(BeTrue()) + Expect(processedSf.Source).To(Equal(sfUpdated)) + Expect(processedSf.Valid).To(BeTrue()) + }) + It("handles deletes for a SnippetsFilter", func() { + processor.CaptureDeleteChange(sfUpdated, sfNsName) + + changed, graph := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + Expect(graph.SnippetsFilters).To(BeEmpty()) + }) + }) + }) + Describe("Ensuring non-changing changes don't override previously changing changes", func() { + // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses + // -- this is done in 'Normal cases of processing changes' + + //nolint:lll + var ( + processor *state.ChangeProcessorImpl + gcNsName, gwNsName, hrNsName, hr2NsName, rgNsName, svcNsName, sliceNsName, secretNsName, cmNsName, btlsNsName, npNsName 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 + cm, cmUpdated, unrelatedCM *apiv1.ConfigMap + btls, btlsUpdated *v1alpha3.BackendTLSPolicy + np, npUpdated *ngfAPI.NginxProxy + ) - 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, - }, - } + BeforeEach(OncePerOrdered, func() { + processor = state.NewChangeProcessorImpl( + state.ChangeProcessorConfig{ + GatewayCtlrName: "test.controller", + GatewayClassName: "test-class", + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }, + ) + + secretNsName = types.NamespacedName{Namespace: "test", Name: "tls-secret"} + secret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretNsName.Name, + Namespace: secretNsName.Namespace, + Generation: 1, + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } + secretUpdated = secret.DeepCopy() + secretUpdated.Generation++ + barSecret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar-secret", + Namespace: "test", + Generation: 1, + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } + barSecretUpdated = barSecret.DeepCopy() + barSecretUpdated.Generation++ + unrelatedSecret = &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unrelated-tls-secret", + Namespace: "unrelated-ns", + Generation: 1, + }, + Type: apiv1.SecretTypeTLS, + Data: map[string][]byte{ + apiv1.TLSCertKey: cert, + apiv1.TLSPrivateKeyKey: key, + }, + } - gcNsName = types.NamespacedName{Name: "test-class"} + gcNsName = types.NamespacedName{Name: "test-class"} - gc = &v1.GatewayClass{ - ObjectMeta: metav1.ObjectMeta{ - Name: gcNsName.Name, - }, - Spec: v1.GatewayClassSpec{ - ControllerName: "test.controller", - }, - } + gc = &v1.GatewayClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: gcNsName.Name, + }, + Spec: v1.GatewayClassSpec{ + ControllerName: "test.controller", + }, + } - gcUpdated = gc.DeepCopy() - gcUpdated.Generation++ + gcUpdated = gc.DeepCopy() + gcUpdated.Generation++ - gwNsName = types.NamespacedName{Namespace: "test", Name: "gw-1"} + gwNsName = types.NamespacedName{Namespace: "test", Name: "gw-1"} - gw1 = &v1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: "gw-1", - Namespace: "test", - Generation: 1, - }, - Spec: v1.GatewaySpec{ - GatewayClassName: gcName, - Listeners: []v1.Listener{ - { - Name: httpListenerName, - Hostname: nil, - Port: 80, - Protocol: v1.HTTPProtocolType, - AllowedRoutes: &v1.AllowedRoutes{ - Namespaces: &v1.RouteNamespaces{ - From: helpers.GetPointer(v1.NamespacesFromSelector), - Selector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "test": "namespace", - }, - }, - }, + gw1 = &v1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gw-1", + Namespace: "test", + Generation: 1, + }, + Spec: v1.GatewaySpec{ + GatewayClassName: gcName, + Listeners: []v1.Listener{ + { + Name: httpListenerName, + Hostname: nil, + Port: 80, + Protocol: v1.HTTPProtocolType, + AllowedRoutes: &v1.AllowedRoutes{ + Namespaces: &v1.RouteNamespaces{ + From: helpers.GetPointer(v1.NamespacesFromSelector), + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "test": "namespace", }, }, + }, + }, + }, + { + Name: httpsListenerName, + Hostname: nil, + Port: 443, + Protocol: v1.HTTPSProtocolType, + TLS: &v1.GatewayTLSConfig{ + Mode: helpers.GetPointer(v1.TLSModeTerminate), + CertificateRefs: []v1.SecretObjectReference{ { - Name: httpsListenerName, - Hostname: nil, - Port: 443, - Protocol: v1.HTTPSProtocolType, - TLS: &v1.GatewayTLSConfig{ - Mode: helpers.GetPointer(v1.TLSModeTerminate), - CertificateRefs: []v1.SecretObjectReference{ - { - Kind: (*v1.Kind)(helpers.GetPointer("Secret")), - Name: v1.ObjectName(secret.Name), - Namespace: (*v1.Namespace)(&secret.Namespace), - }, - }, - }, + 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{ { - 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), - }, - }, - }, + Kind: (*v1.Kind)(helpers.GetPointer("Secret")), + Name: v1.ObjectName(barSecret.Name), + Namespace: (*v1.Namespace)(&barSecret.Namespace), }, }, }, - } - - gw1Updated = gw1.DeepCopy() - gw1Updated.Generation++ - - gw2 = gw1.DeepCopy() - gw2.Name = "gw-2" + }, + }, + }, + } - testNamespace := v1.Namespace("test") - kindService := v1.Kind("Service") - fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) - barRef := createHTTPBackendRef(&kindService, "bar-svc", &testNamespace) + gw1Updated = gw1.DeepCopy() + gw1Updated.Generation++ - hrNsName = types.NamespacedName{Namespace: "test", Name: "hr-1"} + gw2 = gw1.DeepCopy() + gw2.Name = "gw-2" - hr1 = createRoute("hr-1", "gw-1", "foo.example.com", fooRef, barRef) + testNamespace := v1.Namespace("test") + kindService := v1.Kind("Service") + fooRef := createHTTPBackendRef(&kindService, "foo-svc", &testNamespace) + barRef := createHTTPBackendRef(&kindService, "bar-svc", &testNamespace) - hr1Updated = hr1.DeepCopy() - hr1Updated.Generation++ + hrNsName = types.NamespacedName{Namespace: "test", Name: "hr-1"} - hr2NsName = types.NamespacedName{Namespace: "test", Name: "hr-2"} + hr1 = createRoute("hr-1", "gw-1", "foo.example.com", fooRef, barRef) - hr2 = hr1.DeepCopy() - hr2.Name = hr2NsName.Name + hr1Updated = hr1.DeepCopy() + hr1Updated.Generation++ - 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"}, - }, - } + hr2NsName = types.NamespacedName{Namespace: "test", Name: "hr-2"} - 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", - }, - }, - } + hr2 = hr1.DeepCopy() + hr2.Name = hr2NsName.Name - rgNsName = types.NamespacedName{Namespace: "test", Name: "rg-1"} + 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"}, + }, + } - rg1 = &v1beta1.ReferenceGrant{ - ObjectMeta: metav1.ObjectMeta{ - Name: rgNsName.Name, - Namespace: rgNsName.Namespace, - }, - } + 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", + }, + }, + } - rg1Updated = rg1.DeepCopy() - rg1Updated.Generation++ + rgNsName = types.NamespacedName{Namespace: "test", Name: "rg-1"} - rg2 = rg1.DeepCopy() - rg2.Name = "rg-2" + rg1 = &v1beta1.ReferenceGrant{ + ObjectMeta: metav1.ObjectMeta{ + Name: rgNsName.Name, + Namespace: rgNsName.Namespace, + }, + } - cmNsName = types.NamespacedName{Namespace: "test", Name: "cm-1"} - cm = &apiv1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: cmNsName.Name, - Namespace: cmNsName.Namespace, - }, - Data: map[string]string{ - "ca.crt": "value", - }, - } - cmUpdated = cm.DeepCopy() - cmUpdated.Data["ca.crt"] = "updated-value" - - unrelatedCM = &apiv1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "unrelated-cm", - Namespace: "unrelated-ns", - }, - Data: map[string]string{ - "ca.crt": "value", - }, - } - - btlsNsName = types.NamespacedName{Namespace: "test", Name: "btls-1"} - btls = &v1alpha3.BackendTLSPolicy{ - ObjectMeta: metav1.ObjectMeta{ - Name: btlsNsName.Name, - Namespace: btlsNsName.Namespace, - Generation: 1, - }, - Spec: v1alpha3.BackendTLSPolicySpec{ - TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ - { - LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ - Kind: "Service", - Name: v1.ObjectName(svc.Name), - }, - }, - }, - Validation: v1alpha3.BackendTLSPolicyValidation{ - CACertificateRefs: []v1.LocalObjectReference{ - { - Name: v1.ObjectName(cm.Name), - }, - }, - }, - }, - } - btlsUpdated = btls.DeepCopy() + rg1Updated = rg1.DeepCopy() + rg1Updated.Generation++ - npNsName = types.NamespacedName{Name: "np-1"} - np = &ngfAPI.NginxProxy{ - ObjectMeta: metav1.ObjectMeta{ - Name: npNsName.Name, - }, - Spec: ngfAPI.NginxProxySpec{ - Telemetry: &ngfAPI.Telemetry{ - ServiceName: helpers.GetPointer("my-svc"), - }, - }, - } - npUpdated = np.DeepCopy() - }, - ) - // Changing change - a change that makes processor.Process() report changed - // Non-changing change - a change that doesn't do that - // Related resource - a K8s resource that is related to a configured Gateway API resource - // Unrelated resource - a K8s resource that is not related to a configured Gateway API resource - - // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses - // -- this is done in 'Normal cases of processing changes' - Describe( - "Multiple Gateway API resource changes", Ordered, func() { - It( - "should report changed after multiple Upserts", func() { - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(testNs) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(rg1) - processor.CaptureUpsertChange(btls) - processor.CaptureUpsertChange(cm) - processor.CaptureUpsertChange(np) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - When( - "a upsert of updated resources is followed by an upsert of the same generation", func() { - It( - "should report changed", func() { - // these are changing changes - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(rg1Updated) - processor.CaptureUpsertChange(btlsUpdated) - processor.CaptureUpsertChange(cmUpdated) - processor.CaptureUpsertChange(npUpdated) - - // there are non-changing changes - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(rg1Updated) - processor.CaptureUpsertChange(btlsUpdated) - processor.CaptureUpsertChange(cmUpdated) - processor.CaptureUpsertChange(npUpdated) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - }, - ) - It( - "should report changed after upserting new resources", func() { - // we can't have a second GatewayClass, so we don't add it - processor.CaptureUpsertChange(gw2) - processor.CaptureUpsertChange(hr2) - processor.CaptureUpsertChange(rg2) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - When( - "resources are deleted followed by upserts with the same generations", func() { - It( - "should report changed", func() { - // these are changing changes - processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) - processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) - processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) - processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) - processor.CaptureDeleteChange(&v1alpha3.BackendTLSPolicy{}, btlsNsName) - processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) - processor.CaptureDeleteChange(&ngfAPI.NginxProxy{}, npNsName) - - // these are non-changing changes - processor.CaptureUpsertChange(gw2) - processor.CaptureUpsertChange(hr2) - processor.CaptureUpsertChange(rg2) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - }, - ) - It( - "should report changed after deleting resources", func() { - processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) + rg2 = rg1.DeepCopy() + rg2.Name = "rg-2" - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) + cmNsName = types.NamespacedName{Namespace: "test", Name: "cm-1"} + cm = &apiv1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: cmNsName.Name, + Namespace: cmNsName.Namespace, + }, + Data: map[string]string{ + "ca.crt": "value", + }, + } + cmUpdated = cm.DeepCopy() + cmUpdated.Data["ca.crt"] = "updated-value" + + unrelatedCM = &apiv1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unrelated-cm", + Namespace: "unrelated-ns", + }, + Data: map[string]string{ + "ca.crt": "value", + }, + } + + btlsNsName = types.NamespacedName{Namespace: "test", Name: "btls-1"} + btls = &v1alpha3.BackendTLSPolicy{ + ObjectMeta: metav1.ObjectMeta{ + Name: btlsNsName.Name, + Namespace: btlsNsName.Namespace, + Generation: 1, + }, + Spec: v1alpha3.BackendTLSPolicySpec{ + TargetRefs: []v1alpha2.LocalPolicyTargetReferenceWithSectionName{ + { + LocalPolicyTargetReference: v1alpha2.LocalPolicyTargetReference{ + Kind: "Service", + Name: v1.ObjectName(svc.Name), }, - ) + }, }, - ) - Describe( - "Deleting non-existing Gateway API resource", func() { - It( - "should not report changed after deleting non-existing", func() { - processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) - processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) - processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) - processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) - processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) + Validation: v1alpha3.BackendTLSPolicyValidation{ + CACertificateRefs: []v1.LocalObjectReference{ + { + Name: v1.ObjectName(cm.Name), }, - ) + }, }, - ) - Describe( - "Multiple Kubernetes API resource changes", Ordered, func() { - BeforeAll( - func() { - // Set up graph - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(testNs) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(secret) - processor.CaptureUpsertChange(barSecret) - processor.CaptureUpsertChange(cm) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) + }, + } + btlsUpdated = btls.DeepCopy() - It( - "should report changed after multiple Upserts of related resources", func() { - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(ns) - processor.CaptureUpsertChange(secretUpdated) - processor.CaptureUpsertChange(cmUpdated) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - It( - "should report not changed after multiple Upserts of unrelated resources", func() { - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }, - ) - When( - "upserts of related resources are followed by upserts of unrelated resources", func() { - It( - "should report changed", func() { - // these are changing changes - processor.CaptureUpsertChange(barSvc) - processor.CaptureUpsertChange(barSlice) - processor.CaptureUpsertChange(barNs) - processor.CaptureUpsertChange(barSecretUpdated) - processor.CaptureUpsertChange(cmUpdated) - - // there are non-changing changes - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - }, - ) - When( - "deletes of related resources are followed by upserts of unrelated resources", func() { - It( - "should report changed", func() { - // these are changing changes - processor.CaptureDeleteChange(&apiv1.Service{}, svcNsName) - processor.CaptureDeleteChange(&discoveryV1.EndpointSlice{}, sliceNsName) - processor.CaptureDeleteChange( - &apiv1.Namespace{}, - types.NamespacedName{Name: "ns"}, - ) - processor.CaptureDeleteChange(&apiv1.Secret{}, secretNsName) - processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) - - // these are non-changing changes - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - }, - ) - }, - ) - Describe( - "Multiple Kubernetes API and Gateway API resource changes", Ordered, func() { - It( - "should report changed after multiple Upserts of new and related resources", func() { - // new Gateway API resources - processor.CaptureUpsertChange(gc) - processor.CaptureUpsertChange(gw1) - processor.CaptureUpsertChange(testNs) - processor.CaptureUpsertChange(hr1) - processor.CaptureUpsertChange(rg1) - processor.CaptureUpsertChange(btls) - - // related Kubernetes API resources - processor.CaptureUpsertChange(svc) - processor.CaptureUpsertChange(slice) - processor.CaptureUpsertChange(ns) - processor.CaptureUpsertChange(secret) - processor.CaptureUpsertChange(cm) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) - It( - "should report not changed after multiple Upserts of unrelated resources", func() { - // unrelated Kubernetes API resources - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.NoChange)) - }, - ) - It( - "should report changed after upserting changed resources followed by upserting unrelated resources", - func() { - // these are changing changes - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(rg1Updated) - processor.CaptureUpsertChange(btlsUpdated) - - // these are non-changing changes - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) - - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }, - ) + npNsName = types.NamespacedName{Name: "np-1"} + np = &ngfAPI.NginxProxy{ + ObjectMeta: metav1.ObjectMeta{ + Name: npNsName.Name, + }, + Spec: ngfAPI.NginxProxySpec{ + Telemetry: &ngfAPI.Telemetry{ + ServiceName: helpers.GetPointer("my-svc"), }, - ) + }, + } + npUpdated = np.DeepCopy() + }) + // Changing change - a change that makes processor.Process() report changed + // Non-changing change - a change that doesn't do that + // Related resource - a K8s resource that is related to a configured Gateway API resource + // Unrelated resource - a K8s resource that is not related to a configured Gateway API resource + + // Note: in these tests, we deliberately don't fully inspect the returned configuration and statuses + // -- this is done in 'Normal cases of processing changes' + Describe("Multiple Gateway API resource changes", Ordered, func() { + It("should report changed after multiple Upserts", func() { + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(testNs) + processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(rg1) + processor.CaptureUpsertChange(btls) + processor.CaptureUpsertChange(cm) + processor.CaptureUpsertChange(np) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + When("a upsert of updated resources is followed by an upsert of the same generation", func() { + It("should report changed", func() { + // these are changing changes + processor.CaptureUpsertChange(gcUpdated) + processor.CaptureUpsertChange(gw1Updated) + processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(rg1Updated) + processor.CaptureUpsertChange(btlsUpdated) + processor.CaptureUpsertChange(cmUpdated) + processor.CaptureUpsertChange(npUpdated) + + // there are non-changing changes + processor.CaptureUpsertChange(gcUpdated) + processor.CaptureUpsertChange(gw1Updated) + processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(rg1Updated) + processor.CaptureUpsertChange(btlsUpdated) + processor.CaptureUpsertChange(cmUpdated) + processor.CaptureUpsertChange(npUpdated) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + It("should report changed after upserting new resources", func() { + // we can't have a second GatewayClass, so we don't add it + processor.CaptureUpsertChange(gw2) + processor.CaptureUpsertChange(hr2) + processor.CaptureUpsertChange(rg2) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + When("resources are deleted followed by upserts with the same generations", func() { + It("should report changed", func() { + // these are changing changes + processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) + processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) + processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) + processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) + processor.CaptureDeleteChange(&v1alpha3.BackendTLSPolicy{}, btlsNsName) + processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) + processor.CaptureDeleteChange(&ngfAPI.NginxProxy{}, npNsName) + + // these are non-changing changes + processor.CaptureUpsertChange(gw2) + processor.CaptureUpsertChange(hr2) + processor.CaptureUpsertChange(rg2) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + It("should report changed after deleting resources", func() { + processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + Describe("Deleting non-existing Gateway API resource", func() { + It("should not report changed after deleting non-existing", func() { + processor.CaptureDeleteChange(&v1.GatewayClass{}, gcNsName) + processor.CaptureDeleteChange(&v1.Gateway{}, gwNsName) + processor.CaptureDeleteChange(&v1.HTTPRoute{}, hrNsName) + processor.CaptureDeleteChange(&v1.HTTPRoute{}, hr2NsName) + processor.CaptureDeleteChange(&v1beta1.ReferenceGrant{}, rgNsName) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + }) + Describe("Multiple Kubernetes API resource changes", Ordered, func() { + BeforeAll(func() { + // Set up graph + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(testNs) + processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(secret) + processor.CaptureUpsertChange(barSecret) + processor.CaptureUpsertChange(cm) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + + It("should report changed after multiple Upserts of related resources", func() { + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(ns) + processor.CaptureUpsertChange(secretUpdated) + processor.CaptureUpsertChange(cmUpdated) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + It("should report not changed after multiple Upserts of unrelated resources", func() { + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + When("upserts of related resources are followed by upserts of unrelated resources", func() { + It("should report changed", func() { + // these are changing changes + processor.CaptureUpsertChange(barSvc) + processor.CaptureUpsertChange(barSlice) + processor.CaptureUpsertChange(barNs) + processor.CaptureUpsertChange(barSecretUpdated) + processor.CaptureUpsertChange(cmUpdated) + + // there are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + When("deletes of related resources are followed by upserts of unrelated resources", func() { + It("should report changed", func() { + // these are changing changes + processor.CaptureDeleteChange(&apiv1.Service{}, svcNsName) + processor.CaptureDeleteChange(&discoveryV1.EndpointSlice{}, sliceNsName) + processor.CaptureDeleteChange( + &apiv1.Namespace{}, + types.NamespacedName{Name: "ns"}, + ) + processor.CaptureDeleteChange(&apiv1.Secret{}, secretNsName) + processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) + + // these are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + }) + Describe("Multiple Kubernetes API and Gateway API resource changes", Ordered, func() { + It("should report changed after multiple Upserts of new and related resources", func() { + // new Gateway API resources + processor.CaptureUpsertChange(gc) + processor.CaptureUpsertChange(gw1) + processor.CaptureUpsertChange(testNs) + processor.CaptureUpsertChange(hr1) + processor.CaptureUpsertChange(rg1) + processor.CaptureUpsertChange(btls) + + // related Kubernetes API resources + processor.CaptureUpsertChange(svc) + processor.CaptureUpsertChange(slice) + processor.CaptureUpsertChange(ns) + processor.CaptureUpsertChange(secret) + processor.CaptureUpsertChange(cm) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + It("should report not changed after multiple Upserts of unrelated resources", func() { + // unrelated Kubernetes API resources + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.NoChange)) + }) + It("should report changed after upserting changed resources followed by upserting unrelated resources", func() { + // these are changing changes + processor.CaptureUpsertChange(gcUpdated) + processor.CaptureUpsertChange(gw1Updated) + processor.CaptureUpsertChange(hr1Updated) + processor.CaptureUpsertChange(rg1Updated) + processor.CaptureUpsertChange(btlsUpdated) + + // these are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }) + }) + Describe("Edge cases with panic", func() { + var processor state.ChangeProcessor + + BeforeEach(func() { + processor = state.NewChangeProcessorImpl( + state.ChangeProcessorConfig{ + GatewayCtlrName: "test.controller", + GatewayClassName: "my-class", + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }, + ) + }) + + DescribeTable( + "CaptureUpsertChange must panic", + func(obj client.Object) { + process := func() { + processor.CaptureUpsertChange(obj) + } + Expect(process).Should(Panic()) }, + Entry( + "an unsupported resource", + &v1alpha2.TCPRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "tcp"}}, + ), + Entry( + "nil resource", + nil, + ), ) - Describe( - "Edge cases with panic", func() { - var processor state.ChangeProcessor - - BeforeEach( - func() { - processor = state.NewChangeProcessorImpl( - state.ChangeProcessorConfig{ - GatewayCtlrName: "test.controller", - GatewayClassName: "my-class", - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }, - ) - }, - ) - - DescribeTable( - "CaptureUpsertChange must panic", - func(obj client.Object) { - process := func() { - processor.CaptureUpsertChange(obj) - } - Expect(process).Should(Panic()) - }, - Entry( - "an unsupported resource", - &v1alpha2.TCPRoute{ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "tcp"}}, - ), - Entry( - "nil resource", - nil, - ), - ) - DescribeTable( - "CaptureDeleteChange must panic", - func(resourceType ngftypes.ObjectType, nsname types.NamespacedName) { - process := func() { - processor.CaptureDeleteChange(resourceType, nsname) - } - Expect(process).Should(Panic()) - }, - Entry( - "an unsupported resource", - &v1alpha2.TCPRoute{}, - types.NamespacedName{Namespace: "test", Name: "tcp"}, - ), - Entry( - "nil resource type", - nil, - types.NamespacedName{Namespace: "test", Name: "resource"}, - ), - ) + DescribeTable( + "CaptureDeleteChange must panic", + func(resourceType ngftypes.ObjectType, nsname types.NamespacedName) { + process := func() { + processor.CaptureDeleteChange(resourceType, nsname) + } + Expect(process).Should(Panic()) }, + Entry( + "an unsupported resource", + &v1alpha2.TCPRoute{}, + types.NamespacedName{Namespace: "test", Name: "tcp"}, + ), + Entry( + "nil resource type", + nil, + types.NamespacedName{Namespace: "test", Name: "resource"}, + ), ) - }, -) + }) +}) diff --git a/internal/mode/static/state/dataplane/configuration.go b/internal/mode/static/state/dataplane/configuration.go index 60b97bf8d8..b2a3e1e6e6 100644 --- a/internal/mode/static/state/dataplane/configuration.go +++ b/internal/mode/static/state/dataplane/configuration.go @@ -105,31 +105,25 @@ func buildPassthroughServers(g *graph.Graph) []Layer4VirtualServer { if l.Source.Hostname != nil && h == string(*l.Source.Hostname) { foundRouteMatchingListenerHostname = true } - passthroughServersMap[key] = append( - passthroughServersMap[key], Layer4VirtualServer{ - Hostname: h, - UpstreamName: r.Spec.BackendRef.ServicePortReference(), - Port: int32(l.Source.Port), - }, - ) + passthroughServersMap[key] = append(passthroughServersMap[key], Layer4VirtualServer{ + Hostname: h, + UpstreamName: r.Spec.BackendRef.ServicePortReference(), + Port: int32(l.Source.Port), + }) } } if !foundRouteMatchingListenerHostname { if l.Source.Hostname != nil { - listenerPassthroughServers = append( - listenerPassthroughServers, Layer4VirtualServer{ - Hostname: string(*l.Source.Hostname), - IsDefault: true, - Port: int32(l.Source.Port), - }, - ) + listenerPassthroughServers = append(listenerPassthroughServers, Layer4VirtualServer{ + Hostname: string(*l.Source.Hostname), + IsDefault: true, + Port: int32(l.Source.Port), + }) } else { - listenerPassthroughServers = append( - listenerPassthroughServers, Layer4VirtualServer{ - Hostname: "", - Port: int32(l.Source.Port), - }, - ) + listenerPassthroughServers = append(listenerPassthroughServers, Layer4VirtualServer{ + Hostname: "", + Port: int32(l.Source.Port), + }) } } } @@ -317,14 +311,12 @@ func newBackendGroup(refs []graph.BackendRef, sourceNsName types.NamespacedName, } for _, ref := range refs { - backends = append( - backends, Backend{ - UpstreamName: ref.ServicePortReference(), - Weight: ref.Weight, - Valid: ref.Valid, - VerifyTLS: convertBackendTLS(ref.BackendTLSPolicy), - }, - ) + backends = append(backends, Backend{ + UpstreamName: ref.ServicePortReference(), + Weight: ref.Weight, + Valid: ref.Valid, + VerifyTLS: convertBackendTLS(ref.BackendTLSPolicy), + }) } return BackendGroup{ @@ -516,14 +508,12 @@ func (hpr *hostPathRules) upsertRoute( hostRule.GRPC = GRPC hostRule.Policies = append(hostRule.Policies, pols...) - hostRule.MatchRules = append( - hostRule.MatchRules, MatchRule{ - Source: objectSrc, - BackendGroup: newBackendGroup(rule.BackendRefs, routeNsName, i), - Filters: filters, - Match: convertMatch(m), - }, - ) + hostRule.MatchRules = append(hostRule.MatchRules, MatchRule{ + Source: objectSrc, + BackendGroup: newBackendGroup(rule.BackendRefs, routeNsName, i), + Filters: filters, + Match: convertMatch(m), + }) hpr.rulesPerHost[h][key] = hostRule } @@ -559,15 +549,13 @@ func (hpr *hostPathRules) buildServers() []VirtualServer { } // We sort the path rules so the order is preserved after reconfiguration. - sort.Slice( - s.PathRules, func(i, j int) bool { - if s.PathRules[i].Path != s.PathRules[j].Path { - return s.PathRules[i].Path < s.PathRules[j].Path - } + sort.Slice(s.PathRules, func(i, j int) bool { + if s.PathRules[i].Path != s.PathRules[j].Path { + return s.PathRules[i].Path < s.PathRules[j].Path + } - return s.PathRules[i].PathType < s.PathRules[j].PathType - }, - ) + return s.PathRules[i].PathType < s.PathRules[j].PathType + }) servers = append(servers, s) } @@ -594,20 +582,16 @@ func (hpr *hostPathRules) buildServers() []VirtualServer { // if any listeners exist, we need to generate a default server block. if hpr.listenersExist { - servers = append( - servers, VirtualServer{ - IsDefault: true, - Port: hpr.port, - }, - ) + servers = append(servers, VirtualServer{ + IsDefault: true, + Port: hpr.port, + }) } // We sort the servers so the order is preserved after reconfiguration. - sort.Slice( - servers, func(i, j int) bool { - return servers[i].Hostname < servers[j].Hostname - }, - ) + sort.Slice(servers, func(i, j int) bool { + return servers[i].Hostname < servers[j].Hostname + }) return servers } @@ -955,13 +939,10 @@ func buildSnippetsForContext( continue } - snippetsForContext = append( - snippetsForContext, - Snippet{ - Name: createSnippetName(nc, client.ObjectKeyFromObject(filter.Source)), - Contents: snippetValue, - }, - ) + snippetsForContext = append(snippetsForContext, Snippet{ + Name: createSnippetName(nc, client.ObjectKeyFromObject(filter.Source)), + Contents: snippetValue, + }) } return snippetsForContext diff --git a/internal/mode/static/state/dataplane/configuration_test.go b/internal/mode/static/state/dataplane/configuration_test.go index cbc6b9e5d2..ab1a3a005a 100644 --- a/internal/mode/static/state/dataplane/configuration_test.go +++ b/internal/mode/static/state/dataplane/configuration_test.go @@ -269,13 +269,11 @@ func TestBuildConfiguration(t *testing.T) { backends = []Backend{expValidBackend} } - groups = append( - groups, BackendGroup{ - Backends: backends, - Source: client.ObjectKeyFromObject(route.Source), - RuleIdx: idx, - }, - ) + groups = append(groups, BackendGroup{ + Backends: backends, + Source: client.ObjectKeyFromObject(route.Source), + RuleIdx: idx, + }) } return groups @@ -873,29 +871,25 @@ func TestBuildConfiguration(t *testing.T) { }{ { graph: getNormalGraph(), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.HTTPServers = []VirtualServer{} - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }, - ), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.HTTPServers = []VirtualServer{} + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }), msg: "no listeners and routes", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - }, - ) - return g - }, - ), + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + }, + ) + return g + }), expConf: getModifiedExpectedConfiguration( func(conf Configuration) Configuration { conf.SSLServers = []VirtualServer{} @@ -906,1643 +900,1551 @@ func TestBuildConfiguration(t *testing.T) { msg: "http listener with no routes", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1Invalid): routeHR1Invalid, - }, + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1Invalid): routeHR1Invalid, }, - { - Name: "listener-443-1", - Source: listener443, // nil hostname - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR1Invalid): httpsRouteHR1Invalid, - }, - ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-1", + Source: listener443, // nil hostname + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR1Invalid): httpsRouteHR1Invalid, }, - }..., - ) - g.Routes[graph.CreateRouteKey(hr1Invalid)] = routeHR1Invalid - g.ReferencedSecrets[secret1NsName] = secret1 - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.HTTPServers = []VirtualServer{ + ResolvedSecret: &secret1NsName, + }, + }..., + ) + g.Routes[graph.CreateRouteKey(hr1Invalid)] = routeHR1Invalid + g.ReferencedSecrets[secret1NsName] = secret1 + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.HTTPServers = []VirtualServer{ + { + IsDefault: true, + Port: 80, + }, + } + conf.SSLServers = append( + conf.SSLServers, VirtualServer{ + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + ) + return conf + }), + msg: "http and https listeners with no valid routes", + }, + { + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ { - IsDefault: true, - Port: 80, + Name: "listener-443-1", + Source: listener443, // nil hostname + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + ResolvedSecret: &secret1NsName, }, - } - conf.SSLServers = append( - conf.SSLServers, VirtualServer{ + { + Name: "listener-443-with-hostname", + Source: listener443WithHostname, // non-nil hostname + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + ResolvedSecret: &secret2NsName, + }, + }..., + ) + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + secret2NsName: secret2, + } + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.HTTPServers = []VirtualServer{} + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ + { + Hostname: string(hostname), + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, + Port: 443, + }, + { Hostname: wildcardHostname, SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, Port: 443, }, - ) - return conf - }, - ), - msg: "http and https listeners with no valid routes", - }, - { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-443-1", - Source: listener443, // nil hostname - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - ResolvedSecret: &secret1NsName, - }, - { - Name: "listener-443-with-hostname", - Source: listener443WithHostname, // non-nil hostname - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - ResolvedSecret: &secret2NsName, - }, - }..., - ) - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - secret2NsName: secret2, - } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.HTTPServers = []VirtualServer{} - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: string(hostname), - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }..., - ) - conf.SSLKeyPairs["ssl_keypair_test_secret-2"] = SSLKeyPair{ - Cert: []byte("cert-2"), - Key: []byte("privateKey-2"), - } - return conf - }, - ), + }..., + ) + conf.SSLKeyPairs["ssl_keypair_test_secret-2"] = SSLKeyPair{ + Cert: []byte("cert-2"), + Key: []byte("privateKey-2"), + } + return conf + }), msg: "https listeners with no routes", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "invalid-listener", - Source: invalidListener, - Valid: false, - ResolvedSecret: &secret1NsName, - }, - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR1): httpsRouteHR1, - graph.CreateRouteKey(httpsHR2): httpsRouteHR2, - } - g.ReferencedSecrets[secret1NsName] = secret1 - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.HTTPServers = []VirtualServer{} - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }, - ), + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "invalid-listener", + Source: invalidListener, + Valid: false, + ResolvedSecret: &secret1NsName, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR1): httpsRouteHR1, + graph.CreateRouteKey(httpsHR2): httpsRouteHR2, + } + g.ReferencedSecrets[secret1NsName] = secret1 + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.HTTPServers = []VirtualServer{} + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }), msg: "invalid https listener with resolved secret", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): routeHR1, - graph.CreateRouteKey(hr2): routeHR2, - }, + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): routeHR1, + graph.CreateRouteKey(hr2): routeHR2, }, - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): routeHR1, - graph.CreateRouteKey(hr2): routeHR2, - } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, []VirtualServer{ - { - Hostname: "bar.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR2Groups[0], - Source: &hr2.ObjectMeta, - }, - }, - }, - }, - Port: 80, - }, - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR1Groups[0], - Source: &hr1.ObjectMeta, - }, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): routeHR1, + graph.CreateRouteKey(hr2): routeHR2, + } + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, []VirtualServer{ + { + Hostname: "bar.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR2Groups[0], + Source: &hr2.ObjectMeta, }, }, }, - Port: 80, - }, - }..., - ) - conf.SSLServers = []VirtualServer{} - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHR1Groups[0], expHR2Groups[0]} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - - return conf - }, - ), - msg: "one http listener with two routes for different hostnames", - }, - { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(gr): routeGR, }, + Port: 80, }, - ) - g.Routes[graph.CreateRouteKey(gr)] = routeGR - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, VirtualServer{ + { Hostname: "foo.example.com", PathRules: []PathRule{ { Path: "/", PathType: PathTypePrefix, - GRPC: true, MatchRules: []MatchRule{ { - BackendGroup: expGRGroups[0], - Source: &gr.ObjectMeta, + BackendGroup: expHR1Groups[0], + Source: &hr1.ObjectMeta, }, }, }, }, Port: 80, }, - ) - conf.SSLServers = []VirtualServer{} - conf.Upstreams = append(conf.Upstreams, fooUpstream) - conf.BackendGroups = []BackendGroup{expGRGroups[0]} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }, - ), - msg: "one http listener with one grpc route", + }..., + ) + conf.SSLServers = []VirtualServer{} + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHR1Groups[0], expHR2Groups[0]} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + + return conf + }), + msg: "one http listener with two routes for different hostnames", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(gr): routeGR, + }, + }, + ) + g.Routes[graph.CreateRouteKey(gr)] = routeGR + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, VirtualServer{ + Hostname: "foo.example.com", + PathRules: []PathRule{ { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR1): httpsRouteHR1, - graph.CreateRouteKey(httpsHR2): httpsRouteHR2, + Path: "/", + PathType: PathTypePrefix, + GRPC: true, + MatchRules: []MatchRule{ + { + BackendGroup: expGRGroups[0], + Source: &gr.ObjectMeta, + }, }, - ResolvedSecret: &secret1NsName, }, - { - Name: "listener-443-with-hostname", - Source: listener443WithHostname, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - }, - ResolvedSecret: &secret2NsName, + }, + Port: 80, + }, + ) + conf.SSLServers = []VirtualServer{} + conf.Upstreams = append(conf.Upstreams, fooUpstream) + conf.BackendGroups = []BackendGroup{expGRGroups[0]} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }), + msg: "one http listener with one grpc route", + }, + { + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR1): httpsRouteHR1, + graph.CreateRouteKey(httpsHR2): httpsRouteHR2, }, - }..., - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): httpsRouteHR1, - graph.CreateRouteKey(hr2): httpsRouteHR2, - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - secret2NsName: secret2, - } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.HTTPServers = []VirtualServer{} - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "bar.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR2Groups[0], - Source: &httpsHR2.ObjectMeta, - }, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-with-hostname", + Source: listener443WithHostname, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, + }, + ResolvedSecret: &secret2NsName, + }, + }..., + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): httpsRouteHR1, + graph.CreateRouteKey(hr2): httpsRouteHR2, + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + secret2NsName: secret2, + } + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.HTTPServers = []VirtualServer{} + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ + { + Hostname: "bar.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR2Groups[0], + Source: &httpsHR2.ObjectMeta, }, }, }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, }, - { - Hostname: "example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR5Groups[0], - Source: &httpsHR5.ObjectMeta, - }, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + { + Hostname: "example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR5Groups[0], + Source: &httpsHR5.ObjectMeta, }, }, }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, - Port: 443, }, - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR1Groups[0], - Source: &httpsHR1.ObjectMeta, - }, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, + Port: 443, + }, + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR1Groups[0], + Source: &httpsHR1.ObjectMeta, }, }, }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }..., - ) - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{ - expHTTPSHR1Groups[0], - expHTTPSHR2Groups[0], - expHTTPSHR5Groups[0], - } - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{ - "ssl_keypair_test_secret-1": { - Cert: []byte("cert-1"), - Key: []byte("privateKey-1"), + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, }, - "ssl_keypair_test_secret-2": { - Cert: []byte("cert-2"), - Key: []byte("privateKey-2"), + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, }, - } - return conf - }, - ), + }..., + ) + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{ + expHTTPSHR1Groups[0], + expHTTPSHR2Groups[0], + expHTTPSHR5Groups[0], + } + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{ + "ssl_keypair_test_secret-1": { + Cert: []byte("cert-1"), + Key: []byte("privateKey-1"), + }, + "ssl_keypair_test_secret-2": { + Cert: []byte("cert-2"), + Key: []byte("privateKey-2"), + }, + } + return conf + }), msg: "two https listeners each with routes for different hostnames", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr3): routeHR3, - graph.CreateRouteKey(hr4): routeHR4, - }, + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr3): routeHR3, + graph.CreateRouteKey(hr4): routeHR4, }, - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR3): httpsRouteHR3, - graph.CreateRouteKey(httpsHR4): httpsRouteHR4, - }, - ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR3): httpsRouteHR3, + graph.CreateRouteKey(httpsHR4): httpsRouteHR4, }, - }..., - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr3): routeHR3, - graph.CreateRouteKey(hr4): routeHR4, - graph.CreateRouteKey(httpsHR3): httpsRouteHR3, - graph.CreateRouteKey(httpsHR4): httpsRouteHR4, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR3Groups[0], - Source: &hr3.ObjectMeta, - }, - { - BackendGroup: expHR4Groups[1], - Source: &hr4.ObjectMeta, - }, + ResolvedSecret: &secret1NsName, + }, + }..., + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr3): routeHR3, + graph.CreateRouteKey(hr4): routeHR4, + graph.CreateRouteKey(httpsHR3): httpsRouteHR3, + graph.CreateRouteKey(httpsHR4): httpsRouteHR4, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + } + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR3Groups[0], + Source: &hr3.ObjectMeta, + }, + { + BackendGroup: expHR4Groups[1], + Source: &hr4.ObjectMeta, }, }, - { - Path: "/fourth", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR4Groups[0], - Source: &hr4.ObjectMeta, - }, + }, + { + Path: "/fourth", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR4Groups[0], + Source: &hr4.ObjectMeta, }, }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR3Groups[1], - Source: &hr3.ObjectMeta, - }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR3Groups[1], + Source: &hr3.ObjectMeta, }, }, }, - Port: 80, }, - }..., - ) - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR3Groups[0], - Source: &httpsHR3.ObjectMeta, - }, - { - BackendGroup: expHTTPSHR4Groups[1], - Source: &httpsHR4.ObjectMeta, - }, + Port: 80, + }, + }..., + ) + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ + { + Hostname: "foo.example.com", + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR3Groups[0], + Source: &httpsHR3.ObjectMeta, + }, + { + BackendGroup: expHTTPSHR4Groups[1], + Source: &httpsHR4.ObjectMeta, }, }, - { - Path: "/fourth", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR4Groups[0], - Source: &httpsHR4.ObjectMeta, - }, + }, + { + Path: "/fourth", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR4Groups[0], + Source: &httpsHR4.ObjectMeta, }, }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR3Groups[1], - Source: &httpsHR3.ObjectMeta, - }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR3Groups[1], + Source: &httpsHR3.ObjectMeta, }, }, }, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, }, - }..., - ) - conf.Upstreams = append(conf.Upstreams, fooUpstream) - conf.BackendGroups = []BackendGroup{ - expHR3Groups[0], - expHR3Groups[1], - expHR4Groups[0], - expHR4Groups[1], - expHTTPSHR3Groups[0], - expHTTPSHR3Groups[1], - expHTTPSHR4Groups[0], - expHTTPSHR4Groups[1], - } - return conf - }, - ), + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }..., + ) + conf.Upstreams = append(conf.Upstreams, fooUpstream) + conf.BackendGroups = []BackendGroup{ + expHR3Groups[0], + expHR3Groups[1], + expHR4Groups[0], + expHR4Groups[1], + expHTTPSHR3Groups[0], + expHTTPSHR3Groups[1], + expHTTPSHR4Groups[0], + expHTTPSHR4Groups[1], + } + return conf + }), msg: "one http and one https listener with two routes with the same hostname with and without collisions", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr3): routeHR3, - }, + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr3): routeHR3, }, - { - Name: "listener-8080", - Source: listener8080, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr8): routeHR8, - }, + }, + { + Name: "listener-8080", + Source: listener8080, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr8): routeHR8, }, - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR3): httpsRouteHR3, - }, - ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR3): httpsRouteHR3, }, - { - Name: "listener-8443", - Source: listener8443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR7): httpsRouteHR7, - }, - ResolvedSecret: &secret1NsName, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-8443", + Source: listener8443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR7): httpsRouteHR7, }, - }..., - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr3): routeHR3, - graph.CreateRouteKey(hr8): routeHR8, - graph.CreateRouteKey(httpsHR3): httpsRouteHR3, - graph.CreateRouteKey(httpsHR7): httpsRouteHR7, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR3Groups[0], - Source: &hr3.ObjectMeta, - }, + ResolvedSecret: &secret1NsName, + }, + }..., + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr3): routeHR3, + graph.CreateRouteKey(hr8): routeHR8, + graph.CreateRouteKey(httpsHR3): httpsRouteHR3, + graph.CreateRouteKey(httpsHR7): httpsRouteHR7, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + } + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR3Groups[0], + Source: &hr3.ObjectMeta, }, }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR3Groups[1], - Source: &hr3.ObjectMeta, - }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR3Groups[1], + Source: &hr3.ObjectMeta, }, }, }, - Port: 80, - }, - { - IsDefault: true, - Port: 8080, }, - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR8Groups[0], - Source: &hr8.ObjectMeta, - }, + Port: 80, + }, + { + IsDefault: true, + Port: 8080, + }, + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR8Groups[0], + Source: &hr8.ObjectMeta, }, }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR8Groups[1], - Source: &hr8.ObjectMeta, - }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR8Groups[1], + Source: &hr8.ObjectMeta, }, }, }, - Port: 8080, }, - }..., - ) - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR3Groups[0], - Source: &httpsHR3.ObjectMeta, - }, + Port: 8080, + }, + }..., + ) + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ + { + Hostname: "foo.example.com", + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR3Groups[0], + Source: &httpsHR3.ObjectMeta, }, }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR3Groups[1], - Source: &httpsHR3.ObjectMeta, - }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR3Groups[1], + Source: &httpsHR3.ObjectMeta, }, }, }, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - { - IsDefault: true, - Port: 8443, }, - { - Hostname: "foo.example.com", - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR7Groups[0], - Source: &httpsHR7.ObjectMeta, - }, + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + { + IsDefault: true, + Port: 8443, + }, + { + Hostname: "foo.example.com", + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR7Groups[0], + Source: &httpsHR7.ObjectMeta, }, }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR7Groups[1], - Source: &httpsHR7.ObjectMeta, - }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR7Groups[1], + Source: &httpsHR7.ObjectMeta, }, }, }, - Port: 8443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 8443, }, - }..., - ) - conf.Upstreams = append(conf.Upstreams, fooUpstream) - conf.BackendGroups = []BackendGroup{ - expHR3Groups[0], - expHR3Groups[1], - expHR8Groups[0], - expHR8Groups[1], - expHTTPSHR3Groups[0], - expHTTPSHR3Groups[1], - expHTTPSHR7Groups[0], - expHTTPSHR7Groups[1], - } - return conf - }, - ), + Port: 8443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 8443, + }, + }..., + ) + conf.Upstreams = append(conf.Upstreams, fooUpstream) + conf.BackendGroups = []BackendGroup{ + expHR3Groups[0], + expHR3Groups[1], + expHR8Groups[0], + expHR8Groups[1], + expHTTPSHR3Groups[0], + expHTTPSHR3Groups[1], + expHTTPSHR7Groups[0], + expHTTPSHR7Groups[1], + } + return conf + }), msg: "multiple http and https listener; different ports", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.GatewayClass.Valid = false - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): routeHR1, - }, + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.GatewayClass.Valid = false + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): routeHR1, }, - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): routeHR1, - } - return g - }, - ), + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): routeHR1, + } + return g + }), expConf: Configuration{}, msg: "invalid gatewayclass", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.GatewayClass.Valid = false - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): routeHR1, - }, + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.GatewayClass.Valid = false + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): routeHR1, }, - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): routeHR1, - } - return g - }, - ), + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): routeHR1, + } + return g + }), expConf: Configuration{}, msg: "missing gatewayclass", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway = nil - return g - }, - ), + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway = nil + return g + }), expConf: Configuration{}, msg: "missing gateway", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr5): routeHR5, - }, + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr5): routeHR5, }, - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr5): routeHR5, - } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - Source: &hr5.ObjectMeta, - BackendGroup: expHR5Groups[0], - Filters: HTTPFilters{ - RequestRedirect: &expRedirect, - SnippetsFilters: []SnippetsFilter{expExtRefFilter}, - }, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr5): routeHR5, + } + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + Source: &hr5.ObjectMeta, + BackendGroup: expHR5Groups[0], + Filters: HTTPFilters{ + RequestRedirect: &expRedirect, + SnippetsFilters: []SnippetsFilter{expExtRefFilter}, }, }, }, - { - Path: invalidFiltersPath, - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - Source: &hr5.ObjectMeta, - BackendGroup: expHR5Groups[1], - Filters: HTTPFilters{ - InvalidFilter: &InvalidHTTPFilter{}, - }, + }, + { + Path: invalidFiltersPath, + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + Source: &hr5.ObjectMeta, + BackendGroup: expHR5Groups[1], + Filters: HTTPFilters{ + InvalidFilter: &InvalidHTTPFilter{}, }, }, }, }, - Port: 80, }, - }..., - ) - conf.SSLServers = []VirtualServer{} - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHR5Groups[0], expHR5Groups[1]} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }, - ), + Port: 80, + }, + }..., + ) + conf.SSLServers = []VirtualServer{} + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHR5Groups[0], expHR5Groups[1]} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }), msg: "one http listener with one route with filters", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr6): routeHR6, - }, - }, - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR6): httpsRouteHR6, - }, - ResolvedSecret: &secret1NsName, + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr6): routeHR6, }, - { - Name: "listener-443-2", - Source: listener443_2, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{ - TR1Key: &tlsTR1, - TR2Key: &invalidBackendRefTR2, - }, - ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR6): httpsRouteHR6, }, - { - Name: "listener-444-3", - Source: listener444_3, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{ - TR1Key: &tlsTR1, - TR2Key: &invalidBackendRefTR2, - }, - ResolvedSecret: &secret1NsName, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-2", + Source: listener443_2, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{ + TR1Key: &tlsTR1, + TR2Key: &invalidBackendRefTR2, }, - { - Name: "listener-443-4", - Source: listener443_4, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, - ResolvedSecret: &secret1NsName, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-444-3", + Source: listener444_3, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{ + TR1Key: &tlsTR1, + TR2Key: &invalidBackendRefTR2, }, - }..., - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr6): routeHR6, - graph.CreateRouteKey(httpsHR6): httpsRouteHR6, - } - g.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ - TR1Key: &tlsTR1, - TR2Key: &invalidBackendRefTR2, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/valid", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR6Groups[0], - Source: &hr6.ObjectMeta, - }, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-4", + Source: listener443_4, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + ResolvedSecret: &secret1NsName, + }, + }..., + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr6): routeHR6, + graph.CreateRouteKey(httpsHR6): httpsRouteHR6, + } + g.L4Routes = map[graph.L4RouteKey]*graph.L4Route{ + TR1Key: &tlsTR1, + TR2Key: &invalidBackendRefTR2, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + } + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/valid", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR6Groups[0], + Source: &hr6.ObjectMeta, }, }, }, - Port: 80, }, - }..., - ) - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - PathRules: []PathRule{ - { - Path: "/valid", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR6Groups[0], - Source: &httpsHR6.ObjectMeta, - }, + Port: 80, + }, + }..., + ) + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ + { + Hostname: "foo.example.com", + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + PathRules: []PathRule{ + { + Path: "/valid", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR6Groups[0], + Source: &httpsHR6.ObjectMeta, }, }, }, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, }, - }..., - ) - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHR6Groups[0], expHTTPSHR6Groups[0]} - conf.StreamUpstreams = []Upstream{ - { - Endpoints: fooEndpoints, - Name: "default_secure-app_8443", - }, - } - conf.TLSPassthroughServers = []Layer4VirtualServer{ - { - Hostname: "app.example.com", - UpstreamName: "default_secure-app_8443", - Port: 443, - }, - { - Hostname: "*.example.com", - UpstreamName: "", - Port: 443, - IsDefault: true, + Port: 443, }, { - Hostname: "app.example.com", - UpstreamName: "default_secure-app_8443", - Port: 444, - IsDefault: false, - }, - { - Hostname: "", - UpstreamName: "", - Port: 443, - IsDefault: false, + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, }, - } - return conf - }, - ), + }..., + ) + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHR6Groups[0], expHTTPSHR6Groups[0]} + conf.StreamUpstreams = []Upstream{ + { + Endpoints: fooEndpoints, + Name: "default_secure-app_8443", + }, + } + conf.TLSPassthroughServers = []Layer4VirtualServer{ + { + Hostname: "app.example.com", + UpstreamName: "default_secure-app_8443", + Port: 443, + }, + { + Hostname: "*.example.com", + UpstreamName: "", + Port: 443, + IsDefault: true, + }, + { + Hostname: "app.example.com", + UpstreamName: "default_secure-app_8443", + Port: 444, + IsDefault: false, + }, + { + Hostname: "", + UpstreamName: "", + Port: 443, + IsDefault: false, + }, + } + return conf + }), msg: "one http, one https listener, and three tls listeners with routes with valid and invalid rules", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr7): routeHR7, - }, + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr7): routeHR7, }, - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr7): routeHR7, - } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/valid", - PathType: PathTypeExact, - MatchRules: []MatchRule{ - { - BackendGroup: expHR7Groups[1], - Source: &hr7.ObjectMeta, - }, - }, - }, - { - Path: "/valid", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR7Groups[0], - Source: &hr7.ObjectMeta, - }, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr7): routeHR7, + } + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.HTTPServers = append( + conf.HTTPServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/valid", + PathType: PathTypeExact, + MatchRules: []MatchRule{ + { + BackendGroup: expHR7Groups[1], + Source: &hr7.ObjectMeta, }, }, }, - Port: 80, - }, - }..., - ) - conf.SSLServers = []VirtualServer{} - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHR7Groups[0], expHR7Groups[1]} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }, - ), - msg: "duplicate paths with different types", - }, - { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-443-with-hostname", - Source: listener443WithHostname, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - }, - ResolvedSecret: &secret2NsName, - }, - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - }, - ResolvedSecret: &secret1NsName, - }, - }..., - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - secret2NsName: secret2, - } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - // duplicate match rules since two listeners both match this route's hostname - { - BackendGroup: expHTTPSHR5Groups[0], - Source: &httpsHR5.ObjectMeta, - }, - { - BackendGroup: expHTTPSHR5Groups[0], - Source: &httpsHR5.ObjectMeta, - }, + { + Path: "/valid", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR7Groups[0], + Source: &hr7.ObjectMeta, }, }, }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, - Port: 443, }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }..., - ) - conf.HTTPServers = []VirtualServer{} - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHTTPSHR5Groups[0]} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{ - "ssl_keypair_test_secret-1": { - Cert: []byte("cert-1"), - Key: []byte("privateKey-1"), - }, - "ssl_keypair_test_secret-2": { - Cert: []byte("cert-2"), - Key: []byte("privateKey-2"), + Port: 80, }, - } - return conf - }, - ), - msg: "two https listeners with different hostnames but same route; chooses listener with more specific hostname", + }..., + ) + conf.SSLServers = []VirtualServer{} + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHR7Groups[0], expHR7Groups[1]} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }), + msg: "duplicate paths with different types", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-443", - Source: listener443, + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-443-with-hostname", + Source: listener443WithHostname, Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR8): httpsRouteHR8, + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, }, - ResolvedSecret: &secret1NsName, + ResolvedSecret: &secret2NsName, }, - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR8): httpsRouteHR8, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - } - g.ReferencedCaCertConfigMaps = referencedConfigMaps - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR8Groups[0], - Source: &httpsHR8.ObjectMeta, - }, - { - BackendGroup: expHTTPSHR8Groups[1], - Source: &httpsHR8.ObjectMeta, - }, - }, - }, - }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }..., - ) - conf.HTTPServers = []VirtualServer{} - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHTTPSHR8Groups[0], expHTTPSHR8Groups[1]} - conf.CertBundles = map[CertBundleID]CertBundle{ - "cert_bundle_test_configmap-1": []byte("cert-1"), - } - return conf - }, - ), - msg: "https listener with httproute with backend that has a backend TLS policy attached", - }, - { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-443", + { + Name: "listener-443-1", Source: listener443, Valid: true, Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR9): httpsRouteHR9, + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, }, ResolvedSecret: &secret1NsName, }, - ) - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR9): httpsRouteHR9, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - } - g.ReferencedCaCertConfigMaps = referencedConfigMaps - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR9Groups[0], - Source: &httpsHR9.ObjectMeta, - }, - { - BackendGroup: expHTTPSHR9Groups[1], - Source: &httpsHR9.ObjectMeta, - }, + }..., + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + secret2NsName: secret2, + } + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ + { + Hostname: "example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + // duplicate match rules since two listeners both match this route's hostname + { + BackendGroup: expHTTPSHR5Groups[0], + Source: &httpsHR5.ObjectMeta, + }, + { + BackendGroup: expHTTPSHR5Groups[0], + Source: &httpsHR5.ObjectMeta, }, - }, - }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }..., - ) - conf.HTTPServers = []VirtualServer{} - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHTTPSHR9Groups[0], expHTTPSHR9Groups[1]} - conf.CertBundles = map[CertBundleID]CertBundle{ - "cert_bundle_test_configmap-2": []byte("cert-2"), - } - return conf - }, - ), - msg: "https listener with httproute with backend that has a backend TLS policy with binaryData attached", - }, - { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", - } - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }, - ) - g.NginxProxy = nginxProxy - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - conf.Telemetry = Telemetry{ - Endpoint: "my-otel.svc:4563", - Interval: "5s", - BatchSize: 512, - BatchCount: 4, - ServiceName: "ngf:ns:gw:my-svc", - Ratios: []Ratio{}, - SpanAttributes: []SpanAttribute{}, - } - conf.BaseHTTPConfig = BaseHTTPConfig{HTTP2: false, IPFamily: Dual} - return conf - }, - ), - msg: "NginxProxy with tracing config and http2 disabled", - }, - { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", - } - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }, - ) - g.NginxProxy = &graph.NginxProxy{ - Valid: false, - Source: &ngfAPI.NginxProxy{ - Spec: ngfAPI.NginxProxySpec{ - DisableHTTP2: true, - IPFamily: helpers.GetPointer(ngfAPI.Dual), - Telemetry: &ngfAPI.Telemetry{ - Exporter: &ngfAPI.TelemetryExporter{ - Endpoint: "some-endpoint", }, }, }, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, + Port: 443, }, - } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }, - ), - msg: "invalid NginxProxy", + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }..., + ) + conf.HTTPServers = []VirtualServer{} + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHTTPSHR5Groups[0]} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{ + "ssl_keypair_test_secret-1": { + Cert: []byte("cert-1"), + Key: []byte("privateKey-1"), + }, + "ssl_keypair_test_secret-2": { + Cert: []byte("cert-2"), + Key: []byte("privateKey-2"), + }, + } + return conf + }), + msg: "two https listeners with different hostnames but same route; chooses listener with more specific hostname", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, - }, - }, - { - Name: "listener-443", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHRWithPolicy): l7HTTPSRouteWithPolicy, - }, - ResolvedSecret: &secret1NsName, - }, - }..., - ) - g.Gateway.Policies = []*graph.Policy{gwPolicy1, gwPolicy2} - g.Routes = map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, - graph.CreateRouteKey(httpsHRWithPolicy): l7HTTPSRouteWithPolicy, - } - g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ - secret1NsName: secret1, - } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{ - { - IsDefault: true, - Port: 443, - Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-443", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR8): httpsRouteHR8, }, + ResolvedSecret: &secret1NsName, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR8): httpsRouteHR8, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + } + g.ReferencedCaCertConfigMaps = referencedConfigMaps + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ { - Hostname: "policy.com", + Hostname: "foo.example.com", PathRules: []PathRule{ { Path: "/", PathType: PathTypePrefix, MatchRules: []MatchRule{ { - BackendGroup: expHTTPSHRWithPolicyGroups[0], - Source: &httpsHRWithPolicy.ObjectMeta, + BackendGroup: expHTTPSHR8Groups[0], + Source: &httpsHR8.ObjectMeta, + }, + { + BackendGroup: expHTTPSHR8Groups[1], + Source: &httpsHR8.ObjectMeta, }, }, - Policies: []policies.Policy{hrPolicy2.Source}, }, }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, }, { Hostname: wildcardHostname, SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, Port: 443, - Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, }, - } - conf.HTTPServers = []VirtualServer{ - { - IsDefault: true, - Port: 80, - Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, + }..., + ) + conf.HTTPServers = []VirtualServer{} + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHTTPSHR8Groups[0], expHTTPSHR8Groups[1]} + conf.CertBundles = map[CertBundleID]CertBundle{ + "cert_bundle_test_configmap-1": []byte("cert-1"), + } + return conf + }), + msg: "https listener with httproute with backend that has a backend TLS policy attached", + }, + { + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-443", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR9): httpsRouteHR9, }, + ResolvedSecret: &secret1NsName, + }, + ) + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR9): httpsRouteHR9, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + } + g.ReferencedCaCertConfigMaps = referencedConfigMaps + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.SSLServers = append( + conf.SSLServers, []VirtualServer{ { - Hostname: "policy.com", + Hostname: "foo.example.com", PathRules: []PathRule{ { Path: "/", PathType: PathTypePrefix, MatchRules: []MatchRule{ { - Source: &hrWithPolicy.ObjectMeta, - BackendGroup: expHRWithPolicyGroups[0], + BackendGroup: expHTTPSHR9Groups[0], + Source: &httpsHR9.ObjectMeta, + }, + { + BackendGroup: expHTTPSHR9Groups[1], + Source: &httpsHR9.ObjectMeta, }, }, - Policies: []policies.Policy{hrPolicy1.Source}, }, }, - Port: 80, - Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, }, - } - conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{expHRWithPolicyGroups[0], expHTTPSHRWithPolicyGroups[0]} - return conf - }, - ), - msg: "Simple Gateway and HTTPRoute with policies attached", + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }..., + ) + conf.HTTPServers = []VirtualServer{} + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHTTPSHR9Groups[0], expHTTPSHR9Groups[1]} + conf.CertBundles = map[CertBundleID]CertBundle{ + "cert_bundle_test_configmap-2": []byte("cert-2"), + } + return conf + }), + msg: "https listener with httproute with backend that has a backend TLS policy with binaryData attached", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", - } - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }, - ) - g.NginxProxy = nginxProxyIPv4 - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - conf.BaseHTTPConfig = BaseHTTPConfig{HTTP2: true, IPFamily: IPv4} - return conf - }, - ), - msg: "NginxProxy with IPv4 IPFamily and no routes", + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + } + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }, + ) + g.NginxProxy = nginxProxy + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + conf.Telemetry = Telemetry{ + Endpoint: "my-otel.svc:4563", + Interval: "5s", + BatchSize: 512, + BatchCount: 4, + ServiceName: "ngf:ns:gw:my-svc", + Ratios: []Ratio{}, + SpanAttributes: []SpanAttribute{}, + } + conf.BaseHTTPConfig = BaseHTTPConfig{HTTP2: false, IPFamily: Dual} + return conf + }), + msg: "NginxProxy with tracing config and http2 disabled", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", - } - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + } + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }, + ) + g.NginxProxy = &graph.NginxProxy{ + Valid: false, + Source: &ngfAPI.NginxProxy{ + Spec: ngfAPI.NginxProxySpec{ + DisableHTTP2: true, + IPFamily: helpers.GetPointer(ngfAPI.Dual), + Telemetry: &ngfAPI.Telemetry{ + Exporter: &ngfAPI.TelemetryExporter{ + Endpoint: "some-endpoint", + }, + }, }, - ) - g.NginxProxy = nginxProxyIPv6 - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - conf.BaseHTTPConfig = BaseHTTPConfig{HTTP2: true, IPFamily: IPv6} - return conf - }, - ), - msg: "NginxProxy with IPv6 IPFamily and no routes", + }, + } + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }), + msg: "invalid NginxProxy", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ - Name: "gw", - Namespace: "ns", - } - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Listeners = append( + g.Gateway.Listeners, []*graph.Listener{ + { Name: "listener-80-1", Source: listener80, Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, + }, }, - ) - g.NginxProxy = &graph.NginxProxy{ - Valid: true, - Source: &ngfAPI.NginxProxy{ - Spec: ngfAPI.NginxProxySpec{ - RewriteClientIP: &ngfAPI.RewriteClientIP{ - SetIPRecursively: helpers.GetPointer(true), - TrustedAddresses: []ngfAPI.Address{ - { - Type: ngfAPI.AddressTypeCIDR, - Value: "1.1.1.1/32", - }, + { + Name: "listener-443", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHRWithPolicy): l7HTTPSRouteWithPolicy, + }, + ResolvedSecret: &secret1NsName, + }, + }..., + ) + g.Gateway.Policies = []*graph.Policy{gwPolicy1, gwPolicy2} + g.Routes = map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, + graph.CreateRouteKey(httpsHRWithPolicy): l7HTTPSRouteWithPolicy, + } + g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ + secret1NsName: secret1, + } + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{ + { + IsDefault: true, + Port: 443, + Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, + }, + { + Hostname: "policy.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHRWithPolicyGroups[0], + Source: &httpsHRWithPolicy.ObjectMeta, }, - Mode: helpers.GetPointer(ngfAPI.RewriteClientIPModeProxyProtocol), }, + Policies: []policies.Policy{hrPolicy2.Source}, }, }, - } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - conf.BaseHTTPConfig = BaseHTTPConfig{ - HTTP2: true, - IPFamily: Dual, - RewriteClientIPSettings: RewriteClientIPSettings{ - IPRecursive: true, - TrustedAddresses: []string{"1.1.1.1/32"}, - Mode: RewriteIPModeProxyProtocol, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, + }, + } + conf.HTTPServers = []VirtualServer{ + { + IsDefault: true, + Port: 80, + Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, + }, + { + Hostname: "policy.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + Source: &hrWithPolicy.ObjectMeta, + BackendGroup: expHRWithPolicyGroups[0], + }, + }, + Policies: []policies.Policy{hrPolicy1.Source}, + }, }, - } - return conf - }, - ), + Port: 80, + Policies: []policies.Policy{gwPolicy1.Source, gwPolicy2.Source}, + }, + } + conf.Upstreams = []Upstream{fooUpstream} + conf.BackendGroups = []BackendGroup{expHRWithPolicyGroups[0], expHTTPSHRWithPolicyGroups[0]} + return conf + }), + msg: "Simple Gateway and HTTPRoute with policies attached", + }, + { + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + } + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }, + ) + g.NginxProxy = nginxProxyIPv4 + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + conf.BaseHTTPConfig = BaseHTTPConfig{HTTP2: true, IPFamily: IPv4} + return conf + }), + msg: "NginxProxy with IPv4 IPFamily and no routes", + }, + { + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + } + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }, + ) + g.NginxProxy = nginxProxyIPv6 + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + conf.BaseHTTPConfig = BaseHTTPConfig{HTTP2: true, IPFamily: IPv6} + return conf + }), + msg: "NginxProxy with IPv6 IPFamily and no routes", + }, + { + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.Gateway.Source.ObjectMeta = metav1.ObjectMeta{ + Name: "gw", + Namespace: "ns", + } + g.Gateway.Listeners = append( + g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }, + ) + g.NginxProxy = &graph.NginxProxy{ + Valid: true, + Source: &ngfAPI.NginxProxy{ + Spec: ngfAPI.NginxProxySpec{ + RewriteClientIP: &ngfAPI.RewriteClientIP{ + SetIPRecursively: helpers.GetPointer(true), + TrustedAddresses: []ngfAPI.Address{ + { + Type: ngfAPI.AddressTypeCIDR, + Value: "1.1.1.1/32", + }, + }, + Mode: helpers.GetPointer(ngfAPI.RewriteClientIPModeProxyProtocol), + }, + }, + }, + } + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + conf.BaseHTTPConfig = BaseHTTPConfig{ + HTTP2: true, + IPFamily: Dual, + RewriteClientIPSettings: RewriteClientIPSettings{ + IPRecursive: true, + TrustedAddresses: []string{"1.1.1.1/32"}, + Mode: RewriteIPModeProxyProtocol, + }, + } + return conf + }), msg: "NginxProxy with rewriteClientIP details set", }, { - graph: getModifiedGraph( - func(g *graph.Graph) *graph.Graph { - g.SnippetsFilters = map[types.NamespacedName]*graph.SnippetsFilter{ - client.ObjectKeyFromObject(sf1.Source): sf1, - client.ObjectKeyFromObject(sfNotReferenced.Source): sfNotReferenced, - } + graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { + g.SnippetsFilters = map[types.NamespacedName]*graph.SnippetsFilter{ + client.ObjectKeyFromObject(sf1.Source): sf1, + client.ObjectKeyFromObject(sfNotReferenced.Source): sfNotReferenced, + } - return g - }, - ), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.MainSnippets = []Snippet{ - { - Name: createSnippetName( - ngfAPI.NginxContextMain, - client.ObjectKeyFromObject(sf1.Source), - ), - Contents: "main snippet", - }, - } - conf.BaseHTTPConfig.Snippets = []Snippet{ - { - Name: createSnippetName( - ngfAPI.NginxContextHTTP, - client.ObjectKeyFromObject(sf1.Source), - ), - Contents: "http snippet", - }, - } - conf.HTTPServers = []VirtualServer{} - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return g + }), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.MainSnippets = []Snippet{ + { + Name: createSnippetName( + ngfAPI.NginxContextMain, + client.ObjectKeyFromObject(sf1.Source), + ), + Contents: "main snippet", + }, + } + conf.BaseHTTPConfig.Snippets = []Snippet{ + { + Name: createSnippetName( + ngfAPI.NginxContextHTTP, + client.ObjectKeyFromObject(sf1.Source), + ), + Contents: "http snippet", + }, + } + conf.HTTPServers = []VirtualServer{} + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }, - ), + return conf + }), msg: "SnippetsFilters with main and http snippet", }, } for _, test := range tests { - t.Run( - test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - result := BuildConfiguration( - context.TODO(), - test.graph, - fakeResolver, - 1, - ) + t.Run(test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + result := BuildConfiguration( + context.TODO(), + test.graph, + fakeResolver, + 1, + ) - g.Expect(result.BackendGroups).To(ConsistOf(test.expConf.BackendGroups)) - g.Expect(result.Upstreams).To(ConsistOf(test.expConf.Upstreams)) - g.Expect(result.HTTPServers).To(ConsistOf(test.expConf.HTTPServers)) - g.Expect(result.SSLServers).To(ConsistOf(test.expConf.SSLServers)) - g.Expect(result.TLSPassthroughServers).To(ConsistOf(test.expConf.TLSPassthroughServers)) - g.Expect(result.SSLKeyPairs).To(Equal(test.expConf.SSLKeyPairs)) - g.Expect(result.Version).To(Equal(1)) - g.Expect(result.CertBundles).To(Equal(test.expConf.CertBundles)) - g.Expect(result.Telemetry).To(Equal(test.expConf.Telemetry)) - g.Expect(result.BaseHTTPConfig).To(Equal(test.expConf.BaseHTTPConfig)) - }, - ) + g.Expect(result.BackendGroups).To(ConsistOf(test.expConf.BackendGroups)) + g.Expect(result.Upstreams).To(ConsistOf(test.expConf.Upstreams)) + g.Expect(result.HTTPServers).To(ConsistOf(test.expConf.HTTPServers)) + g.Expect(result.SSLServers).To(ConsistOf(test.expConf.SSLServers)) + g.Expect(result.TLSPassthroughServers).To(ConsistOf(test.expConf.TLSPassthroughServers)) + g.Expect(result.SSLKeyPairs).To(Equal(test.expConf.SSLKeyPairs)) + g.Expect(result.Version).To(Equal(1)) + g.Expect(result.CertBundles).To(Equal(test.expConf.CertBundles)) + g.Expect(result.Telemetry).To(Equal(test.expConf.Telemetry)) + g.Expect(result.BaseHTTPConfig).To(Equal(test.expConf.BaseHTTPConfig)) + }) } } @@ -2576,14 +2478,12 @@ func TestGetPath(t *testing.T) { } for _, test := range tests { - t.Run( - test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := getPath(test.path) - g.Expect(result).To(Equal(test.expected)) - }, - ) + t.Run(test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + result := getPath(test.path) + g.Expect(result).To(Equal(test.expected)) + }) } } @@ -2818,15 +2718,13 @@ func TestCreateFilters(t *testing.T) { } for _, test := range tests { - t.Run( - test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := createHTTPFilters(test.filters) + t.Run(test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + result := createHTTPFilters(test.filters) - g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) - }, - ) + g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) + }) } } @@ -2858,14 +2756,12 @@ func TestGetListenerHostname(t *testing.T) { } for _, test := range tests { - t.Run( - test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := getListenerHostname(test.hostname) - g.Expect(result).To(Equal(test.expected)) - }, - ) + t.Run(test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + result := getListenerHostname(test.hostname) + g.Expect(result).To(Equal(test.expected)) + }) } } @@ -2873,13 +2769,11 @@ func refsToValidRules(refs ...[]graph.BackendRef) []graph.RouteRule { rules := make([]graph.RouteRule, 0, len(refs)) for _, ref := range refs { - rules = append( - rules, graph.RouteRule{ - ValidMatches: true, - Filters: graph.RouteRuleFilters{Valid: true}, - BackendRefs: ref, - }, - ) + rules = append(rules, graph.RouteRule{ + ValidMatches: true, + Filters: graph.RouteRuleFilters{Valid: true}, + BackendRefs: ref, + }) } return rules @@ -2968,13 +2862,11 @@ func TestBuildUpstreams(t *testing.T) { createBackendRefs := func(serviceNames ...string) []graph.BackendRef { var backends []graph.BackendRef for _, name := range serviceNames { - backends = append( - backends, graph.BackendRef{ - SvcNsName: types.NamespacedName{Namespace: "test", Name: name}, - ServicePort: apiv1.ServicePort{Port: 80}, - Valid: name != "", - }, - ) + backends = append(backends, graph.BackendRef{ + SvcNsName: types.NamespacedName{Namespace: "test", Name: name}, + ServicePort: apiv1.ServicePort{Port: 80}, + Valid: name != "", + }) } return backends } @@ -3283,14 +3175,12 @@ func TestHostnameMoreSpecific(t *testing.T) { } for _, tc := range tests { - t.Run( - tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - g.Expect(listenerHostnameMoreSpecific(tc.host1, tc.host2)).To(Equal(tc.host1Wins)) - }, - ) + g.Expect(listenerHostnameMoreSpecific(tc.host1, tc.host2)).To(Equal(tc.host1Wins)) + }) } } @@ -3363,13 +3253,11 @@ func TestConvertBackendTLS(t *testing.T) { } for _, tc := range tests { - t.Run( - tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - g.Expect(convertBackendTLS(tc.btp)).To(Equal(tc.expected)) - }, - ) + t.Run(tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + g.Expect(convertBackendTLS(tc.btp)).To(Equal(tc.expected)) + }) } } @@ -3600,19 +3488,17 @@ func TestBuildTelemetry(t *testing.T) { } for _, tc := range tests { - t.Run( - tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - tel := buildTelemetry(tc.g) - sort.Slice( - tel.Ratios, func(i, j int) bool { - return tel.Ratios[i].Value < tel.Ratios[j].Value - }, - ) - g.Expect(tel).To(Equal(tc.expTelemetry)) - }, - ) + t.Run(tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + tel := buildTelemetry(tc.g) + sort.Slice( + tel.Ratios, func(i, j int) bool { + return tel.Ratios[i].Value < tel.Ratios[j].Value + }, + ) + g.Expect(tel).To(Equal(tc.expTelemetry)) + }) } } @@ -3681,18 +3567,16 @@ func TestBuildPolicies(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - pols := buildPolicies(test.policies) - g.Expect(pols).To(HaveLen(len(test.expPolicies))) - for _, pol := range pols { - g.Expect(test.expPolicies).To(ContainElement(pol.GetName())) - } - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + pols := buildPolicies(test.policies) + g.Expect(pols).To(HaveLen(len(test.expPolicies))) + for _, pol := range pols { + g.Expect(test.expPolicies).To(ContainElement(pol.GetName())) + } + }) } } @@ -3726,13 +3610,11 @@ func TestGetAllowedAddressType(t *testing.T) { } for _, tc := range test { - t.Run( - tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - g.Expect(getAllowedAddressType(tc.ipFamily)).To(Equal(tc.expected)) - }, - ) + t.Run(tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + g.Expect(getAllowedAddressType(tc.ipFamily)).To(Equal(tc.expected)) + }) } } @@ -4124,14 +4006,12 @@ func TestBuildRewriteIPSettings(t *testing.T) { } for _, tc := range tests { - t.Run( - tc.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - baseConfig := buildBaseHTTPConfig(tc.g) - g.Expect(baseConfig.RewriteClientIPSettings).To(Equal(tc.expRewriteIPSettings)) - }, - ) + t.Run(tc.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + baseConfig := buildBaseHTTPConfig(tc.g) + g.Expect(baseConfig.RewriteClientIPSettings).To(Equal(tc.expRewriteIPSettings)) + }) } } @@ -4296,14 +4176,12 @@ func TestBuildSnippetForContext(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() + t.Run(test.name, func(t *testing.T) { + t.Parallel() - g := NewWithT(t) - snippets := buildSnippetsForContext(test.snippetsFilters, test.ctx) - g.Expect(snippets).To(ConsistOf(test.expSnippets)) - }, - ) + g := NewWithT(t) + snippets := buildSnippetsForContext(test.snippetsFilters, test.ctx) + g.Expect(snippets).To(ConsistOf(test.expSnippets)) + }) } } diff --git a/internal/mode/static/state/graph/backend_refs_test.go b/internal/mode/static/state/graph/backend_refs_test.go index fdd96b4d2c..e061ae7d60 100644 --- a/internal/mode/static/state/graph/backend_refs_test.go +++ b/internal/mode/static/state/graph/backend_refs_test.go @@ -72,12 +72,10 @@ func TestValidateRouteBackendRef(t *testing.T) { { name: "invalid base ref", ref: RouteBackendRef{ - BackendRef: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Kind = helpers.GetPointer[gatewayv1.Kind]("NotService") - return backend - }, - ), + BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Kind = helpers.GetPointer[gatewayv1.Kind]("NotService") + return backend + }), }, expectedValid: false, expectedCondition: staticConds.NewRouteBackendRefInvalidKind( @@ -87,23 +85,16 @@ func TestValidateRouteBackendRef(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - alwaysTrueRefGrantResolver := func(_ toResource) bool { return true } - - valid, cond := validateRouteBackendRef( - test.ref, - "test", - alwaysTrueRefGrantResolver, - field.NewPath("test"), - ) - - g.Expect(valid).To(Equal(test.expectedValid)) - g.Expect(cond).To(Equal(test.expectedCondition)) - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + alwaysTrueRefGrantResolver := func(_ toResource) bool { return true } + + valid, cond := validateRouteBackendRef(test.ref, "test", alwaysTrueRefGrantResolver, field.NewPath("test")) + + g.Expect(valid).To(Equal(test.expectedValid)) + g.Expect(cond).To(Equal(test.expectedCondition)) + }) } } @@ -127,45 +118,37 @@ func TestValidateBackendRef(t *testing.T) { }, { name: "normal case with implicit namespace", - ref: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Namespace = nil - return backend - }, - ), + ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Namespace = nil + return backend + }), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: true, }, { name: "normal case with implicit kind Service", - ref: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Kind = nil - return backend - }, - ), + ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Kind = nil + return backend + }), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: true, }, { name: "normal case with backend ref allowed by reference grant", - ref: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Namespace = helpers.GetPointer[gatewayv1.Namespace]("cross-ns") - return backend - }, - ), + ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Namespace = helpers.GetPointer[gatewayv1.Namespace]("cross-ns") + return backend + }), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: true, }, { name: "invalid group", - ref: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Group = helpers.GetPointer[gatewayv1.Group]("invalid") - return backend - }, - ), + ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Group = helpers.GetPointer[gatewayv1.Group]("invalid") + return backend + }), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: false, expectedCondition: staticConds.NewRouteBackendRefInvalidKind( @@ -174,12 +157,10 @@ func TestValidateBackendRef(t *testing.T) { }, { name: "not a service kind", - ref: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Kind = helpers.GetPointer[gatewayv1.Kind]("NotService") - return backend - }, - ), + ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Kind = helpers.GetPointer[gatewayv1.Kind]("NotService") + return backend + }), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: false, expectedCondition: staticConds.NewRouteBackendRefInvalidKind( @@ -188,12 +169,10 @@ func TestValidateBackendRef(t *testing.T) { }, { name: "backend ref not allowed by reference grant", - ref: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Namespace = helpers.GetPointer[gatewayv1.Namespace]("invalid") - return backend - }, - ), + ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Namespace = helpers.GetPointer[gatewayv1.Namespace]("invalid") + return backend + }), refGrantResolver: alwaysFalseRefGrantResolver, expectedValid: false, expectedCondition: staticConds.NewRouteBackendRefRefNotPermitted( @@ -202,12 +181,10 @@ func TestValidateBackendRef(t *testing.T) { }, { name: "invalid weight", - ref: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Weight = helpers.GetPointer[int32](-1) - return backend - }, - ), + ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Weight = helpers.GetPointer[int32](-1) + return backend + }), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: false, expectedCondition: staticConds.NewRouteBackendRefUnsupportedValue( @@ -216,12 +193,10 @@ func TestValidateBackendRef(t *testing.T) { }, { name: "nil port", - ref: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Port = nil - return backend - }, - ), + ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Port = nil + return backend + }), refGrantResolver: alwaysTrueRefGrantResolver, expectedValid: false, expectedCondition: staticConds.NewRouteBackendRefUnsupportedValue( @@ -231,17 +206,15 @@ func TestValidateBackendRef(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - valid, cond := validateBackendRef(test.ref, "test", test.refGrantResolver, field.NewPath("test")) + valid, cond := validateBackendRef(test.ref, "test", test.refGrantResolver, field.NewPath("test")) - g.Expect(valid).To(Equal(test.expectedValid)) - g.Expect(cond).To(Equal(test.expectedCondition)) - }, - ) + g.Expect(valid).To(Equal(test.expectedValid)) + g.Expect(cond).To(Equal(test.expectedCondition)) + }) } } @@ -303,12 +276,10 @@ func TestGetIPFamilyAndPortFromRef(t *testing.T) { }, { name: "service does not exist", - ref: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Name = "does-not-exist" - return backend - }, - ), + ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Name = "does-not-exist" + return backend + }), expErr: true, expServicePort: v1.ServicePort{}, expSvcIPFamily: []v1.IPFamily{}, @@ -316,12 +287,10 @@ func TestGetIPFamilyAndPortFromRef(t *testing.T) { }, { 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 - }, - ), + ref: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Port = helpers.GetPointer[gatewayv1.PortNumber](504) + return backend + }), expErr: true, expServicePort: v1.ServicePort{}, expSvcIPFamily: []v1.IPFamily{}, @@ -337,18 +306,16 @@ func TestGetIPFamilyAndPortFromRef(t *testing.T) { refPath := field.NewPath("test") for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - svcIPFamily, servicePort, err := getIPFamilyAndPortFromRef(test.ref, test.svcNsName, services, refPath) + svcIPFamily, servicePort, err := getIPFamilyAndPortFromRef(test.ref, test.svcNsName, services, refPath) - g.Expect(err != nil).To(Equal(test.expErr)) - g.Expect(servicePort).To(Equal(test.expServicePort)) - g.Expect(svcIPFamily).To(Equal(test.expSvcIPFamily)) - }, - ) + g.Expect(err != nil).To(Equal(test.expErr)) + g.Expect(servicePort).To(Equal(test.expServicePort)) + g.Expect(svcIPFamily).To(Equal(test.expSvcIPFamily)) + }) } } @@ -417,22 +384,22 @@ func TestVerifyIPFamily(t *testing.T) { } for _, test := range test { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - err := verifyIPFamily(test.npCfg, test.svcIPFamily) - if test.expErr != nil { - g.Expect(err).To(Equal(test.expErr)) - } else { - g.Expect(err).ToNot(HaveOccurred()) - } - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + err := verifyIPFamily(test.npCfg, test.svcIPFamily) + if test.expErr != nil { + g.Expect(err).To(Equal(test.expErr)) + } else { + g.Expect(err).ToNot(HaveOccurred()) + } + }) } } func TestAddBackendRefsToRulesTest(t *testing.T) { + t.Parallel() + sectionNameRefs := []ParentRef{ { Idx: 0, @@ -636,13 +603,12 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { btp1 := getBtp("btp1", "svc1", "test1") btp2 := getBtp("btp2", "svc2", "test2") btp3 := getBtp("btp1", "svc1", "test") - btp3.Conditions = append( - btp3.Conditions, conditions.Condition{ - Type: "Accepted", - Status: "True", - Reason: "Accepted", - Message: "Policy is accepted", - }, + btp3.Conditions = append(btp3.Conditions, conditions.Condition{ + Type: "Accepted", + Status: "True", + Reason: "Accepted", + Message: "Policy is accepted", + }, ) tests := []struct { @@ -779,21 +745,21 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - g := NewWithT(t) - resolver := newReferenceGrantResolver(nil) - addBackendRefsToRules(test.route, resolver, services, test.policies, nil) - - var actual []BackendRef - if test.route.Spec.Rules != nil { - actual = test.route.Spec.Rules[0].BackendRefs - } - - g.Expect(helpers.Diff(test.expectedBackendRefs, actual)).To(BeEmpty()) - g.Expect(test.route.Conditions).To(Equal(test.expectedConditions)) - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + g := NewWithT(t) + resolver := newReferenceGrantResolver(nil) + addBackendRefsToRules(test.route, resolver, services, test.policies, nil) + + var actual []BackendRef + if test.route.Spec.Rules != nil { + actual = test.route.Spec.Rules[0].BackendRefs + } + + g.Expect(helpers.Diff(test.expectedBackendRefs, actual)).To(BeEmpty()) + g.Expect(test.route.Conditions).To(Equal(test.expectedConditions)) + }) } } @@ -899,12 +865,10 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Weight = nil - return backend - }, - ), + BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Weight = nil + return backend + }), }, expectedBackend: BackendRef{ SvcNsName: svc1NamespacedName, @@ -918,12 +882,10 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Weight = helpers.GetPointer[int32](-1) - return backend - }, - ), + BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Weight = helpers.GetPointer[int32](-1) + return backend + }), }, expectedBackend: BackendRef{ SvcNsName: types.NamespacedName{}, @@ -941,12 +903,10 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Kind = helpers.GetPointer[gatewayv1.Kind]("NotService") - return backend - }, - ), + BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Kind = helpers.GetPointer[gatewayv1.Kind]("NotService") + return backend + }), }, expectedBackend: BackendRef{ SvcNsName: types.NamespacedName{}, @@ -964,12 +924,10 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Name = "not-exist" - return backend - }, - ), + BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Name = "not-exist" + return backend + }), }, expectedBackend: BackendRef{ Weight: 5, @@ -987,12 +945,10 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Name = "service2" - return backend - }, - ), + BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Name = "service2" + return backend + }), }, expectedBackend: BackendRef{ SvcNsName: svc2NamespacedName, @@ -1013,12 +969,10 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Name = "service2" - return backend - }, - ), + BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Name = "service2" + return backend + }), }, expectedBackend: BackendRef{ SvcNsName: svc2NamespacedName, @@ -1033,12 +987,10 @@ func TestCreateBackend(t *testing.T) { }, { ref: gatewayv1.HTTPBackendRef{ - BackendRef: getModifiedRef( - func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { - backend.Name = "service3" - return backend - }, - ), + BackendRef: getModifiedRef(func(backend gatewayv1.BackendRef) gatewayv1.BackendRef { + backend.Name = "service3" + return backend + }), }, expectedBackend: BackendRef{ SvcNsName: svc3NamespacedName, @@ -1071,34 +1023,32 @@ func TestCreateBackend(t *testing.T) { refPath := field.NewPath("test") for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - alwaysTrueRefGrantResolver := func(_ toResource) bool { return true } - - rbr := RouteBackendRef{ - test.ref.BackendRef, - []any{}, - } - backend, cond := createBackendRef( - rbr, - sourceNamespace, - alwaysTrueRefGrantResolver, - services, - refPath, - policies, - test.nginxProxy, - ) - - g.Expect(helpers.Diff(test.expectedBackend, backend)).To(BeEmpty()) - g.Expect(cond).To(Equal(test.expectedCondition)) - - servicePortRef := backend.ServicePortReference() - g.Expect(servicePortRef).To(Equal(test.expectedServicePortReference)) - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + alwaysTrueRefGrantResolver := func(_ toResource) bool { return true } + + rbr := RouteBackendRef{ + test.ref.BackendRef, + []any{}, + } + backend, cond := createBackendRef( + rbr, + sourceNamespace, + alwaysTrueRefGrantResolver, + services, + refPath, + policies, + test.nginxProxy, + ) + + g.Expect(helpers.Diff(test.expectedBackend, backend)).To(BeEmpty()) + g.Expect(cond).To(Equal(test.expectedCondition)) + + servicePortRef := backend.ServicePortReference() + g.Expect(servicePortRef).To(Equal(test.expectedServicePortReference)) + }) } } @@ -1225,16 +1175,14 @@ func TestValidateBackendTLSPolicyMatchingAllBackends(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + g := NewWithT(t) + t.Parallel() - cond := validateBackendTLSPolicyMatchingAllBackends(test.backendRefs) + cond := validateBackendTLSPolicyMatchingAllBackends(test.backendRefs) - g.Expect(cond).To(Equal(test.expectedCondition)) - }, - ) + g.Expect(cond).To(Equal(test.expectedCondition)) + }) } } @@ -1304,22 +1252,15 @@ func TestFindBackendTLSPolicyForService(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - btp, err := findBackendTLSPolicyForService( - test.backendTLSPolicies, - ref.Namespace, - string(ref.Name), - "test", - ) - - g.Expect(btp.Source.Name).To(Equal(test.expectedBtpName)) - g.Expect(err).ToNot(HaveOccurred()) - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + btp, err := findBackendTLSPolicyForService(test.backendTLSPolicies, ref.Namespace, string(ref.Name), "test") + + g.Expect(btp.Source.Name).To(Equal(test.expectedBtpName)) + g.Expect(err).ToNot(HaveOccurred()) + }) } } @@ -1346,13 +1287,11 @@ func TestGetRefGrantFromResourceForRoute(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - g.Expect(getRefGrantFromResourceForRoute(test.routeType, test.ns)).To(Equal(test.expFromResource)) - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + g.Expect(getRefGrantFromResourceForRoute(test.routeType, test.ns)).To(Equal(test.expFromResource)) + }) } } diff --git a/internal/mode/static/state/graph/common_filter.go b/internal/mode/static/state/graph/common_filter.go index 3a9b25b5d9..070d31b76a 100644 --- a/internal/mode/static/state/graph/common_filter.go +++ b/internal/mode/static/state/graph/common_filter.go @@ -76,18 +76,16 @@ func convertHTTPRouteFilters(filters []v1.HTTPRouteFilter) []Filter { routeFilters := make([]Filter, 0, len(filters)) for _, filter := range filters { - routeFilters = append( - routeFilters, Filter{ - RouteType: RouteTypeHTTP, - FilterType: FilterType(filter.Type), - RequestHeaderModifier: filter.RequestHeaderModifier, - ResponseHeaderModifier: filter.ResponseHeaderModifier, - RequestRedirect: filter.RequestRedirect, - URLRewrite: filter.URLRewrite, - RequestMirror: filter.RequestMirror, - ExtensionRef: filter.ExtensionRef, - }, - ) + routeFilters = append(routeFilters, Filter{ + RouteType: RouteTypeHTTP, + FilterType: FilterType(filter.Type), + RequestHeaderModifier: filter.RequestHeaderModifier, + ResponseHeaderModifier: filter.ResponseHeaderModifier, + RequestRedirect: filter.RequestRedirect, + URLRewrite: filter.URLRewrite, + RequestMirror: filter.RequestMirror, + ExtensionRef: filter.ExtensionRef, + }) } return routeFilters @@ -97,16 +95,14 @@ func convertGRPCRouteFilters(filters []v1.GRPCRouteFilter) []Filter { routeFilters := make([]Filter, 0, len(filters)) for _, filter := range filters { - routeFilters = append( - routeFilters, Filter{ - RouteType: RouteTypeGRPC, - FilterType: FilterType(filter.Type), - RequestHeaderModifier: filter.RequestHeaderModifier, - ResponseHeaderModifier: filter.ResponseHeaderModifier, - RequestMirror: filter.RequestMirror, - ExtensionRef: filter.ExtensionRef, - }, - ) + routeFilters = append(routeFilters, Filter{ + RouteType: RouteTypeGRPC, + FilterType: FilterType(filter.Type), + RequestHeaderModifier: filter.RequestHeaderModifier, + ResponseHeaderModifier: filter.ResponseHeaderModifier, + RequestMirror: filter.RequestMirror, + ExtensionRef: filter.ExtensionRef, + }) } return routeFilters @@ -243,11 +239,10 @@ func validateFilterHeaderModifierFields( var allErrs field.ErrorList // Ensure that the header names are case-insensitive unique - allErrs = append( - allErrs, validateRequestHeadersCaseInsensitiveUnique( - headerModifier.Add, - headerModifierPath.Child(add), - )..., + allErrs = append(allErrs, validateRequestHeadersCaseInsensitiveUnique( + headerModifier.Add, + headerModifierPath.Child(add), + )..., ) allErrs = append( allErrs, validateRequestHeadersCaseInsensitiveUnique( diff --git a/internal/mode/static/state/graph/common_filter_test.go b/internal/mode/static/state/graph/common_filter_test.go index 02412beb9b..87310977c1 100644 --- a/internal/mode/static/state/graph/common_filter_test.go +++ b/internal/mode/static/state/graph/common_filter_test.go @@ -122,15 +122,13 @@ func TestValidateFilter(t *testing.T) { filterPath := field.NewPath("test") for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() + t.Run(test.name, func(t *testing.T) { + t.Parallel() - g := NewWithT(t) - allErrs := validateFilter(&validationfakes.FakeHTTPFieldsValidator{}, test.filter, filterPath) - g.Expect(allErrs).To(HaveLen(test.expectErrCount)) - }, - ) + g := NewWithT(t) + allErrs := validateFilter(&validationfakes.FakeHTTPFieldsValidator{}, test.filter, filterPath) + g.Expect(allErrs).To(HaveLen(test.expectErrCount)) + }) } } @@ -304,17 +302,15 @@ func TestValidateFilterResponseHeaderModifier(t *testing.T) { filterPath := field.NewPath("test") for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - allErrs := validateFilterResponseHeaderModifier( - test.validator, test.filter.ResponseHeaderModifier, filterPath, - ) - g.Expect(allErrs).To(HaveLen(test.expectErrCount)) - }, - ) + allErrs := validateFilterResponseHeaderModifier( + test.validator, test.filter.ResponseHeaderModifier, filterPath, + ) + g.Expect(allErrs).To(HaveLen(test.expectErrCount)) + }) } } @@ -454,17 +450,15 @@ func TestValidateFilterRequestHeaderModifier(t *testing.T) { filterPath := field.NewPath("test") for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - allErrs := validateFilterHeaderModifier( - test.validator, test.filter.RequestHeaderModifier, filterPath, - ) - g.Expect(allErrs).To(HaveLen(test.expectErrCount)) - }, - ) + allErrs := validateFilterHeaderModifier( + test.validator, test.filter.RequestHeaderModifier, filterPath, + ) + g.Expect(allErrs).To(HaveLen(test.expectErrCount)) + }) } } @@ -548,15 +542,13 @@ func TestConvertGRPCFilters(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - convertedFilters := convertGRPCRouteFilters(test.grpcFilters) - g.Expect(convertedFilters).To(Equal(test.expFilters)) - }, - ) + convertedFilters := convertGRPCRouteFilters(test.grpcFilters) + g.Expect(convertedFilters).To(Equal(test.expFilters)) + }) } } @@ -658,14 +650,12 @@ func TestConvertHTTPFilters(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - convertedFilters := convertHTTPRouteFilters(test.httpFilters) - g.Expect(convertedFilters).To(Equal(test.expFilters)) - }, - ) + convertedFilters := convertHTTPRouteFilters(test.httpFilters) + g.Expect(convertedFilters).To(Equal(test.expFilters)) + }) } } diff --git a/internal/mode/static/state/graph/extension_ref_filter_test.go b/internal/mode/static/state/graph/extension_ref_filter_test.go index 5f5821e147..780188be31 100644 --- a/internal/mode/static/state/graph/extension_ref_filter_test.go +++ b/internal/mode/static/state/graph/extension_ref_filter_test.go @@ -86,22 +86,20 @@ func TestValidateExtensionRefFilter(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() + t.Run(test.name, func(t *testing.T) { + t.Parallel() - g := NewWithT(t) + g := NewWithT(t) - errs := validateExtensionRefFilter(test.ref, testPath) - g.Expect(errs).To(HaveLen(test.expErrCount)) + errs := validateExtensionRefFilter(test.ref, testPath) + g.Expect(errs).To(HaveLen(test.expErrCount)) - if len(test.errSubString) > 0 { - aggregateErrStr := errs.ToAggregate().Error() - for _, ss := range test.errSubString { - g.Expect(aggregateErrStr).To(ContainSubstring(ss)) - } + if len(test.errSubString) > 0 { + aggregateErrStr := errs.ToAggregate().Error() + for _, ss := range test.errSubString { + g.Expect(aggregateErrStr).To(ContainSubstring(ss)) } - }, - ) + } + }) } } diff --git a/internal/mode/static/state/graph/graph_test.go b/internal/mode/static/state/graph/graph_test.go index 23fc00167e..1fbc26ef7c 100644 --- a/internal/mode/static/state/graph/graph_test.go +++ b/internal/mode/static/state/graph/graph_test.go @@ -956,30 +956,28 @@ func TestBuildGraph(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - g := NewWithT(t) - - // The diffs get very large so the format max length will make sure the output doesn't get truncated. - format.MaxLength = 10000000 - - fakePolicyValidator := &validationfakes.FakePolicyValidator{} - - result := BuildGraph( - test.store, - controllerName, - gcName, - validation.Validators{ - HTTPFieldsValidator: &validationfakes.FakeHTTPFieldsValidator{}, - GenericValidator: &validationfakes.FakeGenericValidator{}, - PolicyValidator: fakePolicyValidator, - }, - protectedPorts, - ) + t.Run(test.name, func(t *testing.T) { + g := NewWithT(t) + + // The diffs get very large so the format max length will make sure the output doesn't get truncated. + format.MaxLength = 10000000 + + fakePolicyValidator := &validationfakes.FakePolicyValidator{} + + result := BuildGraph( + test.store, + controllerName, + gcName, + validation.Validators{ + HTTPFieldsValidator: &validationfakes.FakeHTTPFieldsValidator{}, + GenericValidator: &validationfakes.FakeGenericValidator{}, + PolicyValidator: fakePolicyValidator, + }, + protectedPorts, + ) - g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) - }, - ) + g.Expect(helpers.Diff(test.expected, result)).To(BeEmpty()) + }) } } @@ -1276,15 +1274,13 @@ func TestIsReferenced(t *testing.T) { }, } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + g := NewWithT(t) - test.graph.GatewayClass = test.gc - result := test.graph.IsReferenced(test.resource, client.ObjectKeyFromObject(test.resource)) - g.Expect(result).To(Equal(test.expected)) - }, - ) + test.graph.GatewayClass = test.gc + result := test.graph.IsReferenced(test.resource, client.ObjectKeyFromObject(test.resource)) + g.Expect(result).To(Equal(test.expected)) + }) } } @@ -1410,24 +1406,20 @@ func TestIsNGFPolicyRelevant(t *testing.T) { }, { name: "irrelevant; policy references a Gateway, but the graph's Gateway is nil", - graph: getModifiedGraph( - func(g *Graph) *Graph { - g.Gateway = nil - return g - }, - ), + graph: getModifiedGraph(func(g *Graph) *Graph { + g.Gateway = nil + return g + }), policy: getPolicy(createTestRef(kinds.Gateway, gatewayv1.GroupName, "diff")), nsname: types.NamespacedName{Namespace: "test", Name: "nil-gw"}, expRelevant: false, }, { name: "irrelevant; policy references a Gateway, but the graph's Gateway.Source is nil", - graph: getModifiedGraph( - func(g *Graph) *Graph { - g.Gateway.Source = nil - return g - }, - ), + graph: getModifiedGraph(func(g *Graph) *Graph { + g.Gateway.Source = nil + return g + }), policy: getPolicy(createTestRef(kinds.Gateway, gatewayv1.GroupName, "diff")), nsname: types.NamespacedName{Namespace: "test", Name: "nil-gw-source"}, expRelevant: false, @@ -1435,15 +1427,13 @@ func TestIsNGFPolicyRelevant(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - relevant := test.graph.IsNGFPolicyRelevant(test.policy, policyGVK, test.nsname) - g.Expect(relevant).To(Equal(test.expRelevant)) - }, - ) + relevant := test.graph.IsNGFPolicyRelevant(test.policy, policyGVK, test.nsname) + g.Expect(relevant).To(Equal(test.expRelevant)) + }) } } diff --git a/internal/mode/static/state/graph/grpcroute.go b/internal/mode/static/state/graph/grpcroute.go index 56aa1cdabf..4d4ed05b75 100644 --- a/internal/mode/static/state/graph/grpcroute.go +++ b/internal/mode/static/state/graph/grpcroute.go @@ -195,12 +195,10 @@ func convertGRPCMatches(grpcMatches []v1.GRPCRouteMatch) []v1.HTTPRouteMatch { var hm v1.HTTPRouteMatch hmHeaders := make([]v1.HTTPHeaderMatch, 0, len(gm.Headers)) for _, head := range gm.Headers { - hmHeaders = append( - hmHeaders, v1.HTTPHeaderMatch{ - Name: v1.HTTPHeaderName(head.Name), - Value: head.Value, - }, - ) + hmHeaders = append(hmHeaders, v1.HTTPHeaderMatch{ + Name: v1.HTTPHeaderName(head.Name), + Value: head.Value, + }) } hm.Headers = hmHeaders diff --git a/internal/mode/static/state/graph/grpcroute_test.go b/internal/mode/static/state/graph/grpcroute_test.go index 1ef96287b6..643a33f5d9 100644 --- a/internal/mode/static/state/graph/grpcroute_test.go +++ b/internal/mode/static/state/graph/grpcroute_test.go @@ -1009,19 +1009,17 @@ func TestBuildGRPCRoute(t *testing.T) { gatewayNsNames := []types.NamespacedName{gatewayNsName} for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ - {Namespace: "test", Name: "sf"}: {Valid: true}, - } + snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ + {Namespace: "test", Name: "sf"}: {Valid: true}, + } - route := buildGRPCRoute(test.validator, test.gr, gatewayNsNames, test.http2disabled, snippetsFilters) - g.Expect(helpers.Diff(test.expected, route)).To(BeEmpty()) - }, - ) + route := buildGRPCRoute(test.validator, test.gr, gatewayNsNames, test.http2disabled, snippetsFilters) + g.Expect(helpers.Diff(test.expected, route)).To(BeEmpty()) + }) } } @@ -1088,14 +1086,12 @@ func TestConvertGRPCMatches(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - httpMatches := convertGRPCMatches(test.methodMatches) - g.Expect(helpers.Diff(test.expected, httpMatches)).To(BeEmpty()) - }, - ) + httpMatches := convertGRPCMatches(test.methodMatches) + g.Expect(helpers.Diff(test.expected, httpMatches)).To(BeEmpty()) + }) } } diff --git a/internal/mode/static/state/graph/httproute_test.go b/internal/mode/static/state/graph/httproute_test.go index 040ca92ba4..3a7efb8efe 100644 --- a/internal/mode/static/state/graph/httproute_test.go +++ b/internal/mode/static/state/graph/httproute_test.go @@ -42,18 +42,16 @@ func createHTTPRoute( if path == emptyPathValue { pathValue = nil } - rules = append( - rules, gatewayv1.HTTPRouteRule{ - Matches: []gatewayv1.HTTPRouteMatch{ - { - Path: &gatewayv1.HTTPPathMatch{ - Type: pathType, - Value: pathValue, - }, + rules = append(rules, gatewayv1.HTTPRouteRule{ + Matches: []gatewayv1.HTTPRouteMatch{ + { + Path: &gatewayv1.HTTPPathMatch{ + Type: pathType, + Value: pathValue, }, }, }, - ) + }) } return &gatewayv1.HTTPRoute{ @@ -203,32 +201,30 @@ func TestBuildHTTPRoutes(t *testing.T) { validator := &validationfakes.FakeHTTPFieldsValidator{} for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ - client.ObjectKeyFromObject(sf): { - Source: sf, - Valid: true, - Snippets: map[ngfAPI.NginxContext]string{ - ngfAPI.NginxContextHTTP: "http snippet", - }, + snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ + client.ObjectKeyFromObject(sf): { + Source: sf, + Valid: true, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTP: "http snippet", }, - } + }, + } - routes := buildRoutesForGateways( - validator, - hrRoutes, - map[types.NamespacedName]*gatewayv1.GRPCRoute{}, - test.gwNsNames, - nil, - snippetsFilters, - ) - g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) - }, - ) + routes := buildRoutesForGateways( + validator, + hrRoutes, + map[types.NamespacedName]*gatewayv1.GRPCRoute{}, + test.gwNsNames, + nil, + snippetsFilters, + ) + g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) + }) } } @@ -900,19 +896,17 @@ func TestBuildHTTPRoute(t *testing.T) { gatewayNsNames := []types.NamespacedName{gatewayNsName} for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ - {Namespace: "test", Name: "sf"}: {Valid: true}, - } + snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ + {Namespace: "test", Name: "sf"}: {Valid: true}, + } - route := buildHTTPRoute(test.validator, test.hr, gatewayNsNames, snippetsFilters) - g.Expect(helpers.Diff(test.expected, route)).To(BeEmpty()) - }, - ) + route := buildHTTPRoute(test.validator, test.hr, gatewayNsNames, snippetsFilters) + g.Expect(helpers.Diff(test.expected, route)).To(BeEmpty()) + }) } } @@ -1172,14 +1166,12 @@ func TestValidateMatch(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - allErrs := validateMatch(test.validator, test.match, field.NewPath("test")) - g.Expect(allErrs).To(HaveLen(test.expectErrCount)) - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + allErrs := validateMatch(test.validator, test.match, field.NewPath("test")) + g.Expect(allErrs).To(HaveLen(test.expectErrCount)) + }) } } @@ -1304,15 +1296,13 @@ func TestValidateFilterRedirect(t *testing.T) { filterPath := field.NewPath("test") for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - allErrs := validateFilterRedirect(test.validator, test.requestRedirect, filterPath) - g.Expect(allErrs).To(HaveLen(test.expectErrCount)) - }, - ) + allErrs := validateFilterRedirect(test.validator, test.requestRedirect, filterPath) + g.Expect(allErrs).To(HaveLen(test.expectErrCount)) + }) } } @@ -1426,13 +1416,11 @@ func TestValidateFilterRewrite(t *testing.T) { filterPath := field.NewPath("test") for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - allErrs := validateFilterRewrite(test.validator, test.urlRewrite, filterPath) - g.Expect(allErrs).To(HaveLen(test.expectErrCount)) - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + allErrs := validateFilterRewrite(test.validator, test.urlRewrite, filterPath) + g.Expect(allErrs).To(HaveLen(test.expectErrCount)) + }) } } diff --git a/internal/mode/static/state/graph/route_common.go b/internal/mode/static/state/graph/route_common.go index ef761e2b6e..9a4dbe3a14 100644 --- a/internal/mode/static/state/graph/route_common.go +++ b/internal/mode/static/state/graph/route_common.go @@ -285,14 +285,12 @@ func buildSectionNameRefs( } uniqueSectionsPerGateway[k] = struct{}{} - sectionNameRefs = append( - sectionNameRefs, ParentRef{ - Idx: i, - Gateway: gw, - SectionName: p.SectionName, - Port: p.Port, - }, - ) + sectionNameRefs = append(sectionNameRefs, ParentRef{ + Idx: i, + Gateway: gw, + SectionName: p.SectionName, + Port: p.Port, + }) } return sectionNameRefs, nil @@ -345,11 +343,9 @@ func bindRoutesToListeners( } // Sort the slice by timestamp and name so that we process the routes in the priority order - sort.Slice( - routes, func(i, j int) bool { - return ngfSort.LessClientObject(routes[i].Source, routes[j].Source) - }, - ) + sort.Slice(routes, func(i, j int) bool { + return ngfSort.LessClientObject(routes[i].Source, routes[j].Source) + }) // portHostnamesMap exists to detect duplicate hostnames on the same port portHostnamesMap := make(map[string]struct{}) @@ -474,19 +470,17 @@ func tryToAttachL4RouteToListeners( ) // Sorting the listeners from most specific hostname to the least specific hostname - sort.Slice( - attachableListeners, func(i, j int) bool { - h1 := "" - h2 := "" - if attachableListeners[i].Source.Hostname != nil { - h1 = string(*attachableListeners[i].Source.Hostname) - } - if attachableListeners[j].Source.Hostname != nil { - h2 = string(*attachableListeners[j].Source.Hostname) - } - return h1 == GetMoreSpecificHostname(h1, h2) - }, - ) + sort.Slice(attachableListeners, func(i, j int) bool { + h1 := "" + h2 := "" + if attachableListeners[i].Source.Hostname != nil { + h1 = string(*attachableListeners[i].Source.Hostname) + } + if attachableListeners[j].Source.Hostname != nil { + h2 = string(*attachableListeners[j].Source.Hostname) + } + return h1 == GetMoreSpecificHostname(h1, h2) + }) for _, l := range attachableListeners { routeAllowed, routeAttached, routeHostnamesUnique := bindToListenerL4( diff --git a/internal/mode/static/state/graph/route_common_test.go b/internal/mode/static/state/graph/route_common_test.go index 82ba92db77..c2371c9ede 100644 --- a/internal/mode/static/state/graph/route_common_test.go +++ b/internal/mode/static/state/graph/route_common_test.go @@ -122,20 +122,18 @@ func TestBuildSectionNameRefs(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - result, err := buildSectionNameRefs(test.parentRefs, routeNamespace, gwNsNames) - g.Expect(result).To(Equal(test.expectedRefs)) - if test.expectedError != nil { - g.Expect(err).To(Equal(test.expectedError)) - } else { - g.Expect(err).ToNot(HaveOccurred()) - } - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + result, err := buildSectionNameRefs(test.parentRefs, routeNamespace, gwNsNames) + g.Expect(result).To(Equal(test.expectedRefs)) + if test.expectedError != nil { + g.Expect(err).To(Equal(test.expectedError)) + } else { + g.Expect(err).ToNot(HaveOccurred()) + } + }) } } @@ -215,16 +213,14 @@ func TestFindGatewayForParentRef(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - gw, found := findGatewayForParentRef(test.ref, routeNamespace, gwNsNames) - g.Expect(found).To(Equal(test.expectedFound)) - g.Expect(gw).To(Equal(test.expectedGwNsName)) - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + gw, found := findGatewayForParentRef(test.ref, routeNamespace, gwNsNames) + g.Expect(found).To(Equal(test.expectedFound)) + g.Expect(gw).To(Equal(test.expectedGwNsName)) + }) } } @@ -445,17 +441,13 @@ func TestBindRouteToListeners(t *testing.T) { }, } - invalidNotAttachableListener := createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Valid = false - l.Attachable = false - }, - ) - nonMatchingHostnameListener := createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Source.Hostname = helpers.GetPointer[gatewayv1.Hostname]("bar.example.com") - }, - ) + invalidNotAttachableListener := createModifiedListener("listener-80-1", func(l *Listener) { + l.Valid = false + l.Attachable = false + }) + nonMatchingHostnameListener := createModifiedListener("listener-80-1", func(l *Listener) { + l.Source.Hostname = helpers.GetPointer[gatewayv1.Hostname]("bar.example.com") + }) createGRPCRouteWithSectionNameAndPort := func( sectionName *gatewayv1.SectionName, @@ -544,13 +536,11 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }), }, name: "normal case", }, @@ -577,13 +567,11 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): routeWithMissingSectionName, - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): routeWithMissingSectionName, + } + }), }, name: "section name is nil", }, @@ -612,20 +600,16 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80", func(l *Listener) { - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): routeWithEmptySectionName, - } - }, - ), - createModifiedListener( - "listener-8080", func(l *Listener) { - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): routeWithEmptySectionName, - } - }, - ), + createModifiedListener("listener-80", func(l *Listener) { + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): routeWithEmptySectionName, + } + }), + createModifiedListener("listener-8080", func(l *Listener) { + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): routeWithEmptySectionName, + } + }), }, name: "section name is empty; bind to multiple listeners", }, @@ -842,11 +826,9 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Valid = false - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Valid = false + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -863,14 +845,12 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Valid = false - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Valid = false + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }), }, expectedConditions: []conditions.Condition{staticConds.NewRouteInvalidListener()}, name: "invalid attachable listener", @@ -898,13 +878,11 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): invalidAttachableRoute1, - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): invalidAttachableRoute1, + } + }), }, name: "invalid attachable route", }, @@ -914,11 +892,9 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Valid = false - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Valid = false + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -935,14 +911,12 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Valid = false - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): invalidAttachableRoute2, - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Valid = false + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): invalidAttachableRoute2, + } + }), }, expectedConditions: []conditions.Condition{staticConds.NewRouteInvalidListener()}, name: "invalid attachable listener with invalid attachable route", @@ -953,17 +927,15 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), - }, - } - allowedLabels := map[string]string{"app": "not-allowed"} - l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), + }, + } + allowedLabels := map[string]string{"app": "not-allowed"} + l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -979,17 +951,15 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), - }, - } - allowedLabels := map[string]string{"app": "not-allowed"} - l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), + }, + } + allowedLabels := map[string]string{"app": "not-allowed"} + l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) + }), }, name: "route not allowed via labels", }, @@ -999,17 +969,15 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), - }, - } - allowedLabels := map[string]string{"app": "allowed"} - l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), + }, + } + allowedLabels := map[string]string{"app": "allowed"} + l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1026,20 +994,18 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - allowedLabels := map[string]string{"app": "allowed"} - l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), - }, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + allowedLabels := map[string]string{"app": "allowed"} + l.AllowedRouteLabelSelector = labels.SelectorFromSet(allowedLabels) + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSelector), + }, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }), }, name: "route allowed via labels", }, @@ -1049,15 +1015,13 @@ func TestBindRouteToListeners(t *testing.T) { Source: gwDiffNamespace, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSame), - }, - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSame), + }, + } + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1073,15 +1037,13 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSame), - }, - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSame), + }, + } + }), }, name: "route not allowed via same namespace", }, @@ -1091,15 +1053,13 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSame), - }, - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSame), + }, + } + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1116,18 +1076,16 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromSame), - }, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromSame), + }, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }), }, name: "route allowed via same namespace", }, @@ -1137,15 +1095,13 @@ func TestBindRouteToListeners(t *testing.T) { Source: gwDiffNamespace, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromAll), - }, - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromAll), + }, + } + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1162,18 +1118,16 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer(gatewayv1.NamespacesFromAll), - }, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{ + From: helpers.GetPointer(gatewayv1.NamespacesFromAll), + }, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }), }, name: "route allowed via all namespaces", }, @@ -1183,19 +1137,14 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.SupportedKinds = []gatewayv1.RouteGroupKind{ - { - Kind: gatewayv1.Kind(kinds.HTTPRoute), - Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName), - }, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(gr): getLastNormalGRPCRoute(), - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.SupportedKinds = []gatewayv1.RouteGroupKind{ + {Kind: gatewayv1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(gr): getLastNormalGRPCRoute(), + } + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1211,19 +1160,14 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.SupportedKinds = []gatewayv1.RouteGroupKind{ - { - Kind: gatewayv1.Kind(kinds.HTTPRoute), - Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName), - }, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(gr): getLastNormalGRPCRoute(), - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.SupportedKinds = []gatewayv1.RouteGroupKind{ + {Kind: gatewayv1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(gr): getLastNormalGRPCRoute(), + } + }), }, name: "grpc route not allowed when listener kind is HTTPRoute", }, @@ -1233,18 +1177,16 @@ func TestBindRouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Kinds: []gatewayv1.RouteGroupKind{ - {Kind: "HTTPRoute"}, - }, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Kinds: []gatewayv1.RouteGroupKind{ + {Kind: "HTTPRoute"}, + }, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1261,18 +1203,16 @@ func TestBindRouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-80-1", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Kinds: []gatewayv1.RouteGroupKind{ - {Kind: "HTTPRoute"}, - }, - } - l.Routes = map[RouteKey]*L7Route{ - CreateRouteKey(hr): getLastNormalHTTPRoute(), - } - }, - ), + createModifiedListener("listener-80-1", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Kinds: []gatewayv1.RouteGroupKind{ + {Kind: "HTTPRoute"}, + }, + } + l.Routes = map[RouteKey]*L7Route{ + CreateRouteKey(hr): getLastNormalHTTPRoute(), + } + }), }, name: "http route allowed when listener kind is HTTPRoute", }, @@ -1287,21 +1227,19 @@ func TestBindRouteToListeners(t *testing.T) { }, } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - g := NewWithT(t) - - bindL7RouteToListeners( - test.route, - test.gateway, - namespaces, - ) - - g.Expect(test.route.ParentRefs).To(Equal(test.expectedSectionNameRefs)) - g.Expect(helpers.Diff(test.gateway.Listeners, test.expectedGatewayListeners)).To(BeEmpty()) - g.Expect(helpers.Diff(test.route.Conditions, test.expectedConditions)).To(BeEmpty()) - }, - ) + t.Run(test.name, func(t *testing.T) { + g := NewWithT(t) + + bindL7RouteToListeners( + test.route, + test.gateway, + namespaces, + ) + + g.Expect(test.route.ParentRefs).To(Equal(test.expectedSectionNameRefs)) + g.Expect(helpers.Diff(test.gateway.Listeners, test.expectedGatewayListeners)).To(BeEmpty()) + g.Expect(helpers.Diff(test.route.Conditions, test.expectedConditions)).To(BeEmpty()) + }) } } @@ -1381,14 +1319,12 @@ func TestFindAcceptedHostnames(t *testing.T) { } for _, test := range tests { - t.Run( - test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := findAcceptedHostnames(test.listenerHostname, test.routeHostnames) - g.Expect(result).To(Equal(test.expected)) - }, - ) + t.Run(test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + result := findAcceptedHostnames(test.listenerHostname, test.routeHostnames) + g.Expect(result).To(Equal(test.expected)) + }) } } @@ -1420,14 +1356,12 @@ func TestGetHostname(t *testing.T) { } for _, test := range tests { - t.Run( - test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - result := getHostname(test.h) - g.Expect(result).To(Equal(test.expected)) - }, - ) + t.Run(test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + result := getHostname(test.h) + g.Expect(result).To(Equal(test.expected)) + }) } } @@ -1462,20 +1396,18 @@ func TestValidateHostnames(t *testing.T) { path := field.NewPath("test") for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - err := validateHostnames(test.hostnames, path) - - if test.expectErr { - g.Expect(err).To(HaveOccurred()) - } else { - g.Expect(err).ToNot(HaveOccurred()) - } - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + err := validateHostnames(test.hostnames, path) + + if test.expectErr { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).ToNot(HaveOccurred()) + } + }) } } @@ -1550,18 +1482,11 @@ func TestAllowedRouteType(t *testing.T) { } for _, test := range test { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - g.Expect( - isRouteTypeAllowedByListener( - test.listener, - convertRouteType(test.routeType), - ), - ).To(Equal(test.expResult)) - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + g.Expect(isRouteTypeAllowedByListener(test.listener, convertRouteType(test.routeType))).To(Equal(test.expResult)) + }) } } @@ -1575,11 +1500,9 @@ func TestBindL4RouteToListeners(t *testing.T) { Name: gatewayv1.SectionName(name), Hostname: (*gatewayv1.Hostname)(helpers.GetPointer("foo.example.com")), Protocol: gatewayv1.TLSProtocolType, - TLS: helpers.GetPointer( - gatewayv1.GatewayTLSConfig{ - Mode: helpers.GetPointer(gatewayv1.TLSModeTerminate), - }, - ), + TLS: helpers.GetPointer(gatewayv1.GatewayTLSConfig{ + Mode: helpers.GetPointer(gatewayv1.TLSModeTerminate), + }), }, SupportedKinds: []gatewayv1.RouteGroupKind{ {Kind: kinds.TLSRoute, Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, @@ -1750,13 +1673,11 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-443", func(l *Listener) { - l.L4Routes = map[L4RouteKey]*L4Route{ - CreateRouteKeyL4(tr): getLastNormalRoute(), - } - }, - ), + createModifiedListener("listener-443", func(l *Listener) { + l.L4Routes = map[L4RouteKey]*L4Route{ + CreateRouteKeyL4(tr): getLastNormalRoute(), + } + }), }, name: "normal case", }, @@ -1903,17 +1824,11 @@ func TestBindL4RouteToListeners(t *testing.T) { Source: gwWrongNamespace, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-443", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer( - gatewayv1.FromNamespaces("Same"), - ), - }, - } - }, - ), + createModifiedListener("listener-443", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{From: helpers.GetPointer(gatewayv1.FromNamespaces("Same"))}, + } + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1933,17 +1848,11 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-443", func(l *Listener) { - l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{ - From: helpers.GetPointer( - gatewayv1.FromNamespaces("Same"), - ), - }, - } - }, - ), + createModifiedListener("listener-443", func(l *Listener) { + l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ + Namespaces: &gatewayv1.RouteNamespaces{From: helpers.GetPointer(gatewayv1.FromNamespaces("Same"))}, + } + }), }, name: "route not allowed by listener; in different namespace", }, @@ -1953,11 +1862,9 @@ func TestBindL4RouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-443", func(l *Listener) { - l.Valid = false - }, - ), + createModifiedListener("listener-443", func(l *Listener) { + l.Valid = false + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -1974,29 +1881,27 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-443", func(l *Listener) { - l.Valid = false - r := createNormalRoute(gw) - r.Conditions = append(r.Conditions, staticConds.NewRouteInvalidListener()) - r.ParentRefs = []ParentRef{ - { - Idx: 0, - Gateway: client.ObjectKeyFromObject(gw), - SectionName: tr.Spec.ParentRefs[0].SectionName, - Attachment: &ParentRefAttachmentStatus{ - AcceptedHostnames: map[string][]string{ - "listener-443": {"foo.example.com"}, - }, - Attached: true, + createModifiedListener("listener-443", func(l *Listener) { + l.Valid = false + r := createNormalRoute(gw) + r.Conditions = append(r.Conditions, staticConds.NewRouteInvalidListener()) + r.ParentRefs = []ParentRef{ + { + Idx: 0, + Gateway: client.ObjectKeyFromObject(gw), + SectionName: tr.Spec.ParentRefs[0].SectionName, + Attachment: &ParentRefAttachmentStatus{ + AcceptedHostnames: map[string][]string{ + "listener-443": {"foo.example.com"}, }, + Attached: true, }, - } - l.L4Routes = map[L4RouteKey]*L4Route{ - CreateRouteKeyL4(tr): r, - } - }, - ), + }, + } + l.L4Routes = map[L4RouteKey]*L4Route{ + CreateRouteKeyL4(tr): r, + } + }), }, expectedConditions: []conditions.Condition{staticConds.NewRouteInvalidListener()}, name: "invalid attachable listener", @@ -2007,11 +1912,9 @@ func TestBindL4RouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-443", func(l *Listener) { - l.Source.Hostname = (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")) - }, - ), + createModifiedListener("listener-443", func(l *Listener) { + l.Source.Hostname = (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")) + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -2026,20 +1929,16 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-443", func(l *Listener) { - l.Source.Hostname = (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")) - }, - ), + createModifiedListener("listener-443", func(l *Listener) { + l.Source.Hostname = (*gatewayv1.Hostname)(helpers.GetPointer("*.example.org")) + }), }, name: "route hostname does not match any listener", }, { - route: makeModifiedRoute( - gw, func(r *L4Route) { - r.ParentRefs[0].SectionName = nil - }, - ), + route: makeModifiedRoute(gw, func(r *L4Route) { + r.ParentRefs[0].SectionName = nil + }), gateway: &Gateway{ Source: gw, Valid: true, @@ -2060,22 +1959,18 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-443", func(l *Listener) { - l.L4Routes = map[L4RouteKey]*L4Route{ - CreateRouteKeyL4(tr): getLastNormalRoute(), - } - }, - ), + createModifiedListener("listener-443", func(l *Listener) { + l.L4Routes = map[L4RouteKey]*L4Route{ + CreateRouteKeyL4(tr): getLastNormalRoute(), + } + }), }, name: "nil section name", }, { - route: makeModifiedRoute( - gw, func(r *L4Route) { - r.ParentRefs[0].SectionName = helpers.GetPointer[gatewayv1.SectionName]("") - }, - ), + route: makeModifiedRoute(gw, func(r *L4Route) { + r.ParentRefs[0].SectionName = helpers.GetPointer[gatewayv1.SectionName]("") + }), gateway: &Gateway{ Source: gw, Valid: true, @@ -2097,13 +1992,11 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-443", func(l *Listener) { - l.L4Routes = map[L4RouteKey]*L4Route{ - CreateRouteKeyL4(tr): getLastNormalRoute(), - } - }, - ), + createModifiedListener("listener-443", func(l *Listener) { + l.L4Routes = map[L4RouteKey]*L4Route{ + CreateRouteKeyL4(tr): getLastNormalRoute(), + } + }), }, name: "empty section name", }, @@ -2126,11 +2019,9 @@ func TestBindL4RouteToListeners(t *testing.T) { name: "listener does not exist", }, { - route: makeModifiedRoute( - gw, func(r *L4Route) { - r.Valid = false - }, - ), + route: makeModifiedRoute(gw, func(r *L4Route) { + r.Valid = false + }), gateway: &Gateway{ Source: gw, Valid: true, @@ -2152,13 +2043,11 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-443", func(l *Listener) { - l.L4Routes = map[L4RouteKey]*L4Route{ - CreateRouteKeyL4(tr): getLastNormalRoute(), - } - }, - ), + createModifiedListener("listener-443", func(l *Listener) { + l.L4Routes = map[L4RouteKey]*L4Route{ + CreateRouteKeyL4(tr): getLastNormalRoute(), + } + }), }, name: "invalid attachable route", }, @@ -2168,11 +2057,9 @@ func TestBindL4RouteToListeners(t *testing.T) { Source: gw, Valid: true, Listeners: []*Listener{ - createModifiedListener( - "listener-443", func(l *Listener) { - l.SupportedKinds = nil - }, - ), + createModifiedListener("listener-443", func(l *Listener) { + l.SupportedKinds = nil + }), }, }, expectedSectionNameRefs: []ParentRef{ @@ -2187,11 +2074,9 @@ func TestBindL4RouteToListeners(t *testing.T) { }, }, expectedGatewayListeners: []*Listener{ - createModifiedListener( - "listener-443", func(l *Listener) { - l.SupportedKinds = nil - }, - ), + createModifiedListener("listener-443", func(l *Listener) { + l.SupportedKinds = nil + }), }, name: "route kind not allowed", }, @@ -2206,23 +2091,21 @@ func TestBindL4RouteToListeners(t *testing.T) { }, } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) - - bindL4RouteToListeners( - test.route, - test.gateway, - namespaces, - map[string]struct{}{}, - ) - - g.Expect(test.route.ParentRefs).To(Equal(test.expectedSectionNameRefs)) - g.Expect(helpers.Diff(test.gateway.Listeners, test.expectedGatewayListeners)).To(BeEmpty()) - g.Expect(helpers.Diff(test.route.Conditions, test.expectedConditions)).To(BeEmpty()) - }, - ) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) + + bindL4RouteToListeners( + test.route, + test.gateway, + namespaces, + map[string]struct{}{}, + ) + + g.Expect(test.route.ParentRefs).To(Equal(test.expectedSectionNameRefs)) + g.Expect(helpers.Diff(test.gateway.Listeners, test.expectedGatewayListeners)).To(BeEmpty()) + g.Expect(helpers.Diff(test.route.Conditions, test.expectedConditions)).To(BeEmpty()) + }) } } @@ -2250,15 +2133,7 @@ func TestBuildL4RoutesForGateways_NoGateways(t *testing.T) { refGrantResolver := newReferenceGrantResolver(nil) - g.Expect( - buildL4RoutesForGateways( - tlsRoutes, - nil, - services, - nil, - refGrantResolver, - ), - ).To(BeNil()) + g.Expect(buildL4RoutesForGateways(tlsRoutes, nil, services, nil, refGrantResolver)).To(BeNil()) } func TestTryToAttachL4RouteToListeners_NoAttachableListeners(t *testing.T) { diff --git a/internal/mode/static/state/graph/snippets_filter_test.go b/internal/mode/static/state/graph/snippets_filter_test.go index 183476289d..c5c8545552 100644 --- a/internal/mode/static/state/graph/snippets_filter_test.go +++ b/internal/mode/static/state/graph/snippets_filter_test.go @@ -114,15 +114,13 @@ func TestProcessSnippetsFilters(t *testing.T) { } for _, test := range tests { - t.Run( - test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - processedSnippetsFilters := processSnippetsFilters(test.snippetsFilters) - g.Expect(processedSnippetsFilters).To(BeEquivalentTo(test.expProcessedSnippets)) - }, - ) + processedSnippetsFilters := processSnippetsFilters(test.snippetsFilters) + g.Expect(processedSnippetsFilters).To(BeEquivalentTo(test.expProcessedSnippets)) + }) } } @@ -288,20 +286,18 @@ func TestValidateSnippetsFilter(t *testing.T) { } for _, test := range tests { - t.Run( - test.msg, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.msg, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - cond := validateSnippetsFilter(test.filter) - if test.expCond != (conditions.Condition{}) { - g.Expect(cond).ToNot(BeNil()) - g.Expect(*cond).To(Equal(test.expCond)) - } else { - g.Expect(cond).To(BeNil()) - } - }, - ) + cond := validateSnippetsFilter(test.filter) + if test.expCond != (conditions.Condition{}) { + g.Expect(cond).ToNot(BeNil()) + g.Expect(*cond).To(Equal(test.expCond)) + } else { + g.Expect(cond).To(BeNil()) + } + }) } } @@ -430,24 +426,22 @@ func TestGetSnippetsFilterResolverForNamespace(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - resolve := getSnippetsFilterResolverForNamespace(test.snippetsFilterMap, test.resolveInNamespace) - resolvedSf := resolve(test.extRef) - if test.expResolve { - g.Expect(resolvedSf).ToNot(BeNil()) - g.Expect(resolvedSf.SnippetsFilter).ToNot(BeNil()) - g.Expect(resolvedSf.SnippetsFilter.Referenced).To(BeTrue()) - g.Expect(resolvedSf.SnippetsFilter.Source.Name).To(BeEquivalentTo(test.extRef.Name)) - g.Expect(resolvedSf.SnippetsFilter.Source.Namespace).To(Equal(test.resolveInNamespace)) - g.Expect(resolvedSf.Valid).To(BeEquivalentTo(test.expValid)) - } else { - g.Expect(resolvedSf).To(BeNil()) - } - }, - ) + resolve := getSnippetsFilterResolverForNamespace(test.snippetsFilterMap, test.resolveInNamespace) + resolvedSf := resolve(test.extRef) + if test.expResolve { + g.Expect(resolvedSf).ToNot(BeNil()) + g.Expect(resolvedSf.SnippetsFilter).ToNot(BeNil()) + g.Expect(resolvedSf.SnippetsFilter.Referenced).To(BeTrue()) + g.Expect(resolvedSf.SnippetsFilter.Source.Name).To(BeEquivalentTo(test.extRef.Name)) + g.Expect(resolvedSf.SnippetsFilter.Source.Namespace).To(Equal(test.resolveInNamespace)) + g.Expect(resolvedSf.Valid).To(BeEquivalentTo(test.expValid)) + } else { + g.Expect(resolvedSf).To(BeNil()) + } + }) } } From 64b793295e0b8fca01902e1e9b4981d0cae5da26 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 25 Sep 2024 15:15:40 -0600 Subject: [PATCH 03/16] Fix backend refs test --- .../static/state/graph/backend_refs_test.go | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/internal/mode/static/state/graph/backend_refs_test.go b/internal/mode/static/state/graph/backend_refs_test.go index e061ae7d60..2ea65322ad 100644 --- a/internal/mode/static/state/graph/backend_refs_test.go +++ b/internal/mode/static/state/graph/backend_refs_test.go @@ -466,22 +466,9 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { return hr } - hrWithOneBackend := createRoute("hr1", "Service", 1, "svc1") - hrWithTwoBackends := createRoute("hr2", "Service", 2, "svc1") - hrWithTwoDiffBackends := createRoute("hr2", "Service", 2, "svc1") - hrWithInvalidRule := createRoute("hr3", "NotService", 1, "svc1") - hrWithZeroBackendRefs := createRoute("hr4", "Service", 1, "svc1") - hrWithZeroBackendRefs.Spec.Rules[0].RouteBackendRefs = nil - hrWithTwoDiffBackends.Spec.Rules[0].RouteBackendRefs[1].Name = "svc2" - - hrWithOneBackendInvalid := createRoute("hr1", "Service", 1, "svc1") - hrWithOneBackendInvalid.Valid = false - - hrWithOneBackendInvalidMatches := createRoute("hr1", "Service", 1, "svc1") - hrWithOneBackendInvalidMatches.Spec.Rules[0].ValidMatches = false - - hrWithOneBackendInvalidFilters := createRoute("hr1", "Service", 1, "svc1") - hrWithOneBackendInvalidFilters.Spec.Rules[0].Filters = RouteRuleFilters{Valid: false} + modRoute := func(route *L7Route, mod func(*L7Route) *L7Route) *L7Route { + return mod(route) + } getSvc := func(name string) *v1.Service { return &v1.Service{ @@ -619,7 +606,7 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { expectedConditions []conditions.Condition }{ { - route: hrWithOneBackend, + route: createRoute("hr1", "Service", 1, "svc1"), expectedBackendRefs: []BackendRef{ { SvcNsName: svc1NsName, @@ -633,7 +620,7 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { name: "normal case with one rule with one backend", }, { - route: hrWithTwoBackends, + route: createRoute("hr2", "Service", 2, "svc1"), expectedBackendRefs: []BackendRef{ { SvcNsName: svc1NsName, @@ -653,7 +640,7 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { name: "normal case with one rule with two backends", }, { - route: hrWithTwoBackends, + route: createRoute("hr2", "Service", 2, "svc1"), expectedBackendRefs: []BackendRef{ { SvcNsName: svc1NsName, @@ -675,28 +662,37 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { name: "normal case with one rule with two backends and matching policies", }, { - route: hrWithOneBackendInvalid, + route: modRoute(createRoute("hr1", "Service", 1, "svc1"), func(route *L7Route) *L7Route { + route.Valid = false + return route + }), expectedBackendRefs: nil, expectedConditions: nil, policies: emptyPolicies, name: "invalid route", }, { - route: hrWithOneBackendInvalidMatches, + route: modRoute(createRoute("hr1", "Service", 1, "svc1"), func(route *L7Route) *L7Route { + route.Spec.Rules[0].ValidMatches = false + return route + }), expectedBackendRefs: nil, expectedConditions: nil, policies: emptyPolicies, name: "invalid matches", }, { - route: hrWithOneBackendInvalidFilters, + route: modRoute(createRoute("hr1", "Service", 1, "svc1"), func(route *L7Route) *L7Route { + route.Spec.Rules[0].Filters = RouteRuleFilters{Valid: false} + return route + }), expectedBackendRefs: nil, expectedConditions: nil, policies: emptyPolicies, name: "invalid filters", }, { - route: hrWithInvalidRule, + route: createRoute("hr3", "NotService", 1, "svc1"), expectedBackendRefs: []BackendRef{ { Weight: 1, @@ -711,7 +707,10 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { name: "invalid backendRef", }, { - route: hrWithTwoDiffBackends, + route: modRoute(createRoute("hr2", "Service", 2, "svc1"), func(route *L7Route) *L7Route { + route.Spec.Rules[0].RouteBackendRefs[1].Name = "svc2" + return route + }), expectedBackendRefs: []BackendRef{ { SvcNsName: svc1NsName, @@ -737,7 +736,10 @@ func TestAddBackendRefsToRulesTest(t *testing.T) { name: "invalid backendRef - backend TLS policies do not match for all backends", }, { - route: hrWithZeroBackendRefs, + route: modRoute(createRoute("hr4", "Service", 1, "svc1"), func(route *L7Route) *L7Route { + route.Spec.Rules[0].RouteBackendRefs = nil + return route + }), expectedBackendRefs: nil, expectedConditions: nil, name: "zero backendRefs", From b40f965565b16002c1ed7357f7b5d128543b4e5d Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 25 Sep 2024 15:16:39 -0600 Subject: [PATCH 04/16] Update include path in nginx-plus.conf --- internal/mode/static/nginx/conf/nginx-plus.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/mode/static/nginx/conf/nginx-plus.conf b/internal/mode/static/nginx/conf/nginx-plus.conf index 23d97717c9..08cae40eb7 100644 --- a/internal/mode/static/nginx/conf/nginx-plus.conf +++ b/internal/mode/static/nginx/conf/nginx-plus.conf @@ -1,5 +1,5 @@ load_module /usr/lib/nginx/modules/ngx_http_js_module.so; -include /etc/nginx/module-includes/*.conf; +include /etc/nginx/main-includes/*.conf; worker_processes auto; From 8b716e4cd5a1df288643fe6eab7d4eff4ffda9d1 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 25 Sep 2024 15:19:38 -0600 Subject: [PATCH 05/16] Remove unnecessary len check --- internal/mode/static/nginx/config/includes.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/mode/static/nginx/config/includes.go b/internal/mode/static/nginx/config/includes.go index 9fd69d5639..0995be8d81 100644 --- a/internal/mode/static/nginx/config/includes.go +++ b/internal/mode/static/nginx/config/includes.go @@ -105,11 +105,9 @@ func createIncludesFromServerSnippetsFilters(server dataplane.VirtualServer) []s for _, pr := range server.PathRules { for _, mr := range pr.MatchRules { - if len(mr.Filters.SnippetsFilters) > 0 { - for _, sf := range mr.Filters.SnippetsFilters { - if sf.ServerSnippet != nil { - includes = append(includes, createIncludeFromSnippet(*sf.ServerSnippet)) - } + for _, sf := range mr.Filters.SnippetsFilters { + if sf.ServerSnippet != nil { + includes = append(includes, createIncludeFromSnippet(*sf.ServerSnippet)) } } } From 07597008b4ce618e3e1984bce9667737050e8ae4 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 25 Sep 2024 15:46:37 -0600 Subject: [PATCH 06/16] Add comments to includes.go --- internal/mode/static/nginx/config/includes.go | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/internal/mode/static/nginx/config/includes.go b/internal/mode/static/nginx/config/includes.go index 0995be8d81..f42a071de2 100644 --- a/internal/mode/static/nginx/config/includes.go +++ b/internal/mode/static/nginx/config/includes.go @@ -7,6 +7,10 @@ import ( "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/dataplane" ) +// createIncludeExecuteResultsFromServers creates a list of executeResults -- or NGINX config files -- from all +// the includes in the provided servers. Since there may be duplicate includes, such as configuration for policies that +// apply to multiple route, or snippets filters that are attached to multiple routing rules, this function deduplicates +// all includes, ensuring only a single file per unique include is generated. func createIncludeExecuteResultsFromServers(servers []http.Server) []executeResult { uniqueIncludes := make(map[string][]byte) @@ -37,6 +41,7 @@ func createIncludeExecuteResultsFromServers(servers []http.Server) []executeResu return results } +// createIncludesFromPolicyGenerateResult converts a list of policies.File into a list of includes. func createIncludesFromPolicyGenerateResult(resFiles []policies.File) []shared.Include { if len(resFiles) == 0 { return nil @@ -55,6 +60,7 @@ func createIncludesFromPolicyGenerateResult(resFiles []policies.File) []shared.I return includes } +// createIncludeFromSnippet converts a dataplane.Snippet into an include. func createIncludeFromSnippet(snippet dataplane.Snippet) shared.Include { return shared.Include{ Name: includesFolder + "/" + snippet.Name + ".conf", @@ -62,6 +68,9 @@ func createIncludeFromSnippet(snippet dataplane.Snippet) shared.Include { } } +// deduplicateIncludes deduplicates all the includes using the include name as the identifier. +// Duplicate includes are possible when a single policy targets multiple resources, or a snippets filter +// is referenced on multiple routing rules. func deduplicateIncludes(includes []shared.Include) []shared.Include { uniqueIncludes := make(map[string]shared.Include) for _, i := range includes { @@ -78,6 +87,9 @@ func deduplicateIncludes(includes []shared.Include) []shared.Include { return results } +// createIncludesFromLocationSnippetsFilters creates includes for a location from a list of SnippetsFilters. +// A SnippetsFilter can have both a server snippet and a location snippet. This function converts +// all the location snippets in the SnippetsFilters to includes. func createIncludesFromLocationSnippetsFilters(filters []dataplane.SnippetsFilter) []shared.Include { if len(filters) == 0 { return nil @@ -85,17 +97,18 @@ func createIncludesFromLocationSnippetsFilters(filters []dataplane.SnippetsFilte includes := make([]shared.Include, 0) - if len(filters) > 0 { - for _, f := range filters { - if f.LocationSnippet != nil { - includes = append(includes, createIncludeFromSnippet(*f.LocationSnippet)) - } + for _, f := range filters { + if f.LocationSnippet != nil { + includes = append(includes, createIncludeFromSnippet(*f.LocationSnippet)) } } return deduplicateIncludes(includes) } +// createIncludesFromServerSnippetsFilters creates includes for a server from a dataplane.VirtualServer. +// It finds all the server snippets from the SnippetsFilters on each MatchRule. This function converts all +// the server snippets into includes. func createIncludesFromServerSnippetsFilters(server dataplane.VirtualServer) []shared.Include { if len(server.PathRules) == 0 { return nil @@ -116,6 +129,8 @@ func createIncludesFromServerSnippetsFilters(server dataplane.VirtualServer) []s return deduplicateIncludes(includes) } +// createIncludesFromSnippets converts a list of Snippets to a list of Includes. +// Used for main and http snippets only. Server and location snippets are handled by other functions above. func createIncludesFromSnippets(snippets []dataplane.Snippet) []shared.Include { if len(snippets) == 0 { return nil @@ -130,6 +145,8 @@ func createIncludesFromSnippets(snippets []dataplane.Snippet) []shared.Include { return deduplicateIncludes(includes) } +// createIncludeExecuteResults creates a list of executeResult -- or NGINX config files -- from a list of includes. +// Used for main and http snippets only. Server and location snippets are handled by other functions above. func createIncludeExecuteResults(includes []shared.Include) []executeResult { results := make([]executeResult, 0, len(includes)) From f5b3c0823e075c4a59a97861a2b6998a9771dc8f Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 25 Sep 2024 15:50:27 -0600 Subject: [PATCH 07/16] Move convert func to convert.go --- .../static/state/dataplane/configuration.go | 23 ---------------- .../mode/static/state/dataplane/convert.go | 27 +++++++++++++++++++ 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/internal/mode/static/state/dataplane/configuration.go b/internal/mode/static/state/dataplane/configuration.go index b2a3e1e6e6..96d4476a6e 100644 --- a/internal/mode/static/state/dataplane/configuration.go +++ b/internal/mode/static/state/dataplane/configuration.go @@ -738,29 +738,6 @@ func createHTTPFilters(filters []graph.Filter) HTTPFilters { return result } -func convertSnippetsFilter(filter *graph.SnippetsFilter) SnippetsFilter { - result := SnippetsFilter{} - - if snippet, ok := filter.Snippets[ngfAPI.NginxContextHTTPServer]; ok { - result.ServerSnippet = &Snippet{ - Name: createSnippetName(ngfAPI.NginxContextHTTPServer, client.ObjectKeyFromObject(filter.Source)), - Contents: snippet, - } - } - - if snippet, ok := filter.Snippets[ngfAPI.NginxContextHTTPServerLocation]; ok { - result.LocationSnippet = &Snippet{ - Name: createSnippetName( - ngfAPI.NginxContextHTTPServerLocation, - client.ObjectKeyFromObject(filter.Source), - ), - Contents: snippet, - } - } - - return result -} - // listenerHostnameMoreSpecific returns true if host1 is more specific than host2. func listenerHostnameMoreSpecific(host1, host2 *v1.Hostname) bool { var host1Str, host2Str string diff --git a/internal/mode/static/state/dataplane/convert.go b/internal/mode/static/state/dataplane/convert.go index 4337a0d787..626e5c9030 100644 --- a/internal/mode/static/state/dataplane/convert.go +++ b/internal/mode/static/state/dataplane/convert.go @@ -3,7 +3,11 @@ package dataplane import ( "fmt" + "sigs.k8s.io/controller-runtime/pkg/client" v1 "sigs.k8s.io/gateway-api/apis/v1" + + ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1" + "github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/graph" ) func convertMatch(m v1.HTTPRouteMatch) Match { @@ -104,3 +108,26 @@ func convertPathModifier(path *v1.HTTPPathModifier) *HTTPPathModifier { return nil } + +func convertSnippetsFilter(filter *graph.SnippetsFilter) SnippetsFilter { + result := SnippetsFilter{} + + if snippet, ok := filter.Snippets[ngfAPI.NginxContextHTTPServer]; ok { + result.ServerSnippet = &Snippet{ + Name: createSnippetName(ngfAPI.NginxContextHTTPServer, client.ObjectKeyFromObject(filter.Source)), + Contents: snippet, + } + } + + if snippet, ok := filter.Snippets[ngfAPI.NginxContextHTTPServerLocation]; ok { + result.LocationSnippet = &Snippet{ + Name: createSnippetName( + ngfAPI.NginxContextHTTPServerLocation, + client.ObjectKeyFromObject(filter.Source), + ), + Contents: snippet, + } + } + + return result +} From 283318d435c9cc66c637eb257986dc84ce3c4e1a Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 25 Sep 2024 15:51:58 -0600 Subject: [PATCH 08/16] Fix typos --- internal/mode/static/state/dataplane/types.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/mode/static/state/dataplane/types.go b/internal/mode/static/state/dataplane/types.go index b8c1786df5..5d57b1b6cf 100644 --- a/internal/mode/static/state/dataplane/types.go +++ b/internal/mode/static/state/dataplane/types.go @@ -142,7 +142,7 @@ type HTTPFilters struct { // ResponseHeaderModifiers holds the HTTPHeaderFilter. ResponseHeaderModifiers *HTTPHeaderFilter // SnippetsFilters holds all the SnippetsFilters for the MatchRule. - // Unlike the core and extended filters, there can be more than on SnippetsFilters defined on a routing rule. + // Unlike the core and extended filters, there can be more than one SnippetsFilters defined on a routing rule. SnippetsFilters []SnippetsFilter } @@ -151,7 +151,7 @@ type HTTPFilters struct { type SnippetsFilter struct { // LocationSnippet holds the snippet for the location context. LocationSnippet *Snippet - // ServerSnippet holds the snippet for the location context. + // ServerSnippet holds the snippet for the server context. ServerSnippet *Snippet } From 9743f11bed67be1ed0ed2437f211eb66b6aa734b Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 25 Sep 2024 15:55:03 -0600 Subject: [PATCH 09/16] Fix parallel placement --- internal/mode/static/state/graph/backend_refs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/mode/static/state/graph/backend_refs_test.go b/internal/mode/static/state/graph/backend_refs_test.go index 2ea65322ad..b7c49648da 100644 --- a/internal/mode/static/state/graph/backend_refs_test.go +++ b/internal/mode/static/state/graph/backend_refs_test.go @@ -1178,8 +1178,8 @@ func TestValidateBackendTLSPolicyMatchingAllBackends(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - g := NewWithT(t) t.Parallel() + g := NewWithT(t) cond := validateBackendTLSPolicyMatchingAllBackends(test.backendRefs) From 081b89e27475a2f7488cd1959f23b83491fea5ea Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 25 Sep 2024 15:57:22 -0600 Subject: [PATCH 10/16] Add comment about validation --- internal/mode/static/state/graph/snippets_filter.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/mode/static/state/graph/snippets_filter.go b/internal/mode/static/state/graph/snippets_filter.go index bc6d4a4e35..58020345ea 100644 --- a/internal/mode/static/state/graph/snippets_filter.go +++ b/internal/mode/static/state/graph/snippets_filter.go @@ -85,6 +85,7 @@ func processSnippetsFilters( func createSnippetsMap(snippets []ngfAPI.Snippet) map[ngfAPI.NginxContext]string { snippetsMap := make(map[ngfAPI.NginxContext]string) + // snippets are already validated, so we can assume there's max one snippet per context. for _, snippet := range snippets { snippetsMap[snippet.Context] = snippet.Value } From 17993ce3481fbf903cc7ee8cda0563ae6c949698 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 25 Sep 2024 16:36:37 -0600 Subject: [PATCH 11/16] More formatting changes --- .../static/nginx/config/base_http_config.go | 11 +- .../mode/static/nginx/config/generator.go | 12 +- .../static/nginx/config/generator_test.go | 14 +- internal/mode/static/nginx/config/includes.go | 30 +- .../mode/static/nginx/config/main_config.go | 10 +- internal/mode/static/nginx/config/servers.go | 30 +- .../mode/static/nginx/config/servers_test.go | 116 +- .../static/state/change_processor_test.go | 226 +-- .../state/dataplane/configuration_test.go | 1658 ++++++++--------- .../mode/static/state/graph/common_filter.go | 53 +- .../mode/static/state/graph/grpcroute_test.go | 44 +- .../static/state/graph/route_common_test.go | 26 +- 12 files changed, 988 insertions(+), 1242 deletions(-) diff --git a/internal/mode/static/nginx/config/base_http_config.go b/internal/mode/static/nginx/config/base_http_config.go index 79fb579174..4db43b78b0 100644 --- a/internal/mode/static/nginx/config/base_http_config.go +++ b/internal/mode/static/nginx/config/base_http_config.go @@ -24,13 +24,10 @@ func executeBaseHTTPConfig(conf dataplane.Configuration) []executeResult { } results := make([]executeResult, 0, len(includes)+1) - results = append( - results, - executeResult{ - dest: httpConfigFile, - data: helpers.MustExecuteTemplate(baseHTTPTemplate, hc), - }, - ) + results = append(results, executeResult{ + dest: httpConfigFile, + data: helpers.MustExecuteTemplate(baseHTTPTemplate, hc), + }) results = append(results, createIncludeExecuteResults(includes)...) return results diff --git a/internal/mode/static/nginx/config/generator.go b/internal/mode/static/nginx/config/generator.go index 235247e7d5..fe7fc14ccb 100644 --- a/internal/mode/static/nginx/config/generator.go +++ b/internal/mode/static/nginx/config/generator.go @@ -122,13 +122,11 @@ func (g GeneratorImpl) executeConfigTemplates( files := make([]file.File, 0, len(fileBytes)) for fp, bytes := range fileBytes { - files = append( - files, file.File{ - Path: fp, - Content: bytes, - Type: file.TypeRegular, - }, - ) + files = append(files, file.File{ + Path: fp, + Content: bytes, + Type: file.TypeRegular, + }) } return files diff --git a/internal/mode/static/nginx/config/generator_test.go b/internal/mode/static/nginx/config/generator_test.go index 1e4eae8375..8a56042d6f 100644 --- a/internal/mode/static/nginx/config/generator_test.go +++ b/internal/mode/static/nginx/config/generator_test.go @@ -189,15 +189,11 @@ func TestGenerate(t *testing.T) { certBundle := string(files[8].Content) g.Expect(certBundle).To(Equal("test-cert")) - g.Expect(files[9]).To( - Equal( - file.File{ - Type: file.TypeSecret, - Path: "/etc/nginx/secrets/test-keypair.pem", - Content: []byte("test-cert\ntest-key"), - }, - ), - ) + g.Expect(files[9]).To(Equal(file.File{ + Type: file.TypeSecret, + Path: "/etc/nginx/secrets/test-keypair.pem", + Content: []byte("test-cert\ntest-key"), + })) g.Expect(files[10].Path).To(Equal("/etc/nginx/stream-conf.d/stream.conf")) g.Expect(files[10].Type).To(Equal(file.TypeRegular)) diff --git a/internal/mode/static/nginx/config/includes.go b/internal/mode/static/nginx/config/includes.go index f42a071de2..b3aee6770b 100644 --- a/internal/mode/static/nginx/config/includes.go +++ b/internal/mode/static/nginx/config/includes.go @@ -30,12 +30,10 @@ func createIncludeExecuteResultsFromServers(servers []http.Server) []executeResu results := make([]executeResult, 0, len(uniqueIncludes)) for filename, contents := range uniqueIncludes { - results = append( - results, executeResult{ - dest: filename, - data: contents, - }, - ) + results = append(results, executeResult{ + dest: filename, + data: contents, + }) } return results @@ -49,12 +47,10 @@ func createIncludesFromPolicyGenerateResult(resFiles []policies.File) []shared.I includes := make([]shared.Include, 0, len(resFiles)) for _, file := range resFiles { - includes = append( - includes, shared.Include{ - Name: includesFolder + "/" + file.Name, - Content: file.Content, - }, - ) + includes = append(includes, shared.Include{ + Name: includesFolder + "/" + file.Name, + Content: file.Content, + }) } return includes @@ -151,12 +147,10 @@ func createIncludeExecuteResults(includes []shared.Include) []executeResult { results := make([]executeResult, 0, len(includes)) for _, inc := range includes { - results = append( - results, executeResult{ - dest: inc.Name, - data: inc.Content, - }, - ) + results = append(results, executeResult{ + dest: inc.Name, + data: inc.Content, + }) } return results diff --git a/internal/mode/static/nginx/config/main_config.go b/internal/mode/static/nginx/config/main_config.go index 10529361ad..e0876ae7e0 100644 --- a/internal/mode/static/nginx/config/main_config.go +++ b/internal/mode/static/nginx/config/main_config.go @@ -24,12 +24,10 @@ func executeMainConfig(conf dataplane.Configuration) []executeResult { } results := make([]executeResult, 0, len(includes)+1) - results = append( - results, executeResult{ - dest: mainIncludeFile, - data: helpers.MustExecuteTemplate(mainConfigTemplate, mc), - }, - ) + results = append(results, executeResult{ + dest: mainIncludeFile, + data: helpers.MustExecuteTemplate(mainConfigTemplate, mc), + }) results = append(results, createIncludeExecuteResults(includes)...) return results diff --git a/internal/mode/static/nginx/config/servers.go b/internal/mode/static/nginx/config/servers.go index e840802e0a..33ea858f31 100644 --- a/internal/mode/static/nginx/config/servers.go +++ b/internal/mode/static/nginx/config/servers.go @@ -797,12 +797,10 @@ func generateProxySetHeaders(filters *dataplane.HTTPFilters, grpc bool) []http.H } // If the value of a header field is an empty string then this field will not be passed to a proxied server for _, h := range headerFilter.Remove { - proxySetHeaders = append( - proxySetHeaders, http.Header{ - Name: h, - Value: "", - }, - ) + proxySetHeaders = append(proxySetHeaders, http.Header{ + Name: h, + Value: "", + }) } return append(proxySetHeaders, headers...) @@ -830,12 +828,10 @@ func createHeadersWithVarName(headers []dataplane.HTTPHeader) []http.Header { locHeaders := make([]http.Header, 0, len(headers)) for _, h := range headers { mapVarName := "${" + generateAddHeaderMapVariableName(h.Name) + "}" - locHeaders = append( - locHeaders, http.Header{ - Name: h.Name, - Value: mapVarName + h.Value, - }, - ) + locHeaders = append(locHeaders, http.Header{ + Name: h.Name, + Value: mapVarName + h.Value, + }) } return locHeaders } @@ -843,12 +839,10 @@ func createHeadersWithVarName(headers []dataplane.HTTPHeader) []http.Header { func createHeaders(headers []dataplane.HTTPHeader) []http.Header { locHeaders := make([]http.Header, 0, len(headers)) for _, h := range headers { - locHeaders = append( - locHeaders, http.Header{ - Name: h.Name, - Value: h.Value, - }, - ) + locHeaders = append(locHeaders, http.Header{ + Name: h.Name, + Value: h.Value, + }) } return locHeaders } diff --git a/internal/mode/static/nginx/config/servers_test.go b/internal/mode/static/nginx/config/servers_test.go index 32695666d2..115554cb29 100644 --- a/internal/mode/static/nginx/config/servers_test.go +++ b/internal/mode/static/nginx/config/servers_test.go @@ -1528,22 +1528,18 @@ func TestCreateServers(t *testing.T) { g := NewWithT(t) fakeGenerator := &policiesfakes.FakeGenerator{} - fakeGenerator.GenerateForLocationReturns( - policies.GenerateResultFiles{ - { - Name: "include-1.conf", - Content: []byte("include-1"), - }, + fakeGenerator.GenerateForLocationReturns(policies.GenerateResultFiles{ + { + Name: "include-1.conf", + Content: []byte("include-1"), }, - ) - fakeGenerator.GenerateForInternalLocationReturns( - policies.GenerateResultFiles{ - { - Name: "internal-include-1.conf", - Content: []byte("include-1"), - }, + }) + fakeGenerator.GenerateForInternalLocationReturns(policies.GenerateResultFiles{ + { + Name: "internal-include-1.conf", + Content: []byte("include-1"), }, - ) + }) result, httpMatchPair := createServers(conf, fakeGenerator) @@ -1832,22 +1828,18 @@ func TestCreateServers_Includes(t *testing.T) { } fakeGenerator := &policiesfakes.FakeGenerator{} - fakeGenerator.GenerateForLocationReturns( - policies.GenerateResultFiles{ - { - Name: "ext-policy.conf", - Content: []byte("external policy conf"), - }, + fakeGenerator.GenerateForLocationReturns(policies.GenerateResultFiles{ + { + Name: "ext-policy.conf", + Content: []byte("external policy conf"), }, - ) - fakeGenerator.GenerateForServerReturns( - policies.GenerateResultFiles{ - { - Name: "server-policy.conf", - Content: []byte("server policy conf"), - }, + }) + fakeGenerator.GenerateForServerReturns(policies.GenerateResultFiles{ + { + Name: "server-policy.conf", + Content: []byte("server policy conf"), }, - ) + }) expServers := []http.Server{ { @@ -2068,22 +2060,18 @@ func TestCreateLocations_Includes(t *testing.T) { } fakeGenerator := &policiesfakes.FakeGenerator{} - fakeGenerator.GenerateForLocationReturns( - policies.GenerateResultFiles{ - { - Name: "ext-policy.conf", - Content: []byte("external policy conf"), - }, + fakeGenerator.GenerateForLocationReturns(policies.GenerateResultFiles{ + { + Name: "ext-policy.conf", + Content: []byte("external policy conf"), }, - ) - fakeGenerator.GenerateForInternalLocationReturns( - policies.GenerateResultFiles{ - { - Name: "int-policy.conf", - Content: []byte("internal policy conf"), - }, + }) + fakeGenerator.GenerateForInternalLocationReturns(policies.GenerateResultFiles{ + { + Name: "int-policy.conf", + Content: []byte("internal policy conf"), }, - ) + }) locations, matches, grpc := createLocations(&httpServer, "1", fakeGenerator) @@ -2136,32 +2124,28 @@ func TestCreateLocationsRootPath(t *testing.T) { } if rootPath { - rules = append( - rules, dataplane.PathRule{ - Path: "/", - MatchRules: []dataplane.MatchRule{ - { - Match: dataplane.Match{}, - BackendGroup: fooGroup, - }, + rules = append(rules, dataplane.PathRule{ + Path: "/", + MatchRules: []dataplane.MatchRule{ + { + Match: dataplane.Match{}, + BackendGroup: fooGroup, }, }, - ) + }) } if grpc { - rules = append( - rules, dataplane.PathRule{ - Path: "/grpc", - GRPC: true, - MatchRules: []dataplane.MatchRule{ - { - Match: dataplane.Match{}, - BackendGroup: fooGroup, - }, + rules = append(rules, dataplane.PathRule{ + Path: "/grpc", + GRPC: true, + MatchRules: []dataplane.MatchRule{ + { + Match: dataplane.Match{}, + BackendGroup: fooGroup, }, }, - ) + }) } return rules @@ -2272,12 +2256,10 @@ func TestCreateLocationsRootPath(t *testing.T) { t.Parallel() g := NewWithT(t) - locs, httpMatchPair, grpc := createLocations( - &dataplane.VirtualServer{ - PathRules: test.pathRules, - Port: 80, - }, "1", &policiesfakes.FakeGenerator{}, - ) + locs, httpMatchPair, grpc := createLocations(&dataplane.VirtualServer{ + PathRules: test.pathRules, + Port: 80, + }, "1", &policiesfakes.FakeGenerator{}) g.Expect(locs).To(Equal(test.expLocations)) g.Expect(httpMatchPair).To(BeEmpty()) g.Expect(grpc).To(Equal(test.grpc)) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index db701e16a7..8baa17aa27 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -348,15 +348,13 @@ var _ = Describe("ChangeProcessor", func() { ) BeforeEach(OncePerOrdered, func() { - processor = state.NewChangeProcessorImpl( - state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }, - ) + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }) }) Describe("Process gateway resources", Ordered, func() { @@ -503,10 +501,7 @@ var _ = Describe("ChangeProcessor", func() { gw1 = createGateway( "gateway-1", createHTTPListener(), - createHTTPSListener( - httpsListenerName, - diffNsTLSSecret, - ), // cert in diff namespace than gw + createHTTPSListener(httpsListenerName, diffNsTLSSecret), // cert in diff namespace than gw createTLSListener(tlsListenerName), ) @@ -567,11 +562,8 @@ var _ = Describe("ChangeProcessor", func() { { BackendRefs: []graph.BackendRef{ { - SvcNsName: types.NamespacedName{ - Namespace: "service-ns", - Name: "service", - }, - Weight: 1, + SvcNsName: types.NamespacedName{Namespace: "service-ns", Name: "service"}, + Weight: 1, }, }, ValidMatches: true, @@ -650,11 +642,8 @@ var _ = Describe("ChangeProcessor", func() { Spec: graph.L4RouteSpec{ Hostnames: tr1.Spec.Hostnames, BackendRef: graph.BackendRef{ - SvcNsName: types.NamespacedName{ - Namespace: "tls-service-ns", - Name: "tls-service", - }, - Valid: false, + SvcNsName: types.NamespacedName{Namespace: "tls-service-ns", Name: "tls-service"}, + Valid: false, }, }, Valid: true, @@ -681,11 +670,8 @@ var _ = Describe("ChangeProcessor", func() { Spec: graph.L4RouteSpec{ Hostnames: tr2.Spec.Hostnames, BackendRef: graph.BackendRef{ - SvcNsName: types.NamespacedName{ - Namespace: "tls-service-ns", - Name: "tls-service", - }, - Valid: false, + SvcNsName: types.NamespacedName{Namespace: "tls-service-ns", Name: "tls-service"}, + Valid: false, }, }, Valid: true, @@ -715,14 +701,8 @@ var _ = Describe("ChangeProcessor", func() { Routes: map[graph.RouteKey]*graph.L7Route{routeKey1: expRouteHR1}, L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, SupportedKinds: []v1.RouteGroupKind{ - { - Kind: v1.Kind(kinds.HTTPRoute), - Group: helpers.GetPointer[v1.Group](v1.GroupName), - }, - { - Kind: v1.Kind(kinds.GRPCRoute), - Group: helpers.GetPointer[v1.Group](v1.GroupName), - }, + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, }, }, { @@ -734,14 +714,8 @@ var _ = Describe("ChangeProcessor", func() { L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, ResolvedSecret: helpers.GetPointer(client.ObjectKeyFromObject(diffNsTLSSecret)), SupportedKinds: []v1.RouteGroupKind{ - { - Kind: v1.Kind(kinds.HTTPRoute), - Group: helpers.GetPointer[v1.Group](v1.GroupName), - }, - { - Kind: v1.Kind(kinds.GRPCRoute), - Group: helpers.GetPointer[v1.Group](v1.GroupName), - }, + {Kind: v1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, + {Kind: v1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, }, }, { @@ -752,10 +726,7 @@ var _ = Describe("ChangeProcessor", func() { Routes: map[graph.RouteKey]*graph.L7Route{}, L4Routes: map[graph.L4RouteKey]*graph.L4Route{trKey1: expRouteTR1}, SupportedKinds: []v1.RouteGroupKind{ - { - Kind: v1.Kind(kinds.TLSRoute), - Group: helpers.GetPointer[v1.Group](v1.GroupName), - }, + {Kind: v1.Kind(kinds.TLSRoute), Group: helpers.GetPointer[v1.Group](v1.GroupName)}, }, }, }, @@ -793,12 +764,7 @@ var _ = Describe("ChangeProcessor", func() { changed, graphCfg := processor.Process() Expect(changed).To(Equal(state.ClusterStateChange)) Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect( - helpers.Diff( - &graph.Graph{}, - processor.GetLatestGraph(), - ), - ).To(BeEmpty()) + Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) }) }) When("Gateways don't exist", func() { @@ -809,12 +775,7 @@ var _ = Describe("ChangeProcessor", func() { changed, graphCfg := processor.Process() Expect(changed).To(Equal(state.ClusterStateChange)) Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect( - helpers.Diff( - &graph.Graph{}, - processor.GetLatestGraph(), - ), - ).To(BeEmpty()) + Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) }) }) When("the first TLSRoute is upserted", func() { @@ -824,12 +785,7 @@ var _ = Describe("ChangeProcessor", func() { changed, graphCfg := processor.Process() Expect(changed).To(Equal(state.ClusterStateChange)) Expect(helpers.Diff(&graph.Graph{}, graphCfg)).To(BeEmpty()) - Expect( - helpers.Diff( - &graph.Graph{}, - processor.GetLatestGraph(), - ), - ).To(BeEmpty()) + Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) }) }) When("the different namespace TLS Secret is upserted", func() { @@ -839,12 +795,7 @@ var _ = Describe("ChangeProcessor", func() { changed, graphCfg := processor.Process() Expect(changed).To(Equal(state.NoChange)) Expect(graphCfg).To(BeNil()) - Expect( - helpers.Diff( - &graph.Graph{}, - processor.GetLatestGraph(), - ), - ).To(BeEmpty()) + Expect(helpers.Diff(&graph.Graph{}, processor.GetLatestGraph())).To(BeEmpty()) }) }) When("the first Gateway is upserted", func() { @@ -893,12 +844,7 @@ var _ = Describe("ChangeProcessor", func() { changed, graphCfg := processor.Process() Expect(changed).To(Equal(state.ClusterStateChange)) Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) - Expect( - helpers.Diff( - expGraph, - processor.GetLatestGraph(), - ), - ).To(BeEmpty()) + Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) }) }) }) @@ -1007,10 +953,7 @@ var _ = Describe("ChangeProcessor", func() { "Backend ref to Service tls-service-ns/tls-service not permitted by any ReferenceGrant", ), } - delete( - expGraph.ReferencedServices, - types.NamespacedName{Namespace: "tls-service-ns", Name: "tls-service"}, - ) + delete(expGraph.ReferencedServices, types.NamespacedName{Namespace: "tls-service-ns", Name: "tls-service"}) expRouteTR1.Spec.BackendRef.SvcNsName = types.NamespacedName{} expGraph.ReferencedSecrets[client.ObjectKeyFromObject(diffNsTLSSecret)] = &graph.Secret{ @@ -1310,10 +1253,7 @@ var _ = Describe("ChangeProcessor", func() { Source: sameNsTLSSecret, } - delete( - expGraph.ReferencedServices, - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName, - ) + delete(expGraph.ReferencedServices, expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName) expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} changed, graphCfg := processor.Process() @@ -1357,10 +1297,7 @@ var _ = Describe("ChangeProcessor", func() { Source: sameNsTLSSecret, } - delete( - expGraph.ReferencedServices, - expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName, - ) + delete(expGraph.ReferencedServices, expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName) expRouteHR1.Spec.Rules[0].BackendRefs[0].SvcNsName = types.NamespacedName{} changed, graphCfg := processor.Process() @@ -1580,11 +1517,7 @@ var _ = Describe("ChangeProcessor", func() { testProcessChangedVal(expChanged) } - testDeleteTriggersChange := func( - obj client.Object, - nsname types.NamespacedName, - expChanged state.ChangeType, - ) { + testDeleteTriggersChange := func(obj client.Object, nsname types.NamespacedName, expChanged state.ChangeType) { processor.CaptureDeleteChange(obj, nsname) testProcessChangedVal(expChanged) } @@ -1754,10 +1687,7 @@ var _ = Describe("ChangeProcessor", func() { It("should not trigger a change", func() { testDeleteTriggersChange( noRefSlice, - types.NamespacedName{ - Namespace: noRefSlice.Namespace, - Name: noRefSlice.Name, - }, + types.NamespacedName{Namespace: noRefSlice.Namespace, Name: noRefSlice.Name}, state.NoChange, ) }) @@ -1782,10 +1712,7 @@ var _ = Describe("ChangeProcessor", func() { It("should trigger a change", func() { testDeleteTriggersChange( bazSvc1, - types.NamespacedName{ - Namespace: bazSvc1.Namespace, - Name: bazSvc1.Name, - }, + types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, state.ClusterStateChange, ) }) @@ -1809,10 +1736,7 @@ var _ = Describe("ChangeProcessor", func() { It("should trigger a change", func() { testDeleteTriggersChange( hrMultipleRules, - types.NamespacedName{ - Namespace: hrMultipleRules.Namespace, - Name: hrMultipleRules.Name, - }, + types.NamespacedName{Namespace: hrMultipleRules.Namespace, Name: hrMultipleRules.Name}, state.ClusterStateChange, ) }) @@ -1821,36 +1745,25 @@ var _ = Describe("ChangeProcessor", func() { It("should not trigger a change", func() { testDeleteTriggersChange( bazSvc1, - types.NamespacedName{ - Namespace: bazSvc1.Namespace, - Name: bazSvc1.Name, - }, + types.NamespacedName{Namespace: bazSvc1.Namespace, Name: bazSvc1.Name}, state.NoChange, ) - }, - ) + }) }) When("second referenced service is deleted", func() { It("should not trigger a change", func() { testDeleteTriggersChange( bazSvc2, - types.NamespacedName{ - Namespace: bazSvc2.Namespace, - Name: bazSvc2.Name, - }, + types.NamespacedName{Namespace: bazSvc2.Namespace, Name: bazSvc2.Name}, state.NoChange, ) - }, - ) + }) }) When("final referenced service is deleted", func() { It("should not trigger a change", func() { testDeleteTriggersChange( bazSvc3, - types.NamespacedName{ - Namespace: bazSvc3.Namespace, - Name: bazSvc3.Name, - }, + types.NamespacedName{Namespace: bazSvc3.Namespace, Name: bazSvc3.Name}, state.NoChange, ) }) @@ -1910,15 +1823,13 @@ var _ = Describe("ChangeProcessor", func() { }, }, } - processor = state.NewChangeProcessorImpl( - state.ChangeProcessorConfig{ - GatewayCtlrName: controllerName, - GatewayClassName: gcName, - Logger: zap.New(), - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }, - ) + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: controllerName, + GatewayClassName: gcName, + Logger: zap.New(), + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }) processor.CaptureUpsertChange(gc) processor.CaptureUpsertChange(gw) processor.Process() @@ -1940,10 +1851,7 @@ var _ = Describe("ChangeProcessor", func() { }) When("a namespace is deleted that is not linked to a listener", func() { It("does not trigger an update", func() { - processor.CaptureDeleteChange( - nsNoLabels, - types.NamespacedName{Name: "no-labels"}, - ) + processor.CaptureDeleteChange(nsNoLabels, types.NamespacedName{Name: "no-labels"}) changed, _ := processor.Process() Expect(changed).To(Equal(state.NoChange)) }) @@ -2205,14 +2113,8 @@ var _ = Describe("ChangeProcessor", func() { }) When("the policy is deleted", func() { It("removes the policy from the graph", func() { - processor.CaptureDeleteChange( - &ngfAPI.ClientSettingsPolicy{}, - client.ObjectKeyFromObject(csp), - ) - processor.CaptureDeleteChange( - &ngfAPI.ObservabilityPolicy{}, - client.ObjectKeyFromObject(obs), - ) + processor.CaptureDeleteChange(&ngfAPI.ClientSettingsPolicy{}, client.ObjectKeyFromObject(csp)) + processor.CaptureDeleteChange(&ngfAPI.ObservabilityPolicy{}, client.ObjectKeyFromObject(obs)) changed, graph := processor.Process() Expect(changed).To(Equal(state.ClusterStateChange)) @@ -2313,14 +2215,12 @@ var _ = Describe("ChangeProcessor", func() { ) BeforeEach(OncePerOrdered, func() { - processor = state.NewChangeProcessorImpl( - state.ChangeProcessorConfig{ - GatewayCtlrName: "test.controller", - GatewayClassName: "test-class", - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }, - ) + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: "test.controller", + GatewayClassName: "test-class", + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }) secretNsName = types.NamespacedName{Namespace: "test", Name: "tls-secret"} secret = &apiv1.Secret{ @@ -2769,10 +2669,7 @@ 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"}, - ) + processor.CaptureDeleteChange(&apiv1.Namespace{}, types.NamespacedName{Name: "ns"}) processor.CaptureDeleteChange(&apiv1.Secret{}, secretNsName) processor.CaptureDeleteChange(&apiv1.ConfigMap{}, cmNsName) @@ -2843,18 +2740,15 @@ var _ = Describe("ChangeProcessor", func() { var processor state.ChangeProcessor BeforeEach(func() { - processor = state.NewChangeProcessorImpl( - state.ChangeProcessorConfig{ - GatewayCtlrName: "test.controller", - GatewayClassName: "my-class", - Validators: createAlwaysValidValidators(), - MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), - }, - ) + processor = state.NewChangeProcessorImpl(state.ChangeProcessorConfig{ + GatewayCtlrName: "test.controller", + GatewayClassName: "my-class", + Validators: createAlwaysValidValidators(), + MustExtractGVK: kinds.NewMustExtractGKV(createScheme()), + }) }) - DescribeTable( - "CaptureUpsertChange must panic", + DescribeTable("CaptureUpsertChange must panic", func(obj client.Object) { process := func() { processor.CaptureUpsertChange(obj) diff --git a/internal/mode/static/state/dataplane/configuration_test.go b/internal/mode/static/state/dataplane/configuration_test.go index ab1a3a005a..fbf112a13d 100644 --- a/internal/mode/static/state/dataplane/configuration_test.go +++ b/internal/mode/static/state/dataplane/configuration_test.go @@ -523,19 +523,15 @@ func TestBuildConfiguration(t *testing.T) { Valid: true, } - TR1Key := graph.L4RouteKey{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "secure-app", - }, - } + TR1Key := graph.L4RouteKey{NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "secure-app", + }} - TR2Key := graph.L4RouteKey{ - NamespacedName: types.NamespacedName{ - Namespace: "default", - Name: "secure-app2", - }, - } + TR2Key := graph.L4RouteKey{NamespacedName: types.NamespacedName{ + Namespace: "default", + Name: "secure-app2", + }} httpsHR7, expHTTPSHR7Groups, httpsRouteHR7 := createTestResources( "https-hr-7", @@ -881,89 +877,77 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - }, - ) + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + }) return g }), - expConf: getModifiedExpectedConfiguration( - func(conf Configuration) Configuration { - conf.SSLServers = []VirtualServer{} - conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} - return conf - }, - ), + expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { + conf.SSLServers = []VirtualServer{} + conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{} + return conf + }), msg: "http listener with no routes", }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1Invalid): routeHR1Invalid, - }, + g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1Invalid): routeHR1Invalid, }, - { - Name: "listener-443-1", - Source: listener443, // nil hostname - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR1Invalid): httpsRouteHR1Invalid, - }, - ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-1", + Source: listener443, // nil hostname + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR1Invalid): httpsRouteHR1Invalid, }, - }..., - ) + ResolvedSecret: &secret1NsName, + }, + }...) g.Routes[graph.CreateRouteKey(hr1Invalid)] = routeHR1Invalid g.ReferencedSecrets[secret1NsName] = secret1 return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = []VirtualServer{ - { - IsDefault: true, - Port: 80, - }, - } - conf.SSLServers = append( - conf.SSLServers, VirtualServer{ - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - ) + conf.HTTPServers = []VirtualServer{{ + IsDefault: true, + Port: 80, + }} + conf.SSLServers = append(conf.SSLServers, VirtualServer{ + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }) return conf }), msg: "http and https listeners with no valid routes", }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-443-1", - Source: listener443, // nil hostname - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - ResolvedSecret: &secret1NsName, - }, - { - Name: "listener-443-with-hostname", - Source: listener443WithHostname, // non-nil hostname - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - ResolvedSecret: &secret2NsName, - }, - }..., - ) + g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-443-1", + Source: listener443, // nil hostname + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-with-hostname", + Source: listener443WithHostname, // non-nil hostname + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + ResolvedSecret: &secret2NsName, + }, + }...) g.ReferencedSecrets = map[types.NamespacedName]*graph.Secret{ secret1NsName: secret1, secret2NsName: secret2, @@ -972,20 +956,18 @@ func TestBuildConfiguration(t *testing.T) { }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { conf.HTTPServers = []VirtualServer{} - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: string(hostname), - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }..., - ) + conf.SSLServers = append(conf.SSLServers, []VirtualServer{ + { + Hostname: string(hostname), + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }...) conf.SSLKeyPairs["ssl_keypair_test_secret-2"] = SSLKeyPair{ Cert: []byte("cert-2"), Key: []byte("privateKey-2"), @@ -996,14 +978,12 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "invalid-listener", - Source: invalidListener, - Valid: false, - ResolvedSecret: &secret1NsName, - }, - ) + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "invalid-listener", + Source: invalidListener, + Valid: false, + ResolvedSecret: &secret1NsName, + }) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR1): httpsRouteHR1, graph.CreateRouteKey(httpsHR2): httpsRouteHR2, @@ -1021,17 +1001,15 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): routeHR1, - graph.CreateRouteKey(hr2): routeHR2, - }, + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): routeHR1, + graph.CreateRouteKey(hr2): routeHR2, }, - ) + }) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr1): routeHR1, graph.CreateRouteKey(hr2): routeHR2, @@ -1039,42 +1017,40 @@ func TestBuildConfiguration(t *testing.T) { return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, []VirtualServer{ - { - Hostname: "bar.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR2Groups[0], - Source: &hr2.ObjectMeta, - }, + conf.HTTPServers = append(conf.HTTPServers, []VirtualServer{ + { + Hostname: "bar.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR2Groups[0], + Source: &hr2.ObjectMeta, }, }, }, - Port: 80, }, - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR1Groups[0], - Source: &hr1.ObjectMeta, - }, + Port: 80, + }, + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR1Groups[0], + Source: &hr1.ObjectMeta, }, }, }, - Port: 80, }, - }..., - ) + Port: 80, + }, + }...) conf.SSLServers = []VirtualServer{} conf.Upstreams = []Upstream{fooUpstream} conf.BackendGroups = []BackendGroup{expHR1Groups[0], expHR2Groups[0]} @@ -1086,39 +1062,35 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(gr): routeGR, - }, + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(gr): routeGR, }, - ) + }) g.Routes[graph.CreateRouteKey(gr)] = routeGR return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, VirtualServer{ - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - GRPC: true, - MatchRules: []MatchRule{ - { - BackendGroup: expGRGroups[0], - Source: &gr.ObjectMeta, - }, + conf.HTTPServers = append(conf.HTTPServers, VirtualServer{ + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + GRPC: true, + MatchRules: []MatchRule{ + { + BackendGroup: expGRGroups[0], + Source: &gr.ObjectMeta, }, }, }, - Port: 80, }, - ) + Port: 80, + }) conf.SSLServers = []VirtualServer{} conf.Upstreams = append(conf.Upstreams, fooUpstream) conf.BackendGroups = []BackendGroup{expGRGroups[0]} @@ -1129,29 +1101,27 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR1): httpsRouteHR1, - graph.CreateRouteKey(httpsHR2): httpsRouteHR2, - }, - ResolvedSecret: &secret1NsName, + g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR1): httpsRouteHR1, + graph.CreateRouteKey(httpsHR2): httpsRouteHR2, }, - { - Name: "listener-443-with-hostname", - Source: listener443WithHostname, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - }, - ResolvedSecret: &secret2NsName, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-with-hostname", + Source: listener443WithHostname, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, }, - }..., - ) + ResolvedSecret: &secret2NsName, + }, + }...) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr1): httpsRouteHR1, graph.CreateRouteKey(hr2): httpsRouteHR2, @@ -1165,72 +1135,66 @@ func TestBuildConfiguration(t *testing.T) { }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { conf.HTTPServers = []VirtualServer{} - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "bar.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR2Groups[0], - Source: &httpsHR2.ObjectMeta, - }, + conf.SSLServers = append(conf.SSLServers, []VirtualServer{ + { + Hostname: "bar.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR2Groups[0], + Source: &httpsHR2.ObjectMeta, }, }, }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, }, - { - Hostname: "example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR5Groups[0], - Source: &httpsHR5.ObjectMeta, - }, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + { + Hostname: "example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR5Groups[0], + Source: &httpsHR5.ObjectMeta, }, }, }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, - Port: 443, }, - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR1Groups[0], - Source: &httpsHR1.ObjectMeta, - }, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, + Port: 443, + }, + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR1Groups[0], + Source: &httpsHR1.ObjectMeta, }, }, }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }..., - ) + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }...) conf.Upstreams = []Upstream{fooUpstream} - conf.BackendGroups = []BackendGroup{ - expHTTPSHR1Groups[0], - expHTTPSHR2Groups[0], - expHTTPSHR5Groups[0], - } + conf.BackendGroups = []BackendGroup{expHTTPSHR1Groups[0], expHTTPSHR2Groups[0], expHTTPSHR5Groups[0]} conf.SSLKeyPairs = map[SSLKeyPairID]SSLKeyPair{ "ssl_keypair_test_secret-1": { Cert: []byte("cert-1"), @@ -1247,29 +1211,27 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr3): routeHR3, - graph.CreateRouteKey(hr4): routeHR4, - }, + g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr3): routeHR3, + graph.CreateRouteKey(hr4): routeHR4, }, - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR3): httpsRouteHR3, - graph.CreateRouteKey(httpsHR4): httpsRouteHR4, - }, - ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR3): httpsRouteHR3, + graph.CreateRouteKey(httpsHR4): httpsRouteHR4, }, - }..., - ) + ResolvedSecret: &secret1NsName, + }, + }...) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr3): routeHR3, graph.CreateRouteKey(hr4): routeHR4, @@ -1282,100 +1244,96 @@ func TestBuildConfiguration(t *testing.T) { return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR3Groups[0], - Source: &hr3.ObjectMeta, - }, - { - BackendGroup: expHR4Groups[1], - Source: &hr4.ObjectMeta, - }, + conf.HTTPServers = append(conf.HTTPServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR3Groups[0], + Source: &hr3.ObjectMeta, + }, + { + BackendGroup: expHR4Groups[1], + Source: &hr4.ObjectMeta, }, }, - { - Path: "/fourth", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR4Groups[0], - Source: &hr4.ObjectMeta, - }, + }, + { + Path: "/fourth", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR4Groups[0], + Source: &hr4.ObjectMeta, }, }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR3Groups[1], - Source: &hr3.ObjectMeta, - }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR3Groups[1], + Source: &hr3.ObjectMeta, }, }, }, - Port: 80, }, - }..., - ) - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR3Groups[0], - Source: &httpsHR3.ObjectMeta, - }, - { - BackendGroup: expHTTPSHR4Groups[1], - Source: &httpsHR4.ObjectMeta, - }, + Port: 80, + }, + }...) + conf.SSLServers = append(conf.SSLServers, []VirtualServer{ + { + Hostname: "foo.example.com", + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR3Groups[0], + Source: &httpsHR3.ObjectMeta, + }, + { + BackendGroup: expHTTPSHR4Groups[1], + Source: &httpsHR4.ObjectMeta, }, }, - { - Path: "/fourth", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR4Groups[0], - Source: &httpsHR4.ObjectMeta, - }, + }, + { + Path: "/fourth", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR4Groups[0], + Source: &httpsHR4.ObjectMeta, }, }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR3Groups[1], - Source: &httpsHR3.ObjectMeta, - }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR3Groups[1], + Source: &httpsHR3.ObjectMeta, }, }, }, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, }, - }..., - ) + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }...) conf.Upstreams = append(conf.Upstreams, fooUpstream) conf.BackendGroups = []BackendGroup{ expHR3Groups[0], @@ -1393,44 +1351,42 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr3): routeHR3, - }, + g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr3): routeHR3, }, - { - Name: "listener-8080", - Source: listener8080, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr8): routeHR8, - }, + }, + { + Name: "listener-8080", + Source: listener8080, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr8): routeHR8, }, - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR3): httpsRouteHR3, - }, - ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR3): httpsRouteHR3, }, - { - Name: "listener-8443", - Source: listener8443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR7): httpsRouteHR7, - }, - ResolvedSecret: &secret1NsName, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-8443", + Source: listener8443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR7): httpsRouteHR7, }, - }..., - ) + ResolvedSecret: &secret1NsName, + }, + }...) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr3): routeHR3, graph.CreateRouteKey(hr8): routeHR8, @@ -1443,138 +1399,134 @@ func TestBuildConfiguration(t *testing.T) { return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR3Groups[0], - Source: &hr3.ObjectMeta, - }, + conf.HTTPServers = append(conf.HTTPServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR3Groups[0], + Source: &hr3.ObjectMeta, }, }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR3Groups[1], - Source: &hr3.ObjectMeta, - }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR3Groups[1], + Source: &hr3.ObjectMeta, }, }, }, - Port: 80, - }, - { - IsDefault: true, - Port: 8080, }, - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR8Groups[0], - Source: &hr8.ObjectMeta, - }, + Port: 80, + }, + { + IsDefault: true, + Port: 8080, + }, + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR8Groups[0], + Source: &hr8.ObjectMeta, }, }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR8Groups[1], - Source: &hr8.ObjectMeta, - }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR8Groups[1], + Source: &hr8.ObjectMeta, }, }, }, - Port: 8080, }, - }..., - ) - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR3Groups[0], - Source: &httpsHR3.ObjectMeta, - }, + Port: 8080, + }, + }...) + conf.SSLServers = append(conf.SSLServers, []VirtualServer{ + { + Hostname: "foo.example.com", + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR3Groups[0], + Source: &httpsHR3.ObjectMeta, }, }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR3Groups[1], - Source: &httpsHR3.ObjectMeta, - }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR3Groups[1], + Source: &httpsHR3.ObjectMeta, }, }, }, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - { - IsDefault: true, - Port: 8443, }, - { - Hostname: "foo.example.com", - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR7Groups[0], - Source: &httpsHR7.ObjectMeta, - }, + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + { + IsDefault: true, + Port: 8443, + }, + { + Hostname: "foo.example.com", + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR7Groups[0], + Source: &httpsHR7.ObjectMeta, }, }, - { - Path: "/third", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR7Groups[1], - Source: &httpsHR7.ObjectMeta, - }, + }, + { + Path: "/third", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR7Groups[1], + Source: &httpsHR7.ObjectMeta, }, }, }, - Port: 8443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 8443, }, - }..., - ) + Port: 8443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 8443, + }, + }...) conf.Upstreams = append(conf.Upstreams, fooUpstream) conf.BackendGroups = []BackendGroup{ expHR3Groups[0], @@ -1593,16 +1545,14 @@ func TestBuildConfiguration(t *testing.T) { { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { g.GatewayClass.Valid = false - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): routeHR1, - }, + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): routeHR1, }, - ) + }) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr1): routeHR1, } @@ -1614,16 +1564,14 @@ func TestBuildConfiguration(t *testing.T) { { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { g.GatewayClass.Valid = false - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr1): routeHR1, - }, + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr1): routeHR1, }, - ) + }) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr1): routeHR1, } @@ -1642,59 +1590,55 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr5): routeHR5, - }, + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr5): routeHR5, }, - ) + }) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr5): routeHR5, } return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - Source: &hr5.ObjectMeta, - BackendGroup: expHR5Groups[0], - Filters: HTTPFilters{ - RequestRedirect: &expRedirect, - SnippetsFilters: []SnippetsFilter{expExtRefFilter}, - }, + conf.HTTPServers = append(conf.HTTPServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + Source: &hr5.ObjectMeta, + BackendGroup: expHR5Groups[0], + Filters: HTTPFilters{ + RequestRedirect: &expRedirect, + SnippetsFilters: []SnippetsFilter{expExtRefFilter}, }, }, }, - { - Path: invalidFiltersPath, - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - Source: &hr5.ObjectMeta, - BackendGroup: expHR5Groups[1], - Filters: HTTPFilters{ - InvalidFilter: &InvalidHTTPFilter{}, - }, + }, + { + Path: invalidFiltersPath, + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + Source: &hr5.ObjectMeta, + BackendGroup: expHR5Groups[1], + Filters: HTTPFilters{ + InvalidFilter: &InvalidHTTPFilter{}, }, }, }, }, - Port: 80, }, - }..., - ) + Port: 80, + }, + }...) conf.SSLServers = []VirtualServer{} conf.Upstreams = []Upstream{fooUpstream} conf.BackendGroups = []BackendGroup{expHR5Groups[0], expHR5Groups[1]} @@ -1705,57 +1649,55 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr6): routeHR6, - }, - }, - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR6): httpsRouteHR6, - }, - ResolvedSecret: &secret1NsName, + g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr6): routeHR6, }, - { - Name: "listener-443-2", - Source: listener443_2, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{ - TR1Key: &tlsTR1, - TR2Key: &invalidBackendRefTR2, - }, - ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR6): httpsRouteHR6, }, - { - Name: "listener-444-3", - Source: listener444_3, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{ - TR1Key: &tlsTR1, - TR2Key: &invalidBackendRefTR2, - }, - ResolvedSecret: &secret1NsName, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-2", + Source: listener443_2, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{ + TR1Key: &tlsTR1, + TR2Key: &invalidBackendRefTR2, }, - { - Name: "listener-443-4", - Source: listener443_4, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, - ResolvedSecret: &secret1NsName, + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-444-3", + Source: listener444_3, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{ + TR1Key: &tlsTR1, + TR2Key: &invalidBackendRefTR2, }, - }..., - ) + ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443-4", + Source: listener443_4, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + L4Routes: map[graph.L4RouteKey]*graph.L4Route{}, + ResolvedSecret: &secret1NsName, + }, + }...) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr6): routeHR6, graph.CreateRouteKey(httpsHR6): httpsRouteHR6, @@ -1770,52 +1712,48 @@ func TestBuildConfiguration(t *testing.T) { return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/valid", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR6Groups[0], - Source: &hr6.ObjectMeta, - }, + conf.HTTPServers = append(conf.HTTPServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/valid", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR6Groups[0], + Source: &hr6.ObjectMeta, }, }, }, - Port: 80, }, - }..., - ) - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - PathRules: []PathRule{ - { - Path: "/valid", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR6Groups[0], - Source: &httpsHR6.ObjectMeta, - }, + Port: 80, + }, + }...) + conf.SSLServers = append(conf.SSLServers, []VirtualServer{ + { + Hostname: "foo.example.com", + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + PathRules: []PathRule{ + { + Path: "/valid", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR6Groups[0], + Source: &httpsHR6.ObjectMeta, }, }, }, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, }, - }..., - ) + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }...) conf.Upstreams = []Upstream{fooUpstream} conf.BackendGroups = []BackendGroup{expHR6Groups[0], expHTTPSHR6Groups[0]} conf.StreamUpstreams = []Upstream{ @@ -1855,52 +1793,48 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hr7): routeHR7, - }, + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hr7): routeHR7, }, - ) + }) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hr7): routeHR7, } return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.HTTPServers = append( - conf.HTTPServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/valid", - PathType: PathTypeExact, - MatchRules: []MatchRule{ - { - BackendGroup: expHR7Groups[1], - Source: &hr7.ObjectMeta, - }, + conf.HTTPServers = append(conf.HTTPServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/valid", + PathType: PathTypeExact, + MatchRules: []MatchRule{ + { + BackendGroup: expHR7Groups[1], + Source: &hr7.ObjectMeta, }, }, - { - Path: "/valid", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHR7Groups[0], - Source: &hr7.ObjectMeta, - }, + }, + { + Path: "/valid", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHR7Groups[0], + Source: &hr7.ObjectMeta, }, }, }, - Port: 80, }, - }..., - ) + Port: 80, + }, + }...) conf.SSLServers = []VirtualServer{} conf.Upstreams = []Upstream{fooUpstream} conf.BackendGroups = []BackendGroup{expHR7Groups[0], expHR7Groups[1]} @@ -1911,28 +1845,26 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-443-with-hostname", - Source: listener443WithHostname, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - }, - ResolvedSecret: &secret2NsName, + g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-443-with-hostname", + Source: listener443WithHostname, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, }, - { - Name: "listener-443-1", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR5): httpsRouteHR5, - }, - ResolvedSecret: &secret1NsName, + ResolvedSecret: &secret2NsName, + }, + { + Name: "listener-443-1", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR5): httpsRouteHR5, }, - }..., - ) + ResolvedSecret: &secret1NsName, + }, + }...) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR5): httpsRouteHR5, } @@ -1943,37 +1875,35 @@ func TestBuildConfiguration(t *testing.T) { return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - // duplicate match rules since two listeners both match this route's hostname - { - BackendGroup: expHTTPSHR5Groups[0], - Source: &httpsHR5.ObjectMeta, - }, - { - BackendGroup: expHTTPSHR5Groups[0], - Source: &httpsHR5.ObjectMeta, - }, + conf.SSLServers = append(conf.SSLServers, []VirtualServer{ + { + Hostname: "example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + // duplicate match rules since two listeners both match this route's hostname + { + BackendGroup: expHTTPSHR5Groups[0], + Source: &httpsHR5.ObjectMeta, + }, + { + BackendGroup: expHTTPSHR5Groups[0], + Source: &httpsHR5.ObjectMeta, }, }, }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, - Port: 443, }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - }..., - ) + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-2"}, + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }...) conf.HTTPServers = []VirtualServer{} conf.Upstreams = []Upstream{fooUpstream} conf.BackendGroups = []BackendGroup{expHTTPSHR5Groups[0]} @@ -1993,17 +1923,15 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-443", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR8): httpsRouteHR8, - }, - ResolvedSecret: &secret1NsName, + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-443", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR8): httpsRouteHR8, }, - ) + ResolvedSecret: &secret1NsName, + }) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR8): httpsRouteHR8, } @@ -2014,36 +1942,34 @@ func TestBuildConfiguration(t *testing.T) { return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR8Groups[0], - Source: &httpsHR8.ObjectMeta, - }, - { - BackendGroup: expHTTPSHR8Groups[1], - Source: &httpsHR8.ObjectMeta, - }, + conf.SSLServers = append(conf.SSLServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR8Groups[0], + Source: &httpsHR8.ObjectMeta, + }, + { + BackendGroup: expHTTPSHR8Groups[1], + Source: &httpsHR8.ObjectMeta, }, }, }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, }, - }..., - ) + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }...) conf.HTTPServers = []VirtualServer{} conf.Upstreams = []Upstream{fooUpstream} conf.BackendGroups = []BackendGroup{expHTTPSHR8Groups[0], expHTTPSHR8Groups[1]} @@ -2056,17 +1982,15 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-443", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHR9): httpsRouteHR9, - }, - ResolvedSecret: &secret1NsName, + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-443", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHR9): httpsRouteHR9, }, - ) + ResolvedSecret: &secret1NsName, + }) g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(httpsHR9): httpsRouteHR9, } @@ -2077,36 +2001,34 @@ func TestBuildConfiguration(t *testing.T) { return g }), expConf: getModifiedExpectedConfiguration(func(conf Configuration) Configuration { - conf.SSLServers = append( - conf.SSLServers, []VirtualServer{ - { - Hostname: "foo.example.com", - PathRules: []PathRule{ - { - Path: "/", - PathType: PathTypePrefix, - MatchRules: []MatchRule{ - { - BackendGroup: expHTTPSHR9Groups[0], - Source: &httpsHR9.ObjectMeta, - }, - { - BackendGroup: expHTTPSHR9Groups[1], - Source: &httpsHR9.ObjectMeta, - }, + conf.SSLServers = append(conf.SSLServers, []VirtualServer{ + { + Hostname: "foo.example.com", + PathRules: []PathRule{ + { + Path: "/", + PathType: PathTypePrefix, + MatchRules: []MatchRule{ + { + BackendGroup: expHTTPSHR9Groups[0], + Source: &httpsHR9.ObjectMeta, + }, + { + BackendGroup: expHTTPSHR9Groups[1], + Source: &httpsHR9.ObjectMeta, }, }, }, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, - }, - { - Hostname: wildcardHostname, - SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, - Port: 443, }, - }..., - ) + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + { + Hostname: wildcardHostname, + SSL: &SSL{KeyPairID: "ssl_keypair_test_secret-1"}, + Port: 443, + }, + }...) conf.HTTPServers = []VirtualServer{} conf.Upstreams = []Upstream{fooUpstream} conf.BackendGroups = []BackendGroup{expHTTPSHR9Groups[0], expHTTPSHR9Groups[1]} @@ -2123,14 +2045,12 @@ func TestBuildConfiguration(t *testing.T) { Name: "gw", Namespace: "ns", } - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }, - ) + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }) g.NginxProxy = nginxProxy return g }), @@ -2157,14 +2077,12 @@ func TestBuildConfiguration(t *testing.T) { Name: "gw", Namespace: "ns", } - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }, - ) + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }) g.NginxProxy = &graph.NginxProxy{ Valid: false, Source: &ngfAPI.NginxProxy{ @@ -2190,27 +2108,25 @@ func TestBuildConfiguration(t *testing.T) { }, { graph: getModifiedGraph(func(g *graph.Graph) *graph.Graph { - g.Gateway.Listeners = append( - g.Gateway.Listeners, []*graph.Listener{ - { - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, - }, + g.Gateway.Listeners = append(g.Gateway.Listeners, []*graph.Listener{ + { + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, }, - { - Name: "listener-443", - Source: listener443, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{ - graph.CreateRouteKey(httpsHRWithPolicy): l7HTTPSRouteWithPolicy, - }, - ResolvedSecret: &secret1NsName, + }, + { + Name: "listener-443", + Source: listener443, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{ + graph.CreateRouteKey(httpsHRWithPolicy): l7HTTPSRouteWithPolicy, }, - }..., - ) + ResolvedSecret: &secret1NsName, + }, + }...) g.Gateway.Policies = []*graph.Policy{gwPolicy1, gwPolicy2} g.Routes = map[graph.RouteKey]*graph.L7Route{ graph.CreateRouteKey(hrWithPolicy): l7RouteWithPolicy, @@ -2291,14 +2207,12 @@ func TestBuildConfiguration(t *testing.T) { Name: "gw", Namespace: "ns", } - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }, - ) + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }) g.NginxProxy = nginxProxyIPv4 return g }), @@ -2316,14 +2230,12 @@ func TestBuildConfiguration(t *testing.T) { Name: "gw", Namespace: "ns", } - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }, - ) + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }) g.NginxProxy = nginxProxyIPv6 return g }), @@ -2341,14 +2253,12 @@ func TestBuildConfiguration(t *testing.T) { Name: "gw", Namespace: "ns", } - g.Gateway.Listeners = append( - g.Gateway.Listeners, &graph.Listener{ - Name: "listener-80-1", - Source: listener80, - Valid: true, - Routes: map[graph.RouteKey]*graph.L7Route{}, - }, - ) + g.Gateway.Listeners = append(g.Gateway.Listeners, &graph.Listener{ + Name: "listener-80-1", + Source: listener80, + Valid: true, + Routes: map[graph.RouteKey]*graph.L7Route{}, + }) g.NginxProxy = &graph.NginxProxy{ Valid: true, Source: &ngfAPI.NginxProxy{ @@ -3013,35 +2923,33 @@ func TestBuildUpstreams(t *testing.T) { } fakeResolver := &resolverfakes.FakeServiceResolver{} - fakeResolver.ResolveCalls( - func( - _ context.Context, - svcNsName types.NamespacedName, - _ apiv1.ServicePort, - _ []discoveryV1.AddressType, - ) ([]resolver.Endpoint, error) { - switch svcNsName.Name { - case "bar": - return barEndpoints, nil - case "baz": - return bazEndpoints, nil - case "baz2": - return baz2Endpoints, nil - case "empty-endpoints": - return []resolver.Endpoint{}, errors.New(emptyEndpointsErrMsg) - case "foo": - return fooEndpoints, nil - case "nil-endpoints": - return nil, errors.New(nilEndpointsErrMsg) - case "abc": - return abcEndpoints, nil - case "ipv6-endpoints": - return ipv6Endpoints, nil - default: - return nil, fmt.Errorf("unexpected service %s", svcNsName.Name) - } - }, - ) + fakeResolver.ResolveCalls(func( + _ context.Context, + svcNsName types.NamespacedName, + _ apiv1.ServicePort, + _ []discoveryV1.AddressType, + ) ([]resolver.Endpoint, error) { + switch svcNsName.Name { + case "bar": + return barEndpoints, nil + case "baz": + return bazEndpoints, nil + case "baz2": + return baz2Endpoints, nil + case "empty-endpoints": + return []resolver.Endpoint{}, errors.New(emptyEndpointsErrMsg) + case "foo": + return fooEndpoints, nil + case "nil-endpoints": + return nil, errors.New(nilEndpointsErrMsg) + case "abc": + return abcEndpoints, nil + case "ipv6-endpoints": + return ipv6Endpoints, nil + default: + return nil, fmt.Errorf("unexpected service %s", svcNsName.Name) + } + }) g := NewWithT(t) @@ -3373,14 +3281,12 @@ func TestBuildTelemetry(t *testing.T) { }, }, }, - expTelemetry: createModifiedTelemetry( - func(t Telemetry) Telemetry { - t.Ratios = []Ratio{ - {Name: "$otel_ratio_25", Value: 25}, - } - return t - }, - ), + expTelemetry: createModifiedTelemetry(func(t Telemetry) Telemetry { + t.Ratios = []Ratio{ + {Name: "$otel_ratio_25", Value: 25}, + } + return t + }), msg: "Telemetry configured with observability policy ratio", }, { @@ -3444,15 +3350,13 @@ func TestBuildTelemetry(t *testing.T) { }, }, }, - expTelemetry: createModifiedTelemetry( - func(t Telemetry) Telemetry { - t.Ratios = []Ratio{ - {Name: "$otel_ratio_25", Value: 25}, - {Name: "$otel_ratio_50", Value: 50}, - } - return t - }, - ), + expTelemetry: createModifiedTelemetry(func(t Telemetry) Telemetry { + t.Ratios = []Ratio{ + {Name: "$otel_ratio_25", Value: 25}, + {Name: "$otel_ratio_50", Value: 50}, + } + return t + }), msg: "Multiple policies exist; telemetry ratio is properly set", }, { @@ -3492,11 +3396,9 @@ func TestBuildTelemetry(t *testing.T) { t.Parallel() g := NewWithT(t) tel := buildTelemetry(tc.g) - sort.Slice( - tel.Ratios, func(i, j int) bool { - return tel.Ratios[i].Value < tel.Ratios[j].Value - }, - ) + sort.Slice(tel.Ratios, func(i, j int) bool { + return tel.Ratios[i].Value < tel.Ratios[j].Value + }) g.Expect(tel).To(Equal(tc.expTelemetry)) }) } diff --git a/internal/mode/static/state/graph/common_filter.go b/internal/mode/static/state/graph/common_filter.go index 070d31b76a..5eba3964c0 100644 --- a/internal/mode/static/state/graph/common_filter.go +++ b/internal/mode/static/state/graph/common_filter.go @@ -242,20 +242,15 @@ func validateFilterHeaderModifierFields( allErrs = append(allErrs, validateRequestHeadersCaseInsensitiveUnique( headerModifier.Add, headerModifierPath.Child(add), - )..., - ) - allErrs = append( - allErrs, validateRequestHeadersCaseInsensitiveUnique( - headerModifier.Set, - headerModifierPath.Child(set), - )..., - ) - allErrs = append( - allErrs, validateRequestHeaderStringCaseInsensitiveUnique( - headerModifier.Remove, - headerModifierPath.Child(remove), - )..., - ) + )...) + allErrs = append(allErrs, validateRequestHeadersCaseInsensitiveUnique( + headerModifier.Set, + headerModifierPath.Child(set), + )...) + allErrs = append(allErrs, validateRequestHeaderStringCaseInsensitiveUnique( + headerModifier.Remove, + headerModifierPath.Child(remove), + )...) for _, h := range headerModifier.Add { if err := validator.ValidateFilterHeaderName(string(h.Name)); err != nil { @@ -297,31 +292,25 @@ func validateFilterResponseHeaderModifier( } var allErrs field.ErrorList - allErrs = append( - allErrs, validateResponseHeaders( - responseHeaderModifier.Add, - filterPath.Child(add), - )..., - ) + allErrs = append(allErrs, validateResponseHeaders( + responseHeaderModifier.Add, + filterPath.Child(add), + )...) - allErrs = append( - allErrs, validateResponseHeaders( - responseHeaderModifier.Set, - filterPath.Child(set), - )..., - ) + allErrs = append(allErrs, validateResponseHeaders( + responseHeaderModifier.Set, + filterPath.Child(set), + )...) var removeHeaders []v1.HTTPHeader for _, h := range responseHeaderModifier.Remove { removeHeaders = append(removeHeaders, v1.HTTPHeader{Name: v1.HTTPHeaderName(h)}) } - allErrs = append( - allErrs, validateResponseHeaders( - removeHeaders, - filterPath.Child(remove), - )..., - ) + allErrs = append(allErrs, validateResponseHeaders( + removeHeaders, + filterPath.Child(remove), + )...) return allErrs } diff --git a/internal/mode/static/state/graph/grpcroute_test.go b/internal/mode/static/state/graph/grpcroute_test.go index 643a33f5d9..1045939bfd 100644 --- a/internal/mode/static/state/graph/grpcroute_test.go +++ b/internal/mode/static/state/graph/grpcroute_test.go @@ -203,32 +203,30 @@ func TestBuildGRPCRoutes(t *testing.T) { } for _, test := range tests { - t.Run( - test.name, func(t *testing.T) { - t.Parallel() - g := NewWithT(t) + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := NewWithT(t) - snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ - client.ObjectKeyFromObject(sf): { - Source: sf, - Valid: true, - Snippets: map[ngfAPI.NginxContext]string{ - ngfAPI.NginxContextHTTP: "http snippet", - }, + snippetsFilters := map[types.NamespacedName]*SnippetsFilter{ + client.ObjectKeyFromObject(sf): { + Source: sf, + Valid: true, + Snippets: map[ngfAPI.NginxContext]string{ + ngfAPI.NginxContextHTTP: "http snippet", }, - } + }, + } - routes := buildRoutesForGateways( - validator, - map[types.NamespacedName]*v1.HTTPRoute{}, - grRoutes, - test.gwNsNames, - npCfg, - snippetsFilters, - ) - g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) - }, - ) + routes := buildRoutesForGateways( + validator, + map[types.NamespacedName]*v1.HTTPRoute{}, + grRoutes, + test.gwNsNames, + npCfg, + snippetsFilters, + ) + g.Expect(helpers.Diff(test.expected, routes)).To(BeEmpty()) + }) } } diff --git a/internal/mode/static/state/graph/route_common_test.go b/internal/mode/static/state/graph/route_common_test.go index c2371c9ede..e0d3db46df 100644 --- a/internal/mode/static/state/graph/route_common_test.go +++ b/internal/mode/static/state/graph/route_common_test.go @@ -238,14 +238,8 @@ func TestBindRouteToListeners(t *testing.T) { Attachable: true, Routes: map[RouteKey]*L7Route{}, SupportedKinds: []gatewayv1.RouteGroupKind{ - { - Kind: gatewayv1.Kind(kinds.HTTPRoute), - Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName), - }, - { - Kind: gatewayv1.Kind(kinds.GRPCRoute), - Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName), - }, + {Kind: gatewayv1.Kind(kinds.HTTPRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, + {Kind: gatewayv1.Kind(kinds.GRPCRoute), Group: helpers.GetPointer[gatewayv1.Group](gatewayv1.GroupName)}, }, } } @@ -1826,7 +1820,9 @@ func TestBindL4RouteToListeners(t *testing.T) { Listeners: []*Listener{ createModifiedListener("listener-443", func(l *Listener) { l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{From: helpers.GetPointer(gatewayv1.FromNamespaces("Same"))}, + Namespaces: &gatewayv1.RouteNamespaces{From: helpers.GetPointer( + gatewayv1.FromNamespaces("Same"), + )}, } }), }, @@ -1850,7 +1846,9 @@ func TestBindL4RouteToListeners(t *testing.T) { expectedGatewayListeners: []*Listener{ createModifiedListener("listener-443", func(l *Listener) { l.Source.AllowedRoutes = &gatewayv1.AllowedRoutes{ - Namespaces: &gatewayv1.RouteNamespaces{From: helpers.GetPointer(gatewayv1.FromNamespaces("Same"))}, + Namespaces: &gatewayv1.RouteNamespaces{From: helpers.GetPointer( + gatewayv1.FromNamespaces("Same"), + )}, } }), }, @@ -2133,7 +2131,13 @@ func TestBuildL4RoutesForGateways_NoGateways(t *testing.T) { refGrantResolver := newReferenceGrantResolver(nil) - g.Expect(buildL4RoutesForGateways(tlsRoutes, nil, services, nil, refGrantResolver)).To(BeNil()) + g.Expect(buildL4RoutesForGateways( + tlsRoutes, + nil, + services, + nil, + refGrantResolver, + )).To(BeNil()) } func TestTryToAttachL4RouteToListeners_NoAttachableListeners(t *testing.T) { From 2adb90e55b89a879766f1e6bb75cba92b5e2537c Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 25 Sep 2024 16:46:03 -0600 Subject: [PATCH 12/16] Some more formatting reverts --- .../static/state/change_processor_test.go | 34 ++++++++++++------- .../state/dataplane/configuration_test.go | 3 +- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index 8baa17aa27..f5cf6e8c54 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -1050,7 +1050,8 @@ var _ = Describe("ChangeProcessor", func() { Expect(changed).To(Equal(state.ClusterStateChange)) Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) + }, + ) }) When("the first TLSRoute update with a generation changed is processed", func() { It("returns populated graph", func() { @@ -1067,7 +1068,8 @@ var _ = Describe("ChangeProcessor", func() { Expect(changed).To(Equal(state.ClusterStateChange)) Expect(helpers.Diff(expGraph, graphCfg)).To(BeEmpty()) Expect(helpers.Diff(expGraph, processor.GetLatestGraph())).To(BeEmpty()) - }) + }, + ) }) When("the first Gateway update with a generation changed is processed", func() { It("returns populated graph", func() { @@ -1736,7 +1738,10 @@ var _ = Describe("ChangeProcessor", func() { It("should trigger a change", func() { testDeleteTriggersChange( hrMultipleRules, - types.NamespacedName{Namespace: hrMultipleRules.Namespace, Name: hrMultipleRules.Name}, + types.NamespacedName{ + Namespace: hrMultipleRules.Namespace, + Name: hrMultipleRules.Name, + }, state.ClusterStateChange, ) }) @@ -1877,16 +1882,19 @@ var _ = Describe("ChangeProcessor", func() { Expect(changed).To(Equal(state.ClusterStateChange)) }) }) - When("a namespace that is linked to a listener has its labels changed to no longer match a listener", func() { - It("triggers an update", func() { - nsDifferentLabels.Labels = map[string]string{ - "oranges": "bananas", - } - processor.CaptureUpsertChange(nsDifferentLabels) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) - }) + When( + "a namespace that is linked to a listener has its labels changed to no longer match a listener", + func() { + It("triggers an update", func() { + nsDifferentLabels.Labels = map[string]string{ + "oranges": "bananas", + } + processor.CaptureUpsertChange(nsDifferentLabels) + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }) + }, + ) When("a gateway changes its listener's labels", func() { It("triggers an update when a namespace that matches the new labels is created", func() { gwChangedLabel := gw.DeepCopy() diff --git a/internal/mode/static/state/dataplane/configuration_test.go b/internal/mode/static/state/dataplane/configuration_test.go index fbf112a13d..ebca43e07f 100644 --- a/internal/mode/static/state/dataplane/configuration_test.go +++ b/internal/mode/static/state/dataplane/configuration_test.go @@ -1090,7 +1090,8 @@ func TestBuildConfiguration(t *testing.T) { }, }, Port: 80, - }) + }, + ) conf.SSLServers = []VirtualServer{} conf.Upstreams = append(conf.Upstreams, fooUpstream) conf.BackendGroups = []BackendGroup{expGRGroups[0]} From 45c86b2456d01ec3dd73f3133457534ff6799b7d Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Wed, 25 Sep 2024 16:48:58 -0600 Subject: [PATCH 13/16] One more formatting revert --- .../static/state/change_processor_test.go | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/internal/mode/static/state/change_processor_test.go b/internal/mode/static/state/change_processor_test.go index f5cf6e8c54..d1ec0d3c2c 100644 --- a/internal/mode/static/state/change_processor_test.go +++ b/internal/mode/static/state/change_processor_test.go @@ -2724,24 +2724,26 @@ var _ = Describe("ChangeProcessor", func() { changed, _ := processor.Process() Expect(changed).To(Equal(state.NoChange)) }) - It("should report changed after upserting changed resources followed by upserting unrelated resources", func() { - // these are changing changes - processor.CaptureUpsertChange(gcUpdated) - processor.CaptureUpsertChange(gw1Updated) - processor.CaptureUpsertChange(hr1Updated) - processor.CaptureUpsertChange(rg1Updated) - processor.CaptureUpsertChange(btlsUpdated) - - // these are non-changing changes - processor.CaptureUpsertChange(unrelatedSvc) - processor.CaptureUpsertChange(unrelatedSlice) - processor.CaptureUpsertChange(unrelatedNS) - processor.CaptureUpsertChange(unrelatedSecret) - processor.CaptureUpsertChange(unrelatedCM) + 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) + processor.CaptureUpsertChange(btlsUpdated) - changed, _ := processor.Process() - Expect(changed).To(Equal(state.ClusterStateChange)) - }) + // these are non-changing changes + processor.CaptureUpsertChange(unrelatedSvc) + processor.CaptureUpsertChange(unrelatedSlice) + processor.CaptureUpsertChange(unrelatedNS) + processor.CaptureUpsertChange(unrelatedSecret) + processor.CaptureUpsertChange(unrelatedCM) + + changed, _ := processor.Process() + Expect(changed).To(Equal(state.ClusterStateChange)) + }, + ) }) }) Describe("Edge cases with panic", func() { From 8979e48c8d73463cfb3761e447ee28df7d214d21 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Thu, 26 Sep 2024 09:30:32 -0600 Subject: [PATCH 14/16] More typo fixes --- internal/mode/static/nginx/config/includes.go | 4 ++-- internal/mode/static/state/dataplane/types.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/mode/static/nginx/config/includes.go b/internal/mode/static/nginx/config/includes.go index b3aee6770b..81b7ff8b9f 100644 --- a/internal/mode/static/nginx/config/includes.go +++ b/internal/mode/static/nginx/config/includes.go @@ -9,7 +9,7 @@ import ( // createIncludeExecuteResultsFromServers creates a list of executeResults -- or NGINX config files -- from all // the includes in the provided servers. Since there may be duplicate includes, such as configuration for policies that -// apply to multiple route, or snippets filters that are attached to multiple routing rules, this function deduplicates +// apply to multiple routes, or snippets filters that are attached to multiple routing rules, this function deduplicates // all includes, ensuring only a single file per unique include is generated. func createIncludeExecuteResultsFromServers(servers []http.Server) []executeResult { uniqueIncludes := make(map[string][]byte) @@ -141,7 +141,7 @@ func createIncludesFromSnippets(snippets []dataplane.Snippet) []shared.Include { return deduplicateIncludes(includes) } -// createIncludeExecuteResults creates a list of executeResult -- or NGINX config files -- from a list of includes. +// createIncludeExecuteResults creates a list of executeResults -- or NGINX config files -- from a list of includes. // Used for main and http snippets only. Server and location snippets are handled by other functions above. func createIncludeExecuteResults(includes []shared.Include) []executeResult { results := make([]executeResult, 0, len(includes)) diff --git a/internal/mode/static/state/dataplane/types.go b/internal/mode/static/state/dataplane/types.go index 5d57b1b6cf..9003b3f38d 100644 --- a/internal/mode/static/state/dataplane/types.go +++ b/internal/mode/static/state/dataplane/types.go @@ -147,7 +147,7 @@ type HTTPFilters struct { } // SnippetsFilter holds the location and server snippets in a SnippetsFilter. -// The main and http snippets are store separately in Configuration.MainSnippets and BaseHTTPConfig.Snippets. +// The main and http snippets are stored separately in Configuration.MainSnippets and BaseHTTPConfig.Snippets. type SnippetsFilter struct { // LocationSnippet holds the snippet for the location context. LocationSnippet *Snippet From 1cfbac5b2d21a6f41369d1e524dd3c8f21db0424 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Thu, 26 Sep 2024 17:03:31 -0600 Subject: [PATCH 15/16] Code review --- internal/mode/static/state/dataplane/configuration_test.go | 4 ++-- internal/mode/static/state/graph/common_filter_test.go | 2 +- internal/mode/static/state/graph/httproute.go | 3 ++- internal/mode/static/state/graph/httproute_test.go | 4 +++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/internal/mode/static/state/dataplane/configuration_test.go b/internal/mode/static/state/dataplane/configuration_test.go index ebca43e07f..b0eea67b40 100644 --- a/internal/mode/static/state/dataplane/configuration_test.go +++ b/internal/mode/static/state/dataplane/configuration_test.go @@ -2518,7 +2518,7 @@ func TestCreateFilters(t *testing.T) { ngfAPI.NginxContextHTTPServerLocation: "location snippet 1", ngfAPI.NginxContextMain: "main snippet 1", ngfAPI.NginxContextHTTPServer: "server snippet 1", - ngfAPI.NginxContextHTTP: "main snippet 1", + ngfAPI.NginxContextHTTP: "http snippet 1", }, }, }, @@ -2546,7 +2546,7 @@ func TestCreateFilters(t *testing.T) { ngfAPI.NginxContextHTTPServerLocation: "location snippet 2", ngfAPI.NginxContextMain: "main snippet 2", ngfAPI.NginxContextHTTPServer: "server snippet 2", - ngfAPI.NginxContextHTTP: "main snippet 2", + ngfAPI.NginxContextHTTP: "http snippet 2", }, }, }, diff --git a/internal/mode/static/state/graph/common_filter_test.go b/internal/mode/static/state/graph/common_filter_test.go index 87310977c1..1d9a2d1fa6 100644 --- a/internal/mode/static/state/graph/common_filter_test.go +++ b/internal/mode/static/state/graph/common_filter_test.go @@ -73,7 +73,7 @@ func TestValidateFilter(t *testing.T) { { filter: Filter{ RouteType: RouteTypeHTTP, - FilterType: "RequestMirror", + FilterType: FilterRequestMirror, }, expectErrCount: 1, name: "unsupported HTTP filter type", diff --git a/internal/mode/static/state/graph/httproute.go b/internal/mode/static/state/graph/httproute.go index 31f5f425d1..8cc3b66ea1 100644 --- a/internal/mode/static/state/graph/httproute.go +++ b/internal/mode/static/state/graph/httproute.go @@ -278,7 +278,8 @@ func validatePathMatch( if *path.Type != v1.PathMatchPathPrefix && *path.Type != v1.PathMatchExact { valErr := field.NotSupported( - fieldPath.Child("type"), *path.Type, + fieldPath.Child("type"), + *path.Type, []string{string(v1.PathMatchExact), string(v1.PathMatchPathPrefix)}, ) allErrs = append(allErrs, valErr) diff --git a/internal/mode/static/state/graph/httproute_test.go b/internal/mode/static/state/graph/httproute_test.go index 3a7efb8efe..a655364e60 100644 --- a/internal/mode/static/state/graph/httproute_test.go +++ b/internal/mode/static/state/graph/httproute_test.go @@ -268,7 +268,9 @@ func TestBuildHTTPRoute(t *testing.T) { "hr", gatewayNsName.Name, "example.com", - invalidPath, "/filter", "/", + invalidPath, + "/filter", + "/", ) addFilterToPath(hrDroppedInvalidMatchesAndInvalidFilters, "/filter", invalidFilter) From 9fa61272927b8eb2d75f8a7525f107a14dbddee2 Mon Sep 17 00:00:00 2001 From: Kate Osborn Date: Mon, 30 Sep 2024 08:48:38 -0600 Subject: [PATCH 16/16] Swap actual/expected in server test --- .../mode/static/nginx/config/servers_test.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/mode/static/nginx/config/servers_test.go b/internal/mode/static/nginx/config/servers_test.go index 115554cb29..f3e523a4c0 100644 --- a/internal/mode/static/nginx/config/servers_test.go +++ b/internal/mode/static/nginx/config/servers_test.go @@ -1912,20 +1912,20 @@ func TestCreateServers_Includes(t *testing.T) { conf := dataplane.Configuration{HTTPServers: httpServers, SSLServers: sslServers} - servers, matchPairs := createServers(conf, fakeGenerator) + actualServers, matchPairs := createServers(conf, fakeGenerator) g.Expect(matchPairs).To(BeEmpty()) - g.Expect(servers).To(HaveLen(len(expServers))) + g.Expect(actualServers).To(HaveLen(len(expServers))) - for i, server := range expServers { - g.Expect(server.ServerName).To(Equal(servers[i].ServerName)) + for i, expServer := range expServers { + g.Expect(actualServers[i].ServerName).To(Equal(expServer.ServerName)) - if servers[i].IsDefaultHTTP || servers[i].IsDefaultSSL { - g.Expect(servers[i].Includes).To(BeEmpty()) + if actualServers[i].IsDefaultHTTP || actualServers[i].IsDefaultSSL { + g.Expect(actualServers[i].Includes).To(BeEmpty()) } else { - g.Expect(server.Includes).To(ConsistOf(servers[i].Includes)) - g.Expect(server.Locations).To(HaveLen(1)) - g.Expect(server.Locations[0].Path).To(Equal(servers[i].Locations[0].Path)) - g.Expect(server.Locations[0].Includes).To(ConsistOf(servers[i].Locations[0].Includes)) + g.Expect(actualServers[i].Includes).To(ConsistOf(expServer.Includes)) + g.Expect(actualServers[i].Locations).To(HaveLen(1)) + g.Expect(actualServers[i].Locations[0].Path).To(Equal(expServer.Locations[0].Path)) + g.Expect(actualServers[i].Locations[0].Includes).To(ConsistOf(expServer.Locations[0].Includes)) } } }