diff --git a/test/e2e/testdata/weighted-backend-all-equal.yaml b/test/e2e/testdata/weighted-backend-all-equal.yaml index b81edeb4ae..c13085d69c 100644 --- a/test/e2e/testdata/weighted-backend-all-equal.yaml +++ b/test/e2e/testdata/weighted-backend-all-equal.yaml @@ -10,7 +10,7 @@ spec: - matches: - path: type: PathPrefix - value: / + value: /same-weight backendRefs: - name: infra-backend-v1 port: 8080 diff --git a/test/e2e/testdata/weighted-backend-bluegreen.yaml b/test/e2e/testdata/weighted-backend-bluegreen.yaml index 13be4ab26a..a180f856a0 100644 --- a/test/e2e/testdata/weighted-backend-bluegreen.yaml +++ b/test/e2e/testdata/weighted-backend-bluegreen.yaml @@ -10,7 +10,7 @@ spec: - matches: - path: type: PathPrefix - value: / + value: /blue-green backendRefs: - name: infra-backend-v1 port: 8080 diff --git a/test/e2e/testdata/weight-backend-completing-rollout.yaml b/test/e2e/testdata/weighted-backend-completing-rollout.yaml similarity index 92% rename from test/e2e/testdata/weight-backend-completing-rollout.yaml rename to test/e2e/testdata/weighted-backend-completing-rollout.yaml index 2a36b55712..560c91cbc1 100644 --- a/test/e2e/testdata/weight-backend-completing-rollout.yaml +++ b/test/e2e/testdata/weighted-backend-completing-rollout.yaml @@ -10,7 +10,7 @@ spec: - matches: - path: type: PathPrefix - value: / + value: /complete-rollout backendRefs: - name: infra-backend-v1 port: 8080 diff --git a/test/e2e/tests/weighted_backend.go b/test/e2e/tests/weighted_backend.go index 2ffb239c52..bea61448fc 100644 --- a/test/e2e/tests/weighted_backend.go +++ b/test/e2e/tests/weighted_backend.go @@ -18,215 +18,106 @@ import ( "sigs.k8s.io/gateway-api/conformance/utils/suite" ) +const sendRequests = 50 + func init() { - ConformanceTests = append(ConformanceTests, WeightEqualTest, WeightBlueGreenTest, WeightCompleteRolloutTest) + ConformanceTests = append(ConformanceTests, WeightedBackendTest) } -var WeightEqualTest = suite.ConformanceTest{ - ShortName: "WeightEqualBackend", - Description: "Resource with Weight Backend enabled, and use the all backend weight is equal", - Manifests: []string{"testdata/weighted-backend-all-equal.yaml"}, +var WeightedBackendTest = suite.ConformanceTest{ + ShortName: "WeightedRoute", + Description: "Resource with Weight Backend enabled, and worked as expected.", + Manifests: []string{ + "testdata/weighted-backend-all-equal.yaml", + "testdata/weighted-backend-bluegreen.yaml", + "testdata/weighted-backend-completing-rollout.yaml", + }, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { - t.Run("all backends receive the same weight of traffic", func(t *testing.T) { - const sendRequests = 50 - - ns := "gateway-conformance-infra" - weightEqualRoute := types.NamespacedName{Name: "weight-equal-http-route", Namespace: ns} - gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} - gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, - suite.ControllerName, - kubernetes.NewGatewayRef(gwNN), weightEqualRoute) - - expectedResponse := http.ExpectedResponse{ - Request: http.Request{ - Path: "/", - }, - Response: http.Response{ - StatusCode: 200, - }, - Namespace: ns, - } - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - - // Since we only route to pods with "infra-backend-v 1" and "infra-backend-v 2" prefixes - // So here we use fixed weight values + t.Run("SameWeight", func(t *testing.T) { + // The received request is approximately 1:1 expected := map[string]int{ "infra-backend-v1": sendRequests * .5, "infra-backend-v2": sendRequests * .5, } - weightMap := make(map[string]int) - for i := 0; i < sendRequests; i++ { - cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) - if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } - - podName := cReq.Pod - if len(podName) == 0 { - // it shouldn't be missing here - t.Errorf("failed to get pod header in response: %v", err) - } else { - // all we need is the pod Name prefix - podNamePrefix := ExtractPodNamePrefix(podName) - weightMap[podNamePrefix]++ - } - } - - // We iterate over the actual traffic Map with podNamePrefix as the key to get the actual traffic. - // Given an offset of 3, we expect the expected traffic to be within the actual traffic [actual-3,actual+3] interval. - for prefix, actual := range weightMap { - expect := expected[prefix] - if !AlmostEquals(actual, expect, 3) { - t.Errorf("The actual traffic weights are not consistent with the expected routing weights") - } - } + runWeightedBackendTest(t, suite, "weight-equal-http-route", "/same-weight", "infra-backend", expected) }) - }, -} - -var WeightBlueGreenTest = suite.ConformanceTest{ - ShortName: "WeightBlueGreenBackend", - Description: "Resource with Weight Backend enabled, and use the blue-green policy weight setting", - Manifests: []string{"testdata/weighted-backend-bluegreen.yaml"}, - Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { - t.Run("all backends receive the blue green weight of traffic", func(t *testing.T) { - const sendRequests = 50 - - ns := "gateway-conformance-infra" - weightEqualRoute := types.NamespacedName{Name: "weight-bluegreen-http-route", Namespace: ns} - gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} - gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, - suite.ControllerName, - kubernetes.NewGatewayRef(gwNN), weightEqualRoute) - - expectedResponse := http.ExpectedResponse{ - Request: http.Request{ - Path: "/", - }, - Response: http.Response{ - StatusCode: 200, - }, - Namespace: ns, - } - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - - // Since we only route to pods with "infra-backend-v 1" and "infra-backend-v 2" prefixes - // So here we use fixed weight values + t.Run("BlueGreen", func(t *testing.T) { + // The received request is approximately 9:1 expected := map[string]int{ "infra-backend-v1": sendRequests * .9, "infra-backend-v2": sendRequests * .1, } - weightMap := make(map[string]int) - for i := 0; i < sendRequests; i++ { - cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) - if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } - - podName := cReq.Pod - if len(podName) == 0 { - // it shouldn't be missing here - t.Errorf("failed to get pod header in response: %v", err) - } else { - // all we need is the pod Name prefix - podNamePrefix := ExtractPodNamePrefix(podName) - weightMap[podNamePrefix]++ - } - } - - // We iterate over the actual traffic Map with podNamePrefix as the key to get the actual traffic. - // Given an offset of 3, we expect the expected traffic to be within the actual traffic [actual-3,actual+3] interval. - for prefix, actual := range weightMap { - expect := expected[prefix] - if !AlmostEquals(actual, expect, 3) { - t.Errorf("The actual traffic weights are not consistent with the expected routing weights") - } - } + runWeightedBackendTest(t, suite, "weight-bluegreen-http-route", "/blue-green", "infra-backend", expected) }) - }, -} - -var WeightCompleteRolloutTest = suite.ConformanceTest{ - ShortName: "WeightCompleteRollout", - Description: "Resource with Weight Backend enabled, and use the completing rollout policy weight setting", - Manifests: []string{"testdata/weight-backend-completing-rollout.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 - - ns := "gateway-conformance-infra" - weightEqualRoute := types.NamespacedName{Name: "weight-complete-rollout-http-route", Namespace: ns} - gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} - gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, - suite.ControllerName, - kubernetes.NewGatewayRef(gwNN), weightEqualRoute) - - expectedResponse := http.ExpectedResponse{ - Request: http.Request{ - Path: "/", - }, - Response: http.Response{ - StatusCode: 200, - }, - Namespace: ns, - } - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - - // Since we only route to pods with "infra-backend-v 1" and "infra-backend-v 2" prefixes - // So here we use fixed weight values + t.Run("CompleteRollout", func(t *testing.T) { + // All the requests should be proxied to v1 expected := map[string]int{ "infra-backend-v1": sendRequests * 1, "infra-backend-v2": sendRequests * 0, } - weightMap := make(map[string]int) - for i := 0; i < sendRequests; i++ { - cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) - if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } - - podName := cReq.Pod - if len(podName) == 0 { - // it shouldn't be missing here - t.Errorf("failed to get pod header in response: %v", err) - } else { - // all we need is the pod Name prefix - podNamePrefix := ExtractPodNamePrefix(podName) - weightMap[podNamePrefix]++ - } - } - - // We iterate over the actual traffic Map with podNamePrefix as the key to get the actual traffic. - // Given an offset of 3, we expect the expected traffic to be within the actual traffic [actual-3,actual+3] interval. - for prefix, actual := range weightMap { - expect := expected[prefix] - if !AlmostEquals(actual, expect, 3) { - t.Errorf("The actual traffic weights are not consistent with the expected routing weights") - } - } + runWeightedBackendTest(t, suite, "weight-complete-rollout-http-route", "/complete-rollout", "infra-backend", expected) }) }, } -// ExtractPodNamePrefix Extract the Pod Name prefix -func ExtractPodNamePrefix(podName string) string { - pattern := regexp.MustCompile(`infra-backend-(.+?)-`) +func runWeightedBackendTest(t *testing.T, suite *suite.ConformanceTestSuite, routeName, path, backendName string, expectedOutput map[string]int) { + weightEqualRoute := types.NamespacedName{Name: routeName, Namespace: ConformanceInfraNamespace} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, + suite.ControllerName, + kubernetes.NewGatewayRef(SameNamespaceGateway), weightEqualRoute) + + expectedResponse := http.ExpectedResponse{ + Request: http.Request{ + Path: path, + }, + Response: http.Response{ + StatusCode: 200, + }, + Namespace: ConformanceInfraNamespace, + } + // Make sure the backend is ready + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectedResponse) + + req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") + + weightMap := make(map[string]int) + for range sendRequests { + cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) + if err != nil { + t.Errorf("failed to get expected response: %v", err) + } + + if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { + t.Errorf("failed to compare request and response: %v", err) + } + + podName := cReq.Pod + if len(podName) == 0 { + // it shouldn't be missing here + t.Errorf("failed to get pod header in response: %v", err) + } else { + // all we need is the pod Name prefix + podNamePrefix := extractPodNamePrefix(podName, backendName) + weightMap[podNamePrefix]++ + } + } + + // We iterate over the actual traffic Map with podNamePrefix as the key to get the actual traffic. + // Given an offset of 3, we expect the expected traffic to be within the actual traffic [actual-3,actual+3] interval. + for prefix, actual := range weightMap { + expect := expectedOutput[prefix] + if !AlmostEquals(actual, expect, 3) { + t.Errorf("The actual traffic weights are not consistent with the expected routing weights, actual %s %d, expected %s %d", prefix, actual, prefix, expect) + } + } +} + +func extractPodNamePrefix(podName, prefix string) string { + pattern := regexp.MustCompile(prefix + `-(.+?)-`) match := pattern.FindStringSubmatch(podName) if len(match) > 1 { version := match[1] - return fmt.Sprintf("infra-backend-%s", version) + return fmt.Sprintf("%s-%s", prefix, version) } return podName diff --git a/test/e2e/tests/zone_aware_routing.go b/test/e2e/tests/zone_aware_routing.go index 77ee63ed32..5ac6edc0fa 100644 --- a/test/e2e/tests/zone_aware_routing.go +++ b/test/e2e/tests/zone_aware_routing.go @@ -8,14 +8,8 @@ package tests import ( - "fmt" - "regexp" "testing" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/gateway-api/conformance/utils/http" - "sigs.k8s.io/gateway-api/conformance/utils/kubernetes" "sigs.k8s.io/gateway-api/conformance/utils/suite" ) @@ -29,76 +23,13 @@ var ZoneAwareRoutingTest = suite.ConformanceTest{ Manifests: []string{"testdata/zone-aware-routing.yaml"}, Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { t.Run("only local zone should get requests", func(t *testing.T) { - const sendRequests = 50 - - ns := "gateway-conformance-infra" - zoneAwareRoute := types.NamespacedName{Name: "zone-aware-http-route", Namespace: ns} - gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} - gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, - suite.ControllerName, - kubernetes.NewGatewayRef(gwNN), zoneAwareRoute) - - podReady := corev1.PodCondition{Type: corev1.PodReady, Status: corev1.ConditionTrue} - WaitForPods(t, suite.Client, ns, map[string]string{"app": "zone-aware-backend"}, corev1.PodRunning, podReady) - - expectedResponse := http.ExpectedResponse{ - Request: http.Request{ - Path: "/", - }, - Response: http.Response{ - StatusCode: 200, - }, - Namespace: ns, - } - req := http.MakeRequest(t, &expectedResponse, gwAddr, "HTTP", "http") - // Pods from the backend-local deployment have affinity // for the Envoy Proxy pods so should receive all requests. expected := map[string]int{ "zone-aware-backend-local": sendRequests, "zone-aware-backend-nonlocal": 0, } - reqMap := make(map[string]int) - for i := 0; i < sendRequests; i++ { - cReq, cResp, err := suite.RoundTripper.CaptureRoundTrip(req) - if err != nil { - t.Errorf("failed to get expected response: %v", err) - } - - if err := http.CompareRequest(t, &req, cReq, cResp, expectedResponse); err != nil { - t.Errorf("failed to compare request and response: %v", err) - } - - podName := cReq.Pod - if len(podName) == 0 { - // it shouldn't be missing here - t.Errorf("failed to get pod header in response: %v", err) - } else { - // all we need is the pod Name prefix - podNamePrefix := ZoneAwareRoutingExtractPodNamePrefix(podName) - reqMap[podNamePrefix]++ - } - } - - // We iterate over the actual traffic Map with podNamePrefix as the key to get the actual traffic. - for prefix, actual := range reqMap { - expect := expected[prefix] - if actual != expect { - t.Errorf("The actual traffic distribution between zones is not consistent with the expected: %v", reqMap) - } - } + runWeightedBackendTest(t, suite, "zone-aware-http-route", "/", "zone-aware-backend", expected) }) }, } - -// ZoneAwareRoutingExtractPodNamePrefix extracts the Pod Name prefix -func ZoneAwareRoutingExtractPodNamePrefix(podName string) string { - pattern := regexp.MustCompile(`zone-aware-backend-(.+?)-`) - match := pattern.FindStringSubmatch(podName) - if len(match) > 1 { - version := match[1] - return fmt.Sprintf("zone-aware-backend-%s", version) - } - - return podName -}