Skip to content

Commit

Permalink
e2e test
Browse files Browse the repository at this point in the history
Signed-off-by: huabing zhao <[email protected]>
  • Loading branch information
zhaohuabing committed Dec 1, 2023
1 parent 9734e3a commit c363f43
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 11 deletions.
4 changes: 2 additions & 2 deletions api/v1alpha1/ratelimit_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ package v1alpha1
// +union
type RateLimitSpec struct {
// Type decides the scope for the RateLimits.
// Valid RateLimitType values are "Global".
// Valid RateLimitType values are "Global" or "Local".
//
// +unionDiscriminator
Type RateLimitType `json:"type"`
Expand All @@ -25,7 +25,7 @@ type RateLimitSpec struct {
}

// RateLimitType specifies the types of RateLimiting.
// +kubebuilder:validation:Enum=Global
// +kubebuilder:validation:Enum=Global;Local
type RateLimitType string

const (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,9 +370,10 @@ spec:
type: object
type:
description: Type decides the scope for the RateLimits. Valid
RateLimitType values are "Global".
RateLimitType values are "Global" or "Local".
enum:
- Global
- Local
type: string
required:
- type
Expand Down
2 changes: 1 addition & 1 deletion site/content/en/latest/api/extension_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -1527,7 +1527,7 @@ _Appears in:_

| Field | Description |
| --- | --- |
| `type` _[RateLimitType](#ratelimittype)_ | Type decides the scope for the RateLimits. Valid RateLimitType values are "Global". |
| `type` _[RateLimitType](#ratelimittype)_ | Type decides the scope for the RateLimits. Valid RateLimitType values are "Global" or "Local". |
| `global` _[GlobalRateLimit](#globalratelimit)_ | Global defines global rate limit configuration. |
| `local` _[LocalRateLimit](#localratelimit)_ | Local defines local rate limit configuration. |

Expand Down
92 changes: 92 additions & 0 deletions test/e2e/testdata/local-ratelimit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: ratelimit-specific-user
namespace: gateway-conformance-infra
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: http-ratelimit-specific-user
namespace: gateway-conformance-infra
rateLimit:
type: Local
local:
rules:
- clientSelectors:
- headers:
- name: x-user-id
value: john
limit:
requests: 3
unit: Hour
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
name: ratelimit-all-traffic
namespace: gateway-conformance-infra
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: http-ratelimit-all-traffic
namespace: gateway-conformance-infra
rateLimit:
type: Local
local:
rules:
- limit:
requests: 3
unit: Hour
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-ratelimit-specific-user
namespace: gateway-conformance-infra
spec:
parentRefs:
- name: same-namespace
rules:
- backendRefs:
- name: infra-backend-v1
port: 8080
matches:
- path:
type: Exact
value: /ratelimit-specific-user
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-ratelimit-all-traffic
namespace: gateway-conformance-infra
spec:
parentRefs:
- name: same-namespace
rules:
- backendRefs:
- name: infra-backend-v1
port: 8080
matches:
- path:
type: Exact
value: /ratelimit-all-traffic
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-no-ratelimit
namespace: gateway-conformance-infra
spec:
parentRefs:
- name: same-namespace
rules:
- backendRefs:
- name: infra-backend-v1
port: 8080
matches:
- path:
type: Exact
value: /no-ratelimit
191 changes: 191 additions & 0 deletions test/e2e/tests/local-ratelimit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// 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 (
"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, LocalRateLimitSpecificUserTest)
ConformanceTests = append(ConformanceTests, LocalRateLimitAllTrafficTest)
ConformanceTests = append(ConformanceTests, LocalRateLimitNoLimitRouteTest)
}

var LocalRateLimitSpecificUserTest = suite.ConformanceTest{
ShortName: "LocalRateLimitSpecificUser",
Description: "Limit a specific user",
Manifests: []string{"testdata/local-ratelimit.yaml"},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
t.Run("limit a specific user", func(t *testing.T) {
ns := "gateway-conformance-infra"
routeNN := types.NamespacedName{Name: "http-ratelimit-specific-user", Namespace: ns}
gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "ratelimit-specific-user", Namespace: ns})

expectOkResp := http.ExpectedResponse{
Request: http.Request{
Path: "/ratelimit-specific-user",
Headers: map[string]string{
"x-user-id": "john",
},
},
Response: http.Response{
StatusCode: 200,
},
Namespace: ns,
}

expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")

expectLimitResp := http.ExpectedResponse{
Request: http.Request{
Path: "/ratelimit-specific-user",
Headers: map[string]string{
"x-user-id": "john",
},
},
Response: http.Response{
StatusCode: 429,
},
Namespace: ns,
}
expectLimitReq := http.MakeRequest(t, &expectLimitResp, gwAddr, "HTTP", "http")

// should just send exactly 4 requests, and expect 429

// keep sending requests till get 200 first, that will cost one 200
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp)

// fire the rest request
if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
t.Errorf("fail to get expected response at first three request: %v", err)
}

// this request should be limited because the user is john and the limit is 3
if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, expectLimitReq, expectLimitResp); err != nil {
t.Errorf("fail to get expected response at last fourth request: %v", err)
}

// test another user
expectOkResp = http.ExpectedResponse{
Request: http.Request{
Path: "/ratelimit-specific-user",
Headers: map[string]string{
"x-user-id": "mike",
},
},
Response: http.Response{
StatusCode: 200,
},
Namespace: ns,
}
expectOkReq = http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")
// the requests should not be limited because the user is mike
if err := GotExactExpectedResponse(t, 4, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
t.Errorf("fail to get expected response at first three request: %v", err)
}
})
},
}

