diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 3a38774519..b1b8f241c2 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -231,11 +231,10 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe err.Reason(), )) failedProcessDestination = true - continue } - // ds can be nil if the backendRef weight is 0 - if ds == nil { + // skip backendRefs with weight 0 as they do not affect the traffic distribution + if ds.Weight != nil && *ds.Weight == 0 { continue } allDs = append(allDs, ds) @@ -258,7 +257,7 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe case irRoute.DirectResponse != nil || irRoute.Redirect != nil: // return 500 if any destination setting is invalid // the error is already added to the error list when processing the destination - case failedProcessDestination: + case failedProcessDestination && destination.ToBackendWeights().Valid == 0: irRoute.DirectResponse = &ir.CustomResponse{ StatusCode: ptr.To(uint32(500)), } @@ -662,10 +661,10 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe err.Reason(), )) failedProcessDestination = true - continue } - if ds == nil { + // skip backendRefs with weight 0 as they do not affect the traffic distribution + if ds.Weight != nil && *ds.Weight == 0 { continue } allDs = append(allDs, ds) @@ -684,7 +683,7 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe case irRoute.DirectResponse != nil || irRoute.Redirect != nil: // return 500 if any destination setting is invalid // the error is already added to the error list when processing the destination - case failedProcessDestination: + case failedProcessDestination && destination.ToBackendWeights().Valid == 0: irRoute.DirectResponse = &ir.CustomResponse{ StatusCode: ptr.To(uint32(500)), } @@ -1100,7 +1099,7 @@ func (t *Translator) processUDPRouteParentRefs(udpRoute *UDPRouteContext, resour } // Skip nil destination settings - if ds != nil { + if ds.Weight != nil && *ds.Weight > 0 { destSettings = append(destSettings, ds) } } @@ -1259,7 +1258,7 @@ func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resour continue } // Skip nil destination settings - if ds != nil { + if ds.Weight != nil && *ds.Weight > 0 { destSettings = append(destSettings, ds) } } @@ -1362,11 +1361,17 @@ func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resour func (t *Translator) processDestination(name string, backendRefContext BackendRefContext, parentRef *RouteParentContext, route RouteContext, resources *resource.Resources, ) (ds *ir.DestinationSetting, err status.Error) { - routeType := GetRouteType(route) - weight := uint32(1) - backendRef := GetBackendRef(backendRefContext) - if backendRef.Weight != nil { - weight = uint32(*backendRef.Weight) + var ( + routeType = GetRouteType(route) + backendRef = GetBackendRef(backendRefContext) + weight = (uint32(ptr.Deref(backendRef.Weight, int32(1)))) + ) + + // Create an empty DS without endpoints + // This represents an invalid DS. + emptyDS := &ir.DestinationSetting{ + Name: name, + Weight: &weight, } backendNamespace := NamespaceDerefOr(backendRef.Namespace, route.GetNamespace()) @@ -1374,13 +1379,13 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe { // return with empty endpoint means the backend is invalid and an error to fail the associated route. if err != nil { - return nil, err + return emptyDS, err } } // Skip processing backends with 0 weight if weight == 0 { - return nil, nil + return emptyDS, nil } var envoyProxy *egv1a1.EnvoyProxy @@ -1422,17 +1427,17 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe ds.IsDynamicResolver, ) if tlsErr != nil { - return nil, status.NewRouteStatusError(tlsErr, status.RouteReasonInvalidBackendTLS) + return emptyDS, status.NewRouteStatusError(tlsErr, status.RouteReasonInvalidBackendTLS) } var filtersErr error ds.Filters, filtersErr = t.processDestinationFilters(routeType, backendRefContext, parentRef, route, resources) if filtersErr != nil { - return nil, status.NewRouteStatusError(filtersErr, status.RouteReasonInvalidBackendFilters) + return emptyDS, status.NewRouteStatusError(filtersErr, status.RouteReasonInvalidBackendFilters) } if err := validateDestinationSettings(ds, t.IsEnvoyServiceRouting(envoyProxy), backendRef.Kind); err != nil { - return nil, err + return emptyDS, err } ds.Weight = &weight diff --git a/internal/gatewayapi/testdata/grpcroute-with-mixed-invalid-and-valid-backend-refs.in.yaml b/internal/gatewayapi/testdata/grpcroute-with-mixed-invalid-and-valid-backend-refs.in.yaml new file mode 100644 index 0000000000..c7268e9b91 --- /dev/null +++ b/internal/gatewayapi/testdata/grpcroute-with-mixed-invalid-and-valid-backend-refs.in.yaml @@ -0,0 +1,45 @@ +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 +grpcRoutes: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: GRPCRoute + metadata: + namespace: default + name: grpcroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - method: + method: ExampleExact + type: Exact + backendRefs: + - name: service-1 + port: 8080 + - name: service-not-exist + port: 8080 +services: +- apiVersion: v1 + kind: Service + metadata: + name: service-1 + spec: + clusterIP: 7.7.7.7 + ports: + - port: 8080 diff --git a/internal/gatewayapi/testdata/grpcroute-with-mixed-invalid-and-valid-backend-refs.out.yaml b/internal/gatewayapi/testdata/grpcroute-with-mixed-invalid-and-valid-backend-refs.out.yaml new file mode 100644 index 0000000000..756774185b --- /dev/null +++ b/internal/gatewayapi/testdata/grpcroute-with-mixed-invalid-and-valid-backend-refs.out.yaml @@ -0,0 +1,148 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +grpcRoutes: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: GRPCRoute + metadata: + creationTimestamp: null + name: grpcroute-1 + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + - name: service-not-exist + port: 8080 + matches: + - method: + method: ExampleExact + type: Exact + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: 'Failed to process route rule 0 backendRef 1: service default/service-not-exist + not found.' + reason: BackendNotFound + status: "False" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: true + 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: + name: grpcroute/default/grpcroute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: grpcroute/default/grpcroute-1/rule/0/backend/0 + protocol: GRPC + weight: 1 + - name: grpcroute/default/grpcroute-1/rule/0/backend/1 + weight: 1 + headerMatches: + - distinct: false + name: :path + suffix: /ExampleExact + hostname: '*' + isHTTP2: true + metadata: + kind: GRPCRoute + name: grpcroute-1 + namespace: default + name: grpcroute/default/grpcroute-1/rule/0/match/0/* + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/httproute-attaching-to-listener-with-multiple-backend-backendrefs-diff-address-type.out.yaml b/internal/gatewayapi/testdata/httproute-attaching-to-listener-with-multiple-backend-backendrefs-diff-address-type.out.yaml index ab706c94c7..898cc019cd 100644 --- a/internal/gatewayapi/testdata/httproute-attaching-to-listener-with-multiple-backend-backendrefs-diff-address-type.out.yaml +++ b/internal/gatewayapi/testdata/httproute-attaching-to-listener-with-multiple-backend-backendrefs-diff-address-type.out.yaml @@ -298,8 +298,25 @@ xdsIR: mergeSlashes: true port: 10080 routes: - - directResponse: - statusCode: 500 + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - name: httproute/default/httproute-1/rule/0/backend/0 + weight: 1 + - addressType: IP + endpoints: + - host: 1.1.1.1 + port: 3001 + name: httproute/default/httproute-1/rule/0/backend/1 + protocol: HTTP + weight: 1 + - addressType: FQDN + endpoints: + - host: primary.foo.com + port: 3000 + name: httproute/default/httproute-1/rule/0/backend/2 + protocol: HTTP + weight: 1 hostname: '*' isHTTP2: false metadata: @@ -311,8 +328,18 @@ xdsIR: distinct: false name: "" prefix: /1 - - directResponse: - statusCode: 500 + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - name: httproute/default/httproute-2/rule/0/backend/0 + weight: 1 + - addressType: IP + endpoints: + - host: 1.1.1.1 + port: 3001 + name: httproute/default/httproute-2/rule/0/backend/1 + protocol: HTTP + weight: 1 hostname: '*' isHTTP2: false metadata: @@ -352,8 +379,18 @@ xdsIR: distinct: false name: "" prefix: /3 - - directResponse: - statusCode: 500 + - destination: + name: httproute/default/httproute-3/rule/0 + settings: + - name: httproute/default/httproute-3/rule/0/backend/0 + weight: 1 + - addressType: FQDN + endpoints: + - host: primary.foo.com + port: 3000 + name: httproute/default/httproute-3/rule/0/backend/1 + protocol: HTTP + weight: 1 hostname: '*' isHTTP2: false metadata: diff --git a/internal/gatewayapi/testdata/httproute-with-mixed-invalid-and-valid-backend-refs.in.yaml b/internal/gatewayapi/testdata/httproute-with-mixed-invalid-and-valid-backend-refs.in.yaml new file mode 100644 index 0000000000..809ce1c5d8 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-mixed-invalid-and-valid-backend-refs.in.yaml @@ -0,0 +1,46 @@ +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-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + type: Exact + value: "/exact" + backendRefs: + - name: service-1 + port: 8080 + weight: 80 + - name: service-not-exist + port: 8080 + weight: 20 +services: +- apiVersion: v1 + kind: Service + metadata: + name: service-1 + spec: + clusterIP: 7.7.7.7 + ports: + - port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-mixed-invalid-and-valid-backend-refs.out.yaml b/internal/gatewayapi/testdata/httproute-with-mixed-invalid-and-valid-backend-refs.out.yaml new file mode 100644 index 0000000000..5f20f3bb76 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-mixed-invalid-and-valid-backend-refs.out.yaml @@ -0,0 +1,148 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + rules: + - backendRefs: + - name: service-1 + port: 8080 + weight: 80 + - name: service-not-exist + port: 8080 + weight: 20 + matches: + - path: + type: Exact + value: /exact + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: 'Failed to process route rule 0 backendRef 1: service default/service-not-exist + not found.' + reason: BackendNotFound + status: "False" + 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 + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + http: + - address: 0.0.0.0 + 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: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/0 + protocol: HTTP + weight: 80 + - name: httproute/default/httproute-1/rule/0/backend/1 + weight: 20 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: httproute-1 + namespace: default + name: httproute/default/httproute-1/rule/0/match/0/* + pathMatch: + distinct: false + exact: /exact + name: "" + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/internal/gatewayapi/testdata/httproute-with-some-invalid-backend-refs-no-service.out.yaml b/internal/gatewayapi/testdata/httproute-with-some-invalid-backend-refs-no-service.out.yaml index 94a372711f..70eafae143 100644 --- a/internal/gatewayapi/testdata/httproute-with-some-invalid-backend-refs-no-service.out.yaml +++ b/internal/gatewayapi/testdata/httproute-with-some-invalid-backend-refs-no-service.out.yaml @@ -119,8 +119,20 @@ xdsIR: mergeSlashes: true port: 10080 routes: - - directResponse: - statusCode: 500 + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - name: httproute/default/httproute-1/rule/0/backend/0 + weight: 1 + - name: httproute/default/httproute-1/rule/0/backend/1 + weight: 1 + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + name: httproute/default/httproute-1/rule/0/backend/2 + protocol: HTTP + weight: 1 hostname: '*' isHTTP2: false metadata: diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 2ee6c966fa..38c9787804 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -1502,6 +1502,12 @@ func (r *RouteDestination) Validate() error { } func (r *RouteDestination) NeedsClusterPerSetting() bool { + // When the destination has both valid and invalid backend weights, we use weighted clusters to distribute between + // valid backends and the `invalid-backend-cluster` for 500 responses according to their configured weights. + if r.ToBackendWeights().Invalid > 0 { + return true + } + return r.HasMixedEndpoints() || r.HasFiltersInSettings() || (len(r.Settings) > 1 && r.HasZoneAwareRouting()) diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-invalid-backend.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-invalid-backend.clusters.yaml index 06e9a8da52..d12f63b498 100644 --- a/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-invalid-backend.clusters.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-invalid-backend.clusters.yaml @@ -9,9 +9,26 @@ edsConfig: ads: {} resourceApiVersion: V3 - serviceName: first-route-dest + serviceName: first-route-dest/backend/0 ignoreHealthOnHostRemoval: true lbPolicy: LEAST_REQUEST - name: first-route-dest + name: first-route-dest/backend/0 + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_PREFERRED + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: first-route-dest/backend/1 + ignoreHealthOnHostRemoval: true + lbPolicy: LEAST_REQUEST + name: first-route-dest/backend/1 perConnectionBufferLimitBytes: 32768 type: EDS diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-invalid-backend.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-invalid-backend.endpoints.yaml index 7f8a028132..407ba8414d 100644 --- a/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-invalid-backend.endpoints.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/http-route-weighted-invalid-backend.endpoints.yaml @@ -1,4 +1,4 @@ -- clusterName: first-route-dest +- clusterName: first-route-dest/backend/0 endpoints: - lbEndpoints: - endpoint: @@ -10,6 +10,8 @@ loadBalancingWeight: 1 locality: region: first-route-dest/backend/0 +- clusterName: first-route-dest/backend/1 + endpoints: - loadBalancingWeight: 1 locality: region: first-route-dest/backend/1 diff --git a/release-notes/current.yaml b/release-notes/current.yaml index 3271a4f907..a9bcc60d38 100644 --- a/release-notes/current.yaml +++ b/release-notes/current.yaml @@ -10,7 +10,7 @@ security updates: | new features: | bug fixes: | - + Fix 500 errors caused by partially invalid BackendRefs; traffic is now correctly routed between valid backends and 500 responses according to their configured weights. # Enhancements that improve performance. performance improvements: | diff --git a/test/e2e/testdata/weighted-backend-mixed-valid-and-invalid.yaml b/test/e2e/testdata/weighted-backend-mixed-valid-and-invalid.yaml new file mode 100644 index 0000000000..d77e8487c4 --- /dev/null +++ b/test/e2e/testdata/weighted-backend-mixed-valid-and-invalid.yaml @@ -0,0 +1,20 @@ +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: weight-mixed-valid-and-invalid-http-route + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: PathPrefix + value: /mixed-valid-and-invalid + backendRefs: + - name: infra-backend-v1 + port: 8080 + weight: 80 + - name: infra-backend-not-existing + port: 8080 + weight: 20 diff --git a/test/e2e/tests/weighted_backend.go b/test/e2e/tests/weighted_backend.go index 2ffb239c52..995d11d946 100644 --- a/test/e2e/tests/weighted_backend.go +++ b/test/e2e/tests/weighted_backend.go @@ -19,7 +19,7 @@ import ( ) func init() { - ConformanceTests = append(ConformanceTests, WeightEqualTest, WeightBlueGreenTest, WeightCompleteRolloutTest) + ConformanceTests = append(ConformanceTests, WeightEqualTest, WeightBlueGreenTest, WeightCompleteRolloutTest, WeightMixedValidAndInvalidTest) } var WeightEqualTest = suite.ConformanceTest{ @@ -231,3 +231,49 @@ func ExtractPodNamePrefix(podName string) string { return podName } + +var WeightMixedValidAndInvalidTest = suite.ConformanceTest{ + ShortName: "WeightMixedValidAndInvalid", + Description: "Requests should be distributed to valid and invalid backends according to their weights", + Manifests: []string{"testdata/weighted-backend-mixed-valid-and-invalid.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + t.Run("all backends receive the complete rollout weight of traffic", func(t *testing.T) { + const sendRequests = 50 + + weightEqualRoute := types.NamespacedName{Name: "weight-mixed-valid-and-invalid-http-route", Namespace: ConformanceInfraNamespace} + gatewayRef := kubernetes.NewGatewayRef(SameNamespaceGateway) + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, gatewayRef, weightEqualRoute) + + // Make sure all test resources are ready + kubernetes.NamespacesMustBeReady(t, suite.Client, suite.TimeoutConfig, []string{ConformanceInfraNamespace}) + + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Path: "/mixed-valid-and-invalid", + }, + Namespace: ConformanceInfraNamespace, + } + req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") + + var ( + successCount = 0 + failCount = 0 + ) + for range sendRequests { + _, response, err := suite.RoundTripper.CaptureRoundTrip(req) + if err != nil { + t.Errorf("failed to get expected response: %v", err) + } + if response.StatusCode == 200 { + successCount++ + } else { + failCount++ + } + } + + if !AlmostEquals(successCount, 40, 3) { // The weight of valid backend is 80%, so the expected success count is 50*80%=40 + t.Errorf("The actual success count is not within the expected range, success %d", successCount) + } + }) + }, +}