From 1b13c2192b55dc69eae884727efd25ffa694b339 Mon Sep 17 00:00:00 2001 From: salonichf5 <146118978+salonichf5@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:57:47 -0800 Subject: [PATCH] add functional tests for upstreamSettings policy --- apis/v1alpha1/policy_methods.go | 4 + ...ay.nginx.org_upstreamsettingspolicies.yaml | 6 +- tests/framework/crossplane.go | 17 + .../upstream-settings-policy/cafe.yaml | 66 +++ .../upstream-settings-policy/gateway.yaml | 11 + .../grpc-backend.yaml | 39 ++ .../invalid-usps.yaml | 84 ++++ .../upstream-settings-policy/routes.yaml | 35 ++ .../valid-merge-usps.yaml | 25 ++ .../valid-usps-first-wins.yaml | 21 + .../upstream-settings-policy/valid-usps.yaml | 49 +++ tests/suite/upstream_settings_test.go | 414 ++++++++++++++++++ 12 files changed, 768 insertions(+), 3 deletions(-) create mode 100644 tests/suite/manifests/upstream-settings-policy/cafe.yaml create mode 100644 tests/suite/manifests/upstream-settings-policy/gateway.yaml create mode 100644 tests/suite/manifests/upstream-settings-policy/grpc-backend.yaml create mode 100644 tests/suite/manifests/upstream-settings-policy/invalid-usps.yaml create mode 100644 tests/suite/manifests/upstream-settings-policy/routes.yaml create mode 100644 tests/suite/manifests/upstream-settings-policy/valid-merge-usps.yaml create mode 100644 tests/suite/manifests/upstream-settings-policy/valid-usps-first-wins.yaml create mode 100644 tests/suite/manifests/upstream-settings-policy/valid-usps.yaml create mode 100644 tests/suite/upstream_settings_test.go diff --git a/apis/v1alpha1/policy_methods.go b/apis/v1alpha1/policy_methods.go index 97e7074a62..788abd2263 100644 --- a/apis/v1alpha1/policy_methods.go +++ b/apis/v1alpha1/policy_methods.go @@ -31,3 +31,7 @@ func (p *ObservabilityPolicy) GetPolicyStatus() v1alpha2.PolicyStatus { func (p *ObservabilityPolicy) SetPolicyStatus(status v1alpha2.PolicyStatus) { p.Status = status } + +func (p *UpstreamSettingsPolicy) GetTargetRefs() []v1alpha2.LocalPolicyTargetReference { + return p.Spec.TargetRefs +} diff --git a/config/crd/bases/gateway.nginx.org_upstreamsettingspolicies.yaml b/config/crd/bases/gateway.nginx.org_upstreamsettingspolicies.yaml index dbe0462862..992af79932 100644 --- a/config/crd/bases/gateway.nginx.org_upstreamsettingspolicies.yaml +++ b/config/crd/bases/gateway.nginx.org_upstreamsettingspolicies.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.16.2 + controller-gen.kubebuilder.io/version: v0.16.5 labels: gateway.networking.k8s.io/policy: direct name: upstreamsettingspolicies.gateway.nginx.org @@ -76,13 +76,13 @@ spec: Time defines the maximum time during which requests can be processed through one keep-alive connection. After this time is reached, the connection is closed following the subsequent request processing. Directive: https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive_time - pattern: ^\d{1,4}(ms|s)?$ + pattern: ^[0-9]{1,4}(ms|s|m|h)?$ type: string timeout: description: |- Timeout defines the keep-alive timeout for upstreams. Directive: https://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive_timeout - pattern: ^\d{1,4}(ms|s)?$ + pattern: ^[0-9]{1,4}(ms|s|m|h)?$ type: string type: object targetRefs: diff --git a/tests/framework/crossplane.go b/tests/framework/crossplane.go index b6925dc8e1..33046ff868 100644 --- a/tests/framework/crossplane.go +++ b/tests/framework/crossplane.go @@ -30,6 +30,8 @@ type ExpectedNginxField struct { Location string // Servers are the server names that the directive should exist in. Servers []string + // Upstream are the upstream names that the directive should exist in. + Upstreams []string // ValueSubstringAllowed allows the expected value to be a substring of the real value. // This makes it easier for cases when real values are complex file names or contain things we // don't care about, and we just want to check if a substring exists. @@ -64,6 +66,8 @@ func ValidateNginxFieldExists(conf *Payload, expFieldCfg ExpectedNginxField) err } } } + + return validateUpstreamDirectives(expFieldCfg, directive) } } @@ -75,6 +79,19 @@ func ValidateNginxFieldExists(conf *Payload, expFieldCfg ExpectedNginxField) err return fmt.Errorf("field not found; expected: %+v\nNGINX conf: %s", expFieldCfg, string(b)) } +func validateUpstreamDirectives(expFieldCfg ExpectedNginxField, directive *Directive) error { + for _, upstreamName := range expFieldCfg.Upstreams { + if directive.Directive == "upstream" && directive.Args[0] == upstreamName { + for _, upstreamDirective := range directive.Block { + if expFieldCfg.fieldFound(upstreamDirective) { + return nil + } + } + } + } + return nil +} + func getServerName(serverBlock Directives) string { for _, directive := range serverBlock { if directive.Directive == "server_name" { diff --git a/tests/suite/manifests/upstream-settings-policy/cafe.yaml b/tests/suite/manifests/upstream-settings-policy/cafe.yaml new file mode 100644 index 0000000000..c0466158f8 --- /dev/null +++ b/tests/suite/manifests/upstream-settings-policy/cafe.yaml @@ -0,0 +1,66 @@ +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: apps/v1 +kind: Deployment +metadata: + name: tea +spec: + replicas: 1 + selector: + matchLabels: + app: tea + template: + metadata: + labels: + app: tea + spec: + containers: + - name: tea + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: tea +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: tea +--- diff --git a/tests/suite/manifests/upstream-settings-policy/gateway.yaml b/tests/suite/manifests/upstream-settings-policy/gateway.yaml new file mode 100644 index 0000000000..e6507f613b --- /dev/null +++ b/tests/suite/manifests/upstream-settings-policy/gateway.yaml @@ -0,0 +1,11 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: gateway +spec: + gatewayClassName: nginx + listeners: + - name: http + port: 80 + protocol: HTTP + hostname: "*.example.com" diff --git a/tests/suite/manifests/upstream-settings-policy/grpc-backend.yaml b/tests/suite/manifests/upstream-settings-policy/grpc-backend.yaml new file mode 100644 index 0000000000..3ae352f573 --- /dev/null +++ b/tests/suite/manifests/upstream-settings-policy/grpc-backend.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: Service +metadata: + name: grpc-backend +spec: + selector: + app: grpc-backend + ports: + - protocol: TCP + port: 8080 + targetPort: 50051 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grpc-backend + labels: + app: grpc-backend +spec: + replicas: 1 + selector: + matchLabels: + app: grpc-backend + template: + metadata: + labels: + app: grpc-backend + spec: + containers: + - name: grpc-backend + image: ghcr.io/nginxinc/kic-test-grpc-server:0.2.3 + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + resources: + requests: + cpu: 10m diff --git a/tests/suite/manifests/upstream-settings-policy/invalid-usps.yaml b/tests/suite/manifests/upstream-settings-policy/invalid-usps.yaml new file mode 100644 index 0000000000..575121900a --- /dev/null +++ b/tests/suite/manifests/upstream-settings-policy/invalid-usps.yaml @@ -0,0 +1,84 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: Gateway +metadata: + name: gateway-httproute-allowed +spec: + gatewayClassName: nginx + listeners: + - name: http + port: 80 + protocol: HTTP + hostname: "soda.example.com" + allowedRoutes: + namespaces: + from: All + kinds: + kind: GRPCRoute +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: soda +spec: + replicas: 1 + selector: + matchLabels: + app: soda + template: + metadata: + labels: + app: soda + spec: + containers: + - name: soda + image: nginxdemos/nginx-hello:plain-text + ports: + - containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: soda +spec: + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: soda +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: soda +spec: + parentRefs: + - name: gateway-httproute-allowed + sectionName: http + hostnames: + - "soda.example.com" + rules: + - matches: + - path: + type: Exact + value: /soda + backendRefs: + - name: soda + port: 80 +--- +apiVersion: gateway.nginx.org/v1alpha1 +kind: UpstreamSettingsPolicy +metadata: + name: soda-svc-usp +spec: + zoneSize: 512k + targetRefs: + - group: core + kind: Service + name: soda + keepAlive: + connections: 10 + requests: 3 + time: 10s + timeout: 50s diff --git a/tests/suite/manifests/upstream-settings-policy/routes.yaml b/tests/suite/manifests/upstream-settings-policy/routes.yaml new file mode 100644 index 0000000000..37defe1a3e --- /dev/null +++ b/tests/suite/manifests/upstream-settings-policy/routes.yaml @@ -0,0 +1,35 @@ +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 + backendRefs: + - name: coffee + port: 80 +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: GRPCRoute +metadata: + name: grpc-route +spec: + parentRefs: + - name: gateway + sectionName: http + rules: + - matches: + - method: + service: helloworld.Greeter + method: SayHello + backendRefs: + - name: grpc-backend + port: 8080 diff --git a/tests/suite/manifests/upstream-settings-policy/valid-merge-usps.yaml b/tests/suite/manifests/upstream-settings-policy/valid-merge-usps.yaml new file mode 100644 index 0000000000..d248f49cfb --- /dev/null +++ b/tests/suite/manifests/upstream-settings-policy/valid-merge-usps.yaml @@ -0,0 +1,25 @@ +apiVersion: gateway.nginx.org/v1alpha1 +kind: UpstreamSettingsPolicy +metadata: + name: coffee-svc-usp-1 +spec: + zoneSize: 64k + targetRefs: + - group: core + kind: Service + name: coffee +--- +apiVersion: gateway.nginx.org/v1alpha1 +kind: UpstreamSettingsPolicy +metadata: + name: coffee-svc-usp-2 +spec: + targetRefs: + - group: core + kind: Service + name: coffee + keepAlive: + connections: 100 + requests: 55 + time: 1m + timeout: 5h diff --git a/tests/suite/manifests/upstream-settings-policy/valid-usps-first-wins.yaml b/tests/suite/manifests/upstream-settings-policy/valid-usps-first-wins.yaml new file mode 100644 index 0000000000..1600aea97a --- /dev/null +++ b/tests/suite/manifests/upstream-settings-policy/valid-usps-first-wins.yaml @@ -0,0 +1,21 @@ +apiVersion: gateway.nginx.org/v1alpha1 +kind: UpstreamSettingsPolicy +metadata: + name: coffee-svc-usp-1 +spec: + zoneSize: 64k + targetRefs: + - group: core + kind: Service + name: coffee +--- +apiVersion: gateway.nginx.org/v1alpha1 +kind: UpstreamSettingsPolicy +metadata: + name: coffee-svc-usp-2 +spec: + zoneSize: 128k + targetRefs: + - group: core + kind: Service + name: coffee diff --git a/tests/suite/manifests/upstream-settings-policy/valid-usps.yaml b/tests/suite/manifests/upstream-settings-policy/valid-usps.yaml new file mode 100644 index 0000000000..8b09e7ddc0 --- /dev/null +++ b/tests/suite/manifests/upstream-settings-policy/valid-usps.yaml @@ -0,0 +1,49 @@ +apiVersion: gateway.nginx.org/v1alpha1 +kind: UpstreamSettingsPolicy +metadata: + name: coffee-svc-usp +spec: + zoneSize: 512k + targetRefs: + - group: core + kind: Service + name: coffee + keepAlive: + connections: 10 + requests: 3 + time: 10s + timeout: 50s +--- +apiVersion: gateway.nginx.org/v1alpha1 +kind: UpstreamSettingsPolicy +metadata: + name: tea-multiple-svc-usp +spec: + zoneSize: 128k + targetRefs: + - group: core + kind: Service + name: tea + - group: core + kind: Service + name: coffee + keepAlive: + connections: 12 + requests: 31 + time: 20s + timeout: 40s +--- +apiVersion: gateway.nginx.org/v1alpha1 +kind: UpstreamSettingsPolicy +metadata: + name: grpc-svc-usp +spec: + targetRef: + group: core + kind: Service + name: grpc-backend + keepAlive: + connections: 10 + requests: 3 + time: 10s + timeout: 50s diff --git a/tests/suite/upstream_settings_test.go b/tests/suite/upstream_settings_test.go new file mode 100644 index 0000000000..c7acb766ed --- /dev/null +++ b/tests/suite/upstream_settings_test.go @@ -0,0 +1,414 @@ +package main + +import ( + "context" + "fmt" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + core "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/gateway-api/apis/v1alpha2" + + ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1" + "github.com/nginxinc/nginx-gateway-fabric/tests/framework" +) + +var _ = Describe("UpstreamSettingsPolicy", Ordered, Label("uspolicy"), func() { + var ( + files = []string{ + "upstream-settings-policy/cafe.yaml", + "upstream-settings-policy/gateway.yaml", + "upstream-settings-policy/grpc-backend.yaml", + "upstream-settings-policy/routes.yaml", + } + + namespace = "uspolicy" + ) + + BeforeAll(func() { + ns := &core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + + Expect(resourceManager.Apply([]client.Object{ns})).To(Succeed()) + Expect(resourceManager.ApplyFromFiles(files, namespace)).To(Succeed()) + Expect(resourceManager.WaitForAppsToBeReady(namespace)).To(Succeed()) + }) + + AfterAll(func() { + Expect(resourceManager.DeleteNamespace(namespace)).To(Succeed()) + }) + + When("UpstreamSettingsPolicy are applied to the resources", func() { + snippetsFilter := []string{ + "upstream-settings-policy/valid-usps.yaml", + } + + BeforeAll(func() { + Expect(resourceManager.ApplyFromFiles(snippetsFilter, namespace)).To(Succeed()) + }) + + AfterAll(func() { + Expect(resourceManager.DeleteFromFiles(snippetsFilter, namespace)).To(Succeed()) + }) + + Specify("usPolicies are accepted", func() { + usPolicies := []string{ + "coffee-svc-usp", + "grpc-svc-usp", + "tea-multiple-svc-usp", + } + + for _, name := range usPolicies { + nsname := types.NamespacedName{Name: name, Namespace: namespace} + + err := waitForUSPolicyStatus(nsname, metav1.ConditionTrue, v1alpha2.PolicyReasonAccepted) + Expect(err).ToNot(HaveOccurred(), fmt.Sprintf("%s was not accepted", name)) + } + }) + + Context("verify working traffic", func() { + It("should return a 200 response for HTTPRoute", func() { + port := 80 + if portFwdPort != 0 { + port = portFwdPort + } + baseCoffeeURL := fmt.Sprintf("http://cafe.example.com:%d%s", port, "/coffee") + baseTeaURL := fmt.Sprintf("http://cafe.example.com:%d%s", port, "/tea") + + Eventually( + func() error { + return expectRequestToSucceed(baseCoffeeURL, address, "URI: /coffee") + }). + WithTimeout(timeoutConfig.RequestTimeout). + WithPolling(500 * time.Millisecond). + Should(Succeed()) + + Eventually( + func() error { + return expectRequestToSucceed(baseTeaURL, address, "URI: /tea") + }). + WithTimeout(timeoutConfig.RequestTimeout). + WithPolling(500 * time.Millisecond). + Should(Succeed()) + }) + }) + + Context("nginx directives", func() { + var conf *framework.Payload + + BeforeAll(func() { + podNames, err := framework.GetReadyNGFPodNames(k8sClient, ngfNamespace, releaseName, timeoutConfig.GetTimeout) + Expect(err).ToNot(HaveOccurred()) + Expect(podNames).To(HaveLen(1)) + + ngfPodName := podNames[0] + + conf, err = resourceManager.GetNginxConfig(ngfPodName, ngfNamespace) + Expect(err).ToNot(HaveOccurred()) + }) + + // TODO: important + // The directive file and field value need to be updated based on the + // implementation of the UpstreamSettingsPolicy and how they are specified in the config files. + DescribeTable("are set properly for", + func(expCfgs []framework.ExpectedNginxField) { + for _, expCfg := range expCfgs { + Expect(framework.ValidateNginxFieldExists(conf, expCfg)).To(Succeed()) + } + }, + Entry("HTTPRoute", []framework.ExpectedNginxField{ + { + Directive: "zone default_coffee_80", + Value: "128k", + Upstreams: []string{"default_coffee_80"}, + File: "nginx.conf", + ValueSubstringAllowed: true, + }, + { + Directive: "keepalive", + Value: "12", + Upstreams: []string{"default_coffee_80"}, + File: "nginx.conf", + }, + { + Directive: "keepalive_requests", + Value: "31", + Upstreams: []string{"default_coffee_80"}, + File: "nginx.conf", + }, + { + Directive: "keepalive_time", + Value: "20s", + Upstreams: []string{"default_coffee_80"}, + File: "nginx.conf", + }, + { + Directive: "keepalive_timeout", + Value: "40s", + Upstreams: []string{"default_coffee_80"}, + File: "nginx.conf", + }, + }), + Entry("GRPCRoute", []framework.ExpectedNginxField{ + { + Directive: "zone default_grpc-backend_8080", + Value: "512k", + Upstreams: []string{"default_grpc-backend_8080"}, + File: "nginx.conf", + ValueSubstringAllowed: true, + }, + { + Directive: "keepalive", + Value: "10", + Upstreams: []string{"default_grpc-backend_8080"}, + File: "nginx.conf", + }, + { + Directive: "keepalive_requests", + Value: "3", + Upstreams: []string{"default_grpc-backend_8080"}, + File: "nginx.conf", + }, + { + Directive: "keepalive_time", + Value: "10s", + Upstreams: []string{"default_grpc-backend_8080"}, + File: "nginx.conf", + }, + { + Directive: "keepalive_timeout", + Value: "50s", + Upstreams: []string{"default_grpc-backend_8080"}, + File: "nginx.conf", + }, + }), + ) + }) + }) + + When("When multiple UpstreamSettings Policy target the same service", func() { + Specify("configuring distinct settings then directives are merged", func() { + files := []string{"upstream-settings-policy/valid-merge-usps.yaml"} + Expect(resourceManager.ApplyFromFiles(files, namespace)).To(Succeed()) + + nsname := types.NamespacedName{Name: "coffee-svc-usp-1", Namespace: namespace} + Expect(waitForUSPolicyStatus(nsname, metav1.ConditionTrue, v1alpha2.PolicyReasonTargetNotFound)).To(Succeed()) + + nsname = types.NamespacedName{Name: "coffee-svc-usp-2", Namespace: namespace} + Expect(waitForUSPolicyStatus(nsname, metav1.ConditionTrue, v1alpha2.PolicyReasonTargetNotFound)).To(Succeed()) + + Context("verify working traffic", func() { + It("should return a 200 response for HTTPRoute", func() { + port := 80 + if portFwdPort != 0 { + port = portFwdPort + } + baseURL := fmt.Sprintf("http://cafe.example.com:%d%s", port, "/coffee") + + Eventually( + func() error { + return expectRequestToSucceed(baseURL, address, "URI: /coffee") + }). + WithTimeout(timeoutConfig.RequestTimeout). + WithPolling(500 * time.Millisecond). + Should(Succeed()) + }) + }) + + Context("nginx directives", func() { + var conf *framework.Payload + + BeforeAll(func() { + podNames, err := framework.GetReadyNGFPodNames(k8sClient, ngfNamespace, releaseName, timeoutConfig.GetTimeout) + Expect(err).ToNot(HaveOccurred()) + Expect(podNames).To(HaveLen(1)) + + ngfPodName := podNames[0] + + conf, err = resourceManager.GetNginxConfig(ngfPodName, ngfNamespace) + Expect(err).ToNot(HaveOccurred()) + }) + + // TODO: important + // The directive file and field value need to be updated based on the + // implementation of the UpstreamSettingsPolicy and how they are specified in the config files. + DescribeTable("are set properly for", + func(expCfgs []framework.ExpectedNginxField) { + for _, expCfg := range expCfgs { + Expect(framework.ValidateNginxFieldExists(conf, expCfg)).To(Succeed()) + } + }, + Entry("HTTPRoute", []framework.ExpectedNginxField{ + { + Directive: "zone default_coffee_80", + Value: "64k", + Upstreams: []string{"default_coffee_80"}, + File: "nginx.conf", + ValueSubstringAllowed: true, + }, + { + Directive: "keepalive", + Value: "100", + Upstreams: []string{"default_coffee_80"}, + File: "nginx.conf", + }, + { + Directive: "keepalive_requests", + Value: "55", + Upstreams: []string{"default_coffee_80"}, + File: "nginx.conf", + }, + { + Directive: "keepalive_time", + Value: "1m", + Upstreams: []string{"default_coffee_80"}, + File: "nginx.conf", + }, + { + Directive: "keepalive_timeout", + Value: "5h", + Upstreams: []string{"default_coffee_80"}, + File: "nginx.conf", + }, + }), + ) + }) + + Expect(resourceManager.DeleteFromFiles(files, namespace)).To(Succeed()) + }) + Specify("configuring overlapping settings, the policy created first wins", func() { + files := []string{"upstream-settings-policy/valid-usps-first-wins.yaml"} + Expect(resourceManager.ApplyFromFiles(files, namespace)).To(Succeed()) + + nsname := types.NamespacedName{Name: "coffee-svc-usp-1", Namespace: namespace} + Expect(waitForUSPolicyStatus(nsname, metav1.ConditionTrue, v1alpha2.PolicyReasonTargetNotFound)).To(Succeed()) + + nsname = types.NamespacedName{Name: "coffee-svc-usp-2", Namespace: namespace} + Expect(waitForUSPolicyStatus(nsname, metav1.ConditionTrue, v1alpha2.PolicyReasonTargetNotFound)).To(Succeed()) + + Context("verify working traffic", func() { + It("should return a 200 response for HTTPRoute", func() { + port := 80 + if portFwdPort != 0 { + port = portFwdPort + } + baseURL := fmt.Sprintf("http://cafe.example.com:%d%s", port, "/coffee") + + Eventually( + func() error { + return expectRequestToSucceed(baseURL, address, "URI: /coffee") + }). + WithTimeout(timeoutConfig.RequestTimeout). + WithPolling(500 * time.Millisecond). + Should(Succeed()) + }) + }) + + Context("nginx directives", func() { + var conf *framework.Payload + + BeforeAll(func() { + podNames, err := framework.GetReadyNGFPodNames(k8sClient, ngfNamespace, releaseName, timeoutConfig.GetTimeout) + Expect(err).ToNot(HaveOccurred()) + Expect(podNames).To(HaveLen(1)) + + ngfPodName := podNames[0] + + conf, err = resourceManager.GetNginxConfig(ngfPodName, ngfNamespace) + Expect(err).ToNot(HaveOccurred()) + }) + + // TODO: important + // The directive file and field value need to be updated based on the + // implementation of the UpstreamSettingsPolicy and how they are specified in the config files. + DescribeTable("are set properly for", + func(expCfgs []framework.ExpectedNginxField) { + for _, expCfg := range expCfgs { + Expect(framework.ValidateNginxFieldExists(conf, expCfg)).To(Succeed()) + } + }, + Entry("HTTPRoute", []framework.ExpectedNginxField{ + { + Directive: "zone default_coffee_80", + Value: "128k", + Upstreams: []string{"default_coffee_80"}, + File: "nginx.conf", + ValueSubstringAllowed: true, + }, + }), + ) + }) + Expect(resourceManager.DeleteFromFiles(files, namespace)).To(Succeed()) + }) + }) + + When("UpstreamSettings Policy is invalid", func() { + Specify("if service mentioned in the target ref is not present", func() { + files := []string{"upstream-settings-policy/invalid-usps.yaml"} + + Expect(resourceManager.ApplyFromFiles(files, namespace)).To(Succeed()) + + nsname := types.NamespacedName{Name: "soda-svc-usp", Namespace: namespace} + Expect(waitForUSPolicyStatus(nsname, metav1.ConditionFalse, v1alpha2.PolicyReasonTargetNotFound)).To(Succeed()) + + Expect(resourceManager.DeleteFromFiles(files, namespace)).To(Succeed()) + }) + }) +}) + +func waitForUSPolicyStatus( + usPolicyNsNames types.NamespacedName, + condStatus metav1.ConditionStatus, + condReason v1alpha2.PolicyConditionReason, +) error { + ctx, cancel := context.WithTimeout(context.Background(), timeoutConfig.GetStatusTimeout) + defer cancel() + + GinkgoWriter.Printf( + "Waiting for UpstreamSettings Policy %q to have the condition Accepted/True/Accepted\n", + usPolicyNsNames, + ) + + return wait.PollUntilContextCancel( + ctx, + 500*time.Millisecond, + true, /* poll immediately */ + func(ctx context.Context) (bool, error) { + var usPolicy ngfAPI.UpstreamSettingsPolicy + var err error + + if err := k8sClient.Get(ctx, usPolicyNsNames, &usPolicy); err != nil { + return false, err + } + + if len(usPolicy.Status.Ancestors) == 0 { + GinkgoWriter.Printf("UpstreamSettingsPolicy %q does not have an ancestor status yet\n", usPolicy) + + return false, nil + } + + if len(usPolicy.Status.Ancestors) != 1 { + return false, fmt.Errorf("policy has %d ancestors, expected 1", len(usPolicy.Status.Ancestors)) + } + + ancestors := usPolicy.Status.Ancestors + + for i, ancestor := range ancestors { + if err := ancestorMustEqualTargetRef(ancestor, usPolicy.GetTargetRefs()[i], usPolicy.Namespace); err != nil { + return false, err + } + + err = ancestorStatusMustHaveAcceptedCondition(ancestor, condStatus, condReason) + } + return err == nil, err + }, + ) +}