From 5ea56fcbedc6d34c5117540db82e82f31336224b Mon Sep 17 00:00:00 2001 From: Norwin Schnyder Date: Wed, 20 Aug 2025 19:55:01 +0000 Subject: [PATCH 1/3] BackendTLSPolicy conformance tests for ResolvedRefs status condition Signed-off-by: Norwin Schnyder --- ...endtlspolicy-invalid-ca-certificate-ref.go | 96 ++++++++++++++++++ ...dtlspolicy-invalid-ca-certificate-ref.yaml | 97 +++++++++++++++++++ .../tests/backendtlspolicy-invalid-kind.go | 91 +++++++++++++++++ .../tests/backendtlspolicy-invalid-kind.yaml | 51 ++++++++++ conformance/tests/backendtlspolicy.go | 26 +++-- 5 files changed, 352 insertions(+), 9 deletions(-) create mode 100644 conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.go create mode 100644 conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.yaml create mode 100644 conformance/tests/backendtlspolicy-invalid-kind.go create mode 100644 conformance/tests/backendtlspolicy-invalid-kind.yaml diff --git a/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.go b/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.go new file mode 100644 index 0000000000..2db38d082d --- /dev/null +++ b/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.go @@ -0,0 +1,96 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tests + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatewayv1alpha3 "sigs.k8s.io/gateway-api/apis/v1alpha3" + h "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + "sigs.k8s.io/gateway-api/pkg/features" +) + +func init() { + ConformanceTests = append(ConformanceTests, BackendTLSPolicyCACertificateRefInvalidCACertificateRef) +} + +var BackendTLSPolicyCACertificateRefInvalidCACertificateRef = suite.ConformanceTest{ + ShortName: "BackendTLSPolicyCACertificateRefInvalidCACertificateRef", + Description: "A BackendTLSPolicy that specifies a single invalid CACertificateRef should have the Accepted and ResolvedRefs status condition set False with appropriate reasons, and HTTP requests to a backend targeted by this policy should fail with a 5xx response.", + Features: []features.FeatureName{ + features.SupportGateway, + features.SupportHTTPRoute, + features.SupportBackendTLSPolicy, + }, + Manifests: []string{"tests/backendtlspolicy-invalid-ca-certificate-ref.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "backendtlspolicy-invalid-ca-certificate-ref", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + + serverStr := "abc.example.com" + + kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{ns}) + gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gatewayv1.HTTPRoute{}, false, routeNN) + kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) + + for _, policyNN := range []types.NamespacedName{ + {Name: "nonexistent-ca-certificate-ref", Namespace: ns}, + {Name: "malformed-ca-certificate-ref", Namespace: ns}, + } { + t.Run("BackendTLSPolicy_"+policyNN.Name, func(t *testing.T) { + t.Run("BackendTLSPolicy with a single invalid CACertificateRef has a Accepted Condition with status False and Reason NoValidCACertificate", func(t *testing.T) { + acceptedCond := metav1.Condition{ + Type: string(gatewayv1alpha2.PolicyConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(gatewayv1alpha3.BackendTLSPolicyReasonNoValidCACertificate), + } + + kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, policyNN, gwNN, acceptedCond) + }) + + t.Run("BackendTLSPolicy with a single invalid CACertificateRef has a ResolvedRefs Condition with status False and Reason InvalidCACertificateRef", func(t *testing.T) { + resolvedRefsCond := metav1.Condition{ + Type: string(gatewayv1alpha3.BackendTLSPolicyConditionResolvedRefs), + Status: metav1.ConditionFalse, + Reason: string(gatewayv1alpha3.BackendTLSPolicyReasonInvalidCACertificateRef), + } + + kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, policyNN, gwNN, resolvedRefsCond) + }) + + t.Run("HTTP Request to backend targeted by an invalid BackendTLSPolicy receive a 5xx", func(t *testing.T) { + h.MakeRequestAndExpectFailure(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, + h.ExpectedResponse{ + Namespace: ns, + Request: h.Request{ + Host: serverStr, + Path: "/backendtlspolicy-" + policyNN.Name, + }, + }) + }) + }) + } + }, +} diff --git a/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.yaml b/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.yaml new file mode 100644 index 0000000000..138812fddb --- /dev/null +++ b/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.yaml @@ -0,0 +1,97 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: backendtlspolicy-invalid-ca-certificate-ref + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + namespace: gateway-conformance-infra + hostnames: + - abc.example.com + rules: + - backendRefs: + - name: backendtlspolicy-nonexistent-ca-certificate-ref-test + port: 443 + matches: + - path: + type: Exact + value: /backendtlspolicy-nonexistent-ca-certificate-ref + - backendRefs: + - name: backendtlspolicy-malformed-ca-certificate-ref-test + port: 443 + matches: + - path: + type: Exact + value: /backendtlspolicy-malformed-ca-certificate-ref +--- +apiVersion: v1 +kind: Service +metadata: + name: backendtlspolicy-nonexistent-ca-certificate-ref-test + namespace: gateway-conformance-infra +spec: + selector: + app: tls-backend + ports: + - name: "https" + protocol: TCP + appProtocol: HTTPS + port: 443 + targetPort: 8443 +--- +apiVersion: v1 +kind: Service +metadata: + name: backendtlspolicy-malformed-ca-certificate-ref-test + namespace: gateway-conformance-infra +spec: + selector: + app: tls-backend + ports: + - name: "https" + protocol: TCP + appProtocol: HTTPS + port: 443 + targetPort: 8443 +--- +apiVersion: gateway.networking.k8s.io/v1alpha3 +kind: BackendTLSPolicy +metadata: + name: nonexistent-ca-certificate-ref + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: "" + kind: Service + name: "backendtlspolicy-nonexistent-ca-certificate-ref-test" + validation: + caCertificateRefs: + - group: "" + kind: ConfigMap + name: "nonexistent-ca-certificate" + hostname: "abc.example.com" +--- +apiVersion: gateway.networking.k8s.io/v1alpha3 +kind: BackendTLSPolicy +metadata: + name: malformed-ca-certificate-ref + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: "" + kind: Service + name: "backendtlspolicy-malformed-ca-certificate-ref-test" + validation: + caCertificateRefs: + - group: "" + kind: ConfigMap + name: "malformed-ca-certificate" + hostname: "abc.example.com" +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: malformed-ca-certificate + namespace: gateway-conformance-infra +data: {} diff --git a/conformance/tests/backendtlspolicy-invalid-kind.go b/conformance/tests/backendtlspolicy-invalid-kind.go new file mode 100644 index 0000000000..02dd0335c1 --- /dev/null +++ b/conformance/tests/backendtlspolicy-invalid-kind.go @@ -0,0 +1,91 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tests + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatewayv1alpha3 "sigs.k8s.io/gateway-api/apis/v1alpha3" + h "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + "sigs.k8s.io/gateway-api/pkg/features" +) + +func init() { + ConformanceTests = append(ConformanceTests, BackendTLSPolicyCACertificateRefInvalidKind) +} + +var BackendTLSPolicyCACertificateRefInvalidKind = suite.ConformanceTest{ + ShortName: "BackendTLSPolicyCACertificateRefInvalidKind", + Description: "A BackendTLSPolicy that specifies a single CACertificateRef with an invalid kind should have the Accepted and ResolvedRefs status condition set False with appropriate reasons, and HTTP requests to a backend targeted by this policy should fail with a 5xx response.", + Features: []features.FeatureName{ + features.SupportGateway, + features.SupportHTTPRoute, + features.SupportBackendTLSPolicy, + }, + Manifests: []string{"tests/backendtlspolicy-invalid-kind.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "backendtlspolicy-invalid-kind-test", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + + serverStr := "abc.example.com" + + kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{ns}) + gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gatewayv1.HTTPRoute{}, false, routeNN) + kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) + + policyNN := types.NamespacedName{Name: "invalid-kind", Namespace: ns} + + t.Run("BackendTLSPolicy with a single invalid CACertificateRef has a Accepted Condition with status False and Reason NoValidCACertificate", func(t *testing.T) { + acceptedCond := metav1.Condition{ + Type: string(gatewayv1alpha2.PolicyConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(gatewayv1alpha3.BackendTLSPolicyReasonNoValidCACertificate), + } + + kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, policyNN, gwNN, acceptedCond) + }) + + t.Run("BackendTLSPolicy with a single invalid CACertificateRef has a ResolvedRefs Condition with status False and Reason InvalidKind", func(t *testing.T) { + resolvedRefsCond := metav1.Condition{ + Type: string(gatewayv1alpha3.BackendTLSPolicyConditionResolvedRefs), + Status: metav1.ConditionFalse, + Reason: string(gatewayv1alpha3.BackendTLSPolicyReasonInvalidKind), + } + + kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, policyNN, gwNN, resolvedRefsCond) + }) + + t.Run("HTTP Request to backend targeted by an invalid BackendTLSPolicy receive a 5xx", func(t *testing.T) { + h.MakeRequestAndExpectFailure(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, + h.ExpectedResponse{ + Namespace: ns, + Request: h.Request{ + Host: serverStr, + Path: "/backendtlspolicy-" + policyNN.Name, + }, + }) + }) + }, +} diff --git a/conformance/tests/backendtlspolicy-invalid-kind.yaml b/conformance/tests/backendtlspolicy-invalid-kind.yaml new file mode 100644 index 0000000000..a7eaeb5d5c --- /dev/null +++ b/conformance/tests/backendtlspolicy-invalid-kind.yaml @@ -0,0 +1,51 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: backendtlspolicy-invalid-kind-test + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + namespace: gateway-conformance-infra + hostnames: + - abc.example.com + rules: + - backendRefs: + - name: backendtlspolicy-invalid-kind-test + port: 443 + matches: + - path: + type: Exact + value: /backendtlspolicy-invalid-kind +--- +apiVersion: v1 +kind: Service +metadata: + name: backendtlspolicy-invalid-kind-test + namespace: gateway-conformance-infra +spec: + selector: + app: tls-backend + ports: + - name: "https" + protocol: TCP + appProtocol: HTTPS + port: 443 + targetPort: 8443 +--- +apiVersion: gateway.networking.k8s.io/v1alpha3 +kind: BackendTLSPolicy +metadata: + name: invalid-kind + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: "" + kind: Service + name: "backendtlspolicy-invalid-kind-test" + validation: + caCertificateRefs: + - group: invalid.io + kind: InvalidKind + name: "invalid-kind" + hostname: "abc.example.com" diff --git a/conformance/tests/backendtlspolicy.go b/conformance/tests/backendtlspolicy.go index fc6c6576eb..62ed54c4af 100644 --- a/conformance/tests/backendtlspolicy.go +++ b/conformance/tests/backendtlspolicy.go @@ -20,11 +20,11 @@ import ( "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/gateway-api/apis/v1alpha3" h "sigs.k8s.io/gateway-api/conformance/utils/http" "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" "sigs.k8s.io/gateway-api/conformance/utils/suite" @@ -54,20 +54,20 @@ var BackendTLSPolicy = suite.ConformanceTest{ gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gatewayv1.HTTPRoute{}, false, routeNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) - policyCond := metav1.Condition{ + acceptedCond := metav1.Condition{ Type: string(v1alpha2.PolicyConditionAccepted), Status: metav1.ConditionTrue, Reason: string(v1alpha2.PolicyReasonAccepted), } + resolvedRefsCond := metav1.Condition{ + Type: string(v1alpha3.BackendTLSPolicyConditionResolvedRefs), + Status: metav1.ConditionTrue, + Reason: string(v1alpha3.BackendTLSPolicyReasonResolvedRefs), + } validPolicyNN := types.NamespacedName{Name: "normative-test-backendtlspolicy", Namespace: ns} - kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, validPolicyNN, gwNN, policyCond) - - invalidPolicyNN := types.NamespacedName{Name: "backendtlspolicy-host-mismatch", Namespace: ns} - kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, invalidPolicyNN, gwNN, policyCond) - - invalidCertPolicyNN := types.NamespacedName{Name: "backendtlspolicy-cert-mismatch", Namespace: ns} - kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, invalidCertPolicyNN, gwNN, policyCond) + kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, validPolicyNN, gwNN, acceptedCond) + kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, validPolicyNN, gwNN, resolvedRefsCond) serverStr := "abc.example.com" @@ -107,6 +107,10 @@ var BackendTLSPolicy = suite.ConformanceTest{ // Verify that the request sent to a Service targeted by a BackendTLSPolicy with mismatched host will fail. t.Run("HTTP request sent to Service targeted by BackendTLSPolicy with mismatched hostname should return an HTTP error", func(t *testing.T) { + invalidPolicyNN := types.NamespacedName{Name: "backendtlspolicy-host-mismatch", Namespace: ns} + kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, invalidPolicyNN, gwNN, acceptedCond) + kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, invalidPolicyNN, gwNN, resolvedRefsCond) + h.MakeRequestAndExpectFailure(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, h.ExpectedResponse{ Namespace: ns, @@ -120,6 +124,10 @@ var BackendTLSPolicy = suite.ConformanceTest{ // Verify that request sent to Service targeted by BackendTLSPolicy with mismatched cert should failed. t.Run("HTTP request send to Service targeted by BackendTLSPolicy with mismatched cert should return HTTP error", func(t *testing.T) { + invalidCertPolicyNN := types.NamespacedName{Name: "backendtlspolicy-cert-mismatch", Namespace: ns} + kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, invalidCertPolicyNN, gwNN, acceptedCond) + kubernetes.BackendTLSPolicyMustHaveCondition(t, suite.Client, suite.TimeoutConfig, invalidCertPolicyNN, gwNN, resolvedRefsCond) + h.MakeRequestAndExpectFailure(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, h.ExpectedResponse{ Namespace: ns, From 749768e1e09a6ca8de1cbde0e6cfab922580bd0a Mon Sep 17 00:00:00 2001 From: Norwin Schnyder Date: Fri, 22 Aug 2025 06:13:33 +0000 Subject: [PATCH 2/3] Apply PR feedback Signed-off-by: Norwin Schnyder --- ...ckendtlspolicy-invalid-ca-certificate-ref.go | 11 +++++++---- .../tests/backendtlspolicy-invalid-kind.go | 11 +++++++---- conformance/utils/http/http.go | 17 ++++++++++++++++- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.go b/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.go index 2db38d082d..c41c8ff511 100644 --- a/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.go +++ b/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.go @@ -32,11 +32,11 @@ import ( ) func init() { - ConformanceTests = append(ConformanceTests, BackendTLSPolicyCACertificateRefInvalidCACertificateRef) + ConformanceTests = append(ConformanceTests, BackendTLSPolicyInvalidCACertificateRef) } -var BackendTLSPolicyCACertificateRefInvalidCACertificateRef = suite.ConformanceTest{ - ShortName: "BackendTLSPolicyCACertificateRefInvalidCACertificateRef", +var BackendTLSPolicyInvalidCACertificateRef = suite.ConformanceTest{ + ShortName: "BackendTLSPolicyInvalidCACertificateRef", Description: "A BackendTLSPolicy that specifies a single invalid CACertificateRef should have the Accepted and ResolvedRefs status condition set False with appropriate reasons, and HTTP requests to a backend targeted by this policy should fail with a 5xx response.", Features: []features.FeatureName{ features.SupportGateway, @@ -81,13 +81,16 @@ var BackendTLSPolicyCACertificateRefInvalidCACertificateRef = suite.ConformanceT }) t.Run("HTTP Request to backend targeted by an invalid BackendTLSPolicy receive a 5xx", func(t *testing.T) { - h.MakeRequestAndExpectFailure(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, + h.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, h.ExpectedResponse{ Namespace: ns, Request: h.Request{ Host: serverStr, Path: "/backendtlspolicy-" + policyNN.Name, }, + Response: h.Response{ + StatusCodes: []int{500, 502, 503}, + }, }) }) }) diff --git a/conformance/tests/backendtlspolicy-invalid-kind.go b/conformance/tests/backendtlspolicy-invalid-kind.go index 02dd0335c1..7dd6287677 100644 --- a/conformance/tests/backendtlspolicy-invalid-kind.go +++ b/conformance/tests/backendtlspolicy-invalid-kind.go @@ -32,11 +32,11 @@ import ( ) func init() { - ConformanceTests = append(ConformanceTests, BackendTLSPolicyCACertificateRefInvalidKind) + ConformanceTests = append(ConformanceTests, BackendTLSPolicyInvalidKind) } -var BackendTLSPolicyCACertificateRefInvalidKind = suite.ConformanceTest{ - ShortName: "BackendTLSPolicyCACertificateRefInvalidKind", +var BackendTLSPolicyInvalidKind = suite.ConformanceTest{ + ShortName: "BackendTLSPolicyInvalidKind", Description: "A BackendTLSPolicy that specifies a single CACertificateRef with an invalid kind should have the Accepted and ResolvedRefs status condition set False with appropriate reasons, and HTTP requests to a backend targeted by this policy should fail with a 5xx response.", Features: []features.FeatureName{ features.SupportGateway, @@ -78,13 +78,16 @@ var BackendTLSPolicyCACertificateRefInvalidKind = suite.ConformanceTest{ }) t.Run("HTTP Request to backend targeted by an invalid BackendTLSPolicy receive a 5xx", func(t *testing.T) { - h.MakeRequestAndExpectFailure(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, + h.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, h.ExpectedResponse{ Namespace: ns, Request: h.Request{ Host: serverStr, Path: "/backendtlspolicy-" + policyNN.Name, }, + Response: h.Response{ + StatusCodes: []int{500, 502, 503}, + }, }) }) }, diff --git a/conformance/utils/http/http.go b/conformance/utils/http/http.go index 50f8d1efdd..66990f8465 100644 --- a/conformance/utils/http/http.go +++ b/conformance/utils/http/http.go @@ -92,6 +92,7 @@ type ExpectedRequest struct { // Response defines expected properties of a response from a backend. type Response struct { StatusCode int + StatusCodes []int // alternative to StatusCode, allows multiple acceptable codes Headers map[string]string AbsentHeaders []string Protocol string @@ -304,7 +305,18 @@ func CompareRoundTrip(t *testing.T, req *roundtripper.Request, cReq *roundtrippe return nil } } - if expected.Response.StatusCode != cRes.StatusCode { + if len(expected.Response.StatusCodes) > 0 { + matched := false + for _, code := range expected.Response.StatusCodes { + if code == cRes.StatusCode { + matched = true + break + } + } + if !matched { + return fmt.Errorf("expected status code to be one of %v, got %d", expected.Response.StatusCodes, cRes.StatusCode) + } + } else if expected.Response.StatusCode != cRes.StatusCode { return fmt.Errorf("expected status code to be %d, got %d. CRes: %v", expected.Response.StatusCode, cRes.StatusCode, cRes) } if expected.Response.Protocol != "" && expected.Response.Protocol != cRes.Protocol { @@ -467,6 +479,9 @@ func (er *ExpectedResponse) GetTestCaseName(i int) string { if er.Backend != "" { return fmt.Sprintf("%s should go to %s", reqStr, er.Backend) } + if len(er.Response.StatusCodes) > 0 { + return fmt.Sprintf("%s should receive one of %v", reqStr, er.Response.StatusCodes) + } return fmt.Sprintf("%s should receive a %d", reqStr, er.Response.StatusCode) } From a8a865fcd6c111f59939c0489300089a864d5d97 Mon Sep 17 00:00:00 2001 From: Norwin Schnyder Date: Sat, 23 Aug 2025 06:53:26 +0000 Subject: [PATCH 3/3] deprecate StatusCode of the excpected response in favor of StatusCodes Signed-off-by: Norwin Schnyder --- ...endtlspolicy-invalid-ca-certificate-ref.go | 1 - .../tests/backendtlspolicy-invalid-kind.go | 1 - conformance/utils/echo/pod.go | 20 ++++++++-- conformance/utils/http/http.go | 40 +++++++++---------- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.go b/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.go index c41c8ff511..824d8dd199 100644 --- a/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.go +++ b/conformance/tests/backendtlspolicy-invalid-ca-certificate-ref.go @@ -53,7 +53,6 @@ var BackendTLSPolicyInvalidCACertificateRef = suite.ConformanceTest{ kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{ns}) gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gatewayv1.HTTPRoute{}, false, routeNN) - kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) for _, policyNN := range []types.NamespacedName{ {Name: "nonexistent-ca-certificate-ref", Namespace: ns}, diff --git a/conformance/tests/backendtlspolicy-invalid-kind.go b/conformance/tests/backendtlspolicy-invalid-kind.go index 7dd6287677..5a3bba597e 100644 --- a/conformance/tests/backendtlspolicy-invalid-kind.go +++ b/conformance/tests/backendtlspolicy-invalid-kind.go @@ -53,7 +53,6 @@ var BackendTLSPolicyInvalidKind = suite.ConformanceTest{ kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{ns}) gwAddr := kubernetes.GatewayAndRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), &gatewayv1.HTTPRoute{}, false, routeNN) - kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) policyNN := types.NamespacedName{Name: "invalid-kind", Namespace: ns} diff --git a/conformance/utils/echo/pod.go b/conformance/utils/echo/pod.go index 0222ff12dd..dff03ed5fa 100644 --- a/conformance/utils/echo/pod.go +++ b/conformance/utils/echo/pod.go @@ -20,6 +20,8 @@ import ( "bytes" "context" "fmt" + "slices" + "strconv" "strings" "testing" "time" @@ -84,8 +86,14 @@ func makeRequest(t *testing.T, exp *http.ExpectedResponse) []string { exp.Request.Method = "GET" } - if exp.Response.StatusCode == 0 { - exp.Response.StatusCode = 200 + // if the deprecated field StatusCode is set, append it to StatusCodes for backwards compatibility + //nolint:staticcheck + if exp.Response.StatusCode != 0 { + exp.Response.StatusCodes = append(exp.Response.StatusCodes, exp.Response.StatusCode) + } + + if len(exp.Response.StatusCodes) == 0 { + exp.Response.StatusCodes = []int{200} } r := exp.Request @@ -110,8 +118,12 @@ func compareRequest(exp http.ExpectedResponse, resp Response) error { } wantReq := exp.ExpectedRequest wantResp := exp.Response - if fmt.Sprint(wantResp.StatusCode) != resp.Code { - return fmt.Errorf("wanted status code %v, got %v", wantResp.StatusCode, resp.Code) + statusCode, err := strconv.Atoi(resp.Code) + if err != nil { + return fmt.Errorf("invalid status code '%v': %v", resp.Code, err) + } + if !slices.Contains(wantResp.StatusCodes, statusCode) { + return fmt.Errorf("wanted status code to be one of %v, got %d", wantResp.StatusCodes, statusCode) } if wantReq.Headers != nil { if resp.RequestHeaders == nil { diff --git a/conformance/utils/http/http.go b/conformance/utils/http/http.go index 66990f8465..6cd68dd084 100644 --- a/conformance/utils/http/http.go +++ b/conformance/utils/http/http.go @@ -22,6 +22,7 @@ import ( "math/big" "net" "net/url" + "slices" "strings" "testing" "time" @@ -91,8 +92,9 @@ type ExpectedRequest struct { // Response defines expected properties of a response from a backend. type Response struct { + // Deprecated: Use StatusCodes instead, which supports matching against multiple status codes. StatusCode int - StatusCodes []int // alternative to StatusCode, allows multiple acceptable codes + StatusCodes []int Headers map[string]string AbsentHeaders []string Protocol string @@ -139,8 +141,13 @@ func MakeRequest(t *testing.T, expected *ExpectedResponse, gwAddr, protocol, sch expected.Request.Method = "GET" } - if expected.Response.StatusCode == 0 { - expected.Response.StatusCode = 200 + // if the deprecated field StatusCode is set, append it to StatusCodes for backwards compatibility + if expected.Response.StatusCode != 0 { + expected.Response.StatusCodes = append(expected.Response.StatusCodes, expected.Response.StatusCode) + } + + if len(expected.Response.StatusCodes) == 0 { + expected.Response.StatusCodes = []int{200} } if expected.Request.Protocol == "" { @@ -301,23 +308,14 @@ func WaitForConsistentFailureResponse(t *testing.T, r roundtripper.RoundTripper, func CompareRoundTrip(t *testing.T, req *roundtripper.Request, cReq *roundtripper.CapturedRequest, cRes *roundtripper.CapturedResponse, expected ExpectedResponse) error { if roundtripper.IsTimeoutError(cRes.StatusCode) { - if roundtripper.IsTimeoutError(expected.Response.StatusCode) { - return nil - } - } - if len(expected.Response.StatusCodes) > 0 { - matched := false - for _, code := range expected.Response.StatusCodes { - if code == cRes.StatusCode { - matched = true - break + for _, statusCode := range expected.Response.StatusCodes { + if roundtripper.IsTimeoutError(statusCode) { + return nil } } - if !matched { - return fmt.Errorf("expected status code to be one of %v, got %d", expected.Response.StatusCodes, cRes.StatusCode) - } - } else if expected.Response.StatusCode != cRes.StatusCode { - return fmt.Errorf("expected status code to be %d, got %d. CRes: %v", expected.Response.StatusCode, cRes.StatusCode, cRes) + } + if !slices.Contains(expected.Response.StatusCodes, cRes.StatusCode) { + return fmt.Errorf("expected status code to be one of %v, got %d. CRes: %v", expected.Response.StatusCodes, cRes.StatusCode, cRes) } if expected.Response.Protocol != "" && expected.Response.Protocol != cRes.Protocol { return fmt.Errorf("expected protocol to be %s, got %s", expected.Response.Protocol, cRes.Protocol) @@ -479,10 +477,8 @@ func (er *ExpectedResponse) GetTestCaseName(i int) string { if er.Backend != "" { return fmt.Sprintf("%s should go to %s", reqStr, er.Backend) } - if len(er.Response.StatusCodes) > 0 { - return fmt.Sprintf("%s should receive one of %v", reqStr, er.Response.StatusCodes) - } - return fmt.Sprintf("%s should receive a %d", reqStr, er.Response.StatusCode) + + return fmt.Sprintf("%s should receive one of %v", reqStr, er.Response.StatusCodes) } func setRedirectRequestDefaults(req *roundtripper.Request, cRes *roundtripper.CapturedResponse, expected *ExpectedResponse) {