From f8ffb1722c47796a3fe29a6a3d9b44d9340ee222 Mon Sep 17 00:00:00 2001 From: YaoZengzeng Date: Mon, 15 Jul 2024 16:30:36 +0800 Subject: [PATCH 1/3] add some waypoint related E2E test cases Signed-off-by: YaoZengzeng --- test/e2e/baseline_test.go | 218 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 218 insertions(+) diff --git a/test/e2e/baseline_test.go b/test/e2e/baseline_test.go index 60b08fb39..82e6046f3 100644 --- a/test/e2e/baseline_test.go +++ b/test/e2e/baseline_test.go @@ -21,6 +21,7 @@ package kmesh import ( "fmt" + "strings" "testing" "time" @@ -28,6 +29,7 @@ import ( "istio.io/istio/pkg/test/framework" "istio.io/istio/pkg/test/framework/components/echo" "istio.io/istio/pkg/test/framework/components/echo/check" + "istio.io/istio/pkg/util/sets" ) var ( @@ -138,12 +140,228 @@ spec: }) } +func TestServerSideLB(t *testing.T) { + framework.NewTest(t).Run(func(t framework.TestContext) { + runTestToServiceWaypoint(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) { + // Need HTTP + if opt.Scheme != scheme.HTTP { + return + } + var singleHost echo.Checker = func(result echo.CallResult, _ error) error { + hostnames := make([]string, len(result.Responses)) + for i, r := range result.Responses { + hostnames[i] = r.Hostname + } + unique := sets.SortedList(sets.New(hostnames...)) + if len(unique) != 1 { + return fmt.Errorf("excepted only one destination, got: %v", unique) + } + return nil + } + var multipleHost echo.Checker = func(result echo.CallResult, _ error) error { + hostnames := make([]string, len(result.Responses)) + for i, r := range result.Responses { + hostnames[i] = r.Hostname + } + unique := sets.SortedList(sets.New(hostnames...)) + want := dst.WorkloadsOrFail(t) + wn := []string{} + for _, w := range want { + wn = append(wn, w.PodName()) + } + if len(unique) != len(wn) { + return fmt.Errorf("excepted all destinations (%v), got: %v", wn, unique) + } + return nil + } + + shouldBalance := dst.Config().HasServiceAddressedWaypointProxy() + // Istio client will not reuse connections for HTTP/1.1 + opt.HTTP.HTTP2 = true + // Make sure we make multiple calls + opt.Count = 10 + c := singleHost + if shouldBalance { + c = multipleHost + } + opt.Check = check.And(check.OK(), c) + opt.NewConnectionPerRequest = false + src.CallOrFail(t, opt) + }) + }) +} + +func TestServerRouting(t *testing.T) { + framework.NewTest(t).Run(func(t framework.TestContext) { + runTestToServiceWaypoint(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) { + // Need waypoint proxy and HTTP + if opt.Scheme != scheme.HTTP { + return + } + t.NewSubTest("set header").Run(func(t framework.TestContext) { + t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{ + "Destination": dst.Config().Service, + }, `apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: route +spec: + hosts: + - "{{.Destination}}" + http: + - headers: + request: + add: + istio-custom-header: user-defined-value + route: + - destination: + host: "{{.Destination}}" +`).ApplyOrFail(t) + opt.Check = check.And( + check.OK(), + check.RequestHeader("Istio-Custom-Header", "user-defined-value")) + src.CallOrFail(t, opt) + }) + t.NewSubTest("subset").Run(func(t framework.TestContext) { + t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{ + "Destination": dst.Config().Service, + }, `apiVersion: networking.istio.io/v1alpha3 +kind: VirtualService +metadata: + name: route +spec: + hosts: + - "{{.Destination}}" + http: + - route: + - destination: + host: "{{.Destination}}" + subset: v1 +--- +apiVersion: networking.istio.io/v1beta1 +kind: DestinationRule +metadata: + name: route + namespace: +spec: + host: "{{.Destination}}" + subsets: + - labels: + version: v1 + name: v1 + - labels: + version: v2 + name: v2 +`).ApplyOrFail(t) + var exp string + for _, w := range dst.WorkloadsOrFail(t) { + if strings.Contains(w.PodName(), "-v1") { + exp = w.PodName() + } + } + opt.Count = 10 + opt.Check = check.And( + check.OK(), + check.Hostname(exp)) + src.CallOrFail(t, opt) + }) + }) + }) +} + +func TestWaypointEnvoyFilter(t *testing.T) { + framework.NewTest(t).Run(func(t framework.TestContext) { + runTestToServiceWaypoint(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) { + // Need at least one waypoint proxy and HTTP + if opt.Scheme != scheme.HTTP { + return + } + t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{ + "Destination": "waypoint", + }, `apiVersion: networking.istio.io/v1alpha3 +kind: EnvoyFilter +metadata: + name: inbound +spec: + workloadSelector: + labels: + gateway.networking.k8s.io/gateway-name: "{{.Destination}}" + configPatches: + - applyTo: HTTP_FILTER + match: + context: SIDECAR_INBOUND + listener: + filterChain: + filter: + name: "envoy.filters.network.http_connection_manager" + subFilter: + name: "envoy.filters.http.router" + patch: + operation: INSERT_BEFORE + value: + name: envoy.lua + typed_config: + "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua" + inlineCode: | + function envoy_on_request(request_handle) + request_handle:headers():add("x-lua-inbound", "hello world") + end + - applyTo: VIRTUAL_HOST + match: + context: SIDECAR_INBOUND + patch: + operation: MERGE + value: + request_headers_to_add: + - header: + key: x-vhost-inbound + value: "hello world" + - applyTo: CLUSTER + match: + context: SIDECAR_INBOUND + cluster: {} + patch: + operation: MERGE + value: + http2_protocol_options: {} +`).ApplyOrFail(t) + opt.Count = 5 + opt.Timeout = time.Second * 10 + opt.Check = check.And( + check.OK(), + check.RequestHeaders(map[string]string{ + "X-Lua-Inbound": "hello world", + "X-Vhost-Inbound": "hello world", + })) + src.CallOrFail(t, opt) + }) + }) +} + func runTest(t *testing.T, f func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions)) { framework.NewTest(t).Run(func(t framework.TestContext) { runTestContext(t, f) }) } +// runTestToServiceWaypoint runs a given function against every src/dst pair where a call will traverse a service waypoint +func runTestToServiceWaypoint(t framework.TestContext, f func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions)) { + runTestContext(t, func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions) { + if !dst.Config().HasServiceAddressedWaypointProxy() { + return + } + if !src.Config().HasProxyCapabilities() { + // Only respected if the client knows about waypoints + return + } + if src.Config().HasSidecar() { + // TODO: sidecars do not currently respect waypoints + t.Skip("https://github.com/istio/istio/issues/51445") + } + f(t, src, dst, opt) + }) +} + func runTestContext(t framework.TestContext, f func(t framework.TestContext, src echo.Instance, dst echo.Instance, opt echo.CallOptions)) { svcs := apps.All for _, src := range svcs { From 7a6ba1d16c0c80ce5deee7644ce6af8ca9ae18ed Mon Sep 17 00:00:00 2001 From: YaoZengzeng Date: Tue, 16 Jul 2024 15:53:01 +0800 Subject: [PATCH 2/3] fix comments and add code source statement Signed-off-by: YaoZengzeng --- test/e2e/README.md | 7 +++++++ test/e2e/baseline_test.go | 16 ++++++++++------ test/e2e/main_test.go | 4 ++++ test/e2e/run_test.sh | 6 +++--- 4 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 test/e2e/README.md diff --git a/test/e2e/README.md b/test/e2e/README.md new file mode 100644 index 000000000..05b6213ec --- /dev/null +++ b/test/e2e/README.md @@ -0,0 +1,7 @@ +# Kmesh E2E test + +Kmesh E2E test is used to validate the system as a whole, ensuring that all the individual components and itegrations work together seamlessly. + +It's integrated into CI to ensure that each merge of code will not break existing functions. You can also run it locally during development for self-testing. It plays an important role in maintaining the stability and availability of Kmesh. + +NOTE: Kmesh E2E test framework and test cases is heavily inspired by istio integration framework (https://github.com/istio/istio/tree/master/tests/integration), both in architecture and code. diff --git a/test/e2e/baseline_test.go b/test/e2e/baseline_test.go index 82e6046f3..ca43deb89 100644 --- a/test/e2e/baseline_test.go +++ b/test/e2e/baseline_test.go @@ -17,6 +17,10 @@ * limitations under the License. */ +// NOTE: THE CODE IN THIS FILE IS MAINLY REFERENCED FROM ISTIO INTEGRATION +// FRAMEWORK(https://github.com/istio/istio/tree/master/tests/integration) +// AND ADAPTED FOR KMESH. + package kmesh import ( @@ -73,7 +77,7 @@ spec: - match: - headers: user: - exact: istio-custom-user + exact: kmesh-custom-user route: - destination: host: "{{.Destination}}" @@ -123,7 +127,7 @@ spec: if opt.HTTP.Headers == nil { opt.HTTP.Headers = map[string][]string{} } - opt.HTTP.Headers.Set("user", "istio-custom-user") + opt.HTTP.Headers.Set("user", "kmesh-custom-user") opt.Check = check.And( check.OK(), func(result echo.CallResult, _ error) error { @@ -134,7 +138,7 @@ spec: } return nil }) - opt.HTTP.Headers.Set("user", "istio-custom-user") + opt.HTTP.Headers.Set("user", "kmesh-custom-user") src.CallOrFail(t, opt) }) }) @@ -212,17 +216,17 @@ spec: - headers: request: add: - istio-custom-header: user-defined-value + kmesh-custom-header: user-defined-value route: - destination: host: "{{.Destination}}" `).ApplyOrFail(t) opt.Check = check.And( check.OK(), - check.RequestHeader("Istio-Custom-Header", "user-defined-value")) + check.RequestHeader("Kmesh-Custom-Header", "user-defined-value")) src.CallOrFail(t, opt) }) - t.NewSubTest("subset").Run(func(t framework.TestContext) { + t.NewSubTest("route to a specific subnet").Run(func(t framework.TestContext) { t.ConfigIstio().Eval(apps.Namespace.Name(), map[string]string{ "Destination": dst.Config().Service, }, `apiVersion: networking.istio.io/v1alpha3 diff --git a/test/e2e/main_test.go b/test/e2e/main_test.go index 5d79d362a..e4fc89f03 100644 --- a/test/e2e/main_test.go +++ b/test/e2e/main_test.go @@ -17,6 +17,10 @@ * limitations under the License. */ +// NOTE: THE CODE IN THIS FILE IS MAINLY REFERENCED FROM ISTIO INTEGRATION +// FRAMEWORK(https://github.com/istio/istio/tree/master/tests/integration) +// AND ADAPTED FOR KMESH. + package kmesh import ( diff --git a/test/e2e/run_test.sh b/test/e2e/run_test.sh index 0249cd630..439ca694e 100755 --- a/test/e2e/run_test.sh +++ b/test/e2e/run_test.sh @@ -1,8 +1,8 @@ #!/bin/bash -# NOTE: Kmesh e2e test framework is heavily inspired by istio integration -# framework (https://github.com/istio/istio/tree/master/tests/integration), -# both in architecture and code. +# NOTE: THE CODE IN THIS FILE IS MAINLY REFERENCED FROM ISTIO INTEGRATION +# FRAMEWORK(https://github.com/istio/istio/tree/master/tests/integration) +# AND ADAPTED FOR KMESH. # Exit immediately for non zero status set -e From a0cff6e29ddc6abd79f35d84549adb430e41111c Mon Sep 17 00:00:00 2001 From: YaoZengzeng Date: Tue, 16 Jul 2024 15:56:51 +0800 Subject: [PATCH 3/3] fix spell Signed-off-by: YaoZengzeng --- test/e2e/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/README.md b/test/e2e/README.md index 05b6213ec..3bc082e80 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -1,6 +1,6 @@ # Kmesh E2E test -Kmesh E2E test is used to validate the system as a whole, ensuring that all the individual components and itegrations work together seamlessly. +Kmesh E2E test is used to validate the system as a whole, ensuring that all the individual components and integrations work together seamlessly. It's integrated into CI to ensure that each merge of code will not break existing functions. You can also run it locally during development for self-testing. It plays an important role in maintaining the stability and availability of Kmesh.