diff --git a/VERSION b/VERSION index 3b34d22941..5f152d81a5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.7.2 +v1.7.3 diff --git a/api/v1alpha1/healthcheck_types.go b/api/v1alpha1/healthcheck_types.go index 7fd1cd6a15..b56d55a085 100644 --- a/api/v1alpha1/healthcheck_types.go +++ b/api/v1alpha1/healthcheck_types.go @@ -159,8 +159,9 @@ const ( // HTTPActiveHealthChecker defines the settings of http health check. type HTTPActiveHealthChecker struct { - // Hostname defines the HTTP host that will be requested during health checking. - // Default: HTTPRoute or GRPCRoute hostname. + // Hostname defines the HTTP Host header used for active HTTP health checks. + // Host selection uses this order: this field, the associated Backend endpoint + // hostname if available, then the effective Route hostname. // // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index 123e89c94a..8646634ba9 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -426,8 +426,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml index c7847dd52d..af8a7556d2 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml @@ -478,8 +478,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml index 1236d0d613..7bc5e5c580 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -11521,8 +11521,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -12773,8 +12774,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -14195,8 +14197,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -15522,8 +15525,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ diff --git a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_securitypolicies.yaml index 6d20dad82e..f9231269a0 100644 --- a/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-crds-helm/templates/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -1094,8 +1094,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -2234,8 +2235,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -3575,8 +3577,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -4994,8 +4997,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml index 0e5b09c8e6..3f5343f86b 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml @@ -425,8 +425,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml index f87b1cacfa..8d86777ad6 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyextensionpolicies.yaml @@ -477,8 +477,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml index 66b39ecc0c..f1e6dec7d6 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_envoyproxies.yaml @@ -11520,8 +11520,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -12772,8 +12773,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -14194,8 +14196,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -15521,8 +15524,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml index 684d40a80d..e191a3fcb3 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -1093,8 +1093,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -2233,8 +2234,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -3574,8 +3576,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -4993,8 +4996,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ diff --git a/internal/gatewayapi/backend.go b/internal/gatewayapi/backend.go index bd5aedc67a..60ed97e55a 100644 --- a/internal/gatewayapi/backend.go +++ b/internal/gatewayapi/backend.go @@ -20,8 +20,10 @@ import ( ) func (t *Translator) ProcessBackends(backends []*egv1a1.Backend, backendTLSPolicies []*gwapiv1.BackendTLSPolicy) []*egv1a1.Backend { + backendCopies := backendCopiesWithStatusDeepCopy(backends) res := make([]*egv1a1.Backend, 0, len(backends)) - for _, backend := range backends { + for i := range backends { + backend := backendCopies[i] // Ensure Backends are enabled if !t.BackendEnabled { status.UpdateBackendStatusAcceptedCondition(backend, false, @@ -203,3 +205,15 @@ func validateIP(epIP *egv1a1.IPEndpoint, runningOnHost bool) status.Error { } return nil } + +// backendCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields. +// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine. +func backendCopiesWithStatusDeepCopy(backends []*egv1a1.Backend) []*egv1a1.Backend { + copies := make([]*egv1a1.Backend, len(backends)) + for i, b := range backends { + out := *b + b.Status.DeepCopyInto(&out.Status) + copies[i] = &out + } + return copies +} diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index a00870f687..1a70a0ee8a 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -95,6 +95,8 @@ func (t *Translator) ProcessBackendTrafficPolicies( SectionIndex: make(map[types.NamespacedName]sets.Set[string], gatewayMapSize), } + policyCopies := backendTrafficPolicyCopiesWithStatusDeepCopy(backendTrafficPolicies) + handledPolicies := make(map[types.NamespacedName]*egv1a1.BackendTrafficPolicy, policyMapSize) // Translate @@ -107,7 +109,7 @@ func (t *Translator) ProcessBackendTrafficPolicies( t.buildGatewayPolicyMap(backendTrafficPolicies, gateways, gatewayMap, gatewayPolicyMap) // Process the policies targeting RouteRules - for _, currPolicy := range backendTrafficPolicies { + for i, currPolicy := range backendTrafficPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -115,7 +117,7 @@ func (t *Translator) ProcessBackendTrafficPolicies( if currTarget.Kind != resource.KindGateway && currTarget.SectionName != nil { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] handledPolicies[policyName] = policy res = append(res, policy) } @@ -127,7 +129,7 @@ func (t *Translator) ProcessBackendTrafficPolicies( } // Process the policies targeting Routes - for _, currPolicy := range backendTrafficPolicies { + for i, currPolicy := range backendTrafficPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -135,7 +137,7 @@ func (t *Translator) ProcessBackendTrafficPolicies( if currTarget.Kind != resource.KindGateway && currTarget.SectionName == nil { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] handledPolicies[policyName] = policy res = append(res, policy) } @@ -147,7 +149,7 @@ func (t *Translator) ProcessBackendTrafficPolicies( } // Process the policies targeting Listeners - for _, currPolicy := range backendTrafficPolicies { + for i, currPolicy := range backendTrafficPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -155,7 +157,7 @@ func (t *Translator) ProcessBackendTrafficPolicies( if currTarget.Kind == resource.KindGateway && currTarget.SectionName != nil { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] handledPolicies[policyName] = policy res = append(res, policy) } @@ -166,7 +168,7 @@ func (t *Translator) ProcessBackendTrafficPolicies( } // Process the policies targeting Gateways - for _, currPolicy := range backendTrafficPolicies { + for i, currPolicy := range backendTrafficPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -174,7 +176,7 @@ func (t *Translator) ProcessBackendTrafficPolicies( if currTarget.Kind == resource.KindGateway && currTarget.SectionName == nil { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] handledPolicies[policyName] = policy res = append(res, policy) } @@ -818,9 +820,6 @@ func (t *Translator) applyTrafficFeatureToRoute(route RouteContext, r.Traffic.Timeout = localTo } - // Update the Host field in HealthCheck, now that we have access to the Route Hostname. - r.Traffic.HealthCheck.SetHTTPHostIfAbsent(r.Hostname) - if policy.Spec.UseClientProtocol != nil { r.UseClientProtocol = policy.Spec.UseClientProtocol } @@ -1101,9 +1100,6 @@ func (t *Translator) translateBackendTrafficPolicyForGateway( r.Traffic.Timeout = localTo } - // Update the Host field in HealthCheck, now that we have access to the Route Hostname. - r.Traffic.HealthCheck.SetHTTPHostIfAbsent(r.Hostname) - if policy.Spec.UseClientProtocol != nil { r.UseClientProtocol = policy.Spec.UseClientProtocol } @@ -1847,3 +1843,15 @@ func buildRouteStatName(routeStatName string, metadata *ir.ResourceMetadata) *st return &statName } + +// backendTrafficPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields. +// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine. +func backendTrafficPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.BackendTrafficPolicy) []*egv1a1.BackendTrafficPolicy { + copies := make([]*egv1a1.BackendTrafficPolicy, len(policies)) + for i, p := range policies { + out := *p + p.Status.DeepCopyInto(&out.Status) + copies[i] = &out + } + return copies +} diff --git a/internal/gatewayapi/clienttrafficpolicy.go b/internal/gatewayapi/clienttrafficpolicy.go index f9ed44a1b5..d8ecd0553e 100644 --- a/internal/gatewayapi/clienttrafficpolicy.go +++ b/internal/gatewayapi/clienttrafficpolicy.go @@ -71,13 +71,15 @@ func (t *Translator) ProcessClientTrafficPolicies( gatewayMap[key] = &policyGatewayTargetContext{GatewayContext: gw} } + policyCopies := clientTrafficPolicyCopiesWithStatusDeepCopy(clientTrafficPolicies) + handledPolicies := make(map[types.NamespacedName]*egv1a1.ClientTrafficPolicy) // Translate // 1. First translate Policies with a sectionName set // 2. Then loop again and translate the policies without a sectionName // TODO: Import sort order to ensure policy with same section always appear // before policy with no section so below loops can be flattened into 1. - for _, currPolicy := range clientTrafficPolicies { + for i, currPolicy := range clientTrafficPolicies { policyName := utils.NamespacedName(currPolicy) // This loop only handles policies that target a specific section. When // targeting a policy with a selector, it's not possible to specify a SectionName @@ -87,7 +89,7 @@ func (t *Translator) ProcessClientTrafficPolicies( if hasSectionName(&currTarget) { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] handledPolicies[policyName] = policy res = append(res, policy) } @@ -174,7 +176,7 @@ func (t *Translator) ProcessClientTrafficPolicies( } // Policy with no section set (targeting all sections) - for _, currPolicy := range clientTrafficPolicies { + for i, currPolicy := range clientTrafficPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -182,7 +184,7 @@ func (t *Translator) ProcessClientTrafficPolicies( policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] res = append(res, policy) handledPolicies[policyName] = policy } @@ -1222,3 +1224,15 @@ func translateHeaderModifier(headerModifier *egv1a1.HTTPHeaderFilter, modType st return addRequestHeaders, removeRequestHeaders, removeRequestHeadersOnMatch, errs } + +// clientTrafficPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields. +// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine. +func clientTrafficPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.ClientTrafficPolicy) []*egv1a1.ClientTrafficPolicy { + copies := make([]*egv1a1.ClientTrafficPolicy, len(policies)) + for i, p := range policies { + out := *p + p.Status.DeepCopyInto(&out.Status) + copies[i] = &out + } + return copies +} diff --git a/internal/gatewayapi/envoyextensionpolicy.go b/internal/gatewayapi/envoyextensionpolicy.go index 83ea7be986..a53d24bd55 100644 --- a/internal/gatewayapi/envoyextensionpolicy.go +++ b/internal/gatewayapi/envoyextensionpolicy.go @@ -77,6 +77,8 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( // The routes are grouped by sectionNames of their targetRefs. gatewayRouteMap := make(map[string]map[string]sets.Set[string]) + policyCopies := envoyExtensionPolicyCopiesWithStatusDeepCopy(envoyExtensionPolicies) + handledPolicies := make(map[types.NamespacedName]*egv1a1.EnvoyExtensionPolicy) // Translate @@ -86,7 +88,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( // 4. Finally, the policies targeting Gateways // Process the policies targeting RouteRules - for _, currPolicy := range envoyExtensionPolicies { + for i, currPolicy := range envoyExtensionPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -94,7 +96,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( if currTarget.Kind != resource.KindGateway && currTarget.SectionName != nil { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] res = append(res, policy) handledPolicies[policyName] = policy } @@ -106,7 +108,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( } // Process the policies targeting xRoutes - for _, currPolicy := range envoyExtensionPolicies { + for i, currPolicy := range envoyExtensionPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -114,7 +116,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( if currTarget.Kind != resource.KindGateway && currTarget.SectionName == nil { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] res = append(res, policy) handledPolicies[policyName] = policy } @@ -126,7 +128,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( } // Process the policies targeting Listeners - for _, currPolicy := range envoyExtensionPolicies { + for i, currPolicy := range envoyExtensionPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -134,7 +136,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( if currTarget.Kind == resource.KindGateway && currTarget.SectionName != nil { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] res = append(res, policy) handledPolicies[policyName] = policy } @@ -146,7 +148,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( } // Process the policies targeting Gateways - for _, currPolicy := range envoyExtensionPolicies { + for i, currPolicy := range envoyExtensionPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -154,7 +156,7 @@ func (t *Translator) ProcessEnvoyExtensionPolicies( if currTarget.Kind == resource.KindGateway && currTarget.SectionName == nil { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] res = append(res, policy) handledPolicies[policyName] = policy } @@ -1107,3 +1109,15 @@ func irConfigNameForWasm(policy client.Object, index int) string { irConfigName(policy), strconv.Itoa(index)) } + +// envoyExtensionPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields. +// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine. +func envoyExtensionPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.EnvoyExtensionPolicy) []*egv1a1.EnvoyExtensionPolicy { + copies := make([]*egv1a1.EnvoyExtensionPolicy, len(policies)) + for i, p := range policies { + out := *p + p.Status.DeepCopyInto(&out.Status) + copies[i] = &out + } + return copies +} diff --git a/internal/gatewayapi/envoypatchpolicy.go b/internal/gatewayapi/envoypatchpolicy.go index 5101a504b9..f2f0f59468 100644 --- a/internal/gatewayapi/envoypatchpolicy.go +++ b/internal/gatewayapi/envoypatchpolicy.go @@ -17,10 +17,14 @@ import ( "github.com/envoyproxy/gateway/internal/ir" ) -func (t *Translator) ProcessEnvoyPatchPolicies(envoyPatchPolicies []*egv1a1.EnvoyPatchPolicy, xdsIR resource.XdsIRMap) { +func (t *Translator) ProcessEnvoyPatchPolicies(envoyPatchPolicies []*egv1a1.EnvoyPatchPolicy, xdsIR resource.XdsIRMap) []*egv1a1.EnvoyPatchPolicy { // EnvoyPatchPolicies are already sorted by the provider layer (priority, then timestamp, then name) + policyCopies := envoyPatchPolicyCopiesWithStatusDeepCopy(envoyPatchPolicies) + res := make([]*egv1a1.EnvoyPatchPolicy, 0, len(envoyPatchPolicies)) - for _, policy := range envoyPatchPolicies { + for i := range envoyPatchPolicies { + policy := policyCopies[i] + res = append(res, policy) var ( ancestorRef gwapiv1.ParentReference resolveErr *status.PolicyResolveError @@ -117,4 +121,18 @@ func (t *Translator) ProcessEnvoyPatchPolicies(envoyPatchPolicies []*egv1a1.Envo // Set Accepted=True status.SetAcceptedForPolicyAncestor(&policy.Status, &ancestorRef, t.GatewayControllerName, policy.Generation) } + + return res +} + +// envoyPatchPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields. +// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine. +func envoyPatchPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.EnvoyPatchPolicy) []*egv1a1.EnvoyPatchPolicy { + copies := make([]*egv1a1.EnvoyPatchPolicy, len(policies)) + for i, p := range policies { + out := *p + p.Status.DeepCopyInto(&out.Status) + copies[i] = &out + } + return copies } diff --git a/internal/gatewayapi/extensionserverpolicy.go b/internal/gatewayapi/extensionserverpolicy.go index 9a1b9ff2fc..12530c6f6f 100644 --- a/internal/gatewayapi/extensionserverpolicy.go +++ b/internal/gatewayapi/extensionserverpolicy.go @@ -9,9 +9,11 @@ import ( "encoding/json" "errors" "fmt" + "maps" "strings" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" @@ -36,14 +38,16 @@ func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unst gatewayMap[key] = &policyGatewayTargetContext{GatewayContext: gw} } + policyCopies := extensionServerPolicyCopiesWithStatusDeepCopy(policies) + var errs error // Process the policies targeting Gateways. Only update the policy status if it was accepted. // A policy is considered accepted if at least one targetRef contained inside matched a listener. - for policyIndex, policy := range policies { - policy := &policy + for i := range policies { + policy := policyCopies[i] var policyStatus gwapiv1.PolicyStatus accepted := false - targetRefs, err := extractTargetRefs(policy, gateways) + targetRefs, err := extractTargetRefs(&policy, gateways) if err != nil { errs = errors.Join(errs, fmt.Errorf("error finding targetRefs for policy %s: %w", policy.GetName(), err)) continue @@ -55,7 +59,7 @@ func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unst } // Negative statuses have already been assigned so its safe to skip - gateway := resolveExtServerPolicyGatewayTargetRef(policy, currTarget, gatewayMap) + gateway := resolveExtServerPolicyGatewayTargetRef(&policy, currTarget, gatewayMap) if gateway == nil { // unable to find a matching Gateway for policy continue @@ -64,12 +68,12 @@ func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unst // Append policy extension server policy list for related gateway. gatewayKey := t.getIRKey(gateway.Gateway) unstructuredPolicy := &ir.UnstructuredRef{ - Object: &policies[policyIndex], + Object: &policy, } xdsIR[gatewayKey].ExtensionServerPolicies = append(xdsIR[gatewayKey].ExtensionServerPolicies, unstructuredPolicy) // Set conditions for translation if it got any - if t.translateExtServerPolicyForGateway(policy, gateway, currTarget, xdsIR) { + if t.translateExtServerPolicyForGateway(&policy, gateway, currTarget, xdsIR) { // Set Accepted condition if it is unset // Only add a status condition if the policy was added into the IR // Find its ancestor reference by resolved gateway, even with resolve error @@ -80,8 +84,8 @@ func (t *Translator) ProcessExtensionServerPolicies(policies []unstructured.Unst } } if accepted { - res = append(res, *policy) policy.Object["status"] = PolicyStatusToUnstructured(policyStatus) + res = append(res, policy) } } @@ -188,3 +192,17 @@ func (t *Translator) translateExtServerPolicyForGateway( } return found } + +// extensionServerPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied status entries. +// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine. +func extensionServerPolicyCopiesWithStatusDeepCopy(policies []unstructured.Unstructured) []unstructured.Unstructured { + copies := make([]unstructured.Unstructured, len(policies)) + for i, p := range policies { + p.Object = maps.Clone(p.Object) // shallow copy map - no shared ref for "status" key + if statusObj, ok := policies[i].Object["status"].(map[string]any); ok { + p.Object["status"] = runtime.DeepCopyJSON(statusObj) + } + copies[i] = p + } + return copies +} diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index b1a709e53c..ce31bbd4c0 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -56,14 +56,15 @@ type RoutesTranslator interface { func (t *Translator) ProcessHTTPRoutes(httpRoutes []*gwapiv1.HTTPRoute, gateways []*GatewayContext, resources *resource.Resources, xdsIR resource.XdsIRMap) []*HTTPRouteContext { relevantHTTPRoutes := make([]*HTTPRouteContext, 0, len(httpRoutes)) + httpRouteCopies := httpRouteCopiesWithStatusDeepCopy(httpRoutes) // HTTPRoutes are already sorted by the provider layer - for _, h := range httpRoutes { + for i, h := range httpRoutes { if h == nil { panic("received nil httproute") } - httpRoute := &HTTPRouteContext{HTTPRoute: h} + httpRoute := &HTTPRouteContext{HTTPRoute: httpRouteCopies[i]} // Find out if this route attaches to one of our Gateway's listeners, // and if so, get the list of listeners that allow it to attach for each @@ -83,14 +84,15 @@ func (t *Translator) ProcessHTTPRoutes(httpRoutes []*gwapiv1.HTTPRoute, gateways func (t *Translator) ProcessGRPCRoutes(grpcRoutes []*gwapiv1.GRPCRoute, gateways []*GatewayContext, resources *resource.Resources, xdsIR resource.XdsIRMap) []*GRPCRouteContext { relevantGRPCRoutes := make([]*GRPCRouteContext, 0, len(grpcRoutes)) + grpcRouteCopies := grpcRouteCopiesWithStatusDeepCopy(grpcRoutes) // GRPCRoutes are already sorted by the provider layer - for _, g := range grpcRoutes { + for i, g := range grpcRoutes { if g == nil { panic("received nil grpcroute") } - grpcRoute := &GRPCRouteContext{GRPCRoute: g} + grpcRoute := &GRPCRouteContext{GRPCRoute: grpcRouteCopies[i]} // Find out if this route attaches to one of our Gateway's listeners, // and if so, get the list of listeners that allow it to attach for each @@ -1342,13 +1344,14 @@ func filterEGPrefix(in map[string]string) map[string]string { func (t *Translator) ProcessTLSRoutes(tlsRoutes []*gwapiv1a3.TLSRoute, gateways []*GatewayContext, resources *resource.Resources, xdsIR resource.XdsIRMap) []*TLSRouteContext { relevantTLSRoutes := make([]*TLSRouteContext, 0, len(tlsRoutes)) + tlsRouteCopies := tlsRouteCopiesWithStatusDeepCopy(tlsRoutes) // TLSRoutes are already sorted by the provider layer - for _, tls := range tlsRoutes { + for i, tls := range tlsRoutes { if tls == nil { panic("received nil tlsroute") } - tlsRoute := &TLSRouteContext{TLSRoute: tls} + tlsRoute := &TLSRouteContext{TLSRoute: tlsRouteCopies[i]} // Find out if this route attaches to one of our Gateway's listeners, // and if so, get the list of listeners that allow it to attach for each @@ -1508,13 +1511,14 @@ func (t *Translator) ProcessUDPRoutes(udpRoutes []*gwapiv1a2.UDPRoute, gateways xdsIR resource.XdsIRMap, ) []*UDPRouteContext { relevantUDPRoutes := make([]*UDPRouteContext, 0, len(udpRoutes)) + udpRouteCopies := udpRouteCopiesWithStatusDeepCopy(udpRoutes) // UDPRoutes are already sorted by the provider layer - for _, u := range udpRoutes { + for i, u := range udpRoutes { if u == nil { panic("received nil udproute") } - udpRoute := &UDPRouteContext{UDPRoute: u} + udpRoute := &UDPRouteContext{UDPRoute: udpRouteCopies[i]} // Find out if this route attaches to one of our Gateway's listeners, // and if so, get the list of listeners that allow it to attach for each @@ -1659,13 +1663,14 @@ func (t *Translator) ProcessTCPRoutes(tcpRoutes []*gwapiv1a2.TCPRoute, gateways xdsIR resource.XdsIRMap, ) []*TCPRouteContext { relevantTCPRoutes := make([]*TCPRouteContext, 0, len(tcpRoutes)) + tcpRouteCopies := tcpRouteCopiesWithStatusDeepCopy(tcpRoutes) // TCPRoutes are already sorted by the provider layer - for _, tcp := range tcpRoutes { + for i, tcp := range tcpRoutes { if tcp == nil { panic("received nil tcproute") } - tcpRoute := &TCPRouteContext{TCPRoute: tcp} + tcpRoute := &TCPRouteContext{TCPRoute: tcpRouteCopies[i]} // Find out if this route attaches to one of our Gateway's listeners, // and if so, get the list of listeners that allow it to attach for each @@ -2450,11 +2455,15 @@ func (t *Translator) processBackendDestinationSetting( protocol ir.AppProtocol, ) *ir.DestinationSetting { var dstAddrType *ir.DestinationAddressType + forceHTTP1Upstream := false addrTypeMap := make(map[ir.DestinationAddressType]int) backend := t.GetBackend(backendNamespace, string(backendRef.Name)) for _, ap := range backend.Spec.AppProtocols { protocol = backendAppProtocolToIRAppProtocol(ap, protocol) + // For WebSocket backends, force HTTP/1.1 upstream to ensure Envoy can establish a successful connection, + // as WebSocket over HTTP/2 is not widely supported by upstreams and can lead to connection failures. + forceHTTP1Upstream = forceHTTP1Upstream || isWebSocketBackendAppProtocol(ap) } ds := &ir.DestinationSetting{Name: name} @@ -2463,6 +2472,7 @@ func (t *Translator) processBackendDestinationSetting( if backend.Spec.Type != nil && *backend.Spec.Type == egv1a1.BackendTypeDynamicResolver { ds.IsDynamicResolver = true ds.Protocol = protocol + ds.ForceHTTP1Upstream = forceHTTP1Upstream return ds } @@ -2505,6 +2515,7 @@ func (t *Translator) processBackendDestinationSetting( ds.Endpoints = dstEndpoints ds.AddressType = dstAddrType ds.Protocol = protocol + ds.ForceHTTP1Upstream = forceHTTP1Upstream if backend.Spec.Fallback != nil { // set only the secondary priority, the backend defaults to a primary priority if unset. @@ -2537,6 +2548,8 @@ func backendAppProtocolToIRAppProtocol(ap egv1a1.AppProtocolType, defaultProtoco switch ap { case egv1a1.AppProtocolTypeH2C: return ir.HTTP2 + case egv1a1.AppProtocolTypeWS, egv1a1.AppProtocolTypeWSS: + return ir.HTTP case "grpc": return ir.GRPC default: @@ -2544,6 +2557,15 @@ func backendAppProtocolToIRAppProtocol(ap egv1a1.AppProtocolType, defaultProtoco } } +func isWebSocketBackendAppProtocol(ap egv1a1.AppProtocolType) bool { + switch ap { + case egv1a1.AppProtocolTypeWS, egv1a1.AppProtocolTypeWSS: + return true + default: + return false + } +} + func getStatPattern(routeContext RouteContext, parentRef *RouteParentContext, controllerName string) string { var pattern string var envoyProxy *egv1a1.EnvoyProxy @@ -2571,3 +2593,56 @@ func buildStatName(pattern string, route RouteContext, ruleName *gwapiv1.Section statName = strings.ReplaceAll(statName, egv1a1.StatFormatterBackendRefs, strings.Join(refs, "|")) return statName } + +// Shallow-copy helpers that deep-copy only the Status field. +// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine. + +func httpRouteCopiesWithStatusDeepCopy(routes []*gwapiv1.HTTPRoute) []*gwapiv1.HTTPRoute { + copies := make([]*gwapiv1.HTTPRoute, len(routes)) + for i, r := range routes { + out := *r + r.Status.DeepCopyInto(&out.Status) + copies[i] = &out + } + return copies +} + +func grpcRouteCopiesWithStatusDeepCopy(routes []*gwapiv1.GRPCRoute) []*gwapiv1.GRPCRoute { + copies := make([]*gwapiv1.GRPCRoute, len(routes)) + for i, r := range routes { + out := *r + r.Status.DeepCopyInto(&out.Status) + copies[i] = &out + } + return copies +} + +func tlsRouteCopiesWithStatusDeepCopy(routes []*gwapiv1a3.TLSRoute) []*gwapiv1a3.TLSRoute { + copies := make([]*gwapiv1a3.TLSRoute, len(routes)) + for i, r := range routes { + out := *r + r.Status.DeepCopyInto(&out.Status) + copies[i] = &out + } + return copies +} + +func udpRouteCopiesWithStatusDeepCopy(routes []*gwapiv1a2.UDPRoute) []*gwapiv1a2.UDPRoute { + copies := make([]*gwapiv1a2.UDPRoute, len(routes)) + for i, r := range routes { + out := *r + r.Status.DeepCopyInto(&out.Status) + copies[i] = &out + } + return copies +} + +func tcpRouteCopiesWithStatusDeepCopy(routes []*gwapiv1a2.TCPRoute) []*gwapiv1a2.TCPRoute { + copies := make([]*gwapiv1a2.TCPRoute, len(routes)) + for i, r := range routes { + out := *r + r.Status.DeepCopyInto(&out.Status) + copies[i] = &out + } + return copies +} diff --git a/internal/gatewayapi/runner/runner.go b/internal/gatewayapi/runner/runner.go index c68fd5d56e..674b183213 100644 --- a/internal/gatewayapi/runner/runner.go +++ b/internal/gatewayapi/runner/runner.go @@ -166,7 +166,7 @@ func (r *Runner) startWasmCache(ctx context.Context) { } r.wasmCache = wasm.NewHTTPServerWithFileCache( // HTTP server options - wasm.SeverOptions{ + wasm.ServerOptions{ Salt: salt, TLSConfig: tlsConfig, }, diff --git a/internal/gatewayapi/securitypolicy.go b/internal/gatewayapi/securitypolicy.go index 65ac1a4f66..a0386a4c5b 100644 --- a/internal/gatewayapi/securitypolicy.go +++ b/internal/gatewayapi/securitypolicy.go @@ -114,6 +114,8 @@ func (t *Translator) ProcessSecurityPolicies( // The routes are grouped by sectionNames of their targetRefs gatewayRouteMap := make(map[string]map[string]sets.Set[string], gatewayMapSize) + policyCopies := securityPolicyCopiesWithStatusDeepCopy(securityPolicies) + handledPolicies := make(map[types.NamespacedName]*egv1a1.SecurityPolicy, policyMapSize) // Translate @@ -123,7 +125,7 @@ func (t *Translator) ProcessSecurityPolicies( // 4. Finally, the policies targeting Gateways // Process the policies targeting RouteRules (HTTP + TCP) - for _, currPolicy := range securityPolicies { + for i, currPolicy := range securityPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -131,7 +133,7 @@ func (t *Translator) ProcessSecurityPolicies( if currTarget.Kind != resource.KindGateway && currTarget.SectionName != nil { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] handledPolicies[policyName] = policy res = append(res, policy) } @@ -142,7 +144,7 @@ func (t *Translator) ProcessSecurityPolicies( } } // Process the policies targeting xRoutes (HTTP + TCP) - for _, currPolicy := range securityPolicies { + for i, currPolicy := range securityPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, routes, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -150,7 +152,7 @@ func (t *Translator) ProcessSecurityPolicies( if currTarget.Kind != resource.KindGateway && currTarget.SectionName == nil { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] handledPolicies[policyName] = policy res = append(res, policy) } @@ -161,7 +163,7 @@ func (t *Translator) ProcessSecurityPolicies( } } // Process the policies targeting Listeners - for _, currPolicy := range securityPolicies { + for i, currPolicy := range securityPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -169,7 +171,7 @@ func (t *Translator) ProcessSecurityPolicies( if currTarget.Kind == resource.KindGateway && currTarget.SectionName != nil { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] handledPolicies[policyName] = policy res = append(res, policy) } @@ -180,7 +182,7 @@ func (t *Translator) ProcessSecurityPolicies( } } // Process the policies targeting Gateways - for _, currPolicy := range securityPolicies { + for i, currPolicy := range securityPolicies { policyName := utils.NamespacedName(currPolicy) targetRefs := getPolicyTargetRefs(currPolicy.Spec.PolicyTargetReferences, gateways, currPolicy.Namespace) for _, currTarget := range targetRefs { @@ -188,7 +190,7 @@ func (t *Translator) ProcessSecurityPolicies( if currTarget.Kind == resource.KindGateway && currTarget.SectionName == nil { policy, found := handledPolicies[policyName] if !found { - policy = currPolicy + policy = policyCopies[i] handledPolicies[policyName] = policy res = append(res, policy) } @@ -2129,3 +2131,15 @@ func defaultAuthorizationRuleName(policy *egv1a1.SecurityPolicy, index int) stri irConfigName(policy), strconv.Itoa(index)) } + +// securityPolicyCopiesWithStatusDeepCopy returns shallow copies with deep-copied Status fields. +// Status is mutated during translation and shares a pointer with the watchable coalesce goroutine. +func securityPolicyCopiesWithStatusDeepCopy(policies []*egv1a1.SecurityPolicy) []*egv1a1.SecurityPolicy { + copies := make([]*egv1a1.SecurityPolicy, len(policies)) + for i, p := range policies { + out := *p + p.Status.DeepCopyInto(&out.Status) + copies[i] = &out + } + return copies +} diff --git a/internal/gatewayapi/status/error.go b/internal/gatewayapi/status/error.go index 7e310da62b..57b712fb8e 100644 --- a/internal/gatewayapi/status/error.go +++ b/internal/gatewayapi/status/error.go @@ -225,7 +225,7 @@ func (m *MultiStatusError) Reason() gwapiv1.RouteConditionReason { reasonList = append(reasonList, reason) } sort.Strings(reasonList) - return gwapiv1.RouteConditionReason(strings.Join(reasonList, ", ")) + return gwapiv1.RouteConditionReason(strings.Join(reasonList, ",")) } func (m *MultiStatusError) Type() gwapiv1.RouteConditionType { diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-healthcheck-host-hierarchy.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-healthcheck-host-hierarchy.in.yaml new file mode 100644 index 0000000000..7a45a5401a --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-healthcheck-host-hierarchy.in.yaml @@ -0,0 +1,171 @@ +# Health check Host header hierarchy: +# 1. BackendTrafficPolicy healthCheck.active.http.hostname wins. +# 2. Backend.spec.endpoints[].hostname overrides the route-derived default. +# 3. Effective HTTPRoute/GRPCRoute hostname for the listener remains the fallback +# when the endpoint has no hostname. +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +backends: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + namespace: default + name: backend-with-hostname + spec: + endpoints: + - hostname: backend-endpoint.example.com + fqdn: + hostname: backend-address.example.com + port: 443 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + namespace: default + name: backend-without-hostname + spec: + endpoints: + - fqdn: + hostname: backend-no-endpoint-host.example.com + port: 443 +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-btp-host + spec: + hostnames: + - btp-route.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway + sectionName: http + rules: + - matches: + - path: + value: /btp-host + backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-with-hostname +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-backend-host + spec: + hostnames: + - backend-route.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway + sectionName: http + rules: + - matches: + - path: + value: /backend-host + backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-with-hostname +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-route-host + spec: + hostnames: + - route-fallback.example.com + parentRefs: + - namespace: envoy-gateway + name: gateway + sectionName: http + rules: + - matches: + - path: + value: /route-host + backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-without-hostname +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-btp-host + spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-btp-host + healthCheck: + active: + timeout: 2s + interval: 5s + healthyThreshold: 1 + unhealthyThreshold: 3 + type: HTTP + http: + hostname: btp-health.example.com + path: /healthz + method: GET + expectedStatuses: + - 200 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-backend-host + spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-backend-host + healthCheck: + active: + timeout: 2s + interval: 5s + healthyThreshold: 1 + unhealthyThreshold: 3 + type: HTTP + http: + path: /healthz + method: GET + expectedStatuses: + - 200 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-route-host + spec: + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-route-host + healthCheck: + active: + timeout: 2s + interval: 5s + healthyThreshold: 1 + unhealthyThreshold: 3 + type: HTTP + http: + path: /healthz + method: GET + expectedStatuses: + - 200 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-healthcheck-host-hierarchy.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-healthcheck-host-hierarchy.out.yaml new file mode 100644 index 0000000000..97603fb6ad --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-healthcheck-host-hierarchy.out.yaml @@ -0,0 +1,512 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + name: policy-btp-host + namespace: default + spec: + healthCheck: + active: + healthyThreshold: 1 + http: + expectedStatuses: + - 200 + hostname: btp-health.example.com + method: GET + path: /healthz + interval: 5s + timeout: 2s + type: HTTP + unhealthyThreshold: 3 + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-btp-host + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway + namespace: envoy-gateway + sectionName: http + 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: BackendTrafficPolicy + metadata: + name: policy-backend-host + namespace: default + spec: + healthCheck: + active: + healthyThreshold: 1 + http: + expectedStatuses: + - 200 + method: GET + path: /healthz + interval: 5s + timeout: 2s + type: HTTP + unhealthyThreshold: 3 + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-backend-host + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway + namespace: envoy-gateway + sectionName: http + 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: BackendTrafficPolicy + metadata: + name: policy-route-host + namespace: default + spec: + healthCheck: + active: + healthyThreshold: 1 + http: + expectedStatuses: + - 200 + method: GET + path: /healthz + interval: 5s + timeout: 2s + type: HTTP + unhealthyThreshold: 3 + targetRefs: + - group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-route-host + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +backends: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + name: backend-with-hostname + namespace: default + spec: + endpoints: + - fqdn: + hostname: backend-address.example.com + port: 443 + hostname: backend-endpoint.example.com + status: + conditions: + - lastTransitionTime: null + message: The Backend was accepted + reason: Accepted + status: "True" + type: Accepted +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + name: backend-without-hostname + namespace: default + spec: + endpoints: + - fqdn: + hostname: backend-no-endpoint-host.example.com + port: 443 + status: + conditions: + - lastTransitionTime: null + message: The Backend was accepted + reason: Accepted + status: "True" + type: Accepted +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 3 + 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: + name: httproute-btp-host + namespace: default + spec: + hostnames: + - btp-route.example.com + parentRefs: + - name: gateway + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-with-hostname + matches: + - path: + value: /btp-host + 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 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-backend-host + namespace: default + spec: + hostnames: + - backend-route.example.com + parentRefs: + - name: gateway + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-with-hostname + matches: + - path: + value: /backend-host + 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 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-route-host + namespace: default + spec: + hostnames: + - route-fallback.example.com + parentRefs: + - name: gateway + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-without-hostname + matches: + - path: + value: /route-host + 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 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-966d2c13 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-966d2c13 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + isHTTP2: false + metadata: + kind: Gateway + name: gateway + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: httproute-backend-host + namespace: default + name: httproute/default/httproute-backend-host/rule/0 + settings: + - addressType: FQDN + endpoints: + - host: backend-address.example.com + hostname: backend-endpoint.example.com + port: 443 + metadata: + kind: Backend + name: backend-with-hostname + namespace: default + name: httproute/default/httproute-backend-host/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: backend-route.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-backend-host + namespace: default + policies: + - kind: BackendTrafficPolicy + name: policy-backend-host + namespace: default + name: httproute/default/httproute-backend-host/rule/0/match/0/backend-route_example_com + pathMatch: + distinct: false + name: "" + prefix: /backend-host + traffic: + healthCheck: + active: + healthyThreshold: 1 + http: + expectedStatuses: + - 200 + host: "" + method: GET + path: /healthz + interval: 5s + timeout: 2s + unhealthyThreshold: 3 + - destination: + metadata: + kind: HTTPRoute + name: httproute-route-host + namespace: default + name: httproute/default/httproute-route-host/rule/0 + settings: + - addressType: FQDN + endpoints: + - host: backend-no-endpoint-host.example.com + port: 443 + metadata: + kind: Backend + name: backend-without-hostname + namespace: default + name: httproute/default/httproute-route-host/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: route-fallback.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-route-host + namespace: default + policies: + - kind: BackendTrafficPolicy + name: policy-route-host + namespace: default + name: httproute/default/httproute-route-host/rule/0/match/0/route-fallback_example_com + pathMatch: + distinct: false + name: "" + prefix: /route-host + traffic: + healthCheck: + active: + healthyThreshold: 1 + http: + expectedStatuses: + - 200 + host: "" + method: GET + path: /healthz + interval: 5s + timeout: 2s + unhealthyThreshold: 3 + - destination: + metadata: + kind: HTTPRoute + name: httproute-btp-host + namespace: default + name: httproute/default/httproute-btp-host/rule/0 + settings: + - addressType: FQDN + endpoints: + - host: backend-address.example.com + hostname: backend-endpoint.example.com + port: 443 + metadata: + kind: Backend + name: backend-with-hostname + namespace: default + name: httproute/default/httproute-btp-host/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: btp-route.example.com + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-btp-host + namespace: default + policies: + - kind: BackendTrafficPolicy + name: policy-btp-host + namespace: default + name: httproute/default/httproute-btp-host/rule/0/match/0/btp-route_example_com + pathMatch: + distinct: false + name: "" + prefix: /btp-host + traffic: + healthCheck: + active: + healthyThreshold: 1 + http: + expectedStatuses: + - 200 + host: btp-health.example.com + method: GET + path: /healthz + interval: 5s + timeout: 2s + unhealthyThreshold: 3 + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-healthcheck.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-healthcheck.out.yaml index 340025d579..b6b2065f24 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-healthcheck.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-healthcheck.out.yaml @@ -882,7 +882,7 @@ xdsIR: expectedStatuses: - 200 - 300 - host: '*' + host: "" method: GET path: /healthz interval: 3s @@ -1171,7 +1171,7 @@ xdsIR: text: pong expectedStatuses: - 200 - host: gateway.envoyproxy.io + host: "" method: GET path: /healthz interval: 5s diff --git a/internal/gatewayapi/testdata/envoypatchpolicy-cross-ns-target.out.yaml b/internal/gatewayapi/testdata/envoypatchpolicy-cross-ns-target.out.yaml index 9a88aa4f50..7ce9618579 100644 --- a/internal/gatewayapi/testdata/envoypatchpolicy-cross-ns-target.out.yaml +++ b/internal/gatewayapi/testdata/envoypatchpolicy-cross-ns-target.out.yaml @@ -1,3 +1,24 @@ +envoyPatchPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + name: edit-conn-buffer-bytes + namespace: envoy-gateway-2 + spec: + jsonPatches: + - name: envoy-gateway-gateway-1-http + operation: + op: replace + path: /per_connection_buffer_limit_bytes + value: "1024" + type: type.googleapis.com/envoy.config.listener.v3.Listener + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + type: JSONPatch + status: + ancestors: null gateways: - apiVersion: gateway.networking.k8s.io/v1 kind: Gateway diff --git a/internal/gatewayapi/testdata/envoypatchpolicy-invalid-feature-disabled.out.yaml b/internal/gatewayapi/testdata/envoypatchpolicy-invalid-feature-disabled.out.yaml index 105d725ccb..cf56d028e8 100644 --- a/internal/gatewayapi/testdata/envoypatchpolicy-invalid-feature-disabled.out.yaml +++ b/internal/gatewayapi/testdata/envoypatchpolicy-invalid-feature-disabled.out.yaml @@ -1,3 +1,35 @@ +envoyPatchPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + name: edit-conn-buffer-bytes + namespace: envoy-gateway + spec: + jsonPatches: + - name: envoy-gateway-gateway-1-http + operation: + op: replace + path: /per_connection_buffer_limit_bytes + value: "1024" + type: type.googleapis.com/envoy.config.listener.v3.Listener + targetRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + type: JSONPatch + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + conditions: + - lastTransitionTime: null + message: EnvoyPatchPolicy is disabled in the EnvoyGateway configuration + reason: Disabled + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller gateways: - apiVersion: gateway.networking.k8s.io/v1 kind: Gateway diff --git a/internal/gatewayapi/testdata/envoypatchpolicy-invalid-merge-gateways.out.yaml b/internal/gatewayapi/testdata/envoypatchpolicy-invalid-merge-gateways.out.yaml index b7b4b0f47e..3bbf3494b2 100644 --- a/internal/gatewayapi/testdata/envoypatchpolicy-invalid-merge-gateways.out.yaml +++ b/internal/gatewayapi/testdata/envoypatchpolicy-invalid-merge-gateways.out.yaml @@ -1,3 +1,56 @@ +envoyPatchPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + name: gateway-class-not-exists + namespace: envoy-gateway + spec: + jsonPatches: + - name: envoy-gateway-gateway-1-http + operation: + op: replace + path: /per_connection_buffer_limit_bytes + value: "1024" + type: type.googleapis.com/envoy.config.listener.v3.Listener + targetRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: gateway-class-not-exists + type: JSONPatch + status: + ancestors: null +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + name: edit-ignore-global-limit + namespace: envoy-gateway + spec: + jsonPatches: + - name: envoy-gateway-gateway-1-http + operation: + op: replace + path: /ignore_global_conn_limit + value: "true" + type: type.googleapis.com/envoy.config.listener.v3.Listener + priority: -1 + targetRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + type: JSONPatch + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + conditions: + - 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 diff --git a/internal/gatewayapi/testdata/envoypatchpolicy-invalid-target-kind-merge-gateways.out.yaml b/internal/gatewayapi/testdata/envoypatchpolicy-invalid-target-kind-merge-gateways.out.yaml index b4b746b866..703d7211f7 100644 --- a/internal/gatewayapi/testdata/envoypatchpolicy-invalid-target-kind-merge-gateways.out.yaml +++ b/internal/gatewayapi/testdata/envoypatchpolicy-invalid-target-kind-merge-gateways.out.yaml @@ -1,3 +1,24 @@ +envoyPatchPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + name: edit-conn-buffer-bytes + namespace: envoy-gateway + spec: + jsonPatches: + - name: envoy-gateway-gateway-1-http + operation: + op: replace + path: /per_connection_buffer_limit_bytes + value: "1024" + type: type.googleapis.com/envoy.config.listener.v3.Listener + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + type: JSONPatch + status: + ancestors: null gateways: - apiVersion: gateway.networking.k8s.io/v1 kind: Gateway diff --git a/internal/gatewayapi/testdata/envoypatchpolicy-invalid-target-kind.out.yaml b/internal/gatewayapi/testdata/envoypatchpolicy-invalid-target-kind.out.yaml index 73b9604334..9e5bb4fedf 100644 --- a/internal/gatewayapi/testdata/envoypatchpolicy-invalid-target-kind.out.yaml +++ b/internal/gatewayapi/testdata/envoypatchpolicy-invalid-target-kind.out.yaml @@ -1,3 +1,38 @@ +envoyPatchPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + name: edit-conn-buffer-bytes + namespace: envoy-gateway + spec: + jsonPatches: + - name: envoy-gateway-gateway-1-http + operation: + op: replace + path: /per_connection_buffer_limit_bytes + value: "1024" + type: type.googleapis.com/envoy.config.listener.v3.Listener + targetRef: + group: gateway.networking.k8s.io + kind: MyGateway + name: gateway-1 + type: JSONPatch + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: TargetRef.Group:gateway.networking.k8s.io TargetRef.Kind:MyGateway, + only TargetRef.Group:gateway.networking.k8s.io and TargetRef.Kind:Gateway + is supported. + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller gateways: - apiVersion: gateway.networking.k8s.io/v1 kind: Gateway diff --git a/internal/gatewayapi/testdata/envoypatchpolicy-invalid-target-name.out.yaml b/internal/gatewayapi/testdata/envoypatchpolicy-invalid-target-name.out.yaml index cf77d13518..68ed8b9bff 100644 --- a/internal/gatewayapi/testdata/envoypatchpolicy-invalid-target-name.out.yaml +++ b/internal/gatewayapi/testdata/envoypatchpolicy-invalid-target-name.out.yaml @@ -1,3 +1,60 @@ +envoyPatchPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + generation: 10 + name: gateway-not-exists + namespace: envoy-gateway + spec: + jsonPatches: + - name: envoy-gateway-gateway-1-http + operation: + op: replace + path: /per_connection_buffer_limit_bytes + value: "1024" + type: type.googleapis.com/envoy.config.listener.v3.Listener + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-not-exists + type: JSONPatch + status: + ancestors: null +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + generation: 10 + name: edit-ignore-global-limit + namespace: envoy-gateway + spec: + jsonPatches: + - name: envoy-gateway-gateway-1-http + operation: + op: replace + path: /ignore_global_conn_limit + value: "true" + type: type.googleapis.com/envoy.config.listener.v3.Listener + priority: -1 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + type: JSONPatch + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + observedGeneration: 10 + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller gateways: - apiVersion: gateway.networking.k8s.io/v1 kind: Gateway diff --git a/internal/gatewayapi/testdata/envoypatchpolicy-no-status-for-unknown-gateway.out.yaml b/internal/gatewayapi/testdata/envoypatchpolicy-no-status-for-unknown-gateway.out.yaml index fca40f1fe3..2d295a28a9 100644 --- a/internal/gatewayapi/testdata/envoypatchpolicy-no-status-for-unknown-gateway.out.yaml +++ b/internal/gatewayapi/testdata/envoypatchpolicy-no-status-for-unknown-gateway.out.yaml @@ -1,2 +1,16 @@ +envoyPatchPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + name: target-unknown-gateway + namespace: envoy-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: unknown-gateway + type: JSONPatch + status: + ancestors: null infraIR: {} xdsIR: {} diff --git a/internal/gatewayapi/testdata/envoypatchpolicy-valid-merge-gateways.out.yaml b/internal/gatewayapi/testdata/envoypatchpolicy-valid-merge-gateways.out.yaml index 6f72c47662..241305740c 100644 --- a/internal/gatewayapi/testdata/envoypatchpolicy-valid-merge-gateways.out.yaml +++ b/internal/gatewayapi/testdata/envoypatchpolicy-valid-merge-gateways.out.yaml @@ -1,3 +1,67 @@ +envoyPatchPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + name: edit-conn-buffer-bytes + namespace: envoy-gateway + spec: + jsonPatches: + - name: envoy-gateway-gateway-1-http + operation: + op: replace + path: /per_connection_buffer_limit_bytes + value: "1024" + type: type.googleapis.com/envoy.config.listener.v3.Listener + targetRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + type: JSONPatch + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + 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: EnvoyPatchPolicy + metadata: + name: edit-ignore-global-limit + namespace: envoy-gateway + spec: + jsonPatches: + - name: envoy-gateway-gateway-1-http + operation: + op: replace + path: /ignore_global_conn_limit + value: "true" + type: type.googleapis.com/envoy.config.listener.v3.Listener + priority: -1 + targetRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + type: JSONPatch + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: GatewayClass + name: envoy-gateway-class + conditions: + - 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 diff --git a/internal/gatewayapi/testdata/envoypatchpolicy-valid.out.yaml b/internal/gatewayapi/testdata/envoypatchpolicy-valid.out.yaml index 307508cc62..096008d69f 100644 --- a/internal/gatewayapi/testdata/envoypatchpolicy-valid.out.yaml +++ b/internal/gatewayapi/testdata/envoypatchpolicy-valid.out.yaml @@ -1,3 +1,73 @@ +envoyPatchPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + generation: 10 + name: edit-conn-buffer-bytes + namespace: envoy-gateway + spec: + jsonPatches: + - name: envoy-gateway-gateway-1-http + operation: + op: replace + path: /per_connection_buffer_limit_bytes + value: "1024" + type: type.googleapis.com/envoy.config.listener.v3.Listener + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + type: JSONPatch + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + observedGeneration: 10 + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: EnvoyPatchPolicy + metadata: + generation: 10 + name: edit-ignore-global-limit + namespace: envoy-gateway + spec: + jsonPatches: + - name: envoy-gateway-gateway-1-http + operation: + op: replace + path: /ignore_global_conn_limit + value: "true" + type: type.googleapis.com/envoy.config.listener.v3.Listener + priority: -1 + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + type: JSONPatch + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + observedGeneration: 10 + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller gateways: - apiVersion: gateway.networking.k8s.io/v1 kind: Gateway diff --git a/internal/gatewayapi/testdata/grpcroute-with-conflicting-filters.out.yaml b/internal/gatewayapi/testdata/grpcroute-with-conflicting-filters.out.yaml index 5057df4a69..443cecca1b 100644 --- a/internal/gatewayapi/testdata/grpcroute-with-conflicting-filters.out.yaml +++ b/internal/gatewayapi/testdata/grpcroute-with-conflicting-filters.out.yaml @@ -80,7 +80,7 @@ grpcRoutes: message: |- Dropped Rule(s) [0]: Invalid filter ExtensionRef: unknown kind gateway.envoyproxy.io/HTTPRouteFilter. RequestMirror filter cannot be used when the rule also configures a DirectResponse filter. - reason: IncompatibleFilters, UnsupportedValue + reason: IncompatibleFilters,UnsupportedValue status: "True" type: PartiallyInvalid - lastTransitionTime: null diff --git a/internal/gatewayapi/testdata/httproute-rule-with-non-service-backends-and-websocket-app-protocols.in.yaml b/internal/gatewayapi/testdata/httproute-rule-with-non-service-backends-and-websocket-app-protocols.in.yaml new file mode 100644 index 0000000000..02cadc6d99 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-rule-with-non-service-backends-and-websocket-app-protocols.in.yaml @@ -0,0 +1,107 @@ +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 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-ws + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + value: "/ws" + backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-ws + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-wss + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + value: "/wss" + backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-wss + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-h2c + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + value: "/h2c" + backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-h2c +backends: + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + name: backend-ws + namespace: default + spec: + appProtocols: + - gateway.envoyproxy.io/ws + endpoints: + - ip: + address: 10.244.0.28 + port: 3000 + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + name: backend-wss + namespace: default + spec: + appProtocols: + - gateway.envoyproxy.io/wss + endpoints: + - fqdn: + hostname: websocket.example.com + port: 443 + tls: + wellKnownCACertificates: System + sni: websocket.example.com + - apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + name: backend-h2c + namespace: default + spec: + appProtocols: + - gateway.envoyproxy.io/h2c + endpoints: + - fqdn: + hostname: primary.foo.com + port: 3000 diff --git a/internal/gatewayapi/testdata/httproute-rule-with-non-service-backends-and-websocket-app-protocols.out.yaml b/internal/gatewayapi/testdata/httproute-rule-with-non-service-backends-and-websocket-app-protocols.out.yaml new file mode 100644 index 0000000000..bd2ed04b2a --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-rule-with-non-service-backends-and-websocket-app-protocols.out.yaml @@ -0,0 +1,367 @@ +backends: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + name: backend-ws + namespace: default + spec: + appProtocols: + - gateway.envoyproxy.io/ws + endpoints: + - ip: + address: 10.244.0.28 + port: 3000 + status: + conditions: + - lastTransitionTime: null + message: The Backend was accepted + reason: Accepted + status: "True" + type: Accepted +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + name: backend-wss + namespace: default + spec: + appProtocols: + - gateway.envoyproxy.io/wss + endpoints: + - fqdn: + hostname: websocket.example.com + port: 443 + tls: + sni: websocket.example.com + wellKnownCACertificates: System + status: + conditions: + - lastTransitionTime: null + message: The Backend was accepted + reason: Accepted + status: "True" + type: Accepted +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: Backend + metadata: + name: backend-h2c + namespace: default + spec: + appProtocols: + - gateway.envoyproxy.io/h2c + endpoints: + - fqdn: + hostname: primary.foo.com + port: 3000 + status: + conditions: + - lastTransitionTime: null + message: The Backend was accepted + reason: Accepted + status: "True" + type: Accepted +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 3 + 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: + name: httproute-ws + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-ws + matches: + - path: + value: /ws + 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 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-wss + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-wss + matches: + - path: + value: /wss + 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 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-h2c + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + rules: + - backendRefs: + - group: gateway.envoyproxy.io + kind: Backend + name: backend-h2c + matches: + - path: + value: /h2c + 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 + 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 + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + 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: + - destination: + metadata: + kind: HTTPRoute + name: httproute-wss + namespace: default + name: httproute/default/httproute-wss/rule/0 + settings: + - addressType: FQDN + endpoints: + - host: websocket.example.com + port: 443 + forceHTTP1Upstream: true + metadata: + kind: Backend + name: backend-wss + namespace: default + name: httproute/default/httproute-wss/rule/0/backend/0 + protocol: HTTP + tls: + alpnProtocols: null + caCertificate: + name: backend-wss/default-ca + sni: websocket.example.com + useSystemTrustStore: true + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-wss + namespace: default + name: httproute/default/httproute-wss/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: /wss + - destination: + metadata: + kind: HTTPRoute + name: httproute-h2c + namespace: default + name: httproute/default/httproute-h2c/rule/0 + settings: + - addressType: FQDN + endpoints: + - host: primary.foo.com + port: 3000 + metadata: + kind: Backend + name: backend-h2c + namespace: default + name: httproute/default/httproute-h2c/rule/0/backend/0 + protocol: HTTP2 + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-h2c + namespace: default + name: httproute/default/httproute-h2c/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: /h2c + - destination: + metadata: + kind: HTTPRoute + name: httproute-ws + namespace: default + name: httproute/default/httproute-ws/rule/0 + settings: + - addressType: IP + endpoints: + - host: 10.244.0.28 + port: 3000 + forceHTTP1Upstream: true + metadata: + kind: Backend + name: backend-ws + namespace: default + name: httproute/default/httproute-ws/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-ws + namespace: default + name: httproute/default/httproute-ws/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: /ws + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.in.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.in.yaml index f39c951e5f..09b7115694 100644 --- a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.in.yaml +++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.in.yaml @@ -125,6 +125,42 @@ httpRoutes: path: type: ReplacePrefixMatch replacePrefixMatch: /rewrite +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-5 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/not-found-filter" + backendRefs: + - name: service-1 + port: 8080 + filters: + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: not-found-filter + filters: + - type: URLRewrite + urlRewrite: + path: + type: ReplacePrefixMatch + replacePrefixMatch: /rewrite + - type: ExtensionRef + extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: not-found-filter httpFilters: - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: HTTPRouteFilter diff --git a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml index 5d4c76a9fa..5f23192906 100644 --- a/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-urlrewrite-hostname-filter.out.yaml @@ -16,7 +16,7 @@ gateways: protocol: HTTP status: listeners: - - attachedRoutes: 4 + - attachedRoutes: 5 conditions: - lastTransitionTime: null message: Sending translated listener configuration to the data plane @@ -222,6 +222,62 @@ httpRoutes: name: gateway-1 namespace: envoy-gateway sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: httproute-5 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - filters: + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: not-found-filter + type: ExtensionRef + name: service-1 + port: 8080 + filters: + - type: URLRewrite + urlRewrite: + path: + replacePrefixMatch: /rewrite + type: ReplacePrefixMatch + - extensionRef: + group: gateway.envoyproxy.io + kind: HTTPRouteFilter + name: not-found-filter + type: ExtensionRef + matches: + - path: + value: /not-found-filter + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: |- + Unable to translate HTTPRouteFilter: default/not-found-filter. + Failed to process route rule 0 backendRef 0: Unable to translate HTTPRouteFilter: default/not-found-filter. + reason: InvalidBackendFilters,UnsupportedValue + 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: @@ -357,6 +413,23 @@ xdsIR: path: fullReplace: null prefixMatchReplace: /rewrite + - directResponse: + statusCode: 500 + hostname: gateway.envoyproxy.io + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-5 + namespace: default + name: httproute/default/httproute-5/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: /not-found-filter + urlRewrite: + path: + fullReplace: null + prefixMatchReplace: /rewrite - destination: metadata: kind: HTTPRoute diff --git a/internal/gatewayapi/translator.go b/internal/gatewayapi/translator.go index 08b72d9f1f..dece035e09 100644 --- a/internal/gatewayapi/translator.go +++ b/internal/gatewayapi/translator.go @@ -147,6 +147,7 @@ func newTranslateResult( securityPolicies []*egv1a1.SecurityPolicy, backendTLSPolicies []*gwapiv1.BackendTLSPolicy, envoyExtensionPolicies []*egv1a1.EnvoyExtensionPolicy, + envoyPatchPolicies []*egv1a1.EnvoyPatchPolicy, extPolicies []unstructured.Unstructured, backends []*egv1a1.Backend, xListenerSets []*gwapixv1a1.XListenerSet, @@ -216,6 +217,9 @@ func newTranslateResult( if len(envoyExtensionPolicies) > 0 { translateResult.EnvoyExtensionPolicies = envoyExtensionPolicies } + if len(envoyPatchPolicies) > 0 { + translateResult.EnvoyPatchPolicies = envoyPatchPolicies + } if len(extPolicies) > 0 { translateResult.ExtensionServerPolicies = extPolicies } @@ -264,7 +268,7 @@ func (t *Translator) Translate(resources *resource.Resources) (*TranslateResult, t.ProcessXListenerSetStatus(resources.XListenerSets) // Process EnvoyPatchPolicies - t.ProcessEnvoyPatchPolicies(resources.EnvoyPatchPolicies, xdsIR) + envoyPatchPolicies := t.ProcessEnvoyPatchPolicies(resources.EnvoyPatchPolicies, xdsIR) // Process all Addresses for all relevant Gateways. t.ProcessAddresses(acceptedGateways, xdsIR, infraIR) @@ -358,7 +362,7 @@ func (t *Translator) Translate(resources *resource.Resources) (*TranslateResult, allGateways, httpRoutes, grpcRoutes, tlsRoutes, tcpRoutes, udpRoutes, clientTrafficPolicies, backendTrafficPolicies, securityPolicies, resources.BackendTLSPolicies, envoyExtensionPolicies, - extServerPolicies, backends, resources.XListenerSets, xdsIR, infraIR), errs + envoyPatchPolicies, extServerPolicies, backends, resources.XListenerSets, xdsIR, infraIR), errs } // GetRelevantGateways returns GatewayContexts, containing a copy of the original diff --git a/internal/ir/xds.go b/internal/ir/xds.go index d15b55bd35..8ac2e0f33a 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -67,7 +67,6 @@ var ( ErrHealthCheckUnhealthyThresholdInvalid = errors.New("field HealthCheck.UnhealthyThreshold should be greater than 0") ErrHealthCheckHealthyThresholdInvalid = errors.New("field HealthCheck.HealthyThreshold should be greater than 0") ErrHealthCheckerInvalid = errors.New("health checker setting is invalid, only one health checker can be set") - ErrHCHTTPHostInvalid = errors.New("field HTTPHealthChecker.Host should be specified") ErrHCHTTPPathInvalid = errors.New("field HTTPHealthChecker.Path should be specified") ErrHCHTTPMethodInvalid = errors.New("only one of the GET, HEAD, POST, DELETE, OPTIONS, TRACE, PATCH of HTTPHealthChecker.Method could be set") ErrHCHTTPExpectedStatusesInvalid = errors.New("field HTTPHealthChecker.ExpectedStatuses should be specified") @@ -1726,7 +1725,8 @@ func (r *RouteDestination) Validate() error { func (r *RouteDestination) NeedsClusterPerSetting() bool { return r.HasMixedEndpoints() || r.HasFiltersInSettings() || - (len(r.Settings) > 1 && r.HasPreferLocalZone()) + (len(r.Settings) > 1 && r.HasPreferLocalZone()) || + r.HasMixedUpstreamProtocolRequirements() } // HasMixedEndpoints returns true if the RouteDestination has endpoints of multiple types @@ -1761,6 +1761,27 @@ func (r *RouteDestination) HasPreferLocalZone() bool { return false } +// HasMixedUpstreamProtocolRequirements returns true if destination settings require +// mutually exclusive cluster-level upstream protocol configuration. +func (r *RouteDestination) HasMixedUpstreamProtocolRequirements() bool { + hasForceHTTP1Upstream := false + hasHTTP2Upstream := false + + for _, setting := range r.Settings { + if setting == nil { + continue + } + if setting.ForceHTTP1Upstream { + hasForceHTTP1Upstream = true + } + if setting.Protocol == HTTP2 || setting.Protocol == GRPC { + hasHTTP2Upstream = true + } + } + + return hasForceHTTP1Upstream && hasHTTP2Upstream +} + func (r *RouteDestination) ToBackendWeights() *BackendWeights { w := &BackendWeights{ Name: r.Name, @@ -1810,8 +1831,11 @@ type DestinationSetting struct { // Lower priority endpoints will be used only if higher priority levels are unavailable. Priority *uint32 `json:"priority,omitempty"` // Protocol associated with this destination/port. - Protocol AppProtocol `json:"protocol,omitempty" yaml:"protocol,omitempty"` - Endpoints []*DestinationEndpoint `json:"endpoints,omitempty" yaml:"endpoints,omitempty"` + Protocol AppProtocol `json:"protocol,omitempty" yaml:"protocol,omitempty"` + // ForceHTTP1Upstream requires Envoy to use explicit HTTP/1.1 upstream protocol selection. + // This is used for websocket backends where upstream HTTP/2 negotiation would break upgrades. + ForceHTTP1Upstream bool `json:"forceHTTP1Upstream,omitempty" yaml:"forceHTTP1Upstream,omitempty"` + Endpoints []*DestinationEndpoint `json:"endpoints,omitempty" yaml:"endpoints,omitempty"` // AddressTypeState specifies the state of DestinationEndpoint address type. AddressType *DestinationAddressType `json:"addressType,omitempty" yaml:"addressType,omitempty"` // IPFamily specifies the IP family (IPv4 or IPv6) to use for this destination's endpoints. @@ -2879,12 +2903,6 @@ type ActiveHealthCheck struct { GRPC *GRPCHealthChecker `json:"grpc,omitempty" yaml:"grpc,omitempty"` } -func (h *HealthCheck) SetHTTPHostIfAbsent(host string) { - if h != nil && h.Active != nil && h.Active.HTTP != nil && h.Active.HTTP.Host == "" { - h.Active.HTTP.Host = host - } -} - // Validate the fields within the HealthCheck structure. func (h *HealthCheck) Validate() error { var errs error @@ -2970,9 +2988,6 @@ type HTTPHealthChecker struct { // Validate the fields within the HTTPHealthChecker structure. func (c *HTTPHealthChecker) Validate() error { var errs error - if c.Host == "" { - errs = errors.Join(errs, ErrHCHTTPHostInvalid) - } if c.Path == "" { errs = errors.Join(errs, ErrHCHTTPPathInvalid) } diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index ea14191c46..5194295c01 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -1107,6 +1107,95 @@ func TestRouteDestination_NeedsClusterPerSetting(t *testing.T) { }, expected: true, }, + { + name: "cluster per setting mixed upstream protocol requirements", + input: RouteDestination{ + Name: "valid hostname", + Settings: []*DestinationSetting{ + { + Endpoints: []*DestinationEndpoint{ + { + Host: "10.0.1.1", + Port: 8080, + }, + }, + AddressType: ptr.To(IP), + Protocol: HTTP, + ForceHTTP1Upstream: true, + }, + { + Endpoints: []*DestinationEndpoint{ + { + Host: "10.0.1.2", + Port: 8080, + }, + }, + AddressType: ptr.To(IP), + Protocol: HTTP2, + }, + }, + }, + expected: true, + }, + { + name: "single cluster compatible websocket and http backends", + input: RouteDestination{ + Name: "valid hostname", + Settings: []*DestinationSetting{ + { + Endpoints: []*DestinationEndpoint{ + { + Host: "10.0.1.1", + Port: 8080, + }, + }, + AddressType: ptr.To(IP), + Protocol: HTTP, + ForceHTTP1Upstream: true, + }, + { + Endpoints: []*DestinationEndpoint{ + { + Host: "10.0.1.2", + Port: 8080, + }, + }, + AddressType: ptr.To(IP), + Protocol: HTTP, + }, + }, + }, + expected: false, + }, + { + name: "single cluster compatible http2 and grpc backends", + input: RouteDestination{ + Name: "valid hostname", + Settings: []*DestinationSetting{ + { + Endpoints: []*DestinationEndpoint{ + { + Host: "10.0.1.1", + Port: 8080, + }, + }, + AddressType: ptr.To(IP), + Protocol: HTTP2, + }, + { + Endpoints: []*DestinationEndpoint{ + { + Host: "10.0.1.2", + Port: 8080, + }, + }, + AddressType: ptr.To(IP), + Protocol: GRPC, + }, + }, + }, + expected: false, + }, } for _, test := range tests { @@ -1577,7 +1666,7 @@ func TestValidateHealthCheck(t *testing.T) { want: ErrHealthCheckHealthyThresholdInvalid, }, { - name: "http-health-check: invalid host", + name: "http-health-check: empty host", input: HealthCheck{ &ActiveHealthCheck{ Timeout: MetaV1DurationPtr(time.Second), @@ -1593,7 +1682,6 @@ func TestValidateHealthCheck(t *testing.T) { &OutlierDetection{}, ptr.To[uint32](10), }, - want: ErrHCHTTPHostInvalid, }, { name: "http-health-check: invalid path", diff --git a/internal/wasm/httpserver.go b/internal/wasm/httpserver.go index e7cdfed2e3..f655b8a38e 100644 --- a/internal/wasm/httpserver.go +++ b/internal/wasm/httpserver.go @@ -30,7 +30,7 @@ const ( var _ Cache = &HTTPServer{} -type SeverOptions struct { +type ServerOptions struct { // Salt is used as a hash salt to generate an unguessable path for the Wasm module. Salt []byte // TLSConfig is the TLS configuration for the HTTP server. @@ -41,7 +41,7 @@ type SeverOptions struct { } // setDefault sets the default values for the server options if they are not set. -func (o *SeverOptions) setDefault() { +func (o *ServerOptions) setDefault() { if o.MaxFailedAttempts == 0 { o.MaxFailedAttempts = defaultMaxFailedAttempts } @@ -55,7 +55,7 @@ func (o *SeverOptions) setDefault() { // HTTPServer wraps a local file cache and serves the Wasm modules over HTTP. type HTTPServer struct { - SeverOptions + ServerOptions sync.Mutex // map from the mapping path to the wasm file path in the local cache. // The mapping path is a generated unguessable path to prevent unauthorized users @@ -97,11 +97,11 @@ type wasmModuleEntry struct { // NewHTTPServerWithFileCache creates a HTTP server with a local file cache for Wasm modules. // The local file cache is used to store the Wasm modules downloaded from the original URL. // The HTTP server serves the cached Wasm modules over HTTP to the Envoy Proxies. -func NewHTTPServerWithFileCache(serverOptions SeverOptions, cacheOptions CacheOptions, controllerNamespace string, logger logging.Logger) *HTTPServer { +func NewHTTPServerWithFileCache(serverOptions ServerOptions, cacheOptions CacheOptions, controllerNamespace string, logger logging.Logger) *HTTPServer { logger = logger.WithName("wasm-cache") serverOptions.setDefault() return &HTTPServer{ - SeverOptions: serverOptions, + ServerOptions: serverOptions, mappingPath2Cache: make(map[string]wasmModuleEntry), failedAttempts: make(map[string]attemptEntry), cache: newLocalFileCache(cacheOptions, logger), diff --git a/internal/wasm/httpserver_test.go b/internal/wasm/httpserver_test.go index ed3e636445..aa7d14a3af 100644 --- a/internal/wasm/httpserver_test.go +++ b/internal/wasm/httpserver_test.go @@ -330,7 +330,7 @@ func setupFakeRegistry(host string) error { func startLocalHTTPServer(ctx context.Context, cacheDir string, maxFailedAttempts int, failedAttemptResetDelay, failedAttemptsResetInterval time.Duration) (*HTTPServer, error) { logger := logging.DefaultLogger(os.Stdout, egv1a1.LogLevelInfo) s := NewHTTPServerWithFileCache( - SeverOptions{ + ServerOptions{ Salt: []byte("salt"), MaxFailedAttempts: maxFailedAttempts, FailedAttemptResetDelay: failedAttemptResetDelay, diff --git a/internal/xds/translator/cluster.go b/internal/xds/translator/cluster.go index 6f82c5d4a3..19a043f400 100644 --- a/internal/xds/translator/cluster.go +++ b/internal/xds/translator/cluster.go @@ -67,6 +67,7 @@ type xdsClusterArgs struct { proxyProtocol *ir.ProxyProtocol circuitBreaker *ir.CircuitBreaker healthCheck *ir.HealthCheck + routeHostname string http1Settings *ir.HTTP1Settings http2Settings *ir.HTTP2Settings timeout *ir.Timeout @@ -246,12 +247,16 @@ func buildXdsCluster(args *xdsClusterArgs) (*buildClusterResult, error) { // influence transport socket specific settings requiresAutoHTTPConfig := len(args.settings) > 0 requiresHTTP2Options := false + forceHTTP1UpstreamProtocol := false hasLiteralSNI := false for _, ds := range args.settings { if ds.Protocol == ir.GRPC || ds.Protocol == ir.HTTP2 { requiresHTTP2Options = true } + if ds.ForceHTTP1Upstream { + forceHTTP1UpstreamProtocol = true + } if ds.Protocol == ir.TCP { requiresAutoHTTPConfig = false @@ -315,7 +320,7 @@ func buildXdsCluster(args *xdsClusterArgs) (*buildClusterResult, error) { } // build common, HTTP/1 and HTTP/2 protocol options for cluster - epo, secrets, err := buildTypedExtensionProtocolOptions(args, requiresAutoHTTPConfig, requiresHTTP2Options, requiresAutoSNI) + epo, secrets, err := buildTypedExtensionProtocolOptions(args, requiresAutoHTTPConfig, requiresHTTP2Options, requiresAutoSNI, forceHTTP1UpstreamProtocol) if err != nil { return nil, err } @@ -445,7 +450,7 @@ func buildXdsCluster(args *xdsClusterArgs) (*buildClusterResult, error) { } if args.healthCheck != nil && args.healthCheck.Active != nil { - cluster.HealthChecks = buildXdsHealthCheck(args.healthCheck.Active) + cluster.HealthChecks = buildXdsHealthCheck(args.healthCheck.Active, args.routeHostname) } if args.healthCheck != nil && args.healthCheck.Passive != nil { @@ -566,7 +571,7 @@ func buildZoneAwareLbConfig(preferLocal *ir.PreferLocalZone) *commonv3.LocalityL return lbConfig } -func buildXdsHealthCheck(healthcheck *ir.ActiveHealthCheck) []*corev3.HealthCheck { +func buildXdsHealthCheck(healthcheck *ir.ActiveHealthCheck, routeHostname string) []*corev3.HealthCheck { hc := &corev3.HealthCheck{ Timeout: durationpb.New(healthcheck.Timeout.Duration), Interval: durationpb.New(healthcheck.Interval.Duration), @@ -585,7 +590,7 @@ func buildXdsHealthCheck(healthcheck *ir.ActiveHealthCheck) []*corev3.HealthChec switch { case healthcheck.HTTP != nil: httpChecker := &corev3.HealthCheck_HttpHealthCheck{ - Host: healthcheck.HTTP.Host, + Host: httpHealthCheckHost(healthcheck.HTTP, routeHostname), Path: healthcheck.HTTP.Path, } if healthcheck.HTTP.Method != nil { @@ -618,6 +623,13 @@ func buildXdsHealthCheck(healthcheck *ir.ActiveHealthCheck) []*corev3.HealthChec return []*corev3.HealthCheck{hc} } +func httpHealthCheckHost(healthcheck *ir.HTTPHealthChecker, routeHostname string) string { + if healthcheck.Host != "" { + return healthcheck.Host + } + return routeHostname +} + func buildXdsOutlierDetection(outlierDetection *ir.OutlierDetection) *clusterv3.OutlierDetection { od := &clusterv3.OutlierDetection{ BaseEjectionTime: durationpb.New(outlierDetection.BaseEjectionTime.Duration), @@ -899,7 +911,7 @@ func buildHealthCheckConfig(hc *ir.HealthCheck, ep *ir.DestinationEndpoint) *end } } -func buildTypedExtensionProtocolOptions(args *xdsClusterArgs, requiresAutoHTTPConfig, requiresHTTP2Options, requiresAutoSNI bool) (map[string]*anypb.Any, []*tlsv3.Secret, error) { +func buildTypedExtensionProtocolOptions(args *xdsClusterArgs, requiresAutoHTTPConfig, requiresHTTP2Options, requiresAutoSNI, forceHTTP1UpstreamProtocol bool) (map[string]*anypb.Any, []*tlsv3.Secret, error) { requiresCommonHTTPOptions := (args.timeout != nil && args.timeout.HTTP != nil && (args.timeout.HTTP.MaxConnectionDuration != nil || args.timeout.HTTP.ConnectionIdleTimeout != nil)) || (args.circuitBreaker != nil && args.circuitBreaker.MaxRequestsPerConnection != nil) @@ -910,7 +922,7 @@ func buildTypedExtensionProtocolOptions(args *xdsClusterArgs, requiresAutoHTTPCo requiresHTTPFilters := len(args.settings) > 0 && args.settings[0].Filters != nil && args.settings[0].Filters.CredentialInjection != nil requiredHTTPProtocolOptions := args.useClientProtocol || requiresAutoHTTPConfig || - requiresCommonHTTPOptions || requiresHTTP1Options || requiresHTTP2Options || requiresHTTPFilters || requiresAutoSNI + requiresCommonHTTPOptions || requiresHTTP1Options || requiresHTTP2Options || requiresHTTPFilters || requiresAutoSNI || forceHTTP1UpstreamProtocol if !requiredHTTPProtocolOptions { return nil, nil, nil @@ -940,6 +952,7 @@ func buildTypedExtensionProtocolOptions(args *xdsClusterArgs, requiresAutoHTTPCo // If translation requires HTTP2 enablement or HTTP1 trailers, set appropriate setting // Default to http1 otherwise switch { + // If useClientProtocol is set, force Envoy to use the same protocol upstream as downstream, regardless of other settings. case args.useClientProtocol: protocolOptions.UpstreamProtocolOptions = &httpv3.HttpProtocolOptions_UseDownstreamProtocolConfig{ UseDownstreamProtocolConfig: &httpv3.HttpProtocolOptions_UseDownstreamHttpConfig{ @@ -947,6 +960,15 @@ func buildTypedExtensionProtocolOptions(args *xdsClusterArgs, requiresAutoHTTPCo Http2ProtocolOptions: http2Opts, }, } + // If forceHTTP1UpstreamProtocol is set, force Envoy to use HTTP1 upstream regardless of other settings. + case forceHTTP1UpstreamProtocol: + protocolOptions.UpstreamProtocolOptions = &httpv3.HttpProtocolOptions_ExplicitHttpConfig_{ + ExplicitHttpConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig{ + ProtocolConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig_HttpProtocolOptions{ + HttpProtocolOptions: http1Opts, + }, + }, + } case requiresHTTP2Options: protocolOptions.UpstreamProtocolOptions = &httpv3.HttpProtocolOptions_ExplicitHttpConfig_{ ExplicitHttpConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig{ @@ -1281,6 +1303,7 @@ func (httpRoute *HTTPRouteTranslator) asClusterArgs(name string, settings: settings, tSocket: nil, endpointType: buildEndpointType(settings), + routeHostname: httpRoute.Hostname, metrics: extra.metrics, http1Settings: extra.http1Settings, http2Settings: extra.http2Settings, diff --git a/internal/xds/translator/testdata/in/xds-ir/health-check-host-hierarchy.yaml b/internal/xds/translator/testdata/in/xds-ir/health-check-host-hierarchy.yaml new file mode 100644 index 0000000000..7a3ce9243a --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/health-check-host-hierarchy.yaml @@ -0,0 +1,88 @@ +# Health check Host header hierarchy: +# 1. Explicit BackendTrafficPolicy hostname is a cluster-level health check host. +# 2. Backend endpoint hostname is emitted as Endpoint.HealthCheckConfig.hostname +# when the cluster-level host was only defaulted from the effective route +# hostname for the listener. +# 3. Effective route hostname stays as the cluster-level fallback when no +# endpoint hostname exists. +http: +- name: health-check-host-hierarchy-listener + address: "::" + path: + mergeSlashes: true + escapedSlashesAction: UnescapeAndRedirect + port: 10080 + hostnames: + - "*" + routes: + - name: btp-host-route + hostname: btp-route.example.com + traffic: + healthCheck: + active: + timeout: 2s + interval: 5s + healthyThreshold: 1 + unhealthyThreshold: 3 + http: + host: btp-health.example.com + path: /healthz + method: GET + expectedStatuses: + - 200 + destination: + name: btp-host-route-dest + settings: + - name: btp-host-route-dest/backend/0 + addressType: FQDN + endpoints: + - host: backend-address.example.com + hostname: backend-endpoint.example.com + port: 443 + - name: backend-host-route + hostname: backend-route.example.com + traffic: + healthCheck: + active: + timeout: 2s + interval: 5s + healthyThreshold: 1 + unhealthyThreshold: 3 + http: + host: "" + path: /healthz + method: GET + expectedStatuses: + - 200 + destination: + name: backend-host-route-dest + settings: + - name: backend-host-route-dest/backend/0 + addressType: FQDN + endpoints: + - host: backend-address.example.com + hostname: backend-endpoint.example.com + port: 443 + - name: route-host-route + hostname: route-fallback.example.com + traffic: + healthCheck: + active: + timeout: 2s + interval: 5s + healthyThreshold: 1 + unhealthyThreshold: 3 + http: + host: "" + path: /healthz + method: GET + expectedStatuses: + - 200 + destination: + name: route-host-route-dest + settings: + - name: route-host-route-dest/backend/0 + addressType: FQDN + endpoints: + - host: backend-no-endpoint-host.example.com + port: 443 diff --git a/internal/xds/translator/testdata/in/xds-ir/mixed-websocket-and-h2c-backends.yaml b/internal/xds/translator/testdata/in/xds-ir/mixed-websocket-and-h2c-backends.yaml new file mode 100644 index 0000000000..560512b839 --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/mixed-websocket-and-h2c-backends.yaml @@ -0,0 +1,34 @@ +# mixed websocket and h2c backends should be split into separate clusters with correct protocol options. +# This may not happen in real world, but we want to make sure the translator can handle this case correctly. +http: + - name: "weighted-protocol-listener" + address: "0.0.0.0" + port: 10080 + hostnames: + - "*" + path: + mergeSlashes: true + escapedSlashesAction: UnescapeAndRedirect + routes: + - name: "weighted-protocol-route" + hostname: "*" + pathMatch: + prefix: / + destination: + name: "weighted-protocol-route-dest" + settings: + - name: "weighted-protocol-route-dest/backend/0" + addressType: IP + endpoints: + - host: "10.0.0.1" + port: 8080 + protocol: HTTP + forceHTTP1Upstream: true + weight: 1 + - name: "weighted-protocol-route-dest/backend/1" + addressType: IP + endpoints: + - host: "10.0.0.2" + port: 8080 + protocol: HTTP2 + weight: 1 diff --git a/internal/xds/translator/testdata/in/xds-ir/websocket-backend-force-http1-upstream.yaml b/internal/xds/translator/testdata/in/xds-ir/websocket-backend-force-http1-upstream.yaml new file mode 100644 index 0000000000..b86e8bfdbb --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/websocket-backend-force-http1-upstream.yaml @@ -0,0 +1,43 @@ +http: + - name: "websocket-listener" + address: "0.0.0.0" + port: 10080 + hostnames: + - "*" + path: + mergeSlashes: true + escapedSlashesAction: UnescapeAndRedirect + routes: + - name: "websocket-wss-route" + hostname: "*" + destination: + name: "websocket-wss-route-dest" + settings: + - endpoints: + - host: "websocket.example.com" + port: 443 + name: "websocket-wss-route-dest/backend/0" + protocol: HTTP + forceHTTP1Upstream: true + tls: + alpnProtocols: null + caCertificate: + name: websocket-wss-route/ca + sni: "websocket.example.com" + useSystemTrustStore: true + - name: "ordinary-tls-http-route" + hostname: "*" + destination: + name: "ordinary-tls-http-route-dest" + settings: + - endpoints: + - host: "ordinary.example.com" + port: 443 + name: "ordinary-tls-http-route-dest/backend/0" + protocol: HTTP + tls: + alpnProtocols: null + caCertificate: + name: ordinary-tls-http-route/ca + sni: "ordinary.example.com" + useSystemTrustStore: true diff --git a/internal/xds/translator/testdata/out/xds-ir/health-check-host-hierarchy.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/health-check-host-hierarchy.clusters.yaml new file mode 100644 index 0000000000..396729f67c --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/health-check-host-hierarchy.clusters.yaml @@ -0,0 +1,142 @@ +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + dnsRefreshRate: 30s + healthChecks: + - healthyThreshold: 1 + httpHealthCheck: + expectedStatuses: + - end: "201" + start: "200" + host: btp-health.example.com + method: GET + path: /healthz + interval: 5s + timeout: 2s + unhealthyThreshold: 3 + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + loadAssignment: + clusterName: btp-host-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: backend-address.example.com + portValue: 443 + hostname: backend-endpoint.example.com + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: btp-host-route-dest/backend/0 + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: btp-host-route-dest + perConnectionBufferLimitBytes: 32768 + respectDnsTtl: true + type: STRICT_DNS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + dnsRefreshRate: 30s + healthChecks: + - healthyThreshold: 1 + httpHealthCheck: + expectedStatuses: + - end: "201" + start: "200" + host: backend-route.example.com + method: GET + path: /healthz + interval: 5s + timeout: 2s + unhealthyThreshold: 3 + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + loadAssignment: + clusterName: backend-host-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: backend-address.example.com + portValue: 443 + healthCheckConfig: + hostname: backend-endpoint.example.com + hostname: backend-endpoint.example.com + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: backend-host-route-dest/backend/0 + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: backend-host-route-dest + perConnectionBufferLimitBytes: 32768 + respectDnsTtl: true + type: STRICT_DNS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + dnsRefreshRate: 30s + healthChecks: + - healthyThreshold: 1 + httpHealthCheck: + expectedStatuses: + - end: "201" + start: "200" + host: route-fallback.example.com + method: GET + path: /healthz + interval: 5s + timeout: 2s + unhealthyThreshold: 3 + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + loadAssignment: + clusterName: route-host-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: backend-no-endpoint-host.example.com + portValue: 443 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: route-host-route-dest/backend/0 + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: route-host-route-dest + perConnectionBufferLimitBytes: 32768 + respectDnsTtl: true + type: STRICT_DNS diff --git a/internal/xds/translator/testdata/out/xds-ir/health-check-host-hierarchy.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/health-check-host-hierarchy.endpoints.yaml new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/health-check-host-hierarchy.endpoints.yaml @@ -0,0 +1 @@ +[] diff --git a/internal/xds/translator/testdata/out/xds-ir/health-check-host-hierarchy.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/health-check-host-hierarchy.listeners.yaml new file mode 100644 index 0000000000..a265e79d2a --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/health-check-host-hierarchy.listeners.yaml @@ -0,0 +1,35 @@ +- address: + socketAddress: + address: '::' + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + suppressEnvoyHeaders: true + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: health-check-host-hierarchy-listener + serverHeaderTransformation: PASS_THROUGH + statPrefix: http-10080 + useRemoteAddress: true + name: health-check-host-hierarchy-listener + maxConnectionsToAcceptPerSocketEvent: 1 + name: health-check-host-hierarchy-listener + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/health-check-host-hierarchy.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/health-check-host-hierarchy.routes.yaml new file mode 100644 index 0000000000..98f5a1acaa --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/health-check-host-hierarchy.routes.yaml @@ -0,0 +1,36 @@ +- ignorePortInHostMatching: true + name: health-check-host-hierarchy-listener + virtualHosts: + - domains: + - btp-route.example.com + name: health-check-host-hierarchy-listener/btp-route_example_com + routes: + - match: + prefix: / + name: btp-host-route + route: + cluster: btp-host-route-dest + upgradeConfigs: + - upgradeType: websocket + - domains: + - backend-route.example.com + name: health-check-host-hierarchy-listener/backend-route_example_com + routes: + - match: + prefix: / + name: backend-host-route + route: + cluster: backend-host-route-dest + upgradeConfigs: + - upgradeType: websocket + - domains: + - route-fallback.example.com + name: health-check-host-hierarchy-listener/route-fallback_example_com + routes: + - match: + prefix: / + name: route-host-route + route: + cluster: route-host-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/mixed-websocket-and-h2c-backends.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/mixed-websocket-and-h2c-backends.clusters.yaml new file mode 100644 index 0000000000..44cb21e465 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/mixed-websocket-and-h2c-backends.clusters.yaml @@ -0,0 +1,60 @@ +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: weighted-protocol-route-dest/backend/0 + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: weighted-protocol-route-dest/backend/0 + perConnectionBufferLimitBytes: 32768 + type: EDS + typedExtensionProtocolOptions: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicitHttpConfig: + httpProtocolOptions: {} +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: weighted-protocol-route-dest/backend/1 + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: weighted-protocol-route-dest/backend/1 + perConnectionBufferLimitBytes: 32768 + type: EDS + typedExtensionProtocolOptions: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicitHttpConfig: + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 diff --git a/internal/xds/translator/testdata/out/xds-ir/mixed-websocket-and-h2c-backends.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/mixed-websocket-and-h2c-backends.endpoints.yaml new file mode 100644 index 0000000000..1d09bdb894 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/mixed-websocket-and-h2c-backends.endpoints.yaml @@ -0,0 +1,24 @@ +- clusterName: weighted-protocol-route-dest/backend/0 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 10.0.0.1 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: weighted-protocol-route-dest/backend/0 +- clusterName: weighted-protocol-route-dest/backend/1 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 10.0.0.2 + portValue: 8080 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: weighted-protocol-route-dest/backend/1 diff --git a/internal/xds/translator/testdata/out/xds-ir/mixed-websocket-and-h2c-backends.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/mixed-websocket-and-h2c-backends.listeners.yaml new file mode 100644 index 0000000000..c2549418db --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/mixed-websocket-and-h2c-backends.listeners.yaml @@ -0,0 +1,35 @@ +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + suppressEnvoyHeaders: true + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: weighted-protocol-listener + serverHeaderTransformation: PASS_THROUGH + statPrefix: http-10080 + useRemoteAddress: true + name: weighted-protocol-listener + maxConnectionsToAcceptPerSocketEvent: 1 + name: weighted-protocol-listener + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/mixed-websocket-and-h2c-backends.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/mixed-websocket-and-h2c-backends.routes.yaml new file mode 100644 index 0000000000..9c65376470 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/mixed-websocket-and-h2c-backends.routes.yaml @@ -0,0 +1,20 @@ +- ignorePortInHostMatching: true + name: weighted-protocol-listener + virtualHosts: + - domains: + - '*' + name: weighted-protocol-listener/* + routes: + - match: + prefix: / + name: weighted-protocol-route + route: + clusterNotFoundResponseCode: INTERNAL_SERVER_ERROR + upgradeConfigs: + - upgradeType: websocket + weightedClusters: + clusters: + - name: weighted-protocol-route-dest/backend/0 + weight: 1 + - name: weighted-protocol-route-dest/backend/1 + weight: 1 diff --git a/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.clusters.yaml new file mode 100644 index 0000000000..4dd451f0a5 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.clusters.yaml @@ -0,0 +1,113 @@ +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: websocket-wss-route-dest + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: websocket-wss-route-dest + perConnectionBufferLimitBytes: 32768 + transportSocket: + name: dummy.transport_socket + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: {} + transportSocketMatches: + - match: + name: websocket-wss-route-dest/tls/0 + name: websocket-wss-route-dest/tls/0 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + exact: websocket.example.com + sanType: DNS + validationContextSdsSecretConfig: + name: websocket-wss-route/ca + sdsConfig: + ads: {} + resourceApiVersion: V3 + sni: websocket.example.com + type: EDS + typedExtensionProtocolOptions: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicitHttpConfig: + httpProtocolOptions: {} +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: ordinary-tls-http-route-dest + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + loadBalancingPolicy: + policies: + - typedExtensionConfig: + name: envoy.load_balancing_policies.least_request + typedConfig: + '@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest + localityLbConfig: + localityWeightedLbConfig: {} + name: ordinary-tls-http-route-dest + perConnectionBufferLimitBytes: 32768 + transportSocket: + name: dummy.transport_socket + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: {} + transportSocketMatches: + - match: + name: ordinary-tls-http-route-dest/tls/0 + name: ordinary-tls-http-route-dest/tls/0 + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: + combinedValidationContext: + defaultValidationContext: + matchTypedSubjectAltNames: + - matcher: + exact: ordinary.example.com + sanType: DNS + validationContextSdsSecretConfig: + name: ordinary-tls-http-route/ca + sdsConfig: + ads: {} + resourceApiVersion: V3 + sni: ordinary.example.com + type: EDS + typedExtensionProtocolOptions: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + autoConfig: + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + httpProtocolOptions: {} diff --git a/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.endpoints.yaml new file mode 100644 index 0000000000..b4dfc8cfc3 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.endpoints.yaml @@ -0,0 +1,32 @@ +- clusterName: websocket-wss-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: websocket.example.com + portValue: 443 + loadBalancingWeight: 1 + metadata: + filterMetadata: + envoy.transport_socket_match: + name: websocket-wss-route-dest/tls/0 + loadBalancingWeight: 1 + locality: + region: websocket-wss-route-dest/backend/0 +- clusterName: ordinary-tls-http-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: ordinary.example.com + portValue: 443 + loadBalancingWeight: 1 + metadata: + filterMetadata: + envoy.transport_socket_match: + name: ordinary-tls-http-route-dest/tls/0 + loadBalancingWeight: 1 + locality: + region: ordinary-tls-http-route-dest/backend/0 diff --git a/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.listeners.yaml new file mode 100644 index 0000000000..b9ef1b3a77 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.listeners.yaml @@ -0,0 +1,35 @@ +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + suppressEnvoyHeaders: true + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: websocket-listener + serverHeaderTransformation: PASS_THROUGH + statPrefix: http-10080 + useRemoteAddress: true + name: websocket-listener + maxConnectionsToAcceptPerSocketEvent: 1 + name: websocket-listener + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.routes.yaml new file mode 100644 index 0000000000..1c2ddeacac --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.routes.yaml @@ -0,0 +1,21 @@ +- ignorePortInHostMatching: true + name: websocket-listener + virtualHosts: + - domains: + - '*' + name: websocket-listener/* + routes: + - match: + prefix: / + name: websocket-wss-route + route: + cluster: websocket-wss-route-dest + upgradeConfigs: + - upgradeType: websocket + - match: + prefix: / + name: ordinary-tls-http-route + route: + cluster: ordinary-tls-http-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.secrets.yaml b/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.secrets.yaml new file mode 100644 index 0000000000..4abecf47fc --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/websocket-backend-force-http1-upstream.secrets.yaml @@ -0,0 +1,8 @@ +- name: websocket-wss-route/ca + validationContext: + trustedCa: + filename: /etc/ssl/certs/ca-certificates.crt +- name: ordinary-tls-http-route/ca + validationContext: + trustedCa: + filename: /etc/ssl/certs/ca-certificates.crt diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 8fadbeac36..136c98e4e4 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -25,7 +25,11 @@ bug fixes: | BackendTLSPolicy was ignored when configuring TLS for telemetry backends (access logs, tracing, metrics). Fixed client certificate secret never delivered when it is exclusively referenced by a SecurityPolicy `extAuth`/`jwt`/`oidc` Backend. Fixed xRoute status condition when route has mirror filter and the mirror backend has no endpoints. + Fixed `ws` and `wss` Backend appProtocols to force HTTP/1.1 upstream connections instead of negotiating HTTP/2, avoiding compatibility issues with WebSocket backends that do not support RFC 8441 extended CONNECT. Fixed gateway-helm RBAC in GatewayNamespace mode with explicit `watch.namespaces` list by adding controller-namespace secret read permissions to infra-manager. + Fixed a control plane panic caused by concurrent Status mutation racing with the watchable Map coalesce goroutine. + Fixed status conditions not being updated when a route is rejected due to multiple errors. + Fixed active HTTP health checks to use Backend endpoint hostnames before falling back to the effective Route hostname. # Enhancements that improve performance. performance improvements: | diff --git a/release-notes/v1.7.3.yaml b/release-notes/v1.7.3.yaml new file mode 100644 index 0000000000..25b9897286 --- /dev/null +++ b/release-notes/v1.7.3.yaml @@ -0,0 +1,30 @@ +date: May 8, 2026 + +# Changes that are expected to cause an incompatibility with previous versions, such as deletions or modifications to existing APIs. +breaking changes: | + Bumped `google.golang.org/grpc` to v1.79.3 to address CVE-2026-33186 (Critical, gRPC-Go authorization bypass via non-canonical HTTP/2 `:path` header). + Bumped `go.opentelemetry.io/otel/sdk` to v1.40.0 to address CVE-2026-24051 (High, OpenTelemetry Go SDK path hijacking on macOS/Darwin). + +# Updates addressing vulnerabilities, security flaws, or compliance requirements. +security updates: | + +# New features or capabilities added in this release. +new features: | + + +bug fixes: | + Fixed a control plane panic caused by concurrent Status mutation racing with the watchable Map coalesce goroutine. + Fixed `ws` and `wss` Backend appProtocols to force HTTP/1.1 upstream connections instead of negotiating HTTP/2, avoiding compatibility issues with WebSocket backends that do not support RFC 8441 extended CONNECT. + Fixed status conditions not being updated when a route is rejected due to multiple errors. + Fixed benchmark JSON report emitting `0` for p99 and p999 percentiles by using the nearest Nighthawk histogram percentiles. + Fixed active HTTP health checks to use Backend endpoint hostnames before falling back to the effective Route hostname. + +# Enhancements that improve performance. +performance improvements: | + + +# Deprecated features or APIs. +deprecations: | + +# Other notable changes not covered by the above sections. +Other changes: | diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 0fa44d9347..25734c889f 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -2426,7 +2426,7 @@ _Appears in:_ | Field | Type | Required | Default | Description | | --- | --- | --- | --- | --- | -| `hostname` | _string_ | false | | Hostname defines the HTTP host that will be requested during health checking.
Default: HTTPRoute or GRPCRoute hostname. | +| `hostname` | _string_ | false | | Hostname defines the HTTP Host header used for active HTTP health checks.
Host selection uses this order: this field, the associated Backend endpoint
hostname if available, then the effective Route hostname. | | `path` | _string_ | true | | Path defines the HTTP path that will be requested during health checking. | | `method` | _string_ | false | | Method defines the HTTP method used for health checking.
Defaults to GET | | `expectedStatuses` | _[HTTPStatus](#httpstatus) array_ | false | | ExpectedStatuses defines a list of HTTP response statuses considered healthy.
Defaults to 200 only | diff --git a/test/benchmark/suite/json.go b/test/benchmark/suite/json.go index a46e605565..3e25f80d2e 100644 --- a/test/benchmark/suite/json.go +++ b/test/benchmark/suite/json.go @@ -10,6 +10,8 @@ package suite import ( "encoding/json" "math" + + "github.com/envoyproxy/gateway/test/benchmark/proto" ) func ToJSON(report *BenchmarkSuiteReport) []byte { @@ -53,25 +55,7 @@ func convertCaseReport(report *BenchmarkCaseReport) *JSONTestResult { latency.Max = stat.GetMax().AsDuration().Seconds() * 1000 latency.Min = stat.GetMin().AsDuration().Seconds() * 1000 latency.Mean = stat.GetMean().AsDuration().Seconds() * 1000 - latency.Percentiles = Percentiles{} - for _, p := range stat.Percentiles { - switch p.Percentile { - case 0.5: - latency.Percentiles.P50 = p.GetDuration().AsDuration().Seconds() * 1000 - case 0.75: - latency.Percentiles.P75 = p.GetDuration().AsDuration().Seconds() * 1000 - case 0.8: - latency.Percentiles.P80 = p.GetDuration().AsDuration().Seconds() * 1000 - case 0.9: - latency.Percentiles.P90 = p.GetDuration().AsDuration().Seconds() * 1000 - case 0.95: - latency.Percentiles.P95 = p.GetDuration().AsDuration().Seconds() * 1000 - case 0.99: - latency.Percentiles.P99 = p.GetDuration().AsDuration().Seconds() * 1000 - case 0.999: - latency.Percentiles.P999 = p.GetDuration().AsDuration().Seconds() * 1000 - } - } + latency.Percentiles = getLatencyPercentiles(stat.Percentiles) } } @@ -158,3 +142,28 @@ func getResourceUsage(metrics []float64) *ResourceUsage { Max: maxVal, } } + +func getLatencyPercentiles(percentiles []*proto.Percentile) Percentiles { + return Percentiles{ + P50: getPercentileDurationMs(percentiles, 0.5), + P75: getPercentileDurationMs(percentiles, 0.75), + P80: getPercentileDurationMs(percentiles, 0.8), + P90: getPercentileDurationMs(percentiles, 0.9), + P95: getPercentileDurationMs(percentiles, 0.95), + P99: getPercentileDurationMs(percentiles, 0.99), + P999: getPercentileDurationMs(percentiles, 0.999), + } +} + +func getPercentileDurationMs(percentiles []*proto.Percentile, target float64) float64 { + lastPercentile := float64(-1) + for _, percentile := range percentiles { + current := percentile.GetPercentile() + if current >= target && lastPercentile < current { + return percentile.GetDuration().AsDuration().Seconds() * 1000 + } + lastPercentile = current + } + + return 0 +} diff --git a/test/benchmark/suite/json_test.go b/test/benchmark/suite/json_test.go new file mode 100644 index 0000000000..7a63720710 --- /dev/null +++ b/test/benchmark/suite/json_test.go @@ -0,0 +1,30 @@ +// 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 benchmark + +package suite + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetLatencyPercentiles(t *testing.T) { + result := fakeCaseResult() + require.NotNil(t, result) + require.NotEmpty(t, result.Statistics) + + percentiles := getLatencyPercentiles(result.Statistics[1].Percentiles) + + require.InDelta(t, 0.438127, percentiles.P50, 1e-9) + require.InDelta(t, 0.546047, percentiles.P75, 1e-9) + require.InDelta(t, 0.584511, percentiles.P80, 1e-9) + require.InDelta(t, 0.751775, percentiles.P90, 1e-9) + require.InDelta(t, 1.100415, percentiles.P95, 1e-9) + require.InDelta(t, 55.601151, percentiles.P99, 1e-9) + require.InDelta(t, 60.461055, percentiles.P999, 1e-9) +} diff --git a/test/helm/gateway-crds-helm/all.out.yaml b/test/helm/gateway-crds-helm/all.out.yaml index 02472b4be5..7a23fd9718 100644 --- a/test/helm/gateway-crds-helm/all.out.yaml +++ b/test/helm/gateway-crds-helm/all.out.yaml @@ -21573,8 +21573,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -26515,8 +26516,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -40237,8 +40239,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -41489,8 +41492,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -42911,8 +42915,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -44238,8 +44243,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -46912,8 +46918,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -48052,8 +48059,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -49393,8 +49401,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -50812,8 +50821,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ diff --git a/test/helm/gateway-crds-helm/e2e.out.yaml b/test/helm/gateway-crds-helm/e2e.out.yaml index eada234bb8..737b27dcc6 100644 --- a/test/helm/gateway-crds-helm/e2e.out.yaml +++ b/test/helm/gateway-crds-helm/e2e.out.yaml @@ -926,8 +926,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -6073,8 +6074,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -20102,8 +20104,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -21430,8 +21433,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -22934,8 +22938,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -24341,8 +24346,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -27185,8 +27191,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -28398,8 +28405,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -29812,8 +29820,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -31310,8 +31319,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ diff --git a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml index a3ddf7d7ec..98f7c14138 100644 --- a/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml +++ b/test/helm/gateway-crds-helm/envoy-gateway-crds.out.yaml @@ -903,8 +903,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -5845,8 +5846,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -19567,8 +19569,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -20819,8 +20822,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -22241,8 +22245,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -23568,8 +23573,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -26242,8 +26248,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -27382,8 +27389,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -28723,8 +28731,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ @@ -30142,8 +30151,9 @@ spec: type: array hostname: description: |- - Hostname defines the HTTP host that will be requested during health checking. - Default: HTTPRoute or GRPCRoute hostname. + Hostname defines the HTTP Host header used for active HTTP health checks. + Host selection uses this order: this field, the associated Backend endpoint + hostname if available, then the effective Route hostname. maxLength: 253 minLength: 1 pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$