Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

e2e: add e2e for canary weight #257

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
86 changes: 56 additions & 30 deletions test/ingress/conformance/tests/httproute-canary-weight.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,48 +30,74 @@ var HTTPRouteCanaryWeight = suite.ConformanceTest{
Description: "The Ingress in the higress-conformance-infra namespace uses the canary weight traffic split.",
Manifests: []string{"tests/httproute-canary-weight.yaml"},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
// test if the weight is 0
tt := []struct {
assertion http.Assertion
succRate float64
}{
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/weight-0",
Host: "canary.higress.io",
succRate: 1.0,
assertion: http.Assertion{
// test if the weight is 0
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/weight-0",
Host: "canary.higress.io",
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
},
// test if the weight is 100
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v2",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/weight-100",
Host: "canary.higress.io",
}, { // test if the weight is 100
succRate: 1.0,
assertion: http.Assertion{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v2",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/weight-100",
Host: "canary.higress.io",
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
}, {
succRate: 0.5,
assertion: http.Assertion{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v2",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/weight-50",
Host: "canary.higress.io",
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
},
},
},
}

t.Run("Canary HTTPRoute Traffic Split", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
for _, testcase := range tt {
http.MakeRequestAndCountExpectedResponse(t, suite.RoundTripper, suite.GatewayAddress, testcase.assertion, testcase.succRate)
}
})
},
Expand Down
42 changes: 42 additions & 0 deletions test/ingress/conformance/tests/httproute-canary-weight.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,45 @@ spec:
name: infra-backend-v1
port:
number: 8080
---
# Test if the weight is 50
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "50"
name: ingress-echo-canary-weight-50
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: canary.higress.io
http:
paths:
- path: /weight-50
pathType: Exact
backend:
service:
name: infra-backend-v2
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-echo-weight-50
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: canary.higress.io
http:
paths:
- path: /weight-50
pathType: Exact
backend:
service:
name: infra-backend-v1
port:
number: 8080
75 changes: 75 additions & 0 deletions test/ingress/conformance/utils/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package http

import (
"fmt"
"math"
"net/url"
"strings"
"testing"
Expand Down Expand Up @@ -126,6 +127,10 @@ type Response struct {
// maxTimeToConsistency, the test will fail.
const requiredConsecutiveSuccesses = 3

const totalRequest = 100

const rateDeviation = 0.1

// MakeRequestAndExpectEventuallyConsistentResponse makes a request with the given parameters,
// understanding that the request may fail for some amount of time.
//
Expand Down Expand Up @@ -423,3 +428,73 @@ func setRedirectRequestDefaults(req *roundtripper.Request, cRes *roundtripper.Ca
expected.Request.RedirectRequest.Path = req.URL.Path
}
}

func getRequest(gwAddr string, expected Assertion) roundtripper.Request {
path, query, _ := strings.Cut(expected.Request.ActualRequest.Path, "?")

req := roundtripper.Request{
Method: expected.Request.ActualRequest.Method,
Host: expected.Request.ActualRequest.Host,
URL: url.URL{Scheme: "http", Host: gwAddr, Path: path, RawQuery: query},
Protocol: "HTTP",
Headers: map[string][]string{},
UnfollowRedirect: expected.Request.ActualRequest.UnfollowRedirect,
}

if expected.Request.ActualRequest.Headers != nil {
for name, value := range expected.Request.ActualRequest.Headers {
req.Headers[name] = []string{value}
}
}

backendSetHeaders := []string{}
for name, val := range expected.Response.AdditionalResponseHeaders {
backendSetHeaders = append(backendSetHeaders, name+":"+val)
}
req.Headers["X-Echo-Set-Header"] = []string{strings.Join(backendSetHeaders, ",")}

return req
}

// MakeRequestAndCountExpectedResponse make 'totReq' requests and determine whether to test results according to the succRate
func MakeRequestAndCountExpectedResponse(t *testing.T, r roundtripper.RoundTripper, gwAddr string, expected Assertion, succRate float64) {
t.Helper()

if expected.Request.ActualRequest.Method == "" {
expected.Request.ActualRequest.Method = "GET"
}

if expected.Response.ExpectedResponse.StatusCode == 0 {
expected.Response.ExpectedResponse.StatusCode = 200
}

t.Logf("Making %s request to http://%s%s", expected.Request.ActualRequest.Method, gwAddr, expected.Request.ActualRequest.Path)

req := getRequest(gwAddr, expected)

succ, fail := 0, 0
for i := 0; i < totalRequest; i++ {
cReq, cRes, err := r.CaptureRoundTrip(req)
if err != nil {
fail += 1
t.Logf("Request failed, not ready yet: %v (failed count: %v)", err.Error(), fail)
continue
}

if err := CompareRequest(&req, cReq, cRes, expected); err != nil {
fail += 1
t.Logf("Response expectation failed for request: %v not ready yet: %v (failed count: %v)", req, err, fail)
continue
}

succ += 1
}

rate := float64(succ) / totalRequest
minSuccRate, maxSuccRate := math.Max(succRate-rateDeviation, 0), math.Min(succRate+rateDeviation, 1.0)
if rate < minSuccRate || maxSuccRate < rate {
t.Errorf("Test failed, expect the minSuccRate is %v, the maxSuccRate is %v, but got %v", minSuccRate, maxSuccRate, rate)
return
}
t.Logf("Test passed")
}