mirror of
https://github.com/alibaba/higress.git
synced 2026-02-26 05:30:50 +08:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b7723bac5 | ||
|
|
f402f86374 | ||
|
|
7726d5d138 | ||
|
|
50e7bfddee | ||
|
|
9400f7bf07 | ||
|
|
40f4d7845d | ||
|
|
d84c9e67c3 | ||
|
|
71f5dcd123 | ||
|
|
245c807b85 | ||
|
|
c88ee327ba | ||
|
|
ca0d62c91a | ||
|
|
b000bc6ce9 | ||
|
|
1b8ec8d204 | ||
|
|
a771f59422 | ||
|
|
19570ea100 | ||
|
|
7ceec94a87 | ||
|
|
1092402516 | ||
|
|
5ae9151d37 | ||
|
|
6c6cb0a8f3 | ||
|
|
9c135e05e7 | ||
|
|
e15f77e029 | ||
|
|
146b0a5135 | ||
|
|
a5ef4cd482 | ||
|
|
c8d84a0ad5 | ||
|
|
4cd7975295 | ||
|
|
059f4c682e | ||
|
|
f262883611 | ||
|
|
4fb9efe3bf | ||
|
|
9593cb7340 |
11
.github/workflows/build-and-test.yml
vendored
11
.github/workflows/build-and-test.yml
vendored
@@ -43,7 +43,6 @@ jobs:
|
||||
with:
|
||||
go-version: 1.19
|
||||
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- name: "checkout ${{ github.ref }}"
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -58,13 +57,13 @@ jobs:
|
||||
name: higress
|
||||
path: out/
|
||||
|
||||
conformance-test:
|
||||
gateway-conformance-test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
e2e-test:
|
||||
ingress-conformance-test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
steps:
|
||||
@@ -73,11 +72,11 @@ jobs:
|
||||
with:
|
||||
go-version: 1.19
|
||||
- uses: actions/checkout@v3
|
||||
- name: "Run E2E Tests"
|
||||
run: make e2e-test
|
||||
- name: "Run Ingress Conformance Tests"
|
||||
run: GOPROXY="https://proxy.golang.org,direct" make ingress-conformance-test
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [conformance-test,e2e-test]
|
||||
needs: [ingress-conformance-test,gateway-conformance-test]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@ out
|
||||
.DS_Store
|
||||
coverage.xml
|
||||
.idea/
|
||||
.vscode/
|
||||
bazel-bin
|
||||
bazel-out
|
||||
bazel-testlogs
|
||||
|
||||
12
CODEOWNERS
12
CODEOWNERS
@@ -1,4 +1,10 @@
|
||||
# Top level
|
||||
* @johnlanni @SpecialYang @Lynskylate @gengleilei @NameHaibinZhang
|
||||
/api @johnlanni
|
||||
/envoy @gengleilei @johnlanni @Lynskylate
|
||||
/istio @SpecialYang @johnlanni
|
||||
/pkg @SpecialYang @johnlanni
|
||||
/plugins @johnlanni
|
||||
/registry @NameHaibinZhang @johnlanni
|
||||
/test @Xunzhuo
|
||||
/tools @johnlanni @Xunzhuo
|
||||
|
||||
|
||||
# TODO Add code reviewers for subdirectory.
|
||||
|
||||
@@ -6,12 +6,20 @@
|
||||
|
||||
## 话题
|
||||
|
||||
* [报告安全问题](#报告安全问题)
|
||||
* [报告一般问题](#报告一般问题)
|
||||
* [代码和文档贡献](#代码和文档贡献)
|
||||
* [测试用例贡献](#测试用例贡献)
|
||||
* [参与帮助任何事情](#参与帮助任何事情)
|
||||
* [代码风格](#代码风格)
|
||||
- [为 Higress 做贡献](#为-higress-做贡献)
|
||||
- [话题](#话题)
|
||||
- [报告安全问题](#报告安全问题)
|
||||
- [报告一般问题](#报告一般问题)
|
||||
- [代码和文档贡献](#代码和文档贡献)
|
||||
- [工作区准备](#工作区准备)
|
||||
- [分支定义](#分支定义)
|
||||
- [提交规则](#提交规则)
|
||||
- [提交消息](#提交消息)
|
||||
- [提交内容](#提交内容)
|
||||
- [PR说明](#pr说明)
|
||||
- [测试用例贡献](#测试用例贡献)
|
||||
- [参与帮助任何事情](#参与帮助任何事情)
|
||||
- [代码风格](#代码风格)
|
||||
|
||||
## 报告安全问题
|
||||
|
||||
@@ -96,15 +104,15 @@ upstream no-pushing (push)
|
||||
|
||||
### 分支定义
|
||||
|
||||
现在我们假设通过拉取请求的每个贡献都是针对Higress 中的 [开发分支](https://github.com/alibaba/higress/tree/develop) 。在贡献之前,请注意分支定义会很有帮助。
|
||||
现在我们假设通过拉取请求的每个贡献都是针对 Higress 中的 [主分支](https://github.com/alibaba/higress/tree/main) 。在贡献之前,请注意分支定义会很有帮助。
|
||||
|
||||
作为贡献者,请再次记住,通过拉取请求的每个贡献都是针对分支开发的。而在Higress项目中,还有其他几个分支,我们一般称它们为release分支(如0.6.0、0.6.1)、feature分支、hotfix分支和master分支。
|
||||
作为贡献者,请再次记住,通过拉取请求的每个贡献都是针对主分支的。而在Higress项目中,还有其他几个分支,我们一般称它们为release分支(如0.6.0、0.6.1)、feature分支、hotfix分支。
|
||||
|
||||
当正式发布一个版本时,会有一个发布分支并以版本号命名。
|
||||
|
||||
在发布之后,我们会将发布分支的提交合并到主分支中。
|
||||
|
||||
当我们发现某个版本有bug时,我们会决定在以后的版本中修复它,或者在特定的hotfix版本中修复它。当我们决定修复hotfix版本时,我们会根据对应的release分支checkout hotfix分支,进行代码修复和验证,合并到develop分支和master分支。
|
||||
当我们发现某个版本有bug时,我们会决定在以后的版本中修复它,或者在特定的hotfix版本中修复它。当我们决定修复hotfix版本时,我们会根据对应的release分支checkout hotfix分支,进行代码修复和验证,合并到主分支。
|
||||
|
||||
对于较大的功能,我们将拉出功能分支进行开发和验证。
|
||||
|
||||
|
||||
@@ -6,12 +6,20 @@ It is warmly welcomed if you have interest to hack on Higress. First, we encoura
|
||||
|
||||
## Topics
|
||||
|
||||
* [Reporting security issues](#reporting-security-issues)
|
||||
* [Reporting general issues](#reporting-general-issues)
|
||||
* [Code and doc contribution](#code-and-doc-contribution)
|
||||
* [Test case contribution](#test-case-contribution)
|
||||
* [Engage to help anything](#engage-to-help-anything)
|
||||
* [Code Style](#code-style)
|
||||
- [Contributing to Higress](#contributing-to-higress)
|
||||
- [Topics](#topics)
|
||||
- [Reporting security issues](#reporting-security-issues)
|
||||
- [Reporting general issues](#reporting-general-issues)
|
||||
- [Code and doc contribution](#code-and-doc-contribution)
|
||||
- [Workspace Preparation](#workspace-preparation)
|
||||
- [Branch Definition](#branch-definition)
|
||||
- [Commit Rules](#commit-rules)
|
||||
- [Commit Message](#commit-message)
|
||||
- [Commit Content](#commit-content)
|
||||
- [PR Description](#pr-description)
|
||||
- [Test case contribution](#test-case-contribution)
|
||||
- [Engage to help anything](#engage-to-help-anything)
|
||||
- [Code Style](#code-style)
|
||||
|
||||
## Reporting security issues
|
||||
|
||||
@@ -98,15 +106,15 @@ Adding this, we can easily synchronize local branches with upstream branches.
|
||||
|
||||
### Branch Definition
|
||||
|
||||
Right now we assume every contribution via pull request is for [branch develop](https://github.com/alibaba/higress/tree/develop) in Higress. Before contributing, be aware of branch definition would help a lot.
|
||||
Right now we assume every contribution via pull request is for [branch main](https://github.com/alibaba/higress/tree/main) in Higress. Before contributing, be aware of branch definition would help a lot.
|
||||
|
||||
As a contributor, keep in mind again that every contribution via pull request is for branch develop. While in project Higress, there are several other branches, we generally call them release branches(such as 0.6.0,0.6.1), feature branches, hotfix branches and master branch.
|
||||
As a contributor, keep in mind again that every contribution via pull request is for branch main. While in project Higress, there are several other branches, we generally call them release branches (such as 0.6.0,0.6.1), feature branches, hotfix branches.
|
||||
|
||||
When officially releasing a version, there will be a release branch and named with the version number.
|
||||
|
||||
After the release, we will merge the commit of the release branch into the master branch.
|
||||
After the release, we will merge the commit of the release branch into the main branch.
|
||||
|
||||
When we find that there is a bug in a certain version, we will decide to fix it in a later version or fix it in a specific hotfix version. When we decide to fix the hotfix version, we will checkout the hotfix branch based on the corresponding release branch, perform code repair and verification, and merge it into the develop branch and the master branch.
|
||||
When we find that there is a bug in a certain version, we will decide to fix it in a later version or fix it in a specific hotfix version. When we decide to fix the hotfix version, we will checkout the hotfix branch based on the corresponding release branch, perform code repair and verification, and merge it into the main branch.
|
||||
|
||||
For larger features, we will pull out the feature branch for development and verification.
|
||||
|
||||
|
||||
@@ -96,13 +96,13 @@ export PARENT_GIT_REVISION:=$(TAG)
|
||||
export ENVOY_TAR_PATH:=/home/package/envoy.tar.gz
|
||||
|
||||
build-istio: prebuild
|
||||
cd external/istio; GOOS_LOCAL=linux TARGET_OS=linux TARGET_ARCH=amd64 BUILD_WITH_CONTAINER=1 DOCKER_BUILD_VARIANTS=default DOCKER_TARGETS="docker.pilot" make docker
|
||||
cd external/istio; rm -rf out; GOOS_LOCAL=linux TARGET_OS=linux TARGET_ARCH=amd64 BUILD_WITH_CONTAINER=1 DOCKER_BUILD_VARIANTS=default DOCKER_TARGETS="docker.pilot" make docker
|
||||
|
||||
external/package/envoy.tar.gz:
|
||||
cd external/proxy; BUILD_WITH_CONTAINER=1 make test_release
|
||||
|
||||
build-gateway: prebuild external/package/envoy.tar.gz
|
||||
cd external/istio; GOOS_LOCAL=linux TARGET_OS=linux TARGET_ARCH=amd64 BUILD_WITH_CONTAINER=1 DOCKER_BUILD_VARIANTS=default DOCKER_TARGETS="docker.proxyv2" make docker
|
||||
cd external/istio; rm -rf out; GOOS_LOCAL=linux TARGET_OS=linux TARGET_ARCH=amd64 BUILD_WITH_CONTAINER=1 DOCKER_BUILD_VARIANTS=default DOCKER_TARGETS="docker.proxyv2" make docker
|
||||
|
||||
pre-install:
|
||||
cp api/kubernetes/customresourcedefinitions.gen.yaml helm/higress/crds
|
||||
@@ -176,25 +176,35 @@ clean: clean-higress clean-gateway clean-istio clean-env clean-tool
|
||||
include tools/tools.mk
|
||||
include tools/lint.mk
|
||||
|
||||
.PHONY: e2e-test
|
||||
e2e-test: $(tools/kind) delete-cluster create-cluster kube-load-image install-dev run-e2e-test delete-cluster
|
||||
# gateway-conformance-test runs gateway api conformance tests.
|
||||
.PHONY: gateway-conformance-test
|
||||
gateway-conformance-test:
|
||||
|
||||
# ingress-conformance-test runs ingress api conformance tests.
|
||||
.PHONY: ingress-conformance-test
|
||||
ingress-conformance-test: $(tools/kind) delete-cluster create-cluster docker-build kube-load-image install-dev run-ingress-e2e-test delete-cluster
|
||||
|
||||
# create-cluster creates a kube cluster with kind.
|
||||
.PHONY: create-cluster
|
||||
create-cluster: $(tools/kind)
|
||||
tools/hack/create-cluster.sh
|
||||
|
||||
# delete-cluster deletes a kube cluster.
|
||||
.PHONY: delete-cluster
|
||||
delete-cluster: $(tools/kind) ## Delete kind cluster.
|
||||
$(tools/kind) delete cluster --name higress
|
||||
|
||||
# kube-load-image loads a local built docker image into kube cluster.
|
||||
.PHONY: kube-load-image
|
||||
kube-load-image: docker-build $(tools/kind) ## Install the EG image to a kind cluster using the provided $IMAGE and $TAG.
|
||||
kube-load-image: $(tools/kind) ## Install the EG image to a kind cluster using the provided $IMAGE and $TAG.
|
||||
tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/higress $(TAG)
|
||||
|
||||
.PHONY: run-e2e-test
|
||||
run-e2e-test:
|
||||
# run-ingress-e2e-test starts to run ingress e2e tests.
|
||||
.PHONY: run-ingress-e2e-test
|
||||
run-ingress-e2e-test:
|
||||
@echo -e "\n\033[36mRunning higress conformance tests...\033[0m"
|
||||
@echo -e "\n\033[36mWaiting higress-controller to be ready...\033[0m\n"
|
||||
kubectl wait --timeout=5m -n higress-system deployment/higress-controller --for=condition=Available
|
||||
@echo -e "\n\033[36mWaiting higress-gateway to be ready...\033[0m\n"
|
||||
kubectl wait --timeout=5m -n higress-system deployment/higress-gateway --for=condition=Available
|
||||
go test -v -tags conformance ./test/ingress/e2e_test.go --ingress-class=higress --debug=true --use-unique-ports=true
|
||||
go test -v -tags conformance ./test/ingress/e2e_test.go --ingress-class=higress --debug=true
|
||||
|
||||
@@ -163,7 +163,7 @@ helm install higress -n higress-system oci://higress-registry.cn-hangzhou.cr.ali
|
||||
#### 第四步、 创建 Ingress 资源并测试
|
||||
|
||||
```bash
|
||||
kubectl apply -f https://github.com/alibaba/higress/releases/download/v0.5.2/quickstart.yaml
|
||||
kubectl apply -f https://github.com/alibaba/higress/releases/download/v0.6.0/quickstart.yaml
|
||||
```
|
||||
|
||||
测试 Ingress 生效:
|
||||
@@ -178,7 +178,7 @@ curl localhost/bar
|
||||
#### 卸载资源
|
||||
|
||||
```bash
|
||||
kubectl delete -f https://github.com/alibaba/higress/releases/download/v0.5.2/quickstart.yaml
|
||||
kubectl delete -f https://github.com/alibaba/higress/releases/download/v0.6.0/quickstart.yaml
|
||||
|
||||
helm uninstall higress -n higress-system
|
||||
|
||||
@@ -204,6 +204,7 @@ kind: Ingress
|
||||
metadata:
|
||||
name: simple-example
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: foo.bar.com
|
||||
http:
|
||||
|
||||
@@ -175,6 +175,7 @@ kind: Ingress
|
||||
metadata:
|
||||
name: simple-example
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: foo.bar.com
|
||||
http:
|
||||
|
||||
@@ -28,12 +28,21 @@ import (
|
||||
"istio.io/pkg/version"
|
||||
|
||||
"github.com/alibaba/higress/pkg/bootstrap"
|
||||
innerconstants "github.com/alibaba/higress/pkg/config/constants"
|
||||
)
|
||||
|
||||
var (
|
||||
serverArgs *bootstrap.ServerArgs
|
||||
loggingOptions = log.DefaultOptions()
|
||||
|
||||
serverProvider = func(args *bootstrap.ServerArgs) (bootstrap.ServerInterface, error) {
|
||||
return bootstrap.NewServer(serverArgs)
|
||||
}
|
||||
|
||||
waitForMonitorSignal = func(stop chan struct{}) {
|
||||
cmd.WaitSignal(stop)
|
||||
}
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "higress",
|
||||
}
|
||||
@@ -52,7 +61,7 @@ var (
|
||||
|
||||
stop := make(chan struct{})
|
||||
|
||||
server, err := bootstrap.NewServer(serverArgs)
|
||||
server, err := serverProvider(serverArgs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to create higress server: %v", err)
|
||||
}
|
||||
@@ -61,7 +70,7 @@ var (
|
||||
return fmt.Errorf("fail to start higress server: %v", err)
|
||||
}
|
||||
|
||||
cmd.WaitSignal(stop)
|
||||
waitForMonitorSignal(stop)
|
||||
|
||||
server.WaitUntilCompletion()
|
||||
return nil
|
||||
@@ -85,8 +94,8 @@ func init() {
|
||||
|
||||
serveCmd.PersistentFlags().StringVar(&serverArgs.GatewaySelectorKey, "gatewaySelectorKey", "higress", "gateway resource selector label key")
|
||||
serveCmd.PersistentFlags().StringVar(&serverArgs.GatewaySelectorValue, "gatewaySelectorValue", "higress-gateway", "gateway resource selector label value")
|
||||
serveCmd.PersistentFlags().BoolVar(&serverArgs.EnableStatus, "enableStatus", false, "enable the ingress status syncer which use to update the ip in ingress's status")
|
||||
serveCmd.PersistentFlags().StringVar(&serverArgs.IngressClass, "ingressClass", "", "if not empty, only watch the ingresses have the specified class, otherwise watch all ingresses")
|
||||
serveCmd.PersistentFlags().BoolVar(&serverArgs.EnableStatus, "enableStatus", true, "enable the ingress status syncer which use to update the ip in ingress's status")
|
||||
serveCmd.PersistentFlags().StringVar(&serverArgs.IngressClass, "ingressClass", innerconstants.DefaultIngressClass, "if not empty, only watch the ingresses have the specified class, otherwise watch all ingresses")
|
||||
serveCmd.PersistentFlags().StringVar(&serverArgs.WatchNamespace, "watchNamespace", "", "if not empty, only wath the ingresses in the specified namespace, otherwise watch in all namespacees")
|
||||
serveCmd.PersistentFlags().BoolVar(&serverArgs.Debug, "debug", serverArgs.Debug, "if true, enables more debug http api")
|
||||
serveCmd.PersistentFlags().StringVar(&serverArgs.HttpAddress, "httpAddress", serverArgs.HttpAddress, "the http address")
|
||||
|
||||
78
cmd/higress/main_test.go
Normal file
78
cmd/higress/main_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/alibaba/higress/pkg/bootstrap"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestServe(t *testing.T) {
|
||||
runEBackup := serveCmd.RunE
|
||||
argsBackup := os.Args
|
||||
serverProviderBackup := serverProvider
|
||||
executed := false
|
||||
|
||||
serverProvider = func(args *bootstrap.ServerArgs) (bootstrap.ServerInterface, error) {
|
||||
return &delayedServer{Args: args, Delay: time.Second * 5}, nil
|
||||
}
|
||||
|
||||
serveCmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
executed = true
|
||||
return runEBackup(cmd, args)
|
||||
}
|
||||
defer func() {
|
||||
serverProvider = serverProviderBackup
|
||||
os.Args = argsBackup
|
||||
serveCmd.RunE = runEBackup
|
||||
}()
|
||||
|
||||
a := assert.New(t)
|
||||
|
||||
delay := time.Second * 5
|
||||
|
||||
start := time.Now()
|
||||
os.Args = []string{"/app/higress", "serve"}
|
||||
waitForMonitorSignal = func(stop chan struct{}) {
|
||||
time.Sleep(delay)
|
||||
close(stop)
|
||||
}
|
||||
main()
|
||||
end := time.Now()
|
||||
|
||||
cost := end.Sub(start)
|
||||
a.GreaterOrEqual(cost, delay)
|
||||
|
||||
a.True(executed)
|
||||
}
|
||||
|
||||
type delayedServer struct {
|
||||
Args *bootstrap.ServerArgs
|
||||
Delay time.Duration
|
||||
stop <-chan struct{}
|
||||
}
|
||||
|
||||
func (d *delayedServer) Start(stop <-chan struct{}) error {
|
||||
d.stop = stop
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *delayedServer) WaitUntilCompletion() {
|
||||
<-d.stop
|
||||
}
|
||||
@@ -7,6 +7,7 @@ ARG BASE_VERSION=latest
|
||||
ARG HUB
|
||||
|
||||
# The following section is used as base image if BASE_DISTRIBUTION=debug
|
||||
# This base image is provided by istio, see: https://github.com/istio/istio/blob/master/docker/Dockerfile.base
|
||||
FROM ${HUB}/base:${BASE_VERSION}
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
101
envoy/1.20/patches/envoy/20230201-wasi-stub.patch
Normal file
101
envoy/1.20/patches/envoy/20230201-wasi-stub.patch
Normal file
@@ -0,0 +1,101 @@
|
||||
diff --git a/bazel/foreign_cc/proxy_wasm_cpp_host.patch b/bazel/foreign_cc/proxy_wasm_cpp_host.patch
|
||||
new file mode 100644
|
||||
index 0000000000..a586116e84
|
||||
--- /dev/null
|
||||
+++ b/bazel/foreign_cc/proxy_wasm_cpp_host.patch
|
||||
@@ -0,0 +1,78 @@
|
||||
+diff --git a/include/proxy-wasm/exports.h b/include/proxy-wasm/exports.h
|
||||
+index ded6419..334cee4 100644
|
||||
+--- a/include/proxy-wasm/exports.h
|
||||
++++ b/include/proxy-wasm/exports.h
|
||||
+@@ -129,6 +129,11 @@ Word call_foreign_function(Word function_name, Word function_name_size, Word arg
|
||||
+
|
||||
+ // Runtime environment functions exported from envoy to wasm.
|
||||
+
|
||||
++Word wasi_unstable_path_open(Word fd, Word dir_flags, Word path, Word path_len, Word oflags,
|
||||
++ int64_t fs_rights_base, int64_t fg_rights_inheriting, Word fd_flags,
|
||||
++ Word nwritten_ptr);
|
||||
++Word wasi_unstable_fd_prestat_get(Word fd, Word buf_ptr);
|
||||
++Word wasi_unstable_fd_prestat_dir_name(Word fd, Word path_ptr, Word path_len);
|
||||
+ Word wasi_unstable_fd_write(Word fd, Word iovs, Word iovs_len, Word nwritten_ptr);
|
||||
+ Word wasi_unstable_fd_read(Word, Word, Word, Word);
|
||||
+ Word wasi_unstable_fd_seek(Word, int64_t, Word, Word);
|
||||
+@@ -166,7 +171,7 @@ Word pthread_equal(Word left, Word right);
|
||||
+ #define FOR_ALL_WASI_FUNCTIONS(_f) \
|
||||
+ _f(fd_write) _f(fd_read) _f(fd_seek) _f(fd_close) _f(fd_fdstat_get) _f(environ_get) \
|
||||
+ _f(environ_sizes_get) _f(args_get) _f(args_sizes_get) _f(clock_time_get) _f(random_get) \
|
||||
+- _f(proc_exit)
|
||||
++ _f(proc_exit) _f(path_open) _f(fd_prestat_get) _f(fd_prestat_dir_name)
|
||||
+
|
||||
+ // Helpers to generate a stub to pass to VM, in place of a restricted proxy-wasm capability.
|
||||
+ #define _CREATE_PROXY_WASM_STUB(_fn) \
|
||||
+diff --git a/include/proxy-wasm/wasm_vm.h b/include/proxy-wasm/wasm_vm.h
|
||||
+index c02bb6e..c13c78c 100644
|
||||
+--- a/include/proxy-wasm/wasm_vm.h
|
||||
++++ b/include/proxy-wasm/wasm_vm.h
|
||||
+@@ -110,6 +110,8 @@ using WasmCallback_WWl = Word (*)(Word, int64_t);
|
||||
+ using WasmCallback_WWlWW = Word (*)(Word, int64_t, Word, Word);
|
||||
+ using WasmCallback_WWm = Word (*)(Word, uint64_t);
|
||||
+ using WasmCallback_WWmW = Word (*)(Word, uint64_t, Word);
|
||||
++using WasmCallback_WWWWWWllWW = Word (*)(Word, Word, Word, Word, Word, int64_t, int64_t, Word,
|
||||
++ Word);
|
||||
+ using WasmCallback_dd = double (*)(double);
|
||||
+
|
||||
+ #define FOR_ALL_WASM_VM_IMPORTS(_f) \
|
||||
+@@ -127,7 +129,8 @@ using WasmCallback_dd = double (*)(double);
|
||||
+ _f(proxy_wasm::WasmCallback_WWlWW) \
|
||||
+ _f(proxy_wasm::WasmCallback_WWm) \
|
||||
+ _f(proxy_wasm::WasmCallback_WWmW) \
|
||||
+- _f(proxy_wasm::WasmCallback_dd)
|
||||
++ _f(proxy_wasm::WasmCallback_WWWWWWllWW) \
|
||||
++ _f(proxy_wasm::WasmCallback_dd)
|
||||
+
|
||||
+ enum class Cloneable {
|
||||
+ NotCloneable, // VMs can not be cloned and should be created from scratch.
|
||||
+diff --git a/src/exports.cc b/src/exports.cc
|
||||
+index 0922b2d..d597914 100644
|
||||
+--- a/src/exports.cc
|
||||
++++ b/src/exports.cc
|
||||
+@@ -648,6 +648,25 @@ Word grpc_send(Word token, Word message_ptr, Word message_size, Word end_stream)
|
||||
+ return context->grpcSend(token, message.value(), end_stream);
|
||||
+ }
|
||||
+
|
||||
++// __wasi_errno_t path_open(__wasi_fd_t fd, __wasi_lookupflags_t dirflags, const char *path,
|
||||
++// size_t path_len, __wasi_oflags_t oflags, __wasi_rights_t fs_rights_base, __wasi_rights_t
|
||||
++// fs_rights_inheriting, __wasi_fdflags_t fdflags, __wasi_fd_t *retptr0)
|
||||
++Word wasi_unstable_path_open(Word fd, Word dir_flags, Word path, Word path_len, Word oflags,
|
||||
++ int64_t fs_rights_base, int64_t fg_rights_inheriting, Word fd_flags,
|
||||
++ Word nwritten_ptr) {
|
||||
++ return 44; // __WASI_ERRNO_NOENT
|
||||
++}
|
||||
++
|
||||
++// __wasi_errno_t __wasi_fd_prestat_get(__wasi_fd_t fd, __wasi_prestat_t *retptr0)
|
||||
++Word wasi_unstable_fd_prestat_get(Word fd, Word buf_ptr) {
|
||||
++ return 8; // __WASI_ERRNO_BADF
|
||||
++}
|
||||
++
|
||||
++// __wasi_errno_t __wasi_fd_prestat_dir_name(__wasi_fd_t fd, uint8_t * path, __wasi_size_t path_len)
|
||||
++Word wasi_unstable_fd_prestat_dir_name(Word fd, Word path_ptr, Word path_len) {
|
||||
++ return 52; // __WASI_ERRNO_ENOSYS
|
||||
++}
|
||||
++
|
||||
+ // Implementation of writev-like() syscall that redirects stdout/stderr to Envoy
|
||||
+ // logs.
|
||||
+ Word writevImpl(Word fd, Word iovs, Word iovs_len, Word *nwritten_ptr) {
|
||||
diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl
|
||||
index bfef00ac85..806100b42d 100644
|
||||
--- a/bazel/repositories.bzl
|
||||
+++ b/bazel/repositories.bzl
|
||||
@@ -943,7 +943,11 @@ def _proxy_wasm_cpp_sdk():
|
||||
external_http_archive(name = "proxy_wasm_cpp_sdk")
|
||||
|
||||
def _proxy_wasm_cpp_host():
|
||||
- external_http_archive(name = "proxy_wasm_cpp_host")
|
||||
+ external_http_archive(
|
||||
+ name = "proxy_wasm_cpp_host",
|
||||
+ patches = ["@envoy//bazel/foreign_cc:proxy_wasm_cpp_host.patch"],
|
||||
+ patch_args = ["-p1"],
|
||||
+ )
|
||||
|
||||
def _emscripten_toolchain():
|
||||
external_http_archive(
|
||||
4
go.mod
4
go.mod
@@ -22,12 +22,13 @@ require (
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/go-cmp v0.5.8
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/nacos-group/nacos-sdk-go v1.0.8
|
||||
github.com/nacos-group/nacos-sdk-go/v2 v2.1.2
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
go.uber.org/atomic v1.9.0
|
||||
google.golang.org/grpc v1.48.0
|
||||
google.golang.org/protobuf v1.28.0
|
||||
@@ -98,7 +99,6 @@ require (
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/google/go-containerregistry v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@@ -1258,8 +1258,9 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
@@ -1268,8 +1269,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 0.6.0
|
||||
appVersion: 0.6.2
|
||||
description: Helm chart for deploying higress gateways
|
||||
icon: https://higress.io/img/higress_logo_small.png
|
||||
keywords:
|
||||
@@ -9,4 +9,4 @@ name: higress
|
||||
sources:
|
||||
- http://github.com/alibaba/higress
|
||||
type: application
|
||||
version: 0.6.0
|
||||
version: 0.6.2
|
||||
|
||||
@@ -169,10 +169,10 @@ spec:
|
||||
- "serve"
|
||||
- --gatewaySelectorKey=higress
|
||||
- --gatewaySelectorValue={{ .Release.Namespace }}-{{ include "gateway.name" . }}
|
||||
{{- if not .Values.enableStatus }}
|
||||
- --enableStatus={{ .Values.enableStatus }}
|
||||
{{- if .Values.ingressClass }}
|
||||
- --ingressClass={{ .Values.ingressClass }}
|
||||
{{- end }}
|
||||
- --ingressClass={{ .Values.ingressClass }}
|
||||
{{- if .Values.watchNamespace }}
|
||||
- --watchNamespace={{ .Values.watchNamespace }}
|
||||
{{- end }}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
revision: ""
|
||||
global:
|
||||
# whether to use autoscaling/v2 template for HPA settings
|
||||
# for internal usage only, not to be configured by users.
|
||||
@@ -30,7 +31,7 @@ global:
|
||||
# Dev builds from prow are on gcr.io
|
||||
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
|
||||
# Default tag for Istio images.
|
||||
tag: 0.6.0
|
||||
tag: 0.6.1
|
||||
|
||||
# Specify image pull policy if default behavior isn't desired.
|
||||
# Default behavior: latest images will be Always else IfNotPresent.
|
||||
@@ -318,9 +319,17 @@ global:
|
||||
# mechanisms (e.g., environmental variable CA_PROVIDER).
|
||||
caName: ""
|
||||
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
|
||||
ingressClass: ""
|
||||
|
||||
# IngressClass filters which ingress resources the higress controller watches.
|
||||
# The default ingress class is higress.
|
||||
# There are some special cases for special ingress class.
|
||||
# 1. When the ingress class is set as nginx, the higress controller will watch ingress
|
||||
# resources with the nginx ingress class or without any ingress class.
|
||||
# 2. When the ingress class is set empty, the higress controller will watch all ingress
|
||||
# resources in the k8s cluster.
|
||||
ingressClass: "higress"
|
||||
watchNamespace: ""
|
||||
enableStatus: false
|
||||
enableStatus: true
|
||||
clusterName: ""
|
||||
# meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior
|
||||
# See https://istio.io/docs/reference/config/istio.mesh.v1alpha1/ for all available options
|
||||
@@ -356,7 +365,7 @@ gateway:
|
||||
name: "higress-gateway"
|
||||
replicas: 2
|
||||
image: gateway
|
||||
tag: "0.6.0"
|
||||
tag: "0.6.2"
|
||||
# revision declares which revision this gateway is a part of
|
||||
revision: ""
|
||||
|
||||
@@ -401,10 +410,6 @@ gateway:
|
||||
# Type of service. Set to "None" to disable the service entirely
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- name: status-port
|
||||
port: 15021
|
||||
protocol: TCP
|
||||
targetPort: 15021
|
||||
- name: http2
|
||||
port: 80
|
||||
protocol: TCP
|
||||
@@ -448,7 +453,7 @@ controller:
|
||||
name: "higress-controller"
|
||||
replicas: 1
|
||||
image: higress
|
||||
tag: "0.6.0"
|
||||
tag: "0.6.1"
|
||||
env: {}
|
||||
|
||||
labels: {}
|
||||
@@ -538,7 +543,7 @@ pilot:
|
||||
rollingMaxUnavailable: 25%
|
||||
|
||||
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
|
||||
tag: 0.6.0
|
||||
tag: 0.6.1
|
||||
|
||||
# Can be a full hub/image:tag
|
||||
image: pilot
|
||||
|
||||
@@ -10,7 +10,7 @@ pilot:
|
||||
rollingMaxUnavailable: 25%
|
||||
|
||||
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
|
||||
tag: 0.6.0
|
||||
tag: 0.6.1
|
||||
|
||||
# Can be a full hub/image:tag
|
||||
image: pilot
|
||||
@@ -256,7 +256,7 @@ global:
|
||||
# Dev builds from prow are on gcr.io
|
||||
hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress
|
||||
# Default tag for Istio images.
|
||||
tag: 0.6.0
|
||||
tag: 0.6.1
|
||||
|
||||
# Specify image pull policy if default behavior isn't desired.
|
||||
# Default behavior: latest images will be Always else IfNotPresent.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
dependencies:
|
||||
- name: higress
|
||||
repository: file://../../higress
|
||||
version: 0.6.0
|
||||
digest: sha256:d5a9a1a3ee640635a1251ac1535a95db79975b39f6ab6b7c742c3e0d11f33533
|
||||
generated: "2023-01-19T10:31:59.206741+08:00"
|
||||
version: 0.6.1
|
||||
digest: sha256:35e6287d96a2268039a5b2a2fee5413424616275211bc46aec3f7ff0256c00e5
|
||||
generated: "2023-02-03T16:36:04.629269+08:00"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 0.6.0
|
||||
appVersion: 0.6.2
|
||||
description: Helm chart for deploying higress gateways
|
||||
icon: https://higress.io/img/higress_logo_small.png
|
||||
keywords:
|
||||
@@ -11,6 +11,6 @@ sources:
|
||||
dependencies:
|
||||
- name: higress
|
||||
repository: "file://../../higress"
|
||||
version: 0.6.0
|
||||
version: 0.6.2
|
||||
type: application
|
||||
version: 0.6.0
|
||||
version: 0.6.2
|
||||
|
||||
18
istio/1.12/patches/istio/20230208-waf-stats.patch
Normal file
18
istio/1.12/patches/istio/20230208-waf-stats.patch
Normal file
@@ -0,0 +1,18 @@
|
||||
diff -Naur istio/tools/packaging/common/envoy_bootstrap.json istio_new/tools/packaging/common/envoy_bootstrap.json
|
||||
--- istio/tools/packaging/common/envoy_bootstrap.json 2023-02-08 22:42:41.000000000 +0800
|
||||
+++ istio_new/tools/packaging/common/envoy_bootstrap.json 2023-02-08 22:19:04.000000000 +0800
|
||||
@@ -37,6 +37,14 @@
|
||||
"use_all_default_tags": false,
|
||||
"stats_tags": [
|
||||
{
|
||||
+ "tag_name": "phase",
|
||||
+ "regex": "(_phase=([a-z_]+))"
|
||||
+ },
|
||||
+ {
|
||||
+ "tag_name": "ruleid",
|
||||
+ "regex": "(_ruleid=([0-9]+))"
|
||||
+ },
|
||||
+ {
|
||||
"tag_name": "route",
|
||||
"regex": "^vhost\\..*?\\.route\\.([^\\.]+\\.)upstream"
|
||||
},
|
||||
@@ -88,12 +88,20 @@ type RegistryOptions struct {
|
||||
}
|
||||
|
||||
type ServerArgs struct {
|
||||
Debug bool
|
||||
MeshId string
|
||||
RegionId string
|
||||
NativeIstio bool
|
||||
HttpAddress string
|
||||
GrpcAddress string
|
||||
Debug bool
|
||||
MeshId string
|
||||
RegionId string
|
||||
NativeIstio bool
|
||||
HttpAddress string
|
||||
GrpcAddress string
|
||||
|
||||
// IngressClass filters which ingress resources the higress controller watches.
|
||||
// The default ingress class is higress.
|
||||
// There are some special cases for special ingress class.
|
||||
// 1. When the ingress class is set as nginx, the higress controller will watch ingress
|
||||
// resources with the nginx ingress class or without any ingress class.
|
||||
// 2. When the ingress class is set empty, the higress controller will watch all ingress
|
||||
// resources in the k8s cluster.
|
||||
IngressClass string
|
||||
EnableStatus bool
|
||||
WatchNamespace string
|
||||
@@ -107,6 +115,11 @@ type ServerArgs struct {
|
||||
|
||||
type readinessProbe func() (bool, error)
|
||||
|
||||
type ServerInterface interface {
|
||||
Start(stop <-chan struct{}) error
|
||||
WaitUntilCompletion()
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
*ServerArgs
|
||||
environment *model.Environment
|
||||
|
||||
17
pkg/config/constants/constants.go
Normal file
17
pkg/config/constants/constants.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package constants
|
||||
|
||||
const DefaultIngressClass = "higress"
|
||||
@@ -18,9 +18,112 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
)
|
||||
|
||||
func TestCanaryParse(t *testing.T) {
|
||||
parser := canary{}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
input Annotations
|
||||
expect *CanaryConfig
|
||||
}{
|
||||
{
|
||||
name: "Don't contain the 'enableCanary' key",
|
||||
input: Annotations{},
|
||||
expect: nil,
|
||||
},
|
||||
{
|
||||
name: "the 'enableCanary' is false",
|
||||
input: Annotations{
|
||||
buildNginxAnnotationKey(enableCanary): "false",
|
||||
},
|
||||
expect: &CanaryConfig{
|
||||
Enabled: false,
|
||||
WeightTotal: defaultCanaryWeightTotal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "By header",
|
||||
input: Annotations{
|
||||
buildNginxAnnotationKey(enableCanary): "true",
|
||||
buildNginxAnnotationKey(canaryByHeader): "header",
|
||||
},
|
||||
expect: &CanaryConfig{
|
||||
Enabled: true,
|
||||
Header: "header",
|
||||
WeightTotal: defaultCanaryWeightTotal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "By headerValue",
|
||||
input: Annotations{
|
||||
buildNginxAnnotationKey(enableCanary): "true",
|
||||
buildNginxAnnotationKey(canaryByHeader): "header",
|
||||
buildNginxAnnotationKey(canaryByHeaderValue): "headerValue",
|
||||
},
|
||||
expect: &CanaryConfig{
|
||||
Enabled: true,
|
||||
Header: "header",
|
||||
HeaderValue: "headerValue",
|
||||
WeightTotal: defaultCanaryWeightTotal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "By headerPattern",
|
||||
input: Annotations{
|
||||
buildNginxAnnotationKey(enableCanary): "true",
|
||||
buildNginxAnnotationKey(canaryByHeader): "header",
|
||||
buildNginxAnnotationKey(canaryByHeaderPattern): "headerPattern",
|
||||
},
|
||||
expect: &CanaryConfig{
|
||||
Enabled: true,
|
||||
Header: "header",
|
||||
HeaderPattern: "headerPattern",
|
||||
WeightTotal: defaultCanaryWeightTotal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "By cookie",
|
||||
input: Annotations{
|
||||
buildNginxAnnotationKey(enableCanary): "true",
|
||||
buildNginxAnnotationKey(canaryByCookie): "cookie",
|
||||
},
|
||||
expect: &CanaryConfig{
|
||||
Enabled: true,
|
||||
Cookie: "cookie",
|
||||
WeightTotal: defaultCanaryWeightTotal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "By weight",
|
||||
input: Annotations{
|
||||
buildNginxAnnotationKey(enableCanary): "true",
|
||||
buildNginxAnnotationKey(canaryWeight): "50",
|
||||
buildNginxAnnotationKey(canaryWeightTotal): "100",
|
||||
},
|
||||
expect: &CanaryConfig{
|
||||
Enabled: true,
|
||||
Weight: 50,
|
||||
WeightTotal: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config := &Ingress{}
|
||||
_ = parser.Parse(tt.input, config, nil)
|
||||
if diff := cmp.Diff(tt.expect, config.Canary); diff != "" {
|
||||
t.Fatalf("TestCanaryParse() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyWeight(t *testing.T) {
|
||||
route := &networking.HTTPRoute{
|
||||
Headers: &networking.Headers{
|
||||
|
||||
@@ -37,7 +37,7 @@ type DestinationConfig struct {
|
||||
|
||||
type destination struct{}
|
||||
|
||||
func (a destination) Parse(annotations Annotations, config *Ingress, globalContext *GlobalContext) error {
|
||||
func (a destination) Parse(annotations Annotations, config *Ingress, _ *GlobalContext) error {
|
||||
if !needDestinationConfig(annotations) {
|
||||
return nil
|
||||
}
|
||||
@@ -55,6 +55,9 @@ func (a destination) Parse(annotations Annotations, config *Ingress, globalConte
|
||||
pairs := strings.Fields(line)
|
||||
var weight int64 = 100
|
||||
var addrIndex int
|
||||
if len(pairs) == 0 {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(pairs[0], "%") {
|
||||
weight, err = strconv.ParseInt(strings.TrimSuffix(pairs[0], "%"), 10, 32)
|
||||
if err != nil {
|
||||
|
||||
98
pkg/ingress/kube/annotations/destination_test.go
Normal file
98
pkg/ingress/kube/annotations/destination_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
)
|
||||
|
||||
func TestDestinationParse(t *testing.T) {
|
||||
parser := destination{}
|
||||
|
||||
testCases := []struct {
|
||||
input Annotations
|
||||
expect *DestinationConfig
|
||||
}{
|
||||
{
|
||||
input: Annotations{},
|
||||
expect: nil,
|
||||
},
|
||||
{
|
||||
input: Annotations{
|
||||
buildHigressAnnotationKey(destinationKey): "",
|
||||
},
|
||||
expect: nil,
|
||||
},
|
||||
{
|
||||
input: Annotations{
|
||||
buildHigressAnnotationKey(destinationKey): "100% my-svc.DEFAULT-GROUP.xxxx.nacos:8080 v1",
|
||||
},
|
||||
expect: &DestinationConfig{
|
||||
McpDestination: []*networking.HTTPRouteDestination{
|
||||
{
|
||||
Destination: &networking.Destination{
|
||||
Host: "my-svc.DEFAULT-GROUP.xxxx.nacos",
|
||||
Subset: "v1",
|
||||
Port: &networking.PortSelector{Number: 8080},
|
||||
},
|
||||
Weight: 100,
|
||||
},
|
||||
},
|
||||
WeightSum: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
input: Annotations{
|
||||
buildHigressAnnotationKey(destinationKey): "50% my-svc.DEFAULT-GROUP.xxxx.nacos:8080 v1\n\n" +
|
||||
"50% my-svc.DEFAULT-GROUP.xxxx.nacos:8080 v2",
|
||||
},
|
||||
expect: &DestinationConfig{
|
||||
McpDestination: []*networking.HTTPRouteDestination{
|
||||
{
|
||||
Destination: &networking.Destination{
|
||||
Host: "my-svc.DEFAULT-GROUP.xxxx.nacos",
|
||||
Subset: "v1",
|
||||
Port: &networking.PortSelector{Number: 8080},
|
||||
},
|
||||
Weight: 50,
|
||||
},
|
||||
{
|
||||
Destination: &networking.Destination{
|
||||
Host: "my-svc.DEFAULT-GROUP.xxxx.nacos",
|
||||
Subset: "v2",
|
||||
Port: &networking.PortSelector{Number: 8080},
|
||||
},
|
||||
Weight: 50,
|
||||
},
|
||||
},
|
||||
WeightSum: 100,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
config := &Ingress{}
|
||||
_ = parser.Parse(testCase.input, config, nil)
|
||||
if diff := cmp.Diff(config.Destination, testCase.expect); diff != "" {
|
||||
t.Fatalf("TestDestinationParse() mismatch: (-want +got)\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
89
pkg/ingress/kube/annotations/redirect_test.go
Normal file
89
pkg/ingress/kube/annotations/redirect_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestRedirectParse(t *testing.T) {
|
||||
parser := redirect{}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
input Annotations
|
||||
expect *RedirectConfig
|
||||
}{
|
||||
{
|
||||
name: "Don't contain any redirect keys",
|
||||
input: Annotations{},
|
||||
expect: nil,
|
||||
},
|
||||
{
|
||||
name: "By appRoot",
|
||||
input: Annotations{
|
||||
buildHigressAnnotationKey(appRoot): "/root",
|
||||
buildHigressAnnotationKey(sslRedirect): "true",
|
||||
buildHigressAnnotationKey(forceSSLRedirect): "true",
|
||||
},
|
||||
expect: &RedirectConfig{
|
||||
AppRoot: "/root",
|
||||
httpsRedirect: true,
|
||||
Code: defaultPermanentRedirectCode,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "By temporalRedirect",
|
||||
input: Annotations{
|
||||
buildHigressAnnotationKey(temporalRedirect): "http://www.xxx.org",
|
||||
},
|
||||
expect: &RedirectConfig{
|
||||
URL: "http://www.xxx.org",
|
||||
Code: defaultTemporalRedirectCode,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "By temporalRedirect with invalid url",
|
||||
input: Annotations{
|
||||
buildHigressAnnotationKey(temporalRedirect): "tcp://www.xxx.org",
|
||||
},
|
||||
expect: &RedirectConfig{
|
||||
Code: defaultPermanentRedirectCode,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "By permanentRedirect",
|
||||
input: Annotations{
|
||||
buildHigressAnnotationKey(permanentRedirect): "http://www.xxx.org",
|
||||
},
|
||||
expect: &RedirectConfig{
|
||||
URL: "http://www.xxx.org",
|
||||
Code: defaultPermanentRedirectCode,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
config := &Ingress{}
|
||||
_ = parser.Parse(tt.input, config, nil)
|
||||
if diff := cmp.Diff(tt.expect, config.Redirect, cmp.AllowUnexported(RedirectConfig{})); diff != "" {
|
||||
t.Fatalf("TestRedirectParse() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
152
pkg/ingress/kube/annotations/upstreamtls_test.go
Normal file
152
pkg/ingress/kube/annotations/upstreamtls_test.go
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
)
|
||||
|
||||
func TestUpstreamTLSParse(t *testing.T) {
|
||||
parser := upstreamTLS{}
|
||||
|
||||
testCases := []struct {
|
||||
input Annotations
|
||||
expect *UpstreamTLSConfig
|
||||
}{
|
||||
{
|
||||
input: Annotations{},
|
||||
expect: nil,
|
||||
},
|
||||
{
|
||||
input: Annotations{
|
||||
buildNginxAnnotationKey(proxySSLSecret): "",
|
||||
buildNginxAnnotationKey(backendProtocol): "HTTP2",
|
||||
buildNginxAnnotationKey(proxySSLSecret): "namespace/SSLSecret",
|
||||
buildNginxAnnotationKey(proxySSLVerify): "on",
|
||||
buildNginxAnnotationKey(proxySSLName): "SSLName",
|
||||
buildNginxAnnotationKey(proxySSLServerName): "on",
|
||||
},
|
||||
expect: &UpstreamTLSConfig{
|
||||
BackendProtocol: "HTTP2",
|
||||
SSLVerify: true,
|
||||
SNI: "SSLName",
|
||||
SecretName: "namespace/SSLSecret",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: Annotations{
|
||||
buildNginxAnnotationKey(proxySSLSecret): "",
|
||||
buildNginxAnnotationKey(backendProtocol): "HTTP2",
|
||||
buildNginxAnnotationKey(proxySSLSecret): "", // if there is no ssl secret, it will be return directly
|
||||
buildNginxAnnotationKey(proxySSLVerify): "on",
|
||||
buildNginxAnnotationKey(proxySSLName): "SSLName",
|
||||
buildNginxAnnotationKey(proxySSLServerName): "on",
|
||||
},
|
||||
expect: &UpstreamTLSConfig{
|
||||
BackendProtocol: "HTTP2",
|
||||
SSLVerify: false,
|
||||
SNI: "",
|
||||
SecretName: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
config := &Ingress{}
|
||||
_ = parser.Parse(testCase.input, config, nil)
|
||||
if diff := cmp.Diff(testCase.expect, config.UpstreamTLS); diff != "" {
|
||||
t.Fatalf("TestUpstreamTLSParse() mismatch: \n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyTrafficPolicy(t *testing.T) {
|
||||
parser := upstreamTLS{}
|
||||
|
||||
testCases := []struct {
|
||||
input *networking.TrafficPolicy_PortTrafficPolicy
|
||||
config *Ingress
|
||||
expect *networking.TrafficPolicy_PortTrafficPolicy
|
||||
}{
|
||||
{
|
||||
input: &networking.TrafficPolicy_PortTrafficPolicy{},
|
||||
config: &Ingress{
|
||||
UpstreamTLS: &UpstreamTLSConfig{},
|
||||
},
|
||||
expect: &networking.TrafficPolicy_PortTrafficPolicy{},
|
||||
},
|
||||
{
|
||||
input: &networking.TrafficPolicy_PortTrafficPolicy{},
|
||||
config: &Ingress{
|
||||
UpstreamTLS: &UpstreamTLSConfig{
|
||||
BackendProtocol: "HTTP2",
|
||||
},
|
||||
},
|
||||
expect: &networking.TrafficPolicy_PortTrafficPolicy{
|
||||
ConnectionPool: &networking.ConnectionPoolSettings{
|
||||
Http: &networking.ConnectionPoolSettings_HTTPSettings{
|
||||
H2UpgradePolicy: networking.ConnectionPoolSettings_HTTPSettings_UPGRADE,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: &networking.TrafficPolicy_PortTrafficPolicy{},
|
||||
config: &Ingress{
|
||||
UpstreamTLS: &UpstreamTLSConfig{
|
||||
BackendProtocol: "HTTPS",
|
||||
EnableSNI: true,
|
||||
SNI: "SNI",
|
||||
},
|
||||
},
|
||||
expect: &networking.TrafficPolicy_PortTrafficPolicy{
|
||||
Tls: &networking.ClientTLSSettings{
|
||||
Mode: networking.ClientTLSSettings_SIMPLE,
|
||||
Sni: "SNI",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: &networking.TrafficPolicy_PortTrafficPolicy{},
|
||||
config: &Ingress{
|
||||
UpstreamTLS: &UpstreamTLSConfig{
|
||||
SecretName: "namespace/secretName",
|
||||
SSLVerify: true,
|
||||
},
|
||||
},
|
||||
expect: &networking.TrafficPolicy_PortTrafficPolicy{
|
||||
Tls: &networking.ClientTLSSettings{
|
||||
Mode: networking.ClientTLSSettings_MUTUAL,
|
||||
CredentialName: "kubernetes-ingress://Kubernetes/namespace/secretName",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run("", func(t *testing.T) {
|
||||
parser.ApplyTrafficPolicy(testCase.input, testCase.config)
|
||||
if diff := cmp.Diff(testCase.expect, testCase.input); diff != "" {
|
||||
t.Fatalf("TestApplyTrafficPolicy() mismatch (-want +got): \n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
)
|
||||
|
||||
@@ -51,3 +52,35 @@ func TestExtraSecret(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitBySeparator(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
sep string
|
||||
expect []string
|
||||
}{
|
||||
{
|
||||
input: "a b c d",
|
||||
sep: " ",
|
||||
expect: []string{"a", "b", "c", "d"},
|
||||
},
|
||||
{
|
||||
input: ".1.2.3.4.",
|
||||
sep: ".",
|
||||
expect: []string{"1", "2", "3", "4"},
|
||||
},
|
||||
{
|
||||
input: "1....2....3....4",
|
||||
sep: ".",
|
||||
expect: []string{"1", "2", "3", "4"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
got := splitBySeparator(tt.input, tt.sep)
|
||||
if diff := cmp.Diff(tt.expect, got); diff != "" {
|
||||
t.Errorf("TestSplitBySeparator() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -348,6 +348,13 @@ func extractTLSSecretName(host string, tls []ingress.IngressTLS) string {
|
||||
}
|
||||
|
||||
func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig) error {
|
||||
if convertOptions == nil {
|
||||
return fmt.Errorf("convertOptions is nil")
|
||||
}
|
||||
if wrapper == nil {
|
||||
return fmt.Errorf("wrapperConfig is nil")
|
||||
}
|
||||
|
||||
// Ignore canary config.
|
||||
if wrapper.AnnotationsConfig.IsCanary() {
|
||||
return nil
|
||||
@@ -455,6 +462,13 @@ func (c *controller) ConvertGateway(convertOptions *common.ConvertOptions, wrapp
|
||||
}
|
||||
|
||||
func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig) error {
|
||||
if convertOptions == nil {
|
||||
return fmt.Errorf("convertOptions is nil")
|
||||
}
|
||||
if wrapper == nil {
|
||||
return fmt.Errorf("wrapperConfig is nil")
|
||||
}
|
||||
|
||||
// Canary ingress will be processed in the end.
|
||||
if wrapper.AnnotationsConfig.IsCanary() {
|
||||
convertOptions.CanaryIngresses = append(convertOptions.CanaryIngresses, wrapper)
|
||||
@@ -618,6 +632,13 @@ func (c *controller) ConvertHTTPRoute(convertOptions *common.ConvertOptions, wra
|
||||
}
|
||||
|
||||
func (c *controller) ApplyDefaultBackend(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig) error {
|
||||
if convertOptions == nil {
|
||||
return fmt.Errorf("convertOptions is nil")
|
||||
}
|
||||
if wrapper == nil {
|
||||
return fmt.Errorf("wrapperConfig is nil")
|
||||
}
|
||||
|
||||
if wrapper.AnnotationsConfig.IsCanary() {
|
||||
return nil
|
||||
}
|
||||
@@ -688,6 +709,13 @@ func (c *controller) ApplyDefaultBackend(convertOptions *common.ConvertOptions,
|
||||
}
|
||||
|
||||
func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig) error {
|
||||
if convertOptions == nil {
|
||||
return fmt.Errorf("convertOptions is nil")
|
||||
}
|
||||
if wrapper == nil {
|
||||
return fmt.Errorf("wrapperConfig is nil")
|
||||
}
|
||||
|
||||
byHeader, byWeight := wrapper.AnnotationsConfig.CanaryKind()
|
||||
|
||||
cfg := wrapper.Config
|
||||
@@ -820,6 +848,13 @@ func (c *controller) ApplyCanaryIngress(convertOptions *common.ConvertOptions, w
|
||||
}
|
||||
|
||||
func (c *controller) ConvertTrafficPolicy(convertOptions *common.ConvertOptions, wrapper *common.WrapperConfig) error {
|
||||
if convertOptions == nil {
|
||||
return fmt.Errorf("convertOptions is nil")
|
||||
}
|
||||
if wrapper == nil {
|
||||
return fmt.Errorf("wrapperConfig is nil")
|
||||
}
|
||||
|
||||
if !wrapper.AnnotationsConfig.NeedTrafficPolicy() {
|
||||
return nil
|
||||
}
|
||||
@@ -941,6 +976,10 @@ func (c *controller) createDefaultRoute(wrapper *common.WrapperConfig, backend *
|
||||
}
|
||||
|
||||
func (c *controller) createServiceKey(service *ingress.IngressBackend, namespace string) (common.ServiceKey, error) {
|
||||
if service == nil {
|
||||
return common.ServiceKey{}, fmt.Errorf("ingressBackend is nil")
|
||||
}
|
||||
|
||||
serviceKey := common.ServiceKey{}
|
||||
if service.ServiceName == "" {
|
||||
return serviceKey, errors.New("service name is empty")
|
||||
@@ -965,7 +1004,7 @@ func (c *controller) createServiceKey(service *ingress.IngressBackend, namespace
|
||||
}
|
||||
|
||||
func isCanaryRoute(canary, route *common.WrapperHTTPRoute) bool {
|
||||
return !route.WrapperConfig.AnnotationsConfig.IsCanary() && canary.OriginPath == route.OriginPath &&
|
||||
return route != nil && canary != nil && !route.WrapperConfig.AnnotationsConfig.IsCanary() && canary.OriginPath == route.OriginPath &&
|
||||
canary.OriginPathType == route.OriginPathType
|
||||
}
|
||||
|
||||
@@ -1016,6 +1055,10 @@ func (c *controller) backendToRouteDestination(backend *ingress.IngressBackend,
|
||||
}
|
||||
|
||||
func resolveNamedPort(backend *ingress.IngressBackend, namespace string, serviceLister listerv1.ServiceLister) (int32, error) {
|
||||
if backend == nil {
|
||||
return 0, fmt.Errorf("ingressBackend is nil")
|
||||
}
|
||||
|
||||
svc, err := serviceLister.Services(namespace).Get(backend.ServiceName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -1132,6 +1175,10 @@ func (c *controller) shouldProcessIngressUpdate(ing *ingress.Ingress) (bool, err
|
||||
|
||||
// setDefaultMSEIngressOptionalField sets a default value for optional fields when is not defined.
|
||||
func setDefaultMSEIngressOptionalField(ing *ingress.Ingress) {
|
||||
if ing == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for idx, tls := range ing.Spec.TLS {
|
||||
if len(tls.Hosts) == 0 {
|
||||
ing.Spec.TLS[idx].Hosts = []string{common.DefaultHost}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
160
pkg/ingress/kube/secret/controller_test.go
Normal file
160
pkg/ingress/kube/secret/controller_test.go
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package secret
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
kubeclient "istio.io/istio/pkg/kube"
|
||||
"istio.io/istio/pkg/test/util/retry"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/alibaba/higress/pkg/ingress/kube/util"
|
||||
)
|
||||
|
||||
const (
|
||||
secretFakeName = "fake-secret"
|
||||
secretFakeKey = "fake-key"
|
||||
secretInitValue = "init-value"
|
||||
secretUpdatedValue = "updated-value"
|
||||
)
|
||||
|
||||
var period = time.Second
|
||||
|
||||
func TestController(t *testing.T) {
|
||||
client := kubeclient.NewFakeClient()
|
||||
ctrl := NewController(client, "fake-cluster")
|
||||
|
||||
stop := make(chan struct{})
|
||||
t.Cleanup(func() {
|
||||
close(stop)
|
||||
})
|
||||
|
||||
client.RunAndWait(stop)
|
||||
|
||||
// store secret
|
||||
store := sync.Map{}
|
||||
|
||||
// add event handler
|
||||
ctrl.AddEventHandler(func(name util.ClusterNamespacedName) {
|
||||
t.Logf("event recived, clusterId: %s, namespacedName: %s", name.ClusterId, name.NamespacedName.String())
|
||||
|
||||
retry.UntilSuccessOrFail(t, func() error {
|
||||
secret, err := ctrl.Lister().Secrets(name.NamespacedName.Namespace).Get(name.NamespacedName.Name)
|
||||
if err != nil && !kerrors.IsNotFound(err) {
|
||||
t.Logf("get secret %s error: %v", name.NamespacedName.String(), err)
|
||||
return err
|
||||
}
|
||||
store.Store(name.NamespacedName.String(), secret.Data)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
// start controller
|
||||
go ctrl.Run(stop)
|
||||
|
||||
// wait for cache sync
|
||||
cache.WaitForCacheSync(stop, ctrl.Informer().HasSynced)
|
||||
|
||||
// init secret
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretFakeName,
|
||||
},
|
||||
Type: corev1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
secretFakeKey: []byte(secretInitValue),
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
do func() error
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
name: "create secret",
|
||||
do: func() error {
|
||||
_, err := client.CoreV1().Secrets(metav1.NamespaceDefault).Create(context.Background(),
|
||||
secret, metav1.CreateOptions{})
|
||||
return err
|
||||
},
|
||||
expect: secretInitValue,
|
||||
},
|
||||
{
|
||||
name: "update secret",
|
||||
do: func() error {
|
||||
var getSecret *corev1.Secret
|
||||
// get or create secret
|
||||
getSecret, err := ctrl.Lister().Secrets(metav1.NamespaceDefault).Get(secretFakeName)
|
||||
if err != nil {
|
||||
if !kerrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
getSecret, err = client.CoreV1().Secrets(metav1.NamespaceDefault).Create(context.Background(),
|
||||
secret, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// update secret
|
||||
getSecret.Data[secretFakeKey] = []byte(secretUpdatedValue)
|
||||
_, err = client.CoreV1().Secrets(metav1.NamespaceDefault).Update(context.Background(),
|
||||
getSecret, metav1.UpdateOptions{})
|
||||
return err
|
||||
},
|
||||
expect: secretUpdatedValue,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
if err := testCase.do(); err != nil {
|
||||
t.Fatalf("do %s error: %v", testCase.name, err)
|
||||
}
|
||||
|
||||
// controller Run() with setting period time to 1s.
|
||||
time.Sleep(period)
|
||||
|
||||
secretFullName := model.NamespacedName{
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
Name: secretFakeName,
|
||||
}.String()
|
||||
|
||||
valAny, ok := store.Load(secretFullName)
|
||||
if !ok {
|
||||
t.Fatalf("secret %s not found", secretFullName)
|
||||
}
|
||||
|
||||
val, ok := valAny.(map[string][]byte)
|
||||
if !ok {
|
||||
t.Fatalf("assert secret %s data type error", secretFullName)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(val[secretFakeKey], []byte(testCase.expect)) {
|
||||
t.Fatalf("secret %s data error, expect: %s, got: %s",
|
||||
secretFullName, testCase.expect, string(val[secretFakeKey]))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -14,17 +14,17 @@
|
||||
|
||||
package mcp
|
||||
|
||||
// nolint
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/golang/protobuf/ptypes/any"
|
||||
extensions "istio.io/api/extensions/v1alpha1"
|
||||
mcp "istio.io/api/mcp/v1alpha1"
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
"istio.io/istio/pilot/pkg/xds"
|
||||
cfg "istio.io/istio/pkg/config"
|
||||
)
|
||||
|
||||
type ServiceEntryGenerator struct {
|
||||
@@ -33,31 +33,7 @@ type ServiceEntryGenerator struct {
|
||||
|
||||
func (c ServiceEntryGenerator) Generate(proxy *model.Proxy, push *model.PushContext, w *model.WatchedResource,
|
||||
updates *model.PushRequest) ([]*any.Any, model.XdsLogDetails, error) {
|
||||
resources := make([]*any.Any, 0)
|
||||
configs := push.AllServiceEntries
|
||||
for _, config := range configs {
|
||||
body, err := types.MarshalAny(config.Spec.(*networking.ServiceEntry))
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
createTime, err := types.TimestampProto(config.CreationTimestamp)
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
resource := &mcp.Resource{
|
||||
Body: body,
|
||||
Metadata: &mcp.Metadata{
|
||||
Name: path.Join(config.Namespace, config.Name),
|
||||
CreateTime: createTime,
|
||||
},
|
||||
}
|
||||
mcpAny, err := ptypes.MarshalAny(resource)
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
resources = append(resources, mcpAny)
|
||||
}
|
||||
return resources, model.DefaultXdsLogDetails, nil
|
||||
return generate(proxy, push.AllServiceEntries, w, updates)
|
||||
}
|
||||
|
||||
func (c ServiceEntryGenerator) GenerateDeltas(proxy *model.Proxy, push *model.PushContext, updates *model.PushRequest,
|
||||
@@ -72,31 +48,7 @@ type VirtualServiceGenerator struct {
|
||||
|
||||
func (c VirtualServiceGenerator) Generate(proxy *model.Proxy, push *model.PushContext, w *model.WatchedResource,
|
||||
updates *model.PushRequest) ([]*any.Any, model.XdsLogDetails, error) {
|
||||
resources := make([]*any.Any, 0)
|
||||
configs := push.AllVirtualServices
|
||||
for _, config := range configs {
|
||||
body, err := types.MarshalAny(config.Spec.(*networking.VirtualService))
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
createTime, err := types.TimestampProto(config.CreationTimestamp)
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
resource := &mcp.Resource{
|
||||
Body: body,
|
||||
Metadata: &mcp.Metadata{
|
||||
Name: path.Join(config.Namespace, config.Name),
|
||||
CreateTime: createTime,
|
||||
},
|
||||
}
|
||||
mcpAny, err := ptypes.MarshalAny(resource)
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
resources = append(resources, mcpAny)
|
||||
}
|
||||
return resources, model.DefaultXdsLogDetails, nil
|
||||
return generate(proxy, push.AllVirtualServices, w, updates)
|
||||
}
|
||||
|
||||
func (c VirtualServiceGenerator) GenerateDeltas(proxy *model.Proxy, push *model.PushContext, updates *model.PushRequest,
|
||||
@@ -111,31 +63,7 @@ type DestinationRuleGenerator struct {
|
||||
|
||||
func (c DestinationRuleGenerator) Generate(proxy *model.Proxy, push *model.PushContext, w *model.WatchedResource,
|
||||
updates *model.PushRequest) ([]*any.Any, model.XdsLogDetails, error) {
|
||||
resources := make([]*any.Any, 0)
|
||||
configs := push.AllDestinationRules
|
||||
for _, config := range configs {
|
||||
body, err := types.MarshalAny(config.Spec.(*networking.DestinationRule))
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
createTime, err := types.TimestampProto(config.CreationTimestamp)
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
resource := &mcp.Resource{
|
||||
Body: body,
|
||||
Metadata: &mcp.Metadata{
|
||||
Name: path.Join(config.Namespace, config.Name),
|
||||
CreateTime: createTime,
|
||||
},
|
||||
}
|
||||
mcpAny, err := ptypes.MarshalAny(resource)
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
resources = append(resources, mcpAny)
|
||||
}
|
||||
return resources, model.DefaultXdsLogDetails, nil
|
||||
return generate(proxy, push.AllDestinationRules, w, updates)
|
||||
}
|
||||
|
||||
func (c DestinationRuleGenerator) GenerateDeltas(proxy *model.Proxy, push *model.PushContext, updates *model.PushRequest,
|
||||
@@ -150,31 +78,7 @@ type EnvoyFilterGenerator struct {
|
||||
|
||||
func (c EnvoyFilterGenerator) Generate(proxy *model.Proxy, push *model.PushContext, w *model.WatchedResource,
|
||||
updates *model.PushRequest) ([]*any.Any, model.XdsLogDetails, error) {
|
||||
resources := make([]*any.Any, 0)
|
||||
configs := push.AllEnvoyFilters
|
||||
for _, config := range configs {
|
||||
body, err := types.MarshalAny(config.Spec.(*networking.EnvoyFilter))
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
createTime, err := types.TimestampProto(config.CreationTimestamp)
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
resource := &mcp.Resource{
|
||||
Body: body,
|
||||
Metadata: &mcp.Metadata{
|
||||
Name: path.Join(config.Namespace, config.Name),
|
||||
CreateTime: createTime,
|
||||
},
|
||||
}
|
||||
mcpAny, err := ptypes.MarshalAny(resource)
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
resources = append(resources, mcpAny)
|
||||
}
|
||||
return resources, model.DefaultXdsLogDetails, nil
|
||||
return generate(proxy, push.AllEnvoyFilters, w, updates)
|
||||
}
|
||||
|
||||
func (c EnvoyFilterGenerator) GenerateDeltas(proxy *model.Proxy, push *model.PushContext, updates *model.PushRequest,
|
||||
@@ -189,31 +93,7 @@ type GatewayGenerator struct {
|
||||
|
||||
func (c GatewayGenerator) Generate(proxy *model.Proxy, push *model.PushContext, w *model.WatchedResource,
|
||||
updates *model.PushRequest) ([]*any.Any, model.XdsLogDetails, error) {
|
||||
resources := make([]*any.Any, 0)
|
||||
configs := push.AllGateways
|
||||
for _, config := range configs {
|
||||
body, err := types.MarshalAny(config.Spec.(*networking.Gateway))
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
createTime, err := types.TimestampProto(config.CreationTimestamp)
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
resource := &mcp.Resource{
|
||||
Body: body,
|
||||
Metadata: &mcp.Metadata{
|
||||
Name: path.Join(config.Namespace, config.Name),
|
||||
CreateTime: createTime,
|
||||
},
|
||||
}
|
||||
mcpAny, err := ptypes.MarshalAny(resource)
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
resources = append(resources, mcpAny)
|
||||
}
|
||||
return resources, model.DefaultXdsLogDetails, nil
|
||||
return generate(proxy, push.AllGateways, w, updates)
|
||||
}
|
||||
|
||||
func (c GatewayGenerator) GenerateDeltas(proxy *model.Proxy, push *model.PushContext, updates *model.PushRequest,
|
||||
@@ -227,11 +107,21 @@ type WasmpluginGenerator struct {
|
||||
}
|
||||
|
||||
func (c WasmpluginGenerator) Generate(proxy *model.Proxy, push *model.PushContext, w *model.WatchedResource,
|
||||
updates *model.PushRequest) ([]*any.Any, model.XdsLogDetails, error) {
|
||||
return generate(proxy, push.AllWasmplugins, w, updates)
|
||||
}
|
||||
|
||||
func (c WasmpluginGenerator) GenerateDeltas(proxy *model.Proxy, push *model.PushContext, updates *model.PushRequest,
|
||||
w *model.WatchedResource) ([]*any.Any, []string, model.XdsLogDetails, bool, error) {
|
||||
// TODO: delta implement
|
||||
return nil, nil, model.DefaultXdsLogDetails, false, nil
|
||||
}
|
||||
|
||||
func generate(proxy *model.Proxy, configs []cfg.Config, w *model.WatchedResource,
|
||||
updates *model.PushRequest) ([]*any.Any, model.XdsLogDetails, error) {
|
||||
resources := make([]*any.Any, 0)
|
||||
configs := push.AllWasmplugins
|
||||
for _, config := range configs {
|
||||
body, err := types.MarshalAny(config.Spec.(*extensions.WasmPlugin))
|
||||
body, err := cfg.ToProtoGogo(config.Spec)
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
}
|
||||
@@ -246,6 +136,7 @@ func (c WasmpluginGenerator) Generate(proxy *model.Proxy, push *model.PushContex
|
||||
CreateTime: createTime,
|
||||
},
|
||||
}
|
||||
// nolint
|
||||
mcpAny, err := ptypes.MarshalAny(resource)
|
||||
if err != nil {
|
||||
return nil, model.DefaultXdsLogDetails, err
|
||||
@@ -254,9 +145,3 @@ func (c WasmpluginGenerator) Generate(proxy *model.Proxy, push *model.PushContex
|
||||
}
|
||||
return resources, model.DefaultXdsLogDetails, nil
|
||||
}
|
||||
|
||||
func (c WasmpluginGenerator) GenerateDeltas(proxy *model.Proxy, push *model.PushContext, updates *model.PushRequest,
|
||||
w *model.WatchedResource) ([]*any.Any, []string, model.XdsLogDetails, bool, error) {
|
||||
// TODO: delta implement
|
||||
return nil, nil, model.DefaultXdsLogDetails, false, nil
|
||||
}
|
||||
|
||||
178
pkg/ingress/mcp/generator_test.go
Normal file
178
pkg/ingress/mcp/generator_test.go
Normal file
@@ -0,0 +1,178 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/gogo/protobuf/types"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
extensions "istio.io/api/extensions/v1alpha1"
|
||||
mcp "istio.io/api/mcp/v1alpha1"
|
||||
networking "istio.io/api/networking/v1alpha3"
|
||||
"istio.io/istio/pilot/pkg/model"
|
||||
"istio.io/istio/pkg/config"
|
||||
)
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func() (*model.PushContext, any)
|
||||
generator model.McpResourceGenerator
|
||||
isErr bool
|
||||
}{
|
||||
{
|
||||
name: "VirtualService",
|
||||
fn: func() (*model.PushContext, any) {
|
||||
ctx := model.NewPushContext()
|
||||
cfg := config.Config{
|
||||
Spec: &networking.VirtualService{},
|
||||
}
|
||||
ctx.AllVirtualServices = []config.Config{cfg}
|
||||
return ctx, cfg.Spec
|
||||
},
|
||||
generator: VirtualServiceGenerator{},
|
||||
isErr: false,
|
||||
},
|
||||
{
|
||||
name: "Gateway",
|
||||
fn: func() (*model.PushContext, any) {
|
||||
ctx := model.NewPushContext()
|
||||
cfg := config.Config{
|
||||
Spec: &networking.Gateway{},
|
||||
}
|
||||
ctx.AllGateways = []config.Config{cfg}
|
||||
return ctx, cfg.Spec
|
||||
},
|
||||
generator: GatewayGenerator{},
|
||||
isErr: false,
|
||||
},
|
||||
{
|
||||
name: "EnvoyFilter",
|
||||
fn: func() (*model.PushContext, any) {
|
||||
ctx := model.NewPushContext()
|
||||
cfg := config.Config{
|
||||
Spec: &networking.EnvoyFilter{},
|
||||
}
|
||||
ctx.AllEnvoyFilters = []config.Config{cfg}
|
||||
return ctx, cfg.Spec
|
||||
},
|
||||
generator: EnvoyFilterGenerator{},
|
||||
isErr: false,
|
||||
},
|
||||
{
|
||||
name: "DestinationRule",
|
||||
fn: func() (*model.PushContext, any) {
|
||||
ctx := model.NewPushContext()
|
||||
cfg := config.Config{
|
||||
Spec: &networking.DestinationRule{},
|
||||
}
|
||||
ctx.AllDestinationRules = []config.Config{cfg}
|
||||
return ctx, cfg.Spec
|
||||
},
|
||||
generator: DestinationRuleGenerator{},
|
||||
isErr: false,
|
||||
},
|
||||
{
|
||||
name: "WasmPlugin",
|
||||
fn: func() (*model.PushContext, any) {
|
||||
ctx := model.NewPushContext()
|
||||
cfg := config.Config{
|
||||
Spec: &extensions.WasmPlugin{},
|
||||
}
|
||||
ctx.AllWasmplugins = []config.Config{cfg}
|
||||
return ctx, cfg.Spec
|
||||
},
|
||||
generator: WasmpluginGenerator{},
|
||||
isErr: false,
|
||||
},
|
||||
{
|
||||
name: "ServiceEntry",
|
||||
fn: func() (*model.PushContext, any) {
|
||||
ctx := model.NewPushContext()
|
||||
cfg := config.Config{
|
||||
Spec: &networking.ServiceEntry{},
|
||||
}
|
||||
ctx.AllServiceEntries = []config.Config{cfg}
|
||||
return ctx, cfg.Spec
|
||||
},
|
||||
generator: ServiceEntryGenerator{},
|
||||
isErr: false,
|
||||
},
|
||||
{
|
||||
name: "WasmPlugin with wrong config",
|
||||
fn: func() (*model.PushContext, any) {
|
||||
ctx := model.NewPushContext()
|
||||
cfg := config.Config{
|
||||
Spec: "string",
|
||||
}
|
||||
ctx.AllWasmplugins = []config.Config{cfg}
|
||||
return ctx, cfg.Spec
|
||||
},
|
||||
generator: WasmpluginGenerator{},
|
||||
isErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
err error
|
||||
val []*anypb.Any
|
||||
)
|
||||
|
||||
pushCtx, spec := test.fn()
|
||||
func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil && !test.isErr {
|
||||
t.Fatalf("Failed to generate config: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
val, _, err = test.generator.Generate(nil, pushCtx, nil, nil)
|
||||
if (err != nil && !test.isErr) || (err == nil && test.isErr) {
|
||||
t.Fatalf("Failed to generate config: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if test.isErr { // if the func 'Generate' should occur an error, just return
|
||||
return
|
||||
}
|
||||
|
||||
resource := &mcp.Resource{}
|
||||
err = ptypes.UnmarshalAny(val[0], resource)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
specType := reflect.TypeOf(spec)
|
||||
if specType.Kind() == reflect.Ptr {
|
||||
specType = specType.Elem()
|
||||
}
|
||||
|
||||
target := reflect.New(specType).Interface().(proto.Message)
|
||||
if err = types.UnmarshalAny(resource.Body, target); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !test.isErr && !proto.Equal(spec.(proto.Message), target) {
|
||||
t.Fatal("failed ")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
例如用如下配置使用 request-block 插件 的 1.0.0 版本:
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions.istio.io/v1alpha1
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: request-block
|
||||
@@ -36,7 +36,7 @@ spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
higress: higress-system-higress-gateway
|
||||
pluginConfig:
|
||||
defaultConfig:
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/request-block:1.0.0
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<p>
|
||||
<a href="README_EN.md"> English </a> | 中文
|
||||
</p>
|
||||
|
||||
# 功能说明
|
||||
`bot-detect`插件可以用于识别并阻止互联网爬虫对站点资源的爬取
|
||||
|
||||
|
||||
77
plugins/wasm-cpp/extensions/bot_detect/README_EN.md
Normal file
77
plugins/wasm-cpp/extensions/bot_detect/README_EN.md
Normal file
@@ -0,0 +1,77 @@
|
||||
<p>
|
||||
English | <a href="README.md">中文</a>
|
||||
</p>
|
||||
|
||||
# Description
|
||||
`bot-detect` plugin can be used to identify and prevent web crawlers from crawling websites.
|
||||
|
||||
# Configuration Fields
|
||||
|
||||
| Name | Type | Requirement | Default Value | Description |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| allow | array of string | Optional | - | A regular expression to match the User-Agent request header and will allow access if the match hits |
|
||||
| deny | array of string | Optional | - | A regular expression to match the User-Agent request header and will block the request if the match hits |
|
||||
| blocked_code | number | Optional | 403 | The HTTP status code returned when a request is blocked |
|
||||
| blocked_message | string | Optional | - | The HTTP response Body returned when a request is blocked |
|
||||
|
||||
If field `allow` and field `deny` are not configured at the same time, the default logic to identify crawlers will be executed. By configuring the `allow` field, requests that would otherwise hit the default logic can be allowed. The judgement can be extended by configuring the `deny` field
|
||||
|
||||
The default set of crawler judgment regular expressions is as follows:
|
||||
|
||||
```bash
|
||||
# Bots General matcher 'name/0.0'
|
||||
(?:\/[A-Za-z0-9\.]+|) {0,5}([A-Za-z0-9 \-_\!\[\]:]{0,50}(?:[Aa]rchiver|[Ii]ndexer|[Ss]craper|[Bb]ot|[Ss]pider|[Cc]rawl[a-z]{0,50}))[/ ](\d+)(?:\.(\d+)(?:\.(\d+)|)|)
|
||||
# Bots General matcher 'name 0.0'
|
||||
(?:\/[A-Za-z0-9\.]+|) {0,5}([A-Za-z0-9 \-_\!\[\]:]{0,50}(?:[Aa]rchiver|[Ii]ndexer|[Ss]craper|[Bb]ot|[Ss]pider|[Cc]rawl[a-z]{0,50})) (\d+)(?:\.(\d+)(?:\.(\d+)|)|)
|
||||
# Bots containing spider|scrape|bot(but not CUBOT)|Crawl
|
||||
((?:[A-z0-9]{1,50}|[A-z\-]{1,50} ?|)(?: the |)(?:[Ss][Pp][Ii][Dd][Ee][Rr]|[Ss]crape|[Cc][Rr][Aa][Ww][Ll])[A-z0-9]{0,50})(?:(?:[ /]| v)(\d+)(?:\.(\d+)|)(?:\.(\d+)|)|)
|
||||
# Bots Pattern '/name-0.0'
|
||||
/((?:Ant-)?Nutch|[A-z]+[Bb]ot|[A-z]+[Ss]pider|Axtaris|fetchurl|Isara|ShopSalad|Tailsweep)[ \-](\d+)(?:\.(\d+)(?:\.(\d+))?)?
|
||||
# Bots Pattern 'name/0.0'
|
||||
\b(008|Altresium|Argus|BaiduMobaider|BoardReader|DNSGroup|DataparkSearch|EDI|Goodzer|Grub|INGRID|Infohelfer|LinkedInBot|LOOQ|Nutch|OgScrper|PathDefender|Peew|PostPost|Steeler|Twitterbot|VSE|WebCrunch|WebZIP|Y!J-BR[A-Z]|YahooSeeker|envolk|sproose|wminer)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)
|
||||
# More bots
|
||||
(CSimpleSpider|Cityreview Robot|CrawlDaddy|CrawlFire|Finderbots|Index crawler|Job Roboter|KiwiStatus Spider|Lijit Crawler|QuerySeekerSpider|ScollSpider|Trends Crawler|USyd-NLP-Spider|SiteCat Webbot|BotName\/\$BotVersion|123metaspider-Bot|1470\.net crawler|50\.nu|8bo Crawler Bot|Aboundex|Accoona-[A-z]{1,30}-Agent|AdsBot-Google(?:-[a-z]{1,30}|)|altavista|AppEngine-Google|archive.{0,30}\.org_bot|archiver|Ask Jeeves|[Bb]ai[Dd]u[Ss]pider(?:-[A-Za-z]{1,30})(?:-[A-Za-z]{1,30}|)|bingbot|BingPreview|blitzbot|BlogBridge|Bloglovin|BoardReader Blog Indexer|BoardReader Favicon Fetcher|boitho.com-dc|BotSeer|BUbiNG|\b\w{0,30}favicon\w{0,30}\b|\bYeti(?:-[a-z]{1,30}|)|Catchpoint(?: bot|)|[Cc]harlotte|Checklinks|clumboot|Comodo HTTP\(S\) Crawler|Comodo-Webinspector-Crawler|ConveraCrawler|CRAWL-E|CrawlConvera|Daumoa(?:-feedfetcher|)|Feed Seeker Bot|Feedbin|findlinks|Flamingo_SearchEngine|FollowSite Bot|furlbot|Genieo|gigabot|GomezAgent|gonzo1|(?:[a-zA-Z]{1,30}-|)Googlebot(?:-[a-zA-Z]{1,30}|)|Google SketchUp|grub-client|gsa-crawler|heritrix|HiddenMarket|holmes|HooWWWer|htdig|ia_archiver|ICC-Crawler|Icarus6j|ichiro(?:/mobile|)|IconSurf|IlTrovatore(?:-Setaccio|)|InfuzApp|Innovazion Crawler|InternetArchive|IP2[a-z]{1,30}Bot|jbot\b|KaloogaBot|Kraken|Kurzor|larbin|LEIA|LesnikBot|Linguee Bot|LinkAider|LinkedInBot|Lite Bot|Llaut|lycos|Mail\.RU_Bot|masscan|masidani_bot|Mediapartners-Google|Microsoft .{0,30} Bot|mogimogi|mozDex|MJ12bot|msnbot(?:-media {0,2}|)|msrbot|Mtps Feed Aggregation System|netresearch|Netvibes|NewsGator[^/]{0,30}|^NING|Nutch[^/]{0,30}|Nymesis|ObjectsSearch|OgScrper|Orbiter|OOZBOT|PagePeeker|PagesInventory|PaxleFramework|Peeplo Screenshot Bot|PlantyNet_WebRobot|Pompos|Qwantify|Read%20Later|Reaper|RedCarpet|Retreiver|Riddler|Rival IQ|scooter|Scrapy|Scrubby|searchsight|seekbot|semanticdiscovery|SemrushBot|Simpy|SimplePie|SEOstats|SimpleRSS|SiteCon|Slackbot-LinkExpanding|Slack-ImgProxy|Slurp|snappy|Speedy Spider|Squrl Java|Stringer|TheUsefulbot|ThumbShotsBot|Thumbshots\.ru|Tiny Tiny RSS|Twitterbot|WhatsApp|URL2PNG|Vagabondo|VoilaBot|^vortex|Votay bot|^voyager|WASALive.Bot|Web-sniffer|WebThumb|WeSEE:[A-z]{1,30}|WhatWeb|WIRE|WordPress|Wotbox|www\.almaden\.ibm\.com|Xenu(?:.s|) Link Sleuth|Xerka [A-z]{1,30}Bot|yacy(?:bot|)|YahooSeeker|Yahoo! Slurp|Yandex\w{1,30}|YodaoBot(?:-[A-z]{1,30}|)|YottaaMonitor|Yowedo|^Zao|^Zao-Crawler|ZeBot_www\.ze\.bz|ZooShot|ZyBorg)(?:[ /]v?(\d+)(?:\.(\d+)(?:\.(\d+)|)|)|)
|
||||
```
|
||||
|
||||
# Configuration Samples
|
||||
|
||||
## Release Requests that would otherwise Hit the Crawler Rules
|
||||
```yaml
|
||||
allow:
|
||||
- ".*Go-http-client.*"
|
||||
```
|
||||
|
||||
Without this configuration, the default Golang web library request will be treated as a crawler and access will be denied.
|
||||
|
||||
|
||||
## Add Crawler Judgement
|
||||
```yaml
|
||||
deny:
|
||||
- "spd-tools.*"
|
||||
```
|
||||
|
||||
According to this configuration, the following requests will be denied:
|
||||
|
||||
```bash
|
||||
curl http://example.com -H 'User-Agent: spd-tools/1.1'
|
||||
curl http://exmaple.com -H 'User-Agent: spd-tools'
|
||||
```
|
||||
|
||||
## Only Enabled for Specific Routes or Domains
|
||||
```yaml
|
||||
# Use _rules_ field for fine-grained rule configurations
|
||||
_rules_:
|
||||
# Rule 1: Match by route name
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
# Rule 2: Match by domain
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
allow:
|
||||
- ".*Go-http-client.*"
|
||||
```
|
||||
In the rule sample of `_match_route_`, `route-a` and `route-b` are the route names provided when creating a new gateway route. When the current route names matches the configuration, the rule following shall be applied.
|
||||
In the rule sample of `_match_domain_`, `*.example.com` and `test.com` are the domain names used for request matching. When the current domain name matches the configuration, the rule following shall be applied.
|
||||
All rules shall be checked following the order of items in the `_rules_` field, The first matched rule will be applied. All remained will be ignored.
|
||||
@@ -1,3 +1,7 @@
|
||||
<p>
|
||||
<a href="README_EN.md"> English </a> | 中文
|
||||
</p>
|
||||
|
||||
# 功能说明
|
||||
`custom-response`插件支持配置自定义的响应,包括自定义 HTTP 应答状态码、HTTP 应答头,以及 HTTP 应答 Body。可以用于 Mock 响应,也可以用于判断特定状态码后给出自定义应答,例如在触发网关限流策略时实现自定义响应。
|
||||
|
||||
|
||||
84
plugins/wasm-cpp/extensions/custom_response/README_EN.md
Normal file
84
plugins/wasm-cpp/extensions/custom_response/README_EN.md
Normal file
@@ -0,0 +1,84 @@
|
||||
<p>
|
||||
English | <a href="README.md">中文</a>
|
||||
</p>
|
||||
|
||||
# Description
|
||||
`custom-response` plugin implements a function of sending custom responses, including custom HTTP response status codes, HTTP response headers and HTTP response body, which can be used in the scenarios of response mocking and sending a custom response for specific status codes, such as customizing the response for rate-limited requests.
|
||||
|
||||
# Configuration Fields
|
||||
|
||||
| Name | Type | Requirement | Default Value | Description |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| status_code | number | Optional | 200 | Custom HTTP response status code |
|
||||
| headers | array of string | Optional | - | Custom HTTP response header. Key and value shall be separated using `=`. |
|
||||
| body | string | Optional | - | Custom HTTP response body |
|
||||
| enable_on_status | array of number | Optional | - | The original response status code to match. Generate the custom response only the actual status code matches the configuration. Ignore the status code match if left unconfigured. |
|
||||
|
||||
# Configuration Samples
|
||||
|
||||
## Mock Responses
|
||||
|
||||
```yaml
|
||||
status_code: 200
|
||||
headers:
|
||||
- Content-Type=application/json
|
||||
- Hello=World
|
||||
body: "{\"hello\":\"world\"}"
|
||||
|
||||
```
|
||||
|
||||
According to the configuration above, all the requests will get the following custom response:
|
||||
|
||||
```text
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Hello: World
|
||||
Content-Length: 17
|
||||
|
||||
{"hello":"world"}
|
||||
```
|
||||
|
||||
## Send a Custom Response when Rate-Limited
|
||||
|
||||
```yaml
|
||||
enable_on_status:
|
||||
- 429
|
||||
status_code: 302
|
||||
headers:
|
||||
- Location=https://example.com
|
||||
```
|
||||
|
||||
When rate-limited, normally gateway will return a status code of `429` . Now, rate-limited requests will get the following custom response:
|
||||
|
||||
```text
|
||||
HTTP/1.1 302 Found
|
||||
Location: https://example.com
|
||||
```
|
||||
|
||||
So based on the 302 redirecting mechanism provided by browsers, this can redirect rate-limited users to other pages, for example, a static page hosted on CDN.
|
||||
|
||||
If you'd like to send other responses when rate-limited, please add other fields into the configuration, referring to the Mock Responses scenario.
|
||||
|
||||
## Only Enabled for Specific Routes or Domains
|
||||
```yaml
|
||||
# Use _rules_ field for fine-grained rule configurations
|
||||
_rules_:
|
||||
# Rule 1: Match by route name
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
body: "{\"hello\":\"world\"}"
|
||||
# Rule 2: Match by domain
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
enable_on_status:
|
||||
- 429
|
||||
status_code: 200
|
||||
headers:
|
||||
- Content-Type=application/json
|
||||
body: "{\"errmsg\": \"rate limited\"}"
|
||||
```
|
||||
In the rule sample of `_match_route_`, `route-a` and `route-b` are the route names provided when creating a new gateway route. When the current route names matches the configuration, the rule following shall be applied.
|
||||
In the rule sample of `_match_domain_`, `*.example.com` and `test.com` are the domain names used for request matching. When the current domain name matches the configuration, the rule following shall be applied.
|
||||
All rules shall be checked following the order of items in the `_rules_` field, The first matched rule will be applied. All remained will be ignored.
|
||||
@@ -67,4 +67,4 @@ _rules_:
|
||||
```
|
||||
In the rule sample of `_match_route_`, `route-a` and `route-b` are the route names provided when creating a new gateway route. When the current route names matches the configuration, the rule following shall be applied.
|
||||
In the rule sample of `_match_domain_`, `*.example.com` and `test.com` are the domain names used for request matching. When the current domain name matches the configuration, the rule following shall be applied.
|
||||
All rules shall be checked following the order of items in the `_rules_` field,The first matched rule will be applied. All remained will be ignored.
|
||||
All rules shall be checked following the order of items in the `_rules_` field, The first matched rule will be applied. All remained will be ignored.
|
||||
@@ -1,3 +1,7 @@
|
||||
<p>
|
||||
<a href="README_EN.md"> English </a> | 中文
|
||||
</p>
|
||||
|
||||
# 功能说明
|
||||
`request-block`插件实现了基于 URL、请求头等特征屏蔽 HTTP 请求,可以用于防护部分站点资源不对外部暴露
|
||||
|
||||
@@ -5,9 +9,9 @@
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| block_urls | array of string | 选填,`block_urls`,`block_headers`,`block_bodys` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的字符串 |
|
||||
| block_headers | array of string | 选填,`block_urls`,`block_headers`,`block_bodys` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Header 的字符串 |
|
||||
| block_bodys | array of string | 选填,`block_urls`,`block_headers`,`block_bodys` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Body 的字符串 |
|
||||
| block_urls | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的字符串 |
|
||||
| block_headers | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Header 的字符串 |
|
||||
| block_bodies | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Body 的字符串 |
|
||||
| blocked_code | number | 选填 | 403 | 配置请求被屏蔽时返回的 HTTP 状态码 |
|
||||
| blocked_message | string | 选填 | - | 配置请求被屏蔽时返回的 HTTP 应答 Body |
|
||||
| case_sensitive | bool | 选填 | true | 配置匹配时是否区分大小写,默认区分 |
|
||||
@@ -45,7 +49,7 @@ curl http://exmaple.com -H 'my-header: example-value'
|
||||
|
||||
## 屏蔽请求 body
|
||||
```yaml
|
||||
block_bodys:
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
case_sensitive: false
|
||||
```
|
||||
@@ -65,7 +69,7 @@ _rules_:
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
block_bodys:
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
# 规则二:按域名匹配生效
|
||||
- _match_domain_:
|
||||
@@ -73,7 +77,7 @@ _rules_:
|
||||
- test.com
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
block_bodys:
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
```
|
||||
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
|
||||
@@ -82,5 +86,5 @@ _rules_:
|
||||
|
||||
# 请求 Body 大小限制
|
||||
|
||||
当配置了 `block_bodys` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls` 和 `block_headers` 项时,不会对该请求执行屏蔽操作
|
||||
当配置了 `block_bodys` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits,将返回 `413 Payload Too Large`
|
||||
当配置了 `block_bodies` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls` 和 `block_headers` 项时,不会对该请求执行屏蔽操作
|
||||
当配置了 `block_bodies` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits,将返回 `413 Payload Too Large`
|
||||
|
||||
90
plugins/wasm-cpp/extensions/request_block/README_EN.md
Normal file
90
plugins/wasm-cpp/extensions/request_block/README_EN.md
Normal file
@@ -0,0 +1,90 @@
|
||||
<p>
|
||||
English | <a href="README.md">中文</a>
|
||||
</p>
|
||||
|
||||
# Description
|
||||
`request-block` plugin implements a request blocking function based on request characteristics such as URL and request header. It can be used to protect internal resources from unauthorized access.
|
||||
|
||||
# Configuration Fields
|
||||
|
||||
| Name | Type | Requirement | Default Value | Description |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| block_urls | array of string | Optional. Choose one from following: `block_urls`, `block_headers`, `block_bodies` | - | HTTP URLs to be blocked. |
|
||||
| block_headers | array of string | Optional. Choose one from following: `block_urls`, `block_headers`, `block_bodies` | - | HTTP request headers to be blocked. |
|
||||
| block_bodies | array of string | Optional. Choose one from following: `block_urls` ,`block_headers`, `block_bodies` | - | HTTP request bodies to be blocked. |
|
||||
| blocked_code | number | Optional | 403 | HTTP response status code to be sent when corresponding request is blocked. |
|
||||
| blocked_message | string | Optional | - | HTTP response body to be sent when corresponding request is blocked. |
|
||||
| case_sensitive | bool | Optional | true | Whether to use case-senstive comparison when matching. Enabled by default. |
|
||||
|
||||
# Configuration Samples
|
||||
|
||||
## Block Specific Request URLs
|
||||
```yaml
|
||||
block_urls:
|
||||
- swagger.html
|
||||
- foo=bar
|
||||
case_sensitive: false
|
||||
```
|
||||
|
||||
According to the configuration above, following requests will be blocked:
|
||||
|
||||
```bash
|
||||
curl http://example.com?foo=Bar
|
||||
curl http://exmaple.com/Swagger.html
|
||||
```
|
||||
|
||||
## Block Specific Request Headers
|
||||
```yaml
|
||||
block_headers:
|
||||
- example-key
|
||||
- example-value
|
||||
```
|
||||
|
||||
According to the configuration above, following requests will be blocked:
|
||||
|
||||
```bash
|
||||
curl http://example.com -H 'example-key: 123'
|
||||
curl http://exmaple.com -H 'my-header: example-value'
|
||||
```
|
||||
|
||||
## Block Specific Request Bodies
|
||||
```yaml
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
case_sensitive: false
|
||||
```
|
||||
|
||||
According to the configuration above, following requests will be blocked:
|
||||
|
||||
```bash
|
||||
curl http://example.com -d 'Hello World'
|
||||
curl http://exmaple.com -d 'hello world'
|
||||
```
|
||||
|
||||
## Only Enable for Specific Routes or Domains
|
||||
```yaml
|
||||
# Use _rules_ field for fine-grained rule configurations
|
||||
_rules_:
|
||||
# Rule 1: Match by route name
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
# Rule 2: Match by domain
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
```
|
||||
In the rule sample of `_match_route_`, `route-a` and `route-b` are the route names provided when creating a new gateway route. When the current route names matches the configuration, the rule following shall be applied.
|
||||
In the rule sample of `_match_domain_`, `*.example.com` and `test.com` are the domain names used for request matching. When the current domain name matches the configuration, the rule following shall be applied.
|
||||
All rules shall be checked following the order of items in the `_rules_` field, The first matched rule will be applied. All remained will be ignored.
|
||||
|
||||
# Maximum Request Body Size Limitation
|
||||
|
||||
When `block_bodies` is configured, body matching shall only be performed when its size is smaller than 32MB. If not, and no `block_urls` or `block_headers` configuration is matched, the request won't be blocked.
|
||||
When `block_bodies` is configured, if the size of request body exceeds the global configuration of DownstreamConnectionBufferLimits, a ``413 Payload Too Large`` response will be returned.
|
||||
@@ -108,24 +108,24 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
|
||||
return false;
|
||||
}
|
||||
if (!JsonArrayIterate(
|
||||
configuration, "block_bodys", [&](const json& item) -> bool {
|
||||
configuration, "block_bodies", [&](const json& item) -> bool {
|
||||
auto body = JsonValueAs<std::string>(item);
|
||||
if (body.second != Wasm::Common::JsonParserResultDetail::OK) {
|
||||
LOG_WARN("cannot parse block_bodys");
|
||||
LOG_WARN("cannot parse block_bodies");
|
||||
return false;
|
||||
}
|
||||
if (rule.case_sensitive) {
|
||||
rule.block_bodys.push_back(std::move(body.first.value()));
|
||||
rule.block_bodies.push_back(std::move(body.first.value()));
|
||||
} else {
|
||||
rule.block_bodys.push_back(
|
||||
rule.block_bodies.push_back(
|
||||
absl::AsciiStrToLower(body.first.value()));
|
||||
}
|
||||
return true;
|
||||
})) {
|
||||
LOG_WARN("failed to parse configuration for block_bodys.");
|
||||
LOG_WARN("failed to parse configuration for block_bodies.");
|
||||
return false;
|
||||
}
|
||||
if (rule.block_bodys.empty() && rule.block_headers.empty() &&
|
||||
if (rule.block_bodies.empty() && rule.block_headers.empty() &&
|
||||
rule.block_urls.empty()) {
|
||||
LOG_WARN("there is no block rules");
|
||||
return false;
|
||||
@@ -197,7 +197,7 @@ bool PluginRootContext::checkHeader(const RequestBlockConfigRule& rule,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!rule.block_bodys.empty()) {
|
||||
if (!rule.block_bodies.empty()) {
|
||||
check_body = true;
|
||||
}
|
||||
return true;
|
||||
@@ -212,7 +212,7 @@ bool PluginRootContext::checkBody(const RequestBlockConfigRule& rule,
|
||||
bodystr = absl::AsciiStrToLower(request_body);
|
||||
body = bodystr;
|
||||
}
|
||||
for (const auto& block_body : rule.block_bodys) {
|
||||
for (const auto& block_body : rule.block_bodies) {
|
||||
if (absl::StrContains(body, block_body)) {
|
||||
sendLocalResponse(rule.blocked_code, "", rule.blocked_message, {});
|
||||
return false;
|
||||
|
||||
@@ -45,7 +45,7 @@ struct RequestBlockConfigRule {
|
||||
bool case_sensitive = true;
|
||||
std::vector<std::string> block_urls;
|
||||
std::vector<std::string> block_headers;
|
||||
std::vector<std::string> block_bodys;
|
||||
std::vector<std::string> block_bodies;
|
||||
};
|
||||
|
||||
// PluginRootContext is the root context for all streams processed by the
|
||||
|
||||
@@ -128,7 +128,7 @@ TEST_F(RequestBlockTest, CaseSensitive) {
|
||||
{
|
||||
"block_urls": ["?foo=bar", "swagger.html"],
|
||||
"block_headers": ["headerKey", "headerValue"],
|
||||
"block_bodys": ["Hello World"]
|
||||
"block_bodies": ["Hello World"]
|
||||
})";
|
||||
|
||||
config_.set({configuration.data(), configuration.size()});
|
||||
@@ -188,7 +188,7 @@ TEST_F(RequestBlockTest, CaseInsensitive) {
|
||||
"blocked_code": 404,
|
||||
"block_urls": ["?foo=bar", "swagger.html"],
|
||||
"block_headers": ["headerKey", "headerValue"],
|
||||
"block_bodys": ["Hello World"]
|
||||
"block_bodies": ["Hello World"]
|
||||
})";
|
||||
|
||||
config_.set({configuration.data(), configuration.size()});
|
||||
|
||||
@@ -34,7 +34,7 @@ docker push <your_registry_hub>/request-block:1.0.0
|
||||
### step3. 创建 WasmPlugin 资源
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions.istio.io/v1alpha1
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: request-block
|
||||
@@ -43,7 +43,7 @@ spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
higress: higress-system-higress-gateway
|
||||
pluginConfig:
|
||||
defaultConfig:
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
url: oci://<your_registry_hub>/request-block:1.0.0
|
||||
@@ -70,7 +70,7 @@ content-length: 0
|
||||
## 路由级或域名级生效
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions.istio.io/v1alpha1
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: request-block
|
||||
@@ -79,29 +79,32 @@ spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
higress: higress-system-higress-gateway
|
||||
pluginConfig:
|
||||
defaultConfig:
|
||||
# 跟上面例子一样,这个配置会全局生效,但如果被下面规则匹配到,则会改为执行命中规则的配置
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
_rules_:
|
||||
matchRules:
|
||||
# 路由级生效配置
|
||||
- _match_route_:
|
||||
- default/foo
|
||||
- ingress:
|
||||
- default/foo
|
||||
# default 命名空间下名为 foo 的 ingress 会执行下面这个配置
|
||||
block_bodys:
|
||||
- "foo"
|
||||
- _match_route_:
|
||||
- default/bar
|
||||
# default 命名空间下名为 bar 的 ingress 会执行下面这个配置
|
||||
block_bodys:
|
||||
- "bar"
|
||||
config:
|
||||
block_bodies:
|
||||
- "foo"
|
||||
- ingress:
|
||||
- default/bar
|
||||
# default 命名空间下名为 bar 的 ingress 会执行下面这个配置
|
||||
config:
|
||||
block_bodies:
|
||||
- "bar"
|
||||
# 域名级生效配置
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
# 若请求匹配了上面的域名, 会执行下面这个配置
|
||||
block_bodys:
|
||||
- "foo"
|
||||
- "bar"
|
||||
- domain:
|
||||
- "*.example.com"
|
||||
# 若请求匹配了上面的域名, 会执行下面这个配置
|
||||
config:
|
||||
block_bodies:
|
||||
- "foo"
|
||||
- "bar"
|
||||
url: oci://<your_registry_hub>/request-block:1.0.0
|
||||
```
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ docker push <your_registry_hub>/request-block:1.0.0
|
||||
Read this [document](https://istio.io/latest/docs/reference/config/proxy_extensions/wasm-plugin/) to learn more about wasmplugin.
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions.istio.io/v1alpha1
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: request-block
|
||||
@@ -42,7 +42,7 @@ spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
higress: higress-system-higress-gateway
|
||||
pluginConfig:
|
||||
defaultConfig:
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
url: oci://<your_registry_hub>/request-block:1.0.0
|
||||
@@ -64,7 +64,7 @@ content-length: 0
|
||||
## route-level & domain-level takes effect
|
||||
|
||||
```yaml
|
||||
apiVersion: extensions.istio.io/v1alpha1
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: request-block
|
||||
@@ -73,29 +73,32 @@ spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
higress: higress-system-higress-gateway
|
||||
pluginConfig:
|
||||
defaultConfig:
|
||||
# this config will take effect globally (all incoming requests not matched by rules below)
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
_rules_:
|
||||
# route-level takes effect
|
||||
- _match_route_:
|
||||
- default/foo
|
||||
# the ingress foo in namespace default will use this config
|
||||
block_bodys:
|
||||
- "foo"
|
||||
- _match_route_:
|
||||
- default/bar
|
||||
# the ingress bar in namespace default will use this config
|
||||
block_bodys:
|
||||
- "bar"
|
||||
# domain-level takes effect
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
# if the request's domain matched, this config will be used
|
||||
block_bodys:
|
||||
- "foo"
|
||||
- "bar"
|
||||
matchRules:
|
||||
# ingress-level takes effect
|
||||
- ingress:
|
||||
- default/foo
|
||||
# the ingress foo in namespace default will use this config
|
||||
config:
|
||||
block_bodies:
|
||||
- "foo"
|
||||
- ingress:
|
||||
- default/bar
|
||||
# the ingress bar in namespace default will use this config
|
||||
config:
|
||||
block_bodies:
|
||||
- "bar"
|
||||
# domain-level takes effect
|
||||
- domain:
|
||||
- "*.example.com"
|
||||
# if the request's domain matched, this config will be used
|
||||
config:
|
||||
block_bodies:
|
||||
- "foo"
|
||||
- "bar"
|
||||
url: oci://<your_registry_hub>/request-block:1.0.0
|
||||
```
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| block_urls | array of string | 选填,`block_urls`,`block_headers`,`block_bodys` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的字符串 |
|
||||
| block_headers | array of string | 选填,`block_urls`,`block_headers`,`block_bodys` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Header 的字符串 |
|
||||
| block_bodys | array of string | 选填,`block_urls`,`block_headers`,`block_bodys` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Body 的字符串 |
|
||||
| block_urls | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的字符串 |
|
||||
| block_headers | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Header 的字符串 |
|
||||
| block_bodies | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Body 的字符串 |
|
||||
| blocked_code | number | 选填 | 403 | 配置请求被屏蔽时返回的 HTTP 状态码 |
|
||||
| blocked_message | string | 选填 | - | 配置请求被屏蔽时返回的 HTTP 应答 Body |
|
||||
| case_sensitive | bool | 选填 | true | 配置匹配时是否区分大小写,默认区分 |
|
||||
@@ -45,7 +45,7 @@ curl http://exmaple.com -H 'my-header: example-value'
|
||||
|
||||
## 屏蔽请求 body
|
||||
```yaml
|
||||
block_bodys:
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
case_sensitive: false
|
||||
```
|
||||
@@ -65,7 +65,7 @@ _rules_:
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
block_bodys:
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
# 规则二:按域名匹配生效
|
||||
- _match_domain_:
|
||||
@@ -73,7 +73,7 @@ _rules_:
|
||||
- test.com
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
block_bodys:
|
||||
block_bodies:
|
||||
- "hello world"
|
||||
```
|
||||
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
|
||||
@@ -82,5 +82,5 @@ _rules_:
|
||||
|
||||
# 请求 Body 大小限制
|
||||
|
||||
当配置了 `block_bodys` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls` 和 `block_headers` 项时,不会对该请求执行屏蔽操作
|
||||
当配置了 `block_bodys` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits,将返回 `413 Payload Too Large`
|
||||
当配置了 `block_bodies` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls` 和 `block_headers` 项时,不会对该请求执行屏蔽操作
|
||||
当配置了 `block_bodies` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits,将返回 `413 Payload Too Large`
|
||||
|
||||
@@ -41,7 +41,7 @@ type RequestBlockConfig struct {
|
||||
caseSensitive bool
|
||||
blockUrls []string
|
||||
blockHeaders []string
|
||||
blockBodys []string
|
||||
blockBodies []string
|
||||
}
|
||||
|
||||
func parseConfig(json gjson.Result, config *RequestBlockConfig, log wrapper.Log) error {
|
||||
@@ -75,19 +75,19 @@ func parseConfig(json gjson.Result, config *RequestBlockConfig, log wrapper.Log)
|
||||
config.blockHeaders = append(config.blockHeaders, strings.ToLower(header))
|
||||
}
|
||||
}
|
||||
for _, item := range json.Get("block_bodys").Array() {
|
||||
for _, item := range json.Get("block_bodies").Array() {
|
||||
body := item.String()
|
||||
if body == "" {
|
||||
continue
|
||||
}
|
||||
if config.caseSensitive {
|
||||
config.blockBodys = append(config.blockBodys, body)
|
||||
config.blockBodies = append(config.blockBodies, body)
|
||||
} else {
|
||||
config.blockBodys = append(config.blockBodys, strings.ToLower(body))
|
||||
config.blockBodies = append(config.blockBodies, strings.ToLower(body))
|
||||
}
|
||||
}
|
||||
if len(config.blockUrls) == 0 && len(config.blockHeaders) == 0 &&
|
||||
len(config.blockBodys) == 0 {
|
||||
len(config.blockBodies) == 0 {
|
||||
return errors.New("there is no block rules")
|
||||
}
|
||||
return nil
|
||||
@@ -131,7 +131,7 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config RequestBlockConfig, lo
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(config.blockBodys) == 0 {
|
||||
if len(config.blockBodies) == 0 {
|
||||
ctx.DontReadRequestBody()
|
||||
}
|
||||
return types.ActionContinue
|
||||
@@ -142,7 +142,7 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config RequestBlockConfig, body
|
||||
if !config.caseSensitive {
|
||||
bodyStr = strings.ToLower(bodyStr)
|
||||
}
|
||||
for _, blockBody := range config.blockBodys {
|
||||
for _, blockBody := range config.blockBodies {
|
||||
if strings.Contains(bodyStr, blockBody) {
|
||||
proxywasm.SendHttpResponse(config.blockedCode, nil, []byte(config.blockedMessage), -1)
|
||||
return types.ActionContinue
|
||||
|
||||
@@ -50,6 +50,7 @@ kind: Ingress
|
||||
metadata:
|
||||
name: hello-world
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
|
||||
@@ -9,6 +9,7 @@ metadata:
|
||||
name: echo-gray
|
||||
namespace: default
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
|
||||
@@ -9,6 +9,7 @@ metadata:
|
||||
name: echo
|
||||
namespace: default
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
|
||||
@@ -29,6 +29,7 @@ metadata:
|
||||
name: echo
|
||||
namespace: default
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
|
||||
82
samples/quickstart.yaml
Normal file
82
samples/quickstart.yaml
Normal file
@@ -0,0 +1,82 @@
|
||||
kind: Pod
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: foo-app
|
||||
labels:
|
||||
app: foo
|
||||
spec:
|
||||
containers:
|
||||
- name: foo-app
|
||||
image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/http-echo:0.2.4-alpine
|
||||
args:
|
||||
- "-text=foo"
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: foo-service
|
||||
spec:
|
||||
selector:
|
||||
app: foo
|
||||
ports:
|
||||
# Default port used by the image
|
||||
- port: 5678
|
||||
---
|
||||
kind: Pod
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: bar-app
|
||||
labels:
|
||||
app: bar
|
||||
spec:
|
||||
containers:
|
||||
- name: bar-app
|
||||
image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/http-echo:0.2.4-alpine
|
||||
args:
|
||||
- "-text=bar"
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: bar-service
|
||||
spec:
|
||||
selector:
|
||||
app: bar
|
||||
ports:
|
||||
# Default port used by the image
|
||||
- port: 5678
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: foo
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/foo"
|
||||
backend:
|
||||
service:
|
||||
name: foo-service
|
||||
port:
|
||||
number: 5678
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: bar
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/bar"
|
||||
backend:
|
||||
service:
|
||||
name: bar-service
|
||||
port:
|
||||
number: 5678
|
||||
---
|
||||
@@ -12,10 +12,10 @@ spec:
|
||||
- ingress:
|
||||
- default/foo
|
||||
config:
|
||||
block_bodys:
|
||||
block_bodies:
|
||||
- "foo"
|
||||
- ingress:
|
||||
- default/bar
|
||||
config:
|
||||
block_bodys:
|
||||
block_bodies:
|
||||
- "bar"
|
||||
|
||||
14
samples/wasmplugin/waf.yaml
Normal file
14
samples/wasmplugin/waf.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: extensions.higress.io/v1alpha1
|
||||
kind: WasmPlugin
|
||||
metadata:
|
||||
name: waf
|
||||
namespace: higress-system
|
||||
spec:
|
||||
# build from https://github.com/corazawaf/coraza-proxy-wasm
|
||||
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/corazawaf:1.0.0
|
||||
defaultConfig:
|
||||
rules:
|
||||
- "Include @crs-setup-conf"
|
||||
- "Include @recommended-conf"
|
||||
- "Include @owasp_crs/*.conf"
|
||||
- "SecRuleEngine on"
|
||||
@@ -7,13 +7,22 @@ Higress e2e tests are mainly focusing on two parts for now:
|
||||
|
||||
## Ingress APIs Conformance Tests
|
||||
|
||||
Higress provides make target to run ingress api conformance tests: `make e2e-test`. It can be divided into below steps:
|
||||
### Architecture
|
||||
|
||||

|
||||
|
||||
### Workflow
|
||||
|
||||

|
||||
|
||||
Higress provides make target to run ingress api conformance tests: `make ingress-conformance-test`. It can be divided into below steps:
|
||||
|
||||
1. delete-cluster: checks if we have undeleted kind cluster.
|
||||
2. create-cluster: create a new kind cluster.
|
||||
3. kube-load-image: build a dev image of higress, and load it into kind cluster.
|
||||
4. install-dev: install higress-controller with dev image, and latest higress-gateway, istiod with helm.
|
||||
5. run-e2e-test:
|
||||
3. docker-build: build a dev image of higress.
|
||||
4. kube-load-image: load dev higress-controller image it into kind cluster.
|
||||
5. install-dev: install higress-controller with dev image, and latest higress-gateway, istiod with helm.
|
||||
6. run-e2e-test:
|
||||
1. Setup conformance suite, like define what conformance tests we want to run, in `e2e_test.go` / `higressTests Slice`. Each case we choose to open is difined in `test/ingress/conformance/tests`.
|
||||
2. Prepare resources and install them into cluster, like backend services/deployments.
|
||||
3. Load conformance tests we choose to open in `e2e_test.go` / `higressTests Slice`, and run them one by one, fail if it is not expected.
|
||||
|
||||
BIN
test/ingress/arch.png
Normal file
BIN
test/ingress/arch.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
76
test/ingress/conformance/tests/httproute-canary-header.go
Normal file
76
test/ingress/conformance/tests/httproute-canary-header.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/http"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
)
|
||||
|
||||
func init() {
|
||||
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteRewritePath)
|
||||
}
|
||||
|
||||
var HTTPRouteCanaryHeader = suite.ConformanceTest{
|
||||
ShortName: "HTTPRouteCanaryHeader",
|
||||
Description: "The Ingress in the higress-conformance-infra namespace uses the canary header traffic split",
|
||||
Manifests: []string{"tests/httproute-canary-header.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
testcases := []http.Assertion{
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/echo",
|
||||
Host: "canary.higress.io",
|
||||
Headers: map[string]string{"traffic-split-higress": "true"},
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v2",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/echo",
|
||||
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)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
55
test/ingress/conformance/tests/httproute-canary-header.yaml
Normal file
55
test/ingress/conformance/tests/httproute-canary-header.yaml
Normal file
@@ -0,0 +1,55 @@
|
||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/canary: "true"
|
||||
nginx.ingress.kubernetes.io/canary-by-header: "traffic-split-higress"
|
||||
nginx.ingress.kubernetes.io/canary-by-header-value: "true"
|
||||
name: ingress-echo-canary-value
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: canary.higress.io
|
||||
http:
|
||||
paths:
|
||||
- path: /echo
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ingress-echo
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: canary.higress.io
|
||||
http:
|
||||
paths:
|
||||
- path: /echo
|
||||
pathType: Exact
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v2
|
||||
port:
|
||||
number: 8080
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/http"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
)
|
||||
|
||||
func init() {
|
||||
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteHostNameSameNamespace)
|
||||
}
|
||||
|
||||
var HTTPRouteHostNameSameNamespace = suite.ConformanceTest{
|
||||
ShortName: "HTTPRouteHostNameSameNamespace",
|
||||
Description: "A Ingress in the higress-conformance-infra namespace demonstrates host match ability",
|
||||
Manifests: []string{"tests/httproute-hostname-same-namespace.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
testcases := []http.Assertion{
|
||||
{
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo",
|
||||
Host: "foo.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v2",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo",
|
||||
Host: "bar.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v2",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/bar",
|
||||
Host: "foo.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v3",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/bar",
|
||||
Host: "bar.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/any",
|
||||
Host: "any.bar.com",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("HTTP request should reach infra-backend with different hostname", func(t *testing.T) {
|
||||
for _, testcase := range testcases {
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: httproute-hostname-same-namespace
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: "foo.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/foo"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
- host: "bar.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/foo"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v2
|
||||
port:
|
||||
number: 8080
|
||||
- host: "foo.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/bar"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v2
|
||||
port:
|
||||
number: 8080
|
||||
- host: "bar.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/bar"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v3
|
||||
port:
|
||||
number: 8080
|
||||
- host: "*.bar.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/any"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
91
test/ingress/conformance/tests/httproute-rewrite-host.go
Normal file
91
test/ingress/conformance/tests/httproute-rewrite-host.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/http"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
)
|
||||
|
||||
func init() {
|
||||
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteRewritePath)
|
||||
}
|
||||
|
||||
var HTTPRouteRewriteHost = suite.ConformanceTest{
|
||||
ShortName: "HTTPRouteRewriteHost",
|
||||
Description: "A single Ingress in the higress-conformance-infra namespace uses the rewrite host",
|
||||
Manifests: []string{"tests/httproute-rewrite-host.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
testcases := []http.Assertion{
|
||||
{
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo",
|
||||
Host: "foo.com",
|
||||
},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{
|
||||
Host: "higress.io",
|
||||
Path: "/foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
}, {
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/foo",
|
||||
Host: "bar.com",
|
||||
},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{
|
||||
Host: "higress.io",
|
||||
Path: "/foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v2",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("Rewrite HTTPRoute Host", func(t *testing.T) {
|
||||
for _, testcase := range testcases {
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
44
test/ingress/conformance/tests/httproute-rewrite-host.yaml
Normal file
44
test/ingress/conformance/tests/httproute-rewrite-host.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/upstream-vhost: "higress.io"
|
||||
name: httproute-rewrite-host
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- host: "foo.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/foo"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
- host: "bar.com"
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: "/foo"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v2
|
||||
port:
|
||||
number: 8080
|
||||
56
test/ingress/conformance/tests/httproute-rewrite-path.go
Normal file
56
test/ingress/conformance/tests/httproute-rewrite-path.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/http"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
)
|
||||
|
||||
func init() {
|
||||
HigressConformanceTests = append(HigressConformanceTests, HTTPRouteRewritePath)
|
||||
}
|
||||
|
||||
var HTTPRouteRewritePath = suite.ConformanceTest{
|
||||
ShortName: "HTTPRouteRewritePath",
|
||||
Description: "A single Ingress in the higress-conformance-infra namespace uses the rewrite path",
|
||||
Manifests: []string{"tests/httproute-rewrite-path.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
|
||||
t.Run("Rewrite HTTPRoute Path", func(t *testing.T) {
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.Assertion{
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{Path: "/svc/foo"},
|
||||
ExpectedRequest: &http.ExpectedRequest{
|
||||
Request: http.Request{Path: "/foo"},
|
||||
},
|
||||
},
|
||||
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
33
test/ingress/conformance/tests/httproute-rewrite-path.yaml
Normal file
33
test/ingress/conformance/tests/httproute-rewrite-path.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
# Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: "/$1"
|
||||
name: httproute-rewrite-path
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- pathType: ImplementationSpecific
|
||||
path: "/svc/(.*)"
|
||||
backend:
|
||||
service:
|
||||
name: infra-backend-v1
|
||||
port:
|
||||
number: 8080
|
||||
@@ -27,16 +27,25 @@ func init() {
|
||||
|
||||
var HTTPRouteSimpleSameNamespace = suite.ConformanceTest{
|
||||
ShortName: "HTTPRouteSimpleSameNamespace",
|
||||
Description: "A single Ingress in the higress-conformance-infra namespace attaches to a Gateway in the same namespace",
|
||||
Description: "A single Ingress in the higress-conformance-infra namespace demonstrates basic routing ability",
|
||||
Manifests: []string{"tests/httproute-simple-same-namespace.yaml"},
|
||||
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
|
||||
|
||||
t.Run("Simple HTTP request should reach infra-backend", func(t *testing.T) {
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.ExpectedResponse{
|
||||
Request: http.Request{Path: "/hello-world"},
|
||||
Response: http.Response{StatusCode: 200},
|
||||
Backend: "infra-backend-v1",
|
||||
Namespace: "higress-conformance-infra",
|
||||
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, http.Assertion{
|
||||
Meta: http.AssertionMeta{
|
||||
TargetBackend: "infra-backend-v1",
|
||||
TargetNamespace: "higress-conformance-infra",
|
||||
},
|
||||
Request: http.AssertionRequest{
|
||||
ActualRequest: http.Request{
|
||||
Path: "/hello-world",
|
||||
},
|
||||
},
|
||||
Response: http.AssertionResponse{
|
||||
ExpectedResponse: http.Response{
|
||||
StatusCode: 200,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
@@ -18,6 +18,7 @@ metadata:
|
||||
name: higress-conformance-infra-test
|
||||
namespace: higress-conformance-infra
|
||||
spec:
|
||||
ingressClassName: higress
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
|
||||
@@ -25,32 +25,40 @@ import (
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/roundtripper"
|
||||
)
|
||||
|
||||
// ExpectedResponse defines the response expected for a given request.
|
||||
type ExpectedResponse struct {
|
||||
// Request defines the request to make.
|
||||
Request Request
|
||||
type Assertion struct {
|
||||
Meta AssertionMeta
|
||||
Request AssertionRequest
|
||||
Response AssertionResponse
|
||||
}
|
||||
|
||||
type AssertionMeta struct {
|
||||
// TestCaseName is the User Given TestCase name
|
||||
TestCaseName string
|
||||
// TargetBackend defines the target backend service
|
||||
TargetBackend string
|
||||
// TargetNamespace defines the target backend namespace
|
||||
TargetNamespace string
|
||||
}
|
||||
|
||||
type AssertionRequest struct {
|
||||
// ActualRequest defines the request to make.
|
||||
ActualRequest Request
|
||||
|
||||
// ExpectedRequest defines the request that
|
||||
// is expected to arrive at the backend. If
|
||||
// not specified, the backend request will be
|
||||
// expected to match Request.
|
||||
ExpectedRequest *ExpectedRequest
|
||||
|
||||
RedirectRequest *roundtripper.RedirectRequest
|
||||
}
|
||||
|
||||
// BackendSetResponseHeaders is a set of headers
|
||||
// the echoserver should set in its response.
|
||||
BackendSetResponseHeaders map[string]string
|
||||
|
||||
// Response defines what response the test case
|
||||
type AssertionResponse struct {
|
||||
// ExpectedResponse defines what response the test case
|
||||
// should receive.
|
||||
Response Response
|
||||
|
||||
Backend string
|
||||
Namespace string
|
||||
|
||||
// User Given TestCase name
|
||||
TestCaseName string
|
||||
ExpectedResponse Response
|
||||
// AdditionalResponseHeaders is a set of headers
|
||||
// the echoserver should set in its response.
|
||||
AdditionalResponseHeaders map[string]string
|
||||
}
|
||||
|
||||
// Request can be used as both the request to make and a means to verify
|
||||
@@ -91,38 +99,38 @@ const requiredConsecutiveSuccesses = 3
|
||||
//
|
||||
// Once the request succeeds consistently with the response having the expected status code, make
|
||||
// additional assertions on the response body using the provided ExpectedResponse.
|
||||
func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripper.RoundTripper, timeoutConfig config.TimeoutConfig, gwAddr string, expected ExpectedResponse) {
|
||||
func MakeRequestAndExpectEventuallyConsistentResponse(t *testing.T, r roundtripper.RoundTripper, timeoutConfig config.TimeoutConfig, gwAddr string, expected Assertion) {
|
||||
t.Helper()
|
||||
|
||||
if expected.Request.Method == "" {
|
||||
expected.Request.Method = "GET"
|
||||
if expected.Request.ActualRequest.Method == "" {
|
||||
expected.Request.ActualRequest.Method = "GET"
|
||||
}
|
||||
|
||||
if expected.Response.StatusCode == 0 {
|
||||
expected.Response.StatusCode = 200
|
||||
if expected.Response.ExpectedResponse.StatusCode == 0 {
|
||||
expected.Response.ExpectedResponse.StatusCode = 200
|
||||
}
|
||||
|
||||
t.Logf("Making %s request to http://%s%s", expected.Request.Method, gwAddr, expected.Request.Path)
|
||||
t.Logf("Making %s request to http://%s%s", expected.Request.ActualRequest.Method, gwAddr, expected.Request.ActualRequest.Path)
|
||||
|
||||
path, query, _ := strings.Cut(expected.Request.Path, "?")
|
||||
path, query, _ := strings.Cut(expected.Request.ActualRequest.Path, "?")
|
||||
|
||||
req := roundtripper.Request{
|
||||
Method: expected.Request.Method,
|
||||
Host: expected.Request.Host,
|
||||
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.UnfollowRedirect,
|
||||
UnfollowRedirect: expected.Request.ActualRequest.UnfollowRedirect,
|
||||
}
|
||||
|
||||
if expected.Request.Headers != nil {
|
||||
for name, value := range expected.Request.Headers {
|
||||
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.BackendSetResponseHeaders {
|
||||
for name, val := range expected.Response.AdditionalResponseHeaders {
|
||||
backendSetHeaders = append(backendSetHeaders, name+":"+val)
|
||||
}
|
||||
req.Headers["X-Echo-Set-Header"] = []string{strings.Join(backendSetHeaders, ",")}
|
||||
@@ -170,7 +178,7 @@ func awaitConvergence(t *testing.T, threshold int, maxTimeToConsistency time.Dur
|
||||
// WaitForConsistentResponse repeats the provided request until it completes with a response having
|
||||
// the expected response consistently. The provided threshold determines how many times in
|
||||
// a row this must occur to be considered "consistent".
|
||||
func WaitForConsistentResponse(t *testing.T, r roundtripper.RoundTripper, req roundtripper.Request, expected ExpectedResponse, threshold int, maxTimeToConsistency time.Duration) {
|
||||
func WaitForConsistentResponse(t *testing.T, r roundtripper.RoundTripper, req roundtripper.Request, expected Assertion, threshold int, maxTimeToConsistency time.Duration) {
|
||||
awaitConvergence(t, threshold, maxTimeToConsistency, func(elapsed time.Duration) bool {
|
||||
cReq, cRes, err := r.CaptureRoundTrip(req)
|
||||
if err != nil {
|
||||
@@ -188,43 +196,43 @@ func WaitForConsistentResponse(t *testing.T, r roundtripper.RoundTripper, req ro
|
||||
t.Logf("Request passed")
|
||||
}
|
||||
|
||||
func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedRequest, cRes *roundtripper.CapturedResponse, expected ExpectedResponse) error {
|
||||
if expected.Response.StatusCode != cRes.StatusCode {
|
||||
return fmt.Errorf("expected status code to be %d, got %d", expected.Response.StatusCode, cRes.StatusCode)
|
||||
func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedRequest, cRes *roundtripper.CapturedResponse, expected Assertion) error {
|
||||
if expected.Response.ExpectedResponse.StatusCode != cRes.StatusCode {
|
||||
return fmt.Errorf("expected status code to be %d, got %d", expected.Response.ExpectedResponse.StatusCode, cRes.StatusCode)
|
||||
}
|
||||
if cRes.StatusCode == 200 {
|
||||
// The request expected to arrive at the backend is
|
||||
// the same as the request made, unless otherwise
|
||||
// specified.
|
||||
if expected.ExpectedRequest == nil {
|
||||
expected.ExpectedRequest = &ExpectedRequest{Request: expected.Request}
|
||||
if expected.Request.ExpectedRequest == nil {
|
||||
expected.Request.ExpectedRequest = &ExpectedRequest{Request: expected.Request.ActualRequest}
|
||||
}
|
||||
|
||||
if expected.ExpectedRequest.Method == "" {
|
||||
expected.ExpectedRequest.Method = "GET"
|
||||
if expected.Request.ExpectedRequest.Method == "" {
|
||||
expected.Request.ExpectedRequest.Method = "GET"
|
||||
}
|
||||
|
||||
if expected.ExpectedRequest.Host != "" && expected.ExpectedRequest.Host != cReq.Host {
|
||||
return fmt.Errorf("expected host to be %s, got %s", expected.ExpectedRequest.Host, cReq.Host)
|
||||
if expected.Request.ExpectedRequest.Host != "" && expected.Request.ExpectedRequest.Host != cReq.Host {
|
||||
return fmt.Errorf("expected host to be %s, got %s", expected.Request.ExpectedRequest.Host, cReq.Host)
|
||||
}
|
||||
|
||||
if expected.ExpectedRequest.Path != cReq.Path {
|
||||
return fmt.Errorf("expected path to be %s, got %s", expected.ExpectedRequest.Path, cReq.Path)
|
||||
if expected.Request.ExpectedRequest.Path != cReq.Path {
|
||||
return fmt.Errorf("expected path to be %s, got %s", expected.Request.ExpectedRequest.Path, cReq.Path)
|
||||
}
|
||||
if expected.ExpectedRequest.Method != cReq.Method {
|
||||
return fmt.Errorf("expected method to be %s, got %s", expected.ExpectedRequest.Method, cReq.Method)
|
||||
if expected.Request.ExpectedRequest.Method != cReq.Method {
|
||||
return fmt.Errorf("expected method to be %s, got %s", expected.Request.ExpectedRequest.Method, cReq.Method)
|
||||
}
|
||||
if expected.Namespace != cReq.Namespace {
|
||||
return fmt.Errorf("expected namespace to be %s, got %s", expected.Namespace, cReq.Namespace)
|
||||
if expected.Meta.TargetNamespace != cReq.Namespace {
|
||||
return fmt.Errorf("expected namespace to be %s, got %s", expected.Meta.TargetNamespace, cReq.Namespace)
|
||||
}
|
||||
if expected.ExpectedRequest.Headers != nil {
|
||||
if expected.Request.ExpectedRequest.Headers != nil {
|
||||
if cReq.Headers == nil {
|
||||
return fmt.Errorf("no headers captured, expected %v", len(expected.ExpectedRequest.Headers))
|
||||
return fmt.Errorf("no headers captured, expected %v", len(expected.Request.ExpectedRequest.Headers))
|
||||
}
|
||||
for name, val := range cReq.Headers {
|
||||
cReq.Headers[strings.ToLower(name)] = val
|
||||
}
|
||||
for name, expectedVal := range expected.ExpectedRequest.Headers {
|
||||
for name, expectedVal := range expected.Request.ExpectedRequest.Headers {
|
||||
actualVal, ok := cReq.Headers[strings.ToLower(name)]
|
||||
if !ok {
|
||||
return fmt.Errorf("expected %s header to be set, actual headers: %v", name, cReq.Headers)
|
||||
@@ -235,15 +243,15 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques
|
||||
}
|
||||
}
|
||||
|
||||
if expected.Response.Headers != nil {
|
||||
if expected.Response.ExpectedResponse.Headers != nil {
|
||||
if cRes.Headers == nil {
|
||||
return fmt.Errorf("no headers captured, expected %v", len(expected.ExpectedRequest.Headers))
|
||||
return fmt.Errorf("no headers captured, expected %v", len(expected.Request.ExpectedRequest.Headers))
|
||||
}
|
||||
for name, val := range cRes.Headers {
|
||||
cRes.Headers[strings.ToLower(name)] = val
|
||||
}
|
||||
|
||||
for name, expectedVal := range expected.Response.Headers {
|
||||
for name, expectedVal := range expected.Response.ExpectedResponse.Headers {
|
||||
actualVal, ok := cRes.Headers[strings.ToLower(name)]
|
||||
if !ok {
|
||||
return fmt.Errorf("expected %s header to be set, actual headers: %v", name, cRes.Headers)
|
||||
@@ -253,12 +261,12 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques
|
||||
}
|
||||
}
|
||||
|
||||
if len(expected.Response.AbsentHeaders) > 0 {
|
||||
if len(expected.Response.ExpectedResponse.AbsentHeaders) > 0 {
|
||||
for name, val := range cRes.Headers {
|
||||
cRes.Headers[strings.ToLower(name)] = val
|
||||
}
|
||||
|
||||
for _, name := range expected.Response.AbsentHeaders {
|
||||
for _, name := range expected.Response.ExpectedResponse.AbsentHeaders {
|
||||
val, ok := cRes.Headers[strings.ToLower(name)]
|
||||
if ok {
|
||||
return fmt.Errorf("expected %s header to not be set, got %s", name, val)
|
||||
@@ -268,12 +276,12 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques
|
||||
|
||||
// Verify that headers expected *not* to be present on the
|
||||
// request are actually not present.
|
||||
if len(expected.ExpectedRequest.AbsentHeaders) > 0 {
|
||||
if len(expected.Request.ExpectedRequest.AbsentHeaders) > 0 {
|
||||
for name, val := range cReq.Headers {
|
||||
cReq.Headers[strings.ToLower(name)] = val
|
||||
}
|
||||
|
||||
for _, name := range expected.ExpectedRequest.AbsentHeaders {
|
||||
for _, name := range expected.Request.ExpectedRequest.AbsentHeaders {
|
||||
val, ok := cReq.Headers[strings.ToLower(name)]
|
||||
if ok {
|
||||
return fmt.Errorf("expected %s header to not be set, got %s", name, val)
|
||||
@@ -281,74 +289,74 @@ func CompareRequest(req *roundtripper.Request, cReq *roundtripper.CapturedReques
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(cReq.Pod, expected.Backend) {
|
||||
return fmt.Errorf("expected pod name to start with %s, got %s", expected.Backend, cReq.Pod)
|
||||
if !strings.HasPrefix(cReq.Pod, expected.Meta.TargetBackend) {
|
||||
return fmt.Errorf("expected pod name to start with %s, got %s", expected.Meta.TargetBackend, cReq.Pod)
|
||||
}
|
||||
} else if roundtripper.IsRedirect(cRes.StatusCode) {
|
||||
if expected.RedirectRequest == nil {
|
||||
if expected.Request.RedirectRequest == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
setRedirectRequestDefaults(req, cRes, &expected)
|
||||
|
||||
if expected.RedirectRequest.Host != cRes.RedirectRequest.Host {
|
||||
return fmt.Errorf("expected redirected hostname to be %s, got %s", expected.RedirectRequest.Host, cRes.RedirectRequest.Host)
|
||||
if expected.Request.RedirectRequest.Host != cRes.RedirectRequest.Host {
|
||||
return fmt.Errorf("expected redirected hostname to be %s, got %s", expected.Request.RedirectRequest.Host, cRes.RedirectRequest.Host)
|
||||
}
|
||||
|
||||
if expected.RedirectRequest.Port != cRes.RedirectRequest.Port {
|
||||
return fmt.Errorf("expected redirected port to be %s, got %s", expected.RedirectRequest.Port, cRes.RedirectRequest.Port)
|
||||
if expected.Request.RedirectRequest.Port != cRes.RedirectRequest.Port {
|
||||
return fmt.Errorf("expected redirected port to be %s, got %s", expected.Request.RedirectRequest.Port, cRes.RedirectRequest.Port)
|
||||
}
|
||||
|
||||
if expected.RedirectRequest.Scheme != cRes.RedirectRequest.Scheme {
|
||||
return fmt.Errorf("expected redirected scheme to be %s, got %s", expected.RedirectRequest.Scheme, cRes.RedirectRequest.Scheme)
|
||||
if expected.Request.RedirectRequest.Scheme != cRes.RedirectRequest.Scheme {
|
||||
return fmt.Errorf("expected redirected scheme to be %s, got %s", expected.Request.RedirectRequest.Scheme, cRes.RedirectRequest.Scheme)
|
||||
}
|
||||
|
||||
if expected.RedirectRequest.Path != cRes.RedirectRequest.Path {
|
||||
return fmt.Errorf("expected redirected path to be %s, got %s", expected.RedirectRequest.Path, cRes.RedirectRequest.Path)
|
||||
if expected.Request.RedirectRequest.Path != cRes.RedirectRequest.Path {
|
||||
return fmt.Errorf("expected redirected path to be %s, got %s", expected.Request.RedirectRequest.Path, cRes.RedirectRequest.Path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get User-defined test case name or generate from expected response to a given request.
|
||||
func (er *ExpectedResponse) GetTestCaseName(i int) string {
|
||||
func (er *Assertion) GetTestCaseName(i int) string {
|
||||
|
||||
// If TestCase name is provided then use that or else generate one.
|
||||
if er.TestCaseName != "" {
|
||||
return er.TestCaseName
|
||||
if er.Meta.TestCaseName != "" {
|
||||
return er.Meta.TestCaseName
|
||||
}
|
||||
|
||||
headerStr := ""
|
||||
reqStr := ""
|
||||
|
||||
if er.Request.Headers != nil {
|
||||
if er.Request.ActualRequest.Headers != nil {
|
||||
headerStr = " with headers"
|
||||
}
|
||||
|
||||
reqStr = fmt.Sprintf("%d request to '%s%s'%s", i, er.Request.Host, er.Request.Path, headerStr)
|
||||
reqStr = fmt.Sprintf("%d request to '%s%s'%s", i, er.Request.ActualRequest.Host, er.Request.ActualRequest.Path, headerStr)
|
||||
|
||||
if er.Backend != "" {
|
||||
return fmt.Sprintf("%s should go to %s", reqStr, er.Backend)
|
||||
if er.Meta.TargetBackend != "" {
|
||||
return fmt.Sprintf("%s should go to %s", reqStr, er.Meta.TargetBackend)
|
||||
}
|
||||
return fmt.Sprintf("%s should receive a %d", reqStr, er.Response.StatusCode)
|
||||
return fmt.Sprintf("%s should receive a %d", reqStr, er.Response.ExpectedResponse.StatusCode)
|
||||
}
|
||||
|
||||
func setRedirectRequestDefaults(req *roundtripper.Request, cRes *roundtripper.CapturedResponse, expected *ExpectedResponse) {
|
||||
func setRedirectRequestDefaults(req *roundtripper.Request, cRes *roundtripper.CapturedResponse, expected *Assertion) {
|
||||
// If the expected host is nil it means we do not test host redirect.
|
||||
// In that case we are setting it to the one we got from the response because we do not know the ip/host of the gateway.
|
||||
if expected.RedirectRequest.Host == "" {
|
||||
expected.RedirectRequest.Host = cRes.RedirectRequest.Host
|
||||
if expected.Request.RedirectRequest.Host == "" {
|
||||
expected.Request.RedirectRequest.Host = cRes.RedirectRequest.Host
|
||||
}
|
||||
|
||||
if expected.RedirectRequest.Port == "" {
|
||||
expected.RedirectRequest.Port = req.URL.Port()
|
||||
if expected.Request.RedirectRequest.Port == "" {
|
||||
expected.Request.RedirectRequest.Port = req.URL.Port()
|
||||
}
|
||||
|
||||
if expected.RedirectRequest.Scheme == "" {
|
||||
expected.RedirectRequest.Scheme = req.URL.Scheme
|
||||
if expected.Request.RedirectRequest.Scheme == "" {
|
||||
expected.Request.RedirectRequest.Scheme = req.URL.Scheme
|
||||
}
|
||||
|
||||
if expected.RedirectRequest.Path == "" {
|
||||
expected.RedirectRequest.Path = req.URL.Path
|
||||
if expected.Request.RedirectRequest.Path == "" {
|
||||
expected.Request.RedirectRequest.Path = req.URL.Path
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,19 +39,9 @@ import (
|
||||
// them to the Kubernetes cluster.
|
||||
type Applier struct {
|
||||
NamespaceLabels map[string]string
|
||||
// ValidUniqueListenerPorts maps each listener port of each Gateway in the
|
||||
// manifests to a valid, unique port. There must be as many
|
||||
// ValidUniqueListenerPorts as there are listeners in the set of manifests.
|
||||
// For example, given two Gateways, each with 2 listeners, there should be
|
||||
// four ValidUniqueListenerPorts.
|
||||
// If empty or nil, ports are not modified.
|
||||
ValidUniqueListenerPorts []int
|
||||
|
||||
// IngressClass will be used as the spec.gatewayClassName when applying Gateway resources
|
||||
// IngressClass will be used as the spec.ingressClassName when applying Ingress resources
|
||||
IngressClass string
|
||||
|
||||
// ControllerName will be used as the spec.controllerName when applying GatewayClass resources
|
||||
ControllerName string
|
||||
}
|
||||
|
||||
// prepareNamespace adjusts the Namespace labels.
|
||||
|
||||
@@ -70,7 +70,6 @@ type ConformanceTestSuite struct {
|
||||
RoundTripper roundtripper.RoundTripper
|
||||
GatewayAddress string
|
||||
IngressClassName string
|
||||
ControllerName string
|
||||
Debug bool
|
||||
Cleanup bool
|
||||
BaseManifests string
|
||||
@@ -88,13 +87,6 @@ type Options struct {
|
||||
RoundTripper roundtripper.RoundTripper
|
||||
BaseManifests string
|
||||
NamespaceLabels map[string]string
|
||||
// ValidUniqueListenerPorts maps each listener port of each Gateway in the
|
||||
// manifests to a valid, unique port. There must be as many
|
||||
// ValidUniqueListenerPorts as there are listeners in the set of manifests.
|
||||
// For example, given two Gateways, each with 2 listeners, there should be
|
||||
// four ValidUniqueListenerPorts.
|
||||
// If empty or nil, ports are not modified.
|
||||
ValidUniqueListenerPorts []int
|
||||
|
||||
// CleanupBaseResources indicates whether or not the base test
|
||||
// resources such as Gateways should be cleaned up after the run.
|
||||
@@ -131,8 +123,7 @@ func New(s Options) *ConformanceTestSuite {
|
||||
BaseManifests: s.BaseManifests,
|
||||
GatewayAddress: s.GatewayAddress,
|
||||
Applier: kubernetes.Applier{
|
||||
NamespaceLabels: s.NamespaceLabels,
|
||||
ValidUniqueListenerPorts: s.ValidUniqueListenerPorts,
|
||||
NamespaceLabels: s.NamespaceLabels,
|
||||
},
|
||||
SupportedFeatures: s.SupportedFeatures,
|
||||
TimeoutConfig: s.TimeoutConfig,
|
||||
@@ -150,10 +141,8 @@ func New(s Options) *ConformanceTestSuite {
|
||||
// in the cluster. It also ensures that all relevant resources are ready.
|
||||
func (suite *ConformanceTestSuite) Setup(t *testing.T) {
|
||||
t.Logf("Test Setup: Ensuring IngressClass has been accepted")
|
||||
suite.ControllerName = suite.IngressClassName
|
||||
|
||||
suite.Applier.IngressClass = suite.IngressClassName
|
||||
suite.Applier.ControllerName = suite.ControllerName
|
||||
|
||||
t.Logf("Test Setup: Applying base manifests")
|
||||
suite.Applier.MustApplyWithCleanup(t, suite.Client, suite.TimeoutConfig, suite.BaseManifests, suite.Cleanup)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
//go:build conformance
|
||||
// +build conformance
|
||||
|
||||
// Copyright (c) 2022 Alibaba Group Holding Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,16 +18,15 @@ import (
|
||||
"flag"
|
||||
"testing"
|
||||
|
||||
"github.com/alibaba/higress/test/ingress/conformance/tests"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/flags"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/networking/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/config"
|
||||
)
|
||||
|
||||
var useUniquePorts = flag.Bool("use-unique-ports", true, "whether to use unique ports")
|
||||
"github.com/alibaba/higress/test/ingress/conformance/tests"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/flags"
|
||||
"github.com/alibaba/higress/test/ingress/conformance/utils/suite"
|
||||
)
|
||||
|
||||
func TestHigressConformanceTests(t *testing.T) {
|
||||
flag.Parse()
|
||||
@@ -43,27 +39,23 @@ func TestHigressConformanceTests(t *testing.T) {
|
||||
|
||||
require.NoError(t, v1.AddToScheme(client.Scheme()))
|
||||
|
||||
validUniqueListenerPorts := []int{
|
||||
80,
|
||||
443,
|
||||
}
|
||||
|
||||
if !*useUniquePorts {
|
||||
validUniqueListenerPorts = []int{}
|
||||
}
|
||||
|
||||
cSuite := suite.New(suite.Options{
|
||||
Client: client,
|
||||
IngressClassName: *flags.IngressClassName,
|
||||
Debug: *flags.ShowDebug,
|
||||
CleanupBaseResources: *flags.CleanupBaseResources,
|
||||
ValidUniqueListenerPorts: validUniqueListenerPorts,
|
||||
SupportedFeatures: map[suite.SupportedFeature]bool{},
|
||||
GatewayAddress: "localhost",
|
||||
Client: client,
|
||||
IngressClassName: *flags.IngressClassName,
|
||||
Debug: *flags.ShowDebug,
|
||||
CleanupBaseResources: *flags.CleanupBaseResources,
|
||||
SupportedFeatures: map[suite.SupportedFeature]bool{},
|
||||
GatewayAddress: "localhost",
|
||||
})
|
||||
|
||||
cSuite.Setup(t)
|
||||
higressTests := []suite.ConformanceTest{
|
||||
tests.HTTPRouteSimpleSameNamespace,
|
||||
tests.HTTPRouteHostNameSameNamespace,
|
||||
tests.HTTPRouteRewritePath,
|
||||
tests.HTTPRouteRewriteHost,
|
||||
tests.HTTPRouteCanaryHeader,
|
||||
}
|
||||
|
||||
cSuite.Run(t, higressTests)
|
||||
}
|
||||
|
||||
BIN
test/ingress/pipeline.png
Normal file
BIN
test/ingress/pipeline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 359 KiB |
Reference in New Issue
Block a user