Skip to content

Commit

Permalink
e2e: add weighted backend (#2863)
Browse files Browse the repository at this point in the history
* e2e: add backend weighted

Signed-off-by: ShyunnY <[email protected]>

* fix: Fix weight calculation issue and use AlmostEqual func

Signed-off-by: ShyunnY <[email protected]>

* fix: add additional comments

Signed-off-by: ShyunnY <[email protected]>

---------

Signed-off-by: ShyunnY <[email protected]>
Co-authored-by: Xunzhuo <[email protected]>
  • Loading branch information
ShyunnY and Xunzhuo authored Mar 12, 2024
1 parent 5fba1a2 commit 3ff6f13
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 0 deletions.
20 changes: 20 additions & 0 deletions test/e2e/testdata/weight-backend-completing-rollout.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: weight-complete-rollout-http-route
namespace: gateway-conformance-infra
spec:
parentRefs:
- name: same-namespace
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: infra-backend-v1
port: 8080
weight: 10
- name: infra-backend-v2
port: 8080
weight: 0
20 changes: 20 additions & 0 deletions test/e2e/testdata/weighted-backend-all-equal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: weight-equal-http-route
namespace: gateway-conformance-infra
spec:
parentRefs:
- name: same-namespace
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: infra-backend-v1
port: 8080
weight: 1
- name: infra-backend-v2
port: 8080
weight: 1
20 changes: 20 additions & 0 deletions test/e2e/testdata/weighted-backend-bluegreen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: weight-bluegreen-http-route
namespace: gateway-conformance-infra
spec:
parentRefs:
- name: same-namespace
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: infra-backend-v1
port: 8080
weight: 9
- name: infra-backend-v2
port: 8080
weight: 1
11 changes: 11 additions & 0 deletions test/e2e/tests/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,14 @@ func BackendTrafficPolicyMustBeAccepted(t *testing.T, client client.Client, poli

require.NoErrorf(t, waitErr, "error waiting for BackendTrafficPolicy to be accepted")
}

// AlmostEquals We use a solution similar to istio:
// Given an offset, calculate whether the actual value is within the offset of the expected value
func AlmostEquals(actual, expect, offset int) bool {
upper := actual + offset
lower := actual - offset
if expect < lower || expect > upper {
return false
}
return true
}
234 changes: 234 additions & 0 deletions test/e2e/tests/weighted_backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// Copyright Envoy Gateway Authors
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

//go:build e2e
// +build e2e

package tests

import (
"fmt"
"regexp"
"testing"

"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"
)

func init() {
ConformanceTests = append(ConformanceTests, WeightEqualTest, WeightBlueGreenTest, WeightCompleteRolloutTest)
}

const sendRequest = 50

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"},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
t.Run("all backends receive the same weight of traffic", func(t *testing.T) {
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
expected := map[string]int{
"infra-backend-v1": sendRequest * .5,
"infra-backend-v2": sendRequest * .5,
}
weightMap := make(map[string]int)
for i := 0; i < sendRequest; 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")
}
}
})
},
}

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) {
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
expected := map[string]int{
"infra-backend-v1": sendRequest * .9,
"infra-backend-v2": sendRequest * .1,
}
weightMap := make(map[string]int)
for i := 0; i < sendRequest; 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")
}
}
})
},
}

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) {
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
expected := map[string]int{
"infra-backend-v1": sendRequest * 1,
"infra-backend-v2": sendRequest * 0,
}
weightMap := make(map[string]int)
for i := 0; i < sendRequest; 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")
}
}
})
},
}

// ExtractPodNamePrefix Extract the Pod Name prefix
func ExtractPodNamePrefix(podName string) string {

pattern := regexp.MustCompile(`infra-backend-(.+?)-`)
match := pattern.FindStringSubmatch(podName)
if len(match) > 1 {
version := match[1]
return fmt.Sprintf("infra-backend-%s", version)
}

return podName
}

0 comments on commit 3ff6f13

Please sign in to comment.