Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion test/e2e/testdata/weighted-backend-all-equal.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ spec:
- matches:
- path:
type: PathPrefix
value: /
value: /same-weight
backendRefs:
- name: infra-backend-v1
port: 8080
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/testdata/weighted-backend-bluegreen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ spec:
- matches:
- path:
type: PathPrefix
value: /
value: /blue-green
backendRefs:
- name: infra-backend-v1
port: 8080
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ spec:
- matches:
- path:
type: PathPrefix
value: /
value: /complete-rollout
backendRefs:
- name: infra-backend-v1
port: 8080
Expand Down
259 changes: 75 additions & 184 deletions test/e2e/tests/weighted_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading