From 389b43db9b0d23db37fae1d4070c281d2312826c Mon Sep 17 00:00:00 2001 From: Guy Daich Date: Thu, 19 Dec 2024 19:03:28 -0600 Subject: [PATCH] feat(translator): preserve route order in CTP Signed-off-by: Guy Daich --- api/v1alpha1/clienttrafficpolicy_types.go | 7 + api/v1alpha1/zz_generated.deepcopy.go | 5 + ...y.envoyproxy.io_clienttrafficpolicies.yaml | 7 + internal/gatewayapi/clienttrafficpolicy.go | 4 + internal/gatewayapi/route.go | 7 + internal/gatewayapi/sort.go | 6 +- ...trafficpolicy-preserve-route-order.in.yaml | 112 ++++ ...rafficpolicy-preserve-route-order.out.yaml | 596 ++++++++++++++++++ internal/ir/xds.go | 2 + site/content/en/latest/api/extension_types.md | 1 + site/content/zh/latest/api/extension_types.md | 1 + test/e2e/base/manifests.yaml | 30 +- test/e2e/testdata/preserve-route-order.yaml | 47 ++ test/e2e/tests/preserve_route_order.go | 68 ++ 14 files changed, 876 insertions(+), 17 deletions(-) create mode 100644 internal/gatewayapi/testdata/clienttrafficpolicy-preserve-route-order.in.yaml create mode 100644 internal/gatewayapi/testdata/clienttrafficpolicy-preserve-route-order.out.yaml create mode 100644 test/e2e/testdata/preserve-route-order.yaml create mode 100644 test/e2e/tests/preserve_route_order.go diff --git a/api/v1alpha1/clienttrafficpolicy_types.go b/api/v1alpha1/clienttrafficpolicy_types.go index 6c7129da0607..35cce5d62055 100644 --- a/api/v1alpha1/clienttrafficpolicy_types.go +++ b/api/v1alpha1/clienttrafficpolicy_types.go @@ -98,6 +98,13 @@ type ClientTrafficPolicySpec struct { // // +optional HealthCheck *HealthCheckSettings `json:"healthCheck,omitempty"` + // PreserveRouteOrder determines if the order of matching for HTTPRoutes is determined by Gateway-API + // specification (https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule) + // or preserves the order defined by users in the HTTPRoute's HTTPRouteRule list. + // Default: False + // + // +optional + PreserveRouteOrder *bool `json:"preserveRouteOrder,omitempty"` } // HeaderSettings provides configuration options for headers on the listener. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index dbc28e6aca2d..a232e773cb41 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -939,6 +939,11 @@ func (in *ClientTrafficPolicySpec) DeepCopyInto(out *ClientTrafficPolicySpec) { *out = new(HealthCheckSettings) **out = **in } + if in.PreserveRouteOrder != nil { + in, out := &in.PreserveRouteOrder, &out.PreserveRouteOrder + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientTrafficPolicySpec. diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml index d3afb65b3029..309b061f9e63 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml @@ -489,6 +489,13 @@ spec: - UnescapeAndRedirect type: string type: object + preserveRouteOrder: + description: |- + PreserveRouteOrder determines if the order of matching for HTTPRoutes is determined by Gateway-API + specification (https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule) + or preserves the order defined by users in the HTTPRoute's HTTPRouteRule list. + Default: False + type: boolean targetRef: description: |- TargetRef is the name of the resource this policy is being attached to. diff --git a/internal/gatewayapi/clienttrafficpolicy.go b/internal/gatewayapi/clienttrafficpolicy.go index 1c8d0f8af4ac..4d163099705c 100644 --- a/internal/gatewayapi/clienttrafficpolicy.go +++ b/internal/gatewayapi/clienttrafficpolicy.go @@ -482,6 +482,10 @@ func (t *Translator) translateClientTrafficPolicyForListener(policy *egv1a1.Clie errs = errors.Join(errs, err) } + if policy.Spec.PreserveRouteOrder != nil && *policy.Spec.PreserveRouteOrder { + httpIR.PreserverRouteOrder = true + } + // Early return if got any errors if errs != nil { return errs diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index ba51d964e6ee..9425333636d4 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -8,6 +8,7 @@ package gatewayapi import ( "fmt" "net" + "sort" "strconv" "strings" "time" @@ -53,6 +54,12 @@ type RoutesTranslator interface { func (t *Translator) ProcessHTTPRoutes(httpRoutes []*gwapiv1.HTTPRoute, gateways []*GatewayContext, resources *resource.Resources, xdsIR resource.XdsIRMap) []*HTTPRouteContext { var relevantHTTPRoutes []*HTTPRouteContext + // always sort initially by creation time stamp. Later on, additional sorting based on matcher type and + // match length may occur. + sort.Slice(httpRoutes, func(i, j int) bool { + return httpRoutes[i].CreationTimestamp.Before(&(httpRoutes[j].CreationTimestamp)) + }) + for _, h := range httpRoutes { if h == nil { panic("received nil httproute") diff --git a/internal/gatewayapi/sort.go b/internal/gatewayapi/sort.go index 95643a9e1542..1c444c1baf81 100644 --- a/internal/gatewayapi/sort.go +++ b/internal/gatewayapi/sort.go @@ -85,8 +85,10 @@ func (x XdsIRRoutes) Less(i, j int) bool { func sortXdsIRMap(xdsIR resource.XdsIRMap) { for _, irItem := range xdsIR { for _, http := range irItem.HTTP { - // descending order - sort.Sort(sort.Reverse(XdsIRRoutes(http.Routes))) + if !http.PreserverRouteOrder { + // descending order + sort.Sort(sort.Reverse(XdsIRRoutes(http.Routes))) + } } } } diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-preserve-route-order.in.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-preserve-route-order.in.yaml new file mode 100644 index 000000000000..78059d635dbe --- /dev/null +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-preserve-route-order.in.yaml @@ -0,0 +1,112 @@ +clientTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: ClientTrafficPolicy + metadata: + namespace: envoy-gateway + name: target-gateway-1 + spec: + preserveRouteOrder: false + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: ClientTrafficPolicy + metadata: + namespace: envoy-gateway + name: target-gateway-1-section-http-1 + spec: + preserveRouteOrder: true + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: http-preserve-order +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http-preserve-order + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All + - name: http-2 + protocol: HTTP + port: 8080 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + type: PathPrefix + value: "/" + backendRefs: + - name: service-1 + port: 8080 + - matches: + - path: + type: RegularExpression + value: "/regex/between/prefixes/*" + backendRefs: + - name: service-1 + port: 8080 + - matches: + - path: + type: PathPrefix + value: "/specific/prefix/after/less/specific" + backendRefs: + - name: service-1 + port: 8080 + - matches: + - path: + type: Exact + value: "/exact" + backendRefs: + - name: service-1 + port: 8080 + - matches: + - path: + type: RegularExpression + value: "/regex/between/exacts/*" + backendRefs: + - name: service-1 + port: 8080 + - matches: + - path: + type: Exact + value: "/specific/exact/after/less/specific" + backendRefs: + - name: service-1 + port: 8080 + - matches: + - path: + type: RegularExpression + value: "/short/regex/*" + backendRefs: + - name: service-1 + port: 8080 + - matches: + - path: + type: RegularExpression + value: "/longer/regex/after/shorter/regex/*" + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-preserve-route-order.out.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-preserve-route-order.out.yaml new file mode 100644 index 000000000000..d61ed8ceb62d --- /dev/null +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-preserve-route-order.out.yaml @@ -0,0 +1,596 @@ +clientTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: ClientTrafficPolicy + metadata: + creationTimestamp: null + name: target-gateway-1-section-http-1 + namespace: envoy-gateway + spec: + preserveRouteOrder: true + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + sectionName: http-preserve-order + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http-preserve-order + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: ClientTrafficPolicy + metadata: + creationTimestamp: null + name: target-gateway-1 + namespace: envoy-gateway + spec: + preserveRouteOrder: false + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: There are existing ClientTrafficPolicies that are overriding these + sections [http-preserve-order] + reason: Overridden + status: "True" + type: Overridden + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +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 + name: http-preserve-order + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: All + name: http-2 + port: 8080 + 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-preserve-order + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute + - 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-2 + 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: httproute-1 + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: / + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: RegularExpression + value: /regex/between/prefixes/* + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: PathPrefix + value: /specific/prefix/after/less/specific + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: Exact + value: /exact + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: RegularExpression + value: /regex/between/exacts/* + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: Exact + value: /specific/exact/after/less/specific + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: RegularExpression + value: /short/regex/* + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + type: RegularExpression + value: /longer/regex/after/shorter/regex/* + 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 +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http-preserve-order + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + - address: null + name: envoy-gateway/gateway-1/http-2 + ports: + - containerPort: 8080 + name: http-8080 + protocol: HTTP + servicePort: 8080 + 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: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http-preserve-order + name: envoy-gateway/gateway-1/http-preserve-order + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + preserverRouteOrder: true + routes: + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + - destination: + name: httproute/default/httproute-1/rule/1 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/1/match/0/* + pathMatch: + distinct: false + name: "" + safeRegex: /regex/between/prefixes/* + - destination: + name: httproute/default/httproute-1/rule/2 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/2/match/0/* + pathMatch: + distinct: false + name: "" + prefix: /specific/prefix/after/less/specific + - destination: + name: httproute/default/httproute-1/rule/3 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/3/match/0/* + pathMatch: + distinct: false + exact: /exact + name: "" + - destination: + name: httproute/default/httproute-1/rule/4 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/4/match/0/* + pathMatch: + distinct: false + name: "" + safeRegex: /regex/between/exacts/* + - destination: + name: httproute/default/httproute-1/rule/5 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/5/match/0/* + pathMatch: + distinct: false + exact: /specific/exact/after/less/specific + name: "" + - destination: + name: httproute/default/httproute-1/rule/6 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/6/match/0/* + pathMatch: + distinct: false + name: "" + safeRegex: /short/regex/* + - destination: + name: httproute/default/httproute-1/rule/7 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/7/match/0/* + pathMatch: + distinct: false + name: "" + safeRegex: /longer/regex/after/shorter/regex/* + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http-2 + name: envoy-gateway/gateway-1/http-2 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 8080 + routes: + - destination: + name: httproute/default/httproute-1/rule/5 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/5/match/0/* + pathMatch: + distinct: false + exact: /specific/exact/after/less/specific + name: "" + - destination: + name: httproute/default/httproute-1/rule/3 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/3/match/0/* + pathMatch: + distinct: false + exact: /exact + name: "" + - destination: + name: httproute/default/httproute-1/rule/7 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/7/match/0/* + pathMatch: + distinct: false + name: "" + safeRegex: /longer/regex/after/shorter/regex/* + - destination: + name: httproute/default/httproute-1/rule/1 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/1/match/0/* + pathMatch: + distinct: false + name: "" + safeRegex: /regex/between/prefixes/* + - destination: + name: httproute/default/httproute-1/rule/4 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/4/match/0/* + pathMatch: + distinct: false + name: "" + safeRegex: /regex/between/exacts/* + - destination: + name: httproute/default/httproute-1/rule/6 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/6/match/0/* + pathMatch: + distinct: false + name: "" + safeRegex: /short/regex/* + - destination: + name: httproute/default/httproute-1/rule/2 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/2/match/0/* + pathMatch: + distinct: false + name: "" + prefix: /specific/prefix/after/less/specific + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 4db7b8445aaa..9a6ce4e7b086 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -314,6 +314,8 @@ type HTTPListener struct { Timeout *ClientTimeout `json:"timeout,omitempty" yaml:"clientTimeout,omitempty"` // Connection settings Connection *ClientConnection `json:"connection,omitempty" yaml:"connection,omitempty"` + // PreserveRouteOrder determines if routes should be sorted according to GW-API specs + PreserverRouteOrder bool `json:"preserverRouteOrder,omitempty" yaml:"preserverRouteOrder,omitempty"` } // Validate the fields within the HTTPListener structure diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 5119d756646c..8a0bafb5e7ab 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -674,6 +674,7 @@ _Appears in:_ | `http2` | _[HTTP2Settings](#http2settings)_ | false | HTTP2 provides HTTP/2 configuration on the listener. | | `http3` | _[HTTP3Settings](#http3settings)_ | false | HTTP3 provides HTTP/3 configuration on the listener. | | `healthCheck` | _[HealthCheckSettings](#healthchecksettings)_ | false | HealthCheck provides configuration for determining whether the HTTP/HTTPS listener is healthy. | +| `preserveRouteOrder` | _boolean_ | false | PreserveRouteOrder determines if the order of matching for HTTPRoutes is determined by Gateway-API
specification (https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule)
or preserves the order defined by users in the HTTPRoute's HTTPRouteRule list.
Default: False | #### ClientValidationContext diff --git a/site/content/zh/latest/api/extension_types.md b/site/content/zh/latest/api/extension_types.md index 5119d756646c..8a0bafb5e7ab 100644 --- a/site/content/zh/latest/api/extension_types.md +++ b/site/content/zh/latest/api/extension_types.md @@ -674,6 +674,7 @@ _Appears in:_ | `http2` | _[HTTP2Settings](#http2settings)_ | false | HTTP2 provides HTTP/2 configuration on the listener. | | `http3` | _[HTTP3Settings](#http3settings)_ | false | HTTP3 provides HTTP/3 configuration on the listener. | | `healthCheck` | _[HealthCheckSettings](#healthchecksettings)_ | false | HealthCheck provides configuration for determining whether the HTTP/HTTPS listener is healthy. | +| `preserveRouteOrder` | _boolean_ | false | PreserveRouteOrder determines if the order of matching for HTTPRoutes is determined by Gateway-API
specification (https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPRouteRule)
or preserves the order defined by users in the HTTPRoute's HTTPRouteRule list.
Default: False | #### ClientValidationContext diff --git a/test/e2e/base/manifests.yaml b/test/e2e/base/manifests.yaml index ef3e9841c3bb..42fb45165718 100644 --- a/test/e2e/base/manifests.yaml +++ b/test/e2e/base/manifests.yaml @@ -35,21 +35,21 @@ spec: allowedRoutes: namespaces: from: Same ---- -apiVersion: gateway.networking.k8s.io/v1 -kind: Gateway -metadata: - name: all-namespaces - namespace: gateway-conformance-infra -spec: - gatewayClassName: "{GATEWAY_CLASS_NAME}" - listeners: - - name: http - port: 80 - protocol: HTTP - allowedRoutes: - namespaces: - from: All +#--- +#apiVersion: gateway.networking.k8s.io/v1 +#kind: Gateway +#metadata: +# name: all-namespaces +# namespace: gateway-conformance-infra +#spec: +# gatewayClassName: "{GATEWAY_CLASS_NAME}" +# listeners: +# - name: http +# port: 80 +# protocol: HTTP +# allowedRoutes: +# namespaces: +# from: All --- apiVersion: v1 kind: Service diff --git a/test/e2e/testdata/preserve-route-order.yaml b/test/e2e/testdata/preserve-route-order.yaml new file mode 100644 index 000000000000..cb056ea2898f --- /dev/null +++ b/test/e2e/testdata/preserve-route-order.yaml @@ -0,0 +1,47 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: preserve-route-order-ctp + namespace: gateway-conformance-infra +spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: Gateway + name: same-namespace + preserveRouteOrder: true +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: http-with-preserved-route-order + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: PathPrefix + value: / + filters: + - type: RequestHeaderModifier + requestHeaderModifier: + add: + - name: matched-rule-prefix + value: / + backendRefs: + - name: infra-backend-v1 + port: 8080 + - matches: + - path: + type: PathPrefix + value: /specific + filters: + - type: RequestHeaderModifier + requestHeaderModifier: + add: + - name: matched-rule-prefix + value: /specific + backendRefs: + - name: infra-backend-v1 + port: 8080 \ No newline at end of file diff --git a/test/e2e/tests/preserve_route_order.go b/test/e2e/tests/preserve_route_order.go new file mode 100644 index 000000000000..612dc77a782b --- /dev/null +++ b/test/e2e/tests/preserve_route_order.go @@ -0,0 +1,68 @@ +// 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 ( + "testing" + + "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/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, PreserveRouteOrderTest) +} + +var PreserveRouteOrderTest = suite.ConformanceTest{ + ShortName: "PreserveRouteOrder", + Description: "Route order should be preserved", + Manifests: []string{"testdata/preserve-route-order.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + t.Run("Request will match the less-specific rule due to order preservation", func(t *testing.T) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "http-with-preserved-route-order", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + + ancestorRef := gwapiv1a2.ParentReference{ + Group: gatewayapi.GroupPtr(gwapiv1.GroupName), + Kind: gatewayapi.KindPtr(resource.KindGateway), + Namespace: gatewayapi.NamespacePtr(gwNN.Namespace), + Name: gwapiv1.ObjectName(gwNN.Name), + } + ClientTrafficPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "preserve-route-order-ctp", Namespace: ns}, suite.ControllerName, ancestorRef) + + expected := http.ExpectedResponse{ + Request: http.Request{ + Path: "/specific/foo", + }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Path: "/specific/foo", + Headers: map[string]string{ + "matched-rule-prefix": "/", // the less specific route rule is used because the user-defined order is preserved + }, + }, + }, + Response: http.Response{ + StatusCode: 200, + }, + Namespace: ns, + } + + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expected) + }) + }, +}