From 45f164ed1fb7f2225ff1c2ad6d313cf8c26949e7 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Wed, 30 Oct 2024 18:07:46 -0700 Subject: [PATCH 1/8] tests: direct response Signed-off-by: Arko Dasgupta --- test/e2e/testdata/direct-response.yaml | 64 +++++++++++++++++++++ test/e2e/tests/direct-response.go | 77 ++++++++++++++++++++++++++ test/e2e/tests/response-override.go | 8 +-- 3 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 test/e2e/testdata/direct-response.yaml create mode 100644 test/e2e/tests/direct-response.go diff --git a/test/e2e/testdata/direct-response.yaml b/test/e2e/testdata/direct-response.yaml new file mode 100644 index 00000000000..0e5a92c8693 --- /dev/null +++ b/test/e2e/testdata/direct-response.yaml @@ -0,0 +1,64 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: direct-response + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: PathPrefix + value: /inline + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-inline + - matches: + - path: + type: PathPrefix + value: /value-ref + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-value-ref +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: response-value-ref + namespace: gateway-conformance-infra +data: + response.body: '{"error": "Internal Server Error"}' +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: HTTPRouteFilter +metadata: + name: direct-response-inline + namespace: gateway-conformance-infra +spec: + directResponse: + contentType: text/plain + body: + type: Inline + inline: "Oops! Your request is not found." +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: HTTPRouteFilter +metadata: + name: direct-response-value-ref + namespace: gateway-conformance-infra +spec: + directResponse: + contentType: application/json + body: + type: ValueRef + valueRef: + group: "" + kind: ConfigMap + name: value-ref-response diff --git a/test/e2e/tests/direct-response.go b/test/e2e/tests/direct-response.go new file mode 100644 index 00000000000..595b6908b3f --- /dev/null +++ b/test/e2e/tests/direct-response.go @@ -0,0 +1,77 @@ +// Copyright Envoy Gateway Authors +// SPDX-License-Identifier: Apache-2.0 +// The full text of the Apache license is available in the LICENSE file at +// the root of the repo. + +//go:build e2e + +package tests + +import ( + "fmt" + "io" + "net/http" + "net/url" + "testing" + + "k8s.io/apimachinery/pkg/types" + gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" + gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + httputils "sigs.k8s.io/gateway-api/conformance/utils/http" + "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" + "sigs.k8s.io/gateway-api/conformance/utils/suite" + + "github.com/envoyproxy/gateway/internal/gatewayapi" + "github.com/envoyproxy/gateway/internal/gatewayapi/resource" +) + +func init() { + ConformanceTests = append(ConformanceTests, DirectResponseTest) +} + +var DirectResponseTest = suite.ConformanceTest{ + ShortName: "DirectResponse", + Description: "Direct", + Manifests: []string{"testdata/direct-response.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + t.Run("direct response", func(t *testing.T) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "direct-response", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + + kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) + verifyCustomResponse(t, gwAddr, "/status/404", "text/plain", "Oops! Your request is not found.") + verifyCustomResponse(t, gwAddr, "/status/500", "application/json", `{"error": "Internal Server Error"}`) + }) + }, +} + +func verifyCustomResponse(t *testing.T, gwAddr, path, expectedContentType, expectedBody string) { + reqURL := url.URL{ + Scheme: "http", + Host: httputils.CalculateHost(t, gwAddr, "http"), + Path: path, + } + + rsp, err := http.Get(reqURL.String()) + if err != nil { + t.Fatalf("failed to get response: %v", err) + } + + // Verify that the response body is overridden + defer rsp.Body.Close() + body, err := io.ReadAll(rsp.Body) + if err != nil { + t.Fatalf("failed to read response body: %v", err) + } + if string(body) != expectedBody { + t.Errorf("expected response body to be %s but got %s", expectedBody, string(body)) + } + + // Verify that the content type is overridden + contentType := rsp.Header.Get("Content-Type") + if contentType != expectedContentType { + t.Errorf("expected content type to be %s but got %s", expectedContentType, contentType) + } +} diff --git a/test/e2e/tests/response-override.go b/test/e2e/tests/response-override.go index b21db88e242..77117c9a4fc 100644 --- a/test/e2e/tests/response-override.go +++ b/test/e2e/tests/response-override.go @@ -47,17 +47,17 @@ var ResponseOverrideTest = suite.ConformanceTest{ Name: gwapiv1.ObjectName(gwNN.Name), } BackendTrafficPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "response-override", Namespace: ns}, suite.ControllerName, ancestorRef) - verifyResponseOverride(t, gwAddr, 404, "text/plain", "Oops! Your request is not found.") - verifyResponseOverride(t, gwAddr, 500, "application/json", `{"error": "Internal Server Error"}`) + verifyCustomResponse(t, gwAddr, "/status/404", "text/plain", "Oops! Your request is not found.") + verifyCustomResponse(t, gwAddr, "/status/500", "application/json", `{"error": "Internal Server Error"}`) }) }, } -func verifyResponseOverride(t *testing.T, gwAddr string, statusCode int, expectedContentType string, expectedBody string) { +func verifyCustomResponse(t *testing.T, gwAddr, path, expectedContentType, expectedBody string) { reqURL := url.URL{ Scheme: "http", Host: httputils.CalculateHost(t, gwAddr, "http"), - Path: fmt.Sprintf("/status/%d", statusCode), + Path: path, } rsp, err := http.Get(reqURL.String()) From 461e5016b22a59e274863076f8f04f1db7edbb21 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Fri, 1 Nov 2024 16:36:07 -0700 Subject: [PATCH 2/8] unit tests Signed-off-by: Arko Dasgupta --- internal/gatewayapi/route.go | 2 +- .../httproute-with-direct-response.in.yaml | 83 +++++++++ .../httproute-with-direct-response.out.yaml | 168 ++++++++++++++++++ test/e2e/testdata/direct-response.yaml | 2 +- test/e2e/tests/direct-response.go | 43 +---- test/e2e/tests/response-override.go | 1 - 6 files changed, 255 insertions(+), 44 deletions(-) create mode 100644 internal/gatewayapi/testdata/httproute-with-direct-response.in.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-direct-response.out.yaml diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 648aebaeb5c..e51947411d8 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -237,7 +237,7 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe // If the route has no valid backends then just use a direct response and don't fuss with weighted responses for _, ruleRoute := range ruleRoutes { noValidBackends := ruleRoute.Destination == nil || ruleRoute.Destination.ToBackendWeights().Valid == 0 - if noValidBackends && ruleRoute.Redirect == nil { + if ruleRoute.DirectResponse == nil && noValidBackends && ruleRoute.Redirect == nil { ruleRoute.DirectResponse = &ir.CustomResponse{ StatusCode: ptr.To(uint32(500)), } diff --git a/internal/gatewayapi/testdata/httproute-with-direct-response.in.yaml b/internal/gatewayapi/testdata/httproute-with-direct-response.in.yaml new file mode 100644 index 00000000000..de39f2fcc75 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-direct-response.in.yaml @@ -0,0 +1,83 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + hostname: "*.envoyproxy.io" + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: direct-response + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: /inline + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-inline + - matches: + - path: + type: PathPrefix + value: /value-ref + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-value-ref +configMaps: +- apiVersion: v1 + kind: ConfigMap + metadata: + name: value-ref-response + namespace: default + data: + response.body: '{"error": "Internal Server Error"}' +httpFilters: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: HTTPRouteFilter + metadata: + name: direct-response-inline + namespace: default + spec: + directResponse: + contentType: text/plain + body: + type: Inline + inline: "OK" +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: HTTPRouteFilter + metadata: + name: direct-response-value-ref + namespace: default + spec: + directResponse: + contentType: application/json + statusCode: 502 + body: + type: ValueRef + valueRef: + group: "" + kind: ConfigMap + name: value-ref-response diff --git a/internal/gatewayapi/testdata/httproute-with-direct-response.out.yaml b/internal/gatewayapi/testdata/httproute-with-direct-response.out.yaml new file mode 100644 index 00000000000..80270199bbf --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-direct-response.out.yaml @@ -0,0 +1,168 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + hostname: '*.envoyproxy.io' + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: direct-response + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-inline + type: ExtensionRef + matches: + - path: + type: PathPrefix + value: /inline + - filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-value-ref + type: ExtensionRef + matches: + - path: + type: PathPrefix + value: /value-ref + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - addResponseHeaders: + - append: false + name: Content-Type + value: + - application/json + directResponse: + body: '{"error": "Internal Server Error"}' + statusCode: 502 + hostname: '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: HTTPRoute + name: direct-response + namespace: default + name: httproute/default/direct-response/rule/1/match/0/*_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: /value-ref + - addResponseHeaders: + - append: false + name: Content-Type + value: + - text/plain + directResponse: + body: OK + statusCode: 200 + hostname: '*.envoyproxy.io' + isHTTP2: false + metadata: + kind: HTTPRoute + name: direct-response + namespace: default + name: httproute/default/direct-response/rule/0/match/0/*_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: /inline diff --git a/test/e2e/testdata/direct-response.yaml b/test/e2e/testdata/direct-response.yaml index 0e5a92c8693..a1d2d81e8bb 100644 --- a/test/e2e/testdata/direct-response.yaml +++ b/test/e2e/testdata/direct-response.yaml @@ -31,7 +31,7 @@ spec: apiVersion: v1 kind: ConfigMap metadata: - name: response-value-ref + name: value-ref-response namespace: gateway-conformance-infra data: response.body: '{"error": "Internal Server Error"}' diff --git a/test/e2e/tests/direct-response.go b/test/e2e/tests/direct-response.go index 595b6908b3f..a85cc5b86f5 100644 --- a/test/e2e/tests/direct-response.go +++ b/test/e2e/tests/direct-response.go @@ -8,21 +8,11 @@ package tests import ( - "fmt" - "io" - "net/http" - "net/url" "testing" "k8s.io/apimachinery/pkg/types" - gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" - gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" - httputils "sigs.k8s.io/gateway-api/conformance/utils/http" "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" "sigs.k8s.io/gateway-api/conformance/utils/suite" - - "github.com/envoyproxy/gateway/internal/gatewayapi" - "github.com/envoyproxy/gateway/internal/gatewayapi/resource" ) func init() { @@ -41,37 +31,8 @@ var DirectResponseTest = suite.ConformanceTest{ gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) - verifyCustomResponse(t, gwAddr, "/status/404", "text/plain", "Oops! Your request is not found.") - verifyCustomResponse(t, gwAddr, "/status/500", "application/json", `{"error": "Internal Server Error"}`) + verifyCustomResponse(t, gwAddr, "/status/inline", "text/plain", "Oops! Your request is not found.") + verifyCustomResponse(t, gwAddr, "/status/value-ref", "application/json", `{"error": "Internal Server Error"}`) }) }, } - -func verifyCustomResponse(t *testing.T, gwAddr, path, expectedContentType, expectedBody string) { - reqURL := url.URL{ - Scheme: "http", - Host: httputils.CalculateHost(t, gwAddr, "http"), - Path: path, - } - - rsp, err := http.Get(reqURL.String()) - if err != nil { - t.Fatalf("failed to get response: %v", err) - } - - // Verify that the response body is overridden - defer rsp.Body.Close() - body, err := io.ReadAll(rsp.Body) - if err != nil { - t.Fatalf("failed to read response body: %v", err) - } - if string(body) != expectedBody { - t.Errorf("expected response body to be %s but got %s", expectedBody, string(body)) - } - - // Verify that the content type is overridden - contentType := rsp.Header.Get("Content-Type") - if contentType != expectedContentType { - t.Errorf("expected content type to be %s but got %s", expectedContentType, contentType) - } -} diff --git a/test/e2e/tests/response-override.go b/test/e2e/tests/response-override.go index 77117c9a4fc..e4eccc898cb 100644 --- a/test/e2e/tests/response-override.go +++ b/test/e2e/tests/response-override.go @@ -8,7 +8,6 @@ package tests import ( - "fmt" "io" "net/http" "net/url" From 976bf58767a46d5d77502c2cd03c97a275cc1d8b Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Fri, 1 Nov 2024 17:28:23 -0700 Subject: [PATCH 3/8] fix ns Signed-off-by: Arko Dasgupta --- test/e2e/testdata/direct-response.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/e2e/testdata/direct-response.yaml b/test/e2e/testdata/direct-response.yaml index a1d2d81e8bb..29d007a474e 100644 --- a/test/e2e/testdata/direct-response.yaml +++ b/test/e2e/testdata/direct-response.yaml @@ -32,7 +32,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: value-ref-response - namespace: gateway-conformance-infra + namespace: same-namespace data: response.body: '{"error": "Internal Server Error"}' --- @@ -40,7 +40,7 @@ apiVersion: gateway.envoyproxy.io/v1alpha1 kind: HTTPRouteFilter metadata: name: direct-response-inline - namespace: gateway-conformance-infra + namespace: same-namespace spec: directResponse: contentType: text/plain @@ -52,7 +52,7 @@ apiVersion: gateway.envoyproxy.io/v1alpha1 kind: HTTPRouteFilter metadata: name: direct-response-value-ref - namespace: gateway-conformance-infra + namespace: same-namespace spec: directResponse: contentType: application/json From ec4943a8a565aec4aedce90650573c7dfff98f09 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Fri, 1 Nov 2024 17:49:39 -0700 Subject: [PATCH 4/8] docs for direct response Signed-off-by: Arko Dasgupta --- .../latest/tasks/traffic/direct-response.md | 284 ++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 site/content/en/latest/tasks/traffic/direct-response.md diff --git a/site/content/en/latest/tasks/traffic/direct-response.md b/site/content/en/latest/tasks/traffic/direct-response.md new file mode 100644 index 00000000000..4b9aaa5551e --- /dev/null +++ b/site/content/en/latest/tasks/traffic/direct-response.md @@ -0,0 +1,284 @@ +--- +title: "Direct Response" +--- + +Direct responses are valuable in cases where you want the gateway itself +to handle certain requests without forwarding them to backend services. +This task shows you how to configure them. + +## Installation + +Follow the steps from the [Quickstart](../../quickstart) to install Envoy Gateway and the example manifest. +Before proceeding, you should be able to query the example backend using HTTP. + +## Testing Direct Response + +{{< tabpane text=true >}} +{{% tab header="Apply from stdin" %}} + +```shell +cat <}} + +```shell +curl --header "Host: timeout.example.com" http://${GATEWAY_HOST}/?delay=3s -I +``` + +```console +HTTP/1.1 200 OK +content-type: application/json +x-content-type-options: nosniff +date: Mon, 04 Mar 2024 02:34:21 GMT +content-length: 480 +``` + +Then we set the request timeout to 2 seconds. In this case, Envoy Gateway will respond with a timeout. + +{{< tabpane text=true >}} +{{% tab header="Apply from stdin" %}} + +```shell +cat <}} + +```shell +curl --verbose --header "Host: www.example.com" http://$GATEWAY_HOST/inline +``` + +```console +* Trying 127.0.0.1:80... +* Connected to 127.0.0.1 (127.0.0.1) port 80 +> GET /inline HTTP/1.1 +> Host: www.example.com +> User-Agent: curl/8.4.0 +> Accept: */* +> +< HTTP/1.1 503 Service Unavailable +< content-type: text/plain +< content-length: 32 +< date: Sat, 02 Nov 2024 00:35:48 GMT +< +* Connection #0 to host 127.0.0.1 left intact +Oops! Your request is not found. +``` + +```shell +curl --verbose --header "Host: www.example.com" http://$GATEWAY_HOST/value-ref +``` + +```console +* Trying 127.0.0.1:80... +* Connected to 127.0.0.1 (127.0.0.1) port 80 +> GET /value-ref HTTP/1.1 +> Host: www.example.com +> User-Agent: curl/8.4.0 +> Accept: */* +> +< HTTP/1.1 500 Internal Server Error +< content-type: application/json +< content-length: 34 +< date: Sat, 02 Nov 2024 00:35:55 GMT +< +* Connection #0 to host 127.0.0.1 left intact +{"error": "Internal Server Error"} +``` From 3fc07bcfb1bcbf18572b10dad12bffd4b1c90a68 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Fri, 1 Nov 2024 18:05:27 -0700 Subject: [PATCH 5/8] negative tests Signed-off-by: Arko Dasgupta --- .../httproute-with-direct-response.in.yaml | 36 ++++++++++++++++ .../httproute-with-direct-response.out.yaml | 42 ++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/internal/gatewayapi/testdata/httproute-with-direct-response.in.yaml b/internal/gatewayapi/testdata/httproute-with-direct-response.in.yaml index de39f2fcc75..bd9a316227e 100644 --- a/internal/gatewayapi/testdata/httproute-with-direct-response.in.yaml +++ b/internal/gatewayapi/testdata/httproute-with-direct-response.in.yaml @@ -46,6 +46,27 @@ httpRoutes: group: gateway.envoyproxy.io kind: HTTPRouteFilter name: direct-response-value-ref +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: direct-response-with-errors + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - matches: + - path: + type: PathPrefix + value: /value-ref-not-found + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-value-ref-not-found configMaps: - apiVersion: v1 kind: ConfigMap @@ -66,6 +87,21 @@ httpFilters: body: type: Inline inline: "OK" +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: HTTPRouteFilter + metadata: + name: direct-response-value-ref-not-exit + namespace: default + spec: + directResponse: + contentType: application/json + statusCode: 502 + body: + type: ValueRef + valueRef: + group: "" + kind: ConfigMap + name: value-ref-does-not-exist - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: HTTPRouteFilter metadata: diff --git a/internal/gatewayapi/testdata/httproute-with-direct-response.out.yaml b/internal/gatewayapi/testdata/httproute-with-direct-response.out.yaml index 80270199bbf..29b6b051366 100644 --- a/internal/gatewayapi/testdata/httproute-with-direct-response.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-direct-response.out.yaml @@ -17,7 +17,7 @@ gateways: protocol: HTTP status: listeners: - - attachedRoutes: 1 + - attachedRoutes: 2 conditions: - lastTransitionTime: null message: Sending translated listener configuration to the data plane @@ -91,6 +91,46 @@ httpRoutes: name: gateway-1 namespace: envoy-gateway sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: direct-response-with-errors + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: direct-response-value-ref-not-found + type: ExtensionRef + matches: + - path: + type: PathPrefix + value: /value-ref-not-found + status: + parents: + - conditions: + - lastTransitionTime: null + message: 'Unable to translate HTTPRouteFilter: default/direct-response-value-ref-not-found' + reason: UnsupportedValue + status: "False" + type: Accepted + - lastTransitionTime: null + message: 'Unable to translate HTTPRouteFilter: default/direct-response-value-ref-not-found' + reason: BackendNotFound + status: "False" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http infraIR: envoy-gateway/gateway-1: proxy: From f1145d9665837001944780e8eedf2d6db7bbf853 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Sun, 3 Nov 2024 08:38:29 -0800 Subject: [PATCH 6/8] fix ns Signed-off-by: Arko Dasgupta --- test/e2e/testdata/direct-response.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/e2e/testdata/direct-response.yaml b/test/e2e/testdata/direct-response.yaml index 29d007a474e..a1d2d81e8bb 100644 --- a/test/e2e/testdata/direct-response.yaml +++ b/test/e2e/testdata/direct-response.yaml @@ -32,7 +32,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: value-ref-response - namespace: same-namespace + namespace: gateway-conformance-infra data: response.body: '{"error": "Internal Server Error"}' --- @@ -40,7 +40,7 @@ apiVersion: gateway.envoyproxy.io/v1alpha1 kind: HTTPRouteFilter metadata: name: direct-response-inline - namespace: same-namespace + namespace: gateway-conformance-infra spec: directResponse: contentType: text/plain @@ -52,7 +52,7 @@ apiVersion: gateway.envoyproxy.io/v1alpha1 kind: HTTPRouteFilter metadata: name: direct-response-value-ref - namespace: same-namespace + namespace: gateway-conformance-infra spec: directResponse: contentType: application/json From 8c02a656ef70948a6082eac72c70e6248a3667f8 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Sun, 3 Nov 2024 10:30:33 -0800 Subject: [PATCH 7/8] wait until config is programmed Signed-off-by: Arko Dasgupta --- test/e2e/tests/direct-response.go | 4 +-- test/e2e/tests/response-override.go | 55 ++++++++++++++++++----------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/test/e2e/tests/direct-response.go b/test/e2e/tests/direct-response.go index a85cc5b86f5..9c6eb591c73 100644 --- a/test/e2e/tests/direct-response.go +++ b/test/e2e/tests/direct-response.go @@ -31,8 +31,8 @@ var DirectResponseTest = suite.ConformanceTest{ gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) - verifyCustomResponse(t, gwAddr, "/status/inline", "text/plain", "Oops! Your request is not found.") - verifyCustomResponse(t, gwAddr, "/status/value-ref", "application/json", `{"error": "Internal Server Error"}`) + verifyCustomResponse(t, suite.TimeoutConfig, gwAddr, "/status/inline", "text/plain", "Oops! Your request is not found.") + verifyCustomResponse(t, suite.TimeoutConfig, gwAddr, "/status/value-ref", "application/json", `{"error": "Internal Server Error"}`) }) }, } diff --git a/test/e2e/tests/response-override.go b/test/e2e/tests/response-override.go index e4eccc898cb..c7c12bd2c10 100644 --- a/test/e2e/tests/response-override.go +++ b/test/e2e/tests/response-override.go @@ -12,13 +12,16 @@ import ( "net/http" "net/url" "testing" + "time" "k8s.io/apimachinery/pkg/types" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/gateway-api/conformance/utils/config" httputils "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/conformance/utils/tlog" "github.com/envoyproxy/gateway/internal/gatewayapi" "github.com/envoyproxy/gateway/internal/gatewayapi/resource" @@ -46,37 +49,47 @@ var ResponseOverrideTest = suite.ConformanceTest{ Name: gwapiv1.ObjectName(gwNN.Name), } BackendTrafficPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "response-override", Namespace: ns}, suite.ControllerName, ancestorRef) - verifyCustomResponse(t, gwAddr, "/status/404", "text/plain", "Oops! Your request is not found.") - verifyCustomResponse(t, gwAddr, "/status/500", "application/json", `{"error": "Internal Server Error"}`) + verifyCustomResponse(t, suite.TimeoutConfig, gwAddr, "/status/404", "text/plain", "Oops! Your request is not found.") + verifyCustomResponse(t, suite.TimeoutConfig, gwAddr, "/status/500", "application/json", `{"error": "Internal Server Error"}`) }) }, } -func verifyCustomResponse(t *testing.T, gwAddr, path, expectedContentType, expectedBody string) { +func verifyCustomResponse(t *testing.T, timeoutConfig config.TimeoutConfig, gwAddr, path, expectedContentType, expectedBody string) { reqURL := url.URL{ Scheme: "http", Host: httputils.CalculateHost(t, gwAddr, "http"), Path: path, } - rsp, err := http.Get(reqURL.String()) - if err != nil { - t.Fatalf("failed to get response: %v", err) - } + httputils.AwaitConvergence(t, timeoutConfig.RequiredConsecutiveSuccesses, timeoutConfig.MaxTimeToConsistency, func(elapsed time.Duration) bool { + rsp, err := http.Get(reqURL.String()) + if err != nil { + tlog.Logf(t, "failed to get response: %v", err) + return false + } - // Verify that the response body is overridden - defer rsp.Body.Close() - body, err := io.ReadAll(rsp.Body) - if err != nil { - t.Fatalf("failed to read response body: %v", err) - } - if string(body) != expectedBody { - t.Errorf("expected response body to be %s but got %s", expectedBody, string(body)) - } + // Verify that the response body is overridden + defer rsp.Body.Close() + body, err := io.ReadAll(rsp.Body) + if err != nil { + tlog.Logf(t, "failed to read response body: %v", err) + return false + } + if string(body) != expectedBody { + tlog.Logf(t, "expected response body to be %s but got %s", expectedBody, string(body)) + return false + } - // Verify that the content type is overridden - contentType := rsp.Header.Get("Content-Type") - if contentType != expectedContentType { - t.Errorf("expected content type to be %s but got %s", expectedContentType, contentType) - } + // Verify that the content type is overridden + contentType := rsp.Header.Get("Content-Type") + if contentType != expectedContentType { + tlog.Logf(t, "expected content type to be %s but got %s", expectedContentType, contentType) + return false + } + + return true + }) + + tlog.Logf(t, "Request passed") } From 64aedeaae45186f6e46397b5ecfde32cedb3911d Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Sun, 3 Nov 2024 12:58:39 -0800 Subject: [PATCH 8/8] fix path Signed-off-by: Arko Dasgupta --- test/e2e/tests/direct-response.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/tests/direct-response.go b/test/e2e/tests/direct-response.go index 9c6eb591c73..12c667fdd30 100644 --- a/test/e2e/tests/direct-response.go +++ b/test/e2e/tests/direct-response.go @@ -31,8 +31,8 @@ var DirectResponseTest = suite.ConformanceTest{ gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) kubernetes.HTTPRouteMustHaveResolvedRefsConditionsTrue(t, suite.Client, suite.TimeoutConfig, routeNN, gwNN) - verifyCustomResponse(t, suite.TimeoutConfig, gwAddr, "/status/inline", "text/plain", "Oops! Your request is not found.") - verifyCustomResponse(t, suite.TimeoutConfig, gwAddr, "/status/value-ref", "application/json", `{"error": "Internal Server Error"}`) + verifyCustomResponse(t, suite.TimeoutConfig, gwAddr, "/inline", "text/plain", "Oops! Your request is not found.") + verifyCustomResponse(t, suite.TimeoutConfig, gwAddr, "/value-ref", "application/json", `{"error": "Internal Server Error"}`) }) }, }