var LocalRateLimitAllTrafficTest = suite.ConformanceTest{
ShortName: "LocalRateLimitAllTraffic",
Description: "Limit all traffic on a route",
Manifests: []string{"testdata/local-ratelimit.yaml"},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
t.Run("limit all traffic on a route", func(t *testing.T) {
ns := "gateway-conformance-infra"
routeNN := types.NamespacedName{Name: "http-ratelimit-all-traffic", Namespace: ns}
gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)
SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: "ratelimit-all-traffic", Namespace: ns})

expectOkResp := http.ExpectedResponse{
Request: http.Request{
Path: "/ratelimit-all-traffic",
},
Response: http.Response{
StatusCode: 200,
},
Namespace: ns,
}

expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")

expectLimitResp := http.ExpectedResponse{
Request: http.Request{
Path: "/ratelimit-all-traffic",
},
Response: http.Response{
StatusCode: 429,
},
Namespace: ns,
}
expectLimitReq := http.MakeRequest(t, &expectLimitResp, gwAddr, "HTTP", "http")

// should just send exactly 4 requests, and expect 429

// keep sending requests till get 200 first, that will cost one 200
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp)

// fire the rest request
if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
t.Errorf("fail to get expected response at first three request: %v", err)
}

// this request should be limited because the user is john and the limit is 3
if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, expectLimitReq, expectLimitResp); err != nil {
t.Errorf("fail to get expected response at last fourth request: %v", err)
}
})
},
}

var LocalRateLimitNoLimitRouteTest = suite.ConformanceTest{
ShortName: "LocalRateLimitNoLimitRoute",
Description: "No rate limit on this route",
Manifests: []string{"testdata/local-ratelimit.yaml"},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
t.Run("no rate limit on this route", func(t *testing.T) {
ns := "gateway-conformance-infra"
routeNN := types.NamespacedName{Name: "SecurityPolicyMustBeAccepted(t, suite.Client, types.NamespacedName{Name: \"ratelimit-specific-user\", Namespace: ns})", Namespace: ns}
gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns}
gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN)

expectOkResp := http.ExpectedResponse{
Request: http.Request{
Path: "/no-ratelimit",
},
Response: http.Response{
StatusCode: 200,
},
Namespace: ns,
}

expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http")

// should just send exactly 4 requests, and expect 429

// keep sending requests till get 200 first, that will cost one 200
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp)

// the requests should not be limited because there is no rate limit on this route
if err := GotExactExpectedResponse(t, 3, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
t.Errorf("fail to get expected response at last fourth request: %v", err)
}
})
},
}
14 changes: 7 additions & 7 deletions test/e2e/tests/ratelimit.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ var RateLimitTest = suite.ConformanceTest{
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp)

// fire the rest request
if err := GotExactNExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil {
t.Errorf("fail to get expected response at first three request: %v", err)
}
if err := GotExactNExpectedResponse(t, 1, suite.RoundTripper, expectLimitReq, expectLimitResp); err != nil {
if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, expectLimitReq, expectLimitResp); err != nil {
t.Errorf("fail to get expected response at last fourth request: %v", err)
}
})
Expand Down Expand Up @@ -165,15 +165,15 @@ var RateLimitBasedJwtClaimsTest = suite.ConformanceTest{
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, JwtOkResp)

// fire the rest request
if err := GotExactNExpectedResponse(t, 2, suite.RoundTripper, JwtReq, JwtOkResp); err != nil {
if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, JwtReq, JwtOkResp); err != nil {
t.Errorf("failed to get expected response at third request: %v", err)
}
if err := GotExactNExpectedResponse(t, 1, suite.RoundTripper, JwtReq, expectLimitResp); err != nil {
if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, JwtReq, expectLimitResp); err != nil {
t.Errorf("failed to get expected response at the fourth request: %v", err)
}

// Carrying different jwt claims will not be limited
if err := GotExactNExpectedResponse(t, 4, suite.RoundTripper, difJwtReq, expectOkResp); err != nil {
if err := GotExactExpectedResponse(t, 4, suite.RoundTripper, difJwtReq, expectOkResp); err != nil {
t.Errorf("failed to get expected response for the request with a different jwt: %v", err)
}

Expand All @@ -188,15 +188,15 @@ var RateLimitBasedJwtClaimsTest = suite.ConformanceTest{
Namespace: ns,
}
noTokenReq := http.MakeRequest(t, &noTokenResp, gwAddr, "HTTP", "http")
if err := GotExactNExpectedResponse(t, 1, suite.RoundTripper, noTokenReq, noTokenResp); err != nil {
if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, noTokenReq, noTokenResp); err != nil {
t.Errorf("failed to get expected response: %v", err)
}

})
},
}

func GotExactNExpectedResponse(t *testing.T, n int, r roundtripper.RoundTripper, req roundtripper.Request, resp http.ExpectedResponse) error {
func GotExactExpectedResponse(t *testing.T, n int, r roundtripper.RoundTripper, req roundtripper.Request, resp http.ExpectedResponse) error {
for i := 0; i < n; i++ {
cReq, cRes, err := r.CaptureRoundTrip(req)
if err != nil {
Expand Down

0 comments on commit c363f43

Please sign in to comment.