diff --git a/Makefile b/Makefile index a275127b..615e9550 100755 --- a/Makefile +++ b/Makefile @@ -26,14 +26,6 @@ test.examples: | xargs -I {} bash -c 'dirname {}' \ | xargs -I {} bash -c 'cd {} && go test ./...' -.PHONY: test.e2e -test.e2e: - @go test -v ./e2e -count=1 - -.PHONY: test.e2e.single -test.e2e.single: - @go test -v ./e2e -run '/${name}' -count=1 - .PHONY: run run: @envoy -c ./examples/${name}/envoy.yaml --concurrency 2 --log-format '%v' @@ -63,35 +55,6 @@ check: git diff --exit-code; \ fi -# Build docker images of *compat* variant of Wasm Image Specification with built example binaries, -# and push to ghcr.io/tetratelabs/proxy-wasm-go-sdk-examples. -# See https://github.com/solo-io/wasm/blob/master/spec/spec-compat.md for details. -# Only-used in github workflow on the main branch, and not for developers. -repository := ghcr.io/tetratelabs/proxy-wasm-go-sdk-example - -.PHONY: wasm_image.build_push -wasm_image.build_push: - @for f in `find ./examples -type f -name "main.go"`; do \ - name=`echo $$f | sed -e 's/\\//-/g' | sed -e 's/\.-examples-//g' -e 's/\-main\.go//g'` ; \ - ref=${repository}:$$name; \ - docker build -t $$ref . -f examples/wasm-image.Dockerfile --build-arg WASM_BINARY_PATH=$$(dirname $$f)/main.wasm; \ - docker push $$ref; \ - done - -# Build OCI images of *compat* variant of Wasm Image Specification with built example binaries, -# and push to ghcr.io/tetratelabs/proxy-wasm-go-sdk-examples. -# See https://github.com/solo-io/wasm/blob/master/spec/spec-compat.md for details. -# Only-used in github workflow on the main branch, and not for developers. -# Requires "buildah" CLI. -.PHONY: wasm_image.build_push_oci -wasm_image.build_push_oci: - @for f in `find ./examples -type f -name "main.go"`; do \ - name=`echo $$f | sed -e 's/\\//-/g' | sed -e 's/\.-examples-//g' -e 's/\-main\.go//g'` ; \ - ref=${repository}:$$name-oci; \ - buildah bud -f examples/wasm-image.Dockerfile --build-arg WASM_BINARY_PATH=$$(dirname $$f)/main.wasm -t $$ref .; \ - buildah push $$ref; \ - done - .PHONY: tidy tidy: ## Runs go mod tidy on every module @find . -name "go.mod" \ diff --git a/README.md b/README.md index 2383eb6d..f3c20a95 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ > such as Lua or External Processing which should be comparable or better or worse depending on the use case. > > If you are already using this SDK, but still want to continue using Wasm for some reason instead of Lua or External Processing, -> we strongly recommend migrating to the Rust or C++ SDK due to the memory issue of TinyGo described in the like above. +> we strongly recommend migrating to the Rust or C++ SDK due to the memory issue of TinyGo described in the link above. > > We keep this repository open and not archived for the existing users, but we cannot provide any support or guarantee for the future development of this SDK. diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go deleted file mode 100644 index ed83e9db..00000000 --- a/e2e/e2e_test.go +++ /dev/null @@ -1,427 +0,0 @@ -// Copyright 2020-2024 Tetrate -// -// 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 e2e - -import ( - "bytes" - "fmt" - "io" - "net/http" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func Test_dispatch_call_on_tick(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - var count int = 1 - require.Eventually(t, func() bool { - if checkMessage(stdErr.String(), []string{ - fmt.Sprintf("called %d for contextID=1", count), - fmt.Sprintf("called %d for contextID=2", count), - ":status: 200", ":status: 503", - }) { - count++ - } - return count == 6 - }, 5*time.Second, 10*time.Millisecond, stdErr.String()) -} - -func Test_foreign_call_on_tick(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - var count int = 1 - require.Eventually(t, func() bool { - if strings.Contains(stdErr.String(), fmt.Sprintf("foreign function (compress) called: %d", count)) { - count++ - } - return count == 6 - }, 5*time.Second, 10*time.Millisecond, stdErr.String()) -} - -func Test_helloworld(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - require.Eventually(t, func() bool { - return checkMessage(stdErr.String(), []string{ - "OnPluginStart from Go!", - "It's", - }) - }, 10*time.Second, 100*time.Millisecond, stdErr.String()) -} - -func Test_http_auth_random(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - key := "this-is-key" - value := "this-is-value" - req, err := http.NewRequest("GET", "http://localhost:18000/uuid", nil) - require.NoError(t, err) - req.Header.Add(key, value) - require.Eventually(t, func() bool { - res, err := http.DefaultClient.Do(req) - if err != nil { - return false - } - defer res.Body.Close() - return checkMessage(stdErr.String(), []string{ - "access forbidden", - "access granted", - "response header from httpbin: :status: 200", - }) - }, 10*time.Second, 100*time.Millisecond, stdErr.String()) -} - -func Test_http_body(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - - for _, mode := range []string{ - "request", - "response", - } { - t.Run(mode, func(t *testing.T) { - for _, tc := range []struct { - op, expBody string - }{ - {op: "append", expBody: `[original body][this is appended body]`}, - {op: "prepend", expBody: `[this is prepended body][original body]`}, - {op: "replace", expBody: `[this is replaced body]`}, - // Should fall back to to the replace. - {op: "invalid", expBody: `[this is replaced body]`}, - } { - tc := tc - t.Run(tc.op, func(t *testing.T) { - require.Eventually(t, func() bool { - req, err := http.NewRequest("PUT", "http://localhost:18000/anything", - bytes.NewBuffer([]byte(`[original body]`))) - require.NoError(t, err) - req.Header.Add("buffer-replace-at", mode) - req.Header.Add("buffer-operation", tc.op) - res, err := http.DefaultClient.Do(req) - if err != nil { - return false - } - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - require.NoError(t, err) - return tc.expBody == string(body) && checkMessage(stdErr.String(), []string{ - fmt.Sprintf(`original %s body: [original body]`, mode)}, - ) - }, 5*time.Second, 500*time.Millisecond) - }) - } - }) - } -} - -func Test_http_headers(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - req, err := http.NewRequest("GET", "http://localhost:18000/uuid", nil) - require.NoError(t, err) - key := "this-is-key" - value := "this-is-value" - req.Header.Add(key, value) - require.Eventually(t, func() bool { - res, err := http.DefaultClient.Do(req) - if err != nil { - return false - } - defer res.Body.Close() - require.Equal(t, res.Header.Get("x-wasm-header"), "demo-wasm") - require.Equal(t, res.Header.Get("x-proxy-wasm-go-sdk-example"), "http_headers") - return checkMessage(stdErr.String(), []string{ - key, value, "server: envoy", "x-wasm-header", "x-proxy-wasm-go-sdk-example", - }) - }, 10*time.Second, 100*time.Millisecond, stdErr.String()) -} - -func Test_http_routing(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - var primary, canary bool - require.Eventually(t, func() bool { - res, err := http.Get("http://localhost:18000") - if err != nil { - return false - } - raw, err := io.ReadAll(res.Body) - require.NoError(t, err) - defer res.Body.Close() - body := string(raw) - if strings.Contains(body, "canary") { - canary = true - } - if strings.Contains(body, "primary") { - primary = true - } - return primary && canary - }, 10*time.Second, 100*time.Millisecond, stdErr.String()) -} - -func Test_metrics(t *testing.T) { - _, kill := startEnvoy(t, 8001) - defer kill() - - const customHeaderKey = "my-custom-header" - customHeaderToExpectedCounts := map[string]int{ - "foo": 3, - "bar": 5, - } - for headerValue, expCount := range customHeaderToExpectedCounts { - var actualCount int - require.Eventually(t, func() bool { - req, err := http.NewRequest("GET", "http://localhost:18000", nil) - require.NoError(t, err) - req.Header.Add(customHeaderKey, headerValue) - res, err := http.DefaultClient.Do(req) - if err != nil { - return false - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return false - } - actualCount++ - return actualCount == expCount - }, 10*time.Second, 100*time.Millisecond, "Endpoint not healthy.") - } - - for headerValue, expCount := range customHeaderToExpectedCounts { - expectedMetric := fmt.Sprintf("custom_header_value_counts{value=\"%s\",reporter=\"wasmgosdk\"} %d", headerValue, expCount) - require.Eventually(t, func() bool { - res, err := http.Get("http://localhost:8001/stats/prometheus") - if err != nil { - return false - } - defer res.Body.Close() - raw, err := io.ReadAll(res.Body) - require.NoError(t, err) - return checkMessage(string(raw), []string{expectedMetric}) - }, 10*time.Second, 100*time.Millisecond, "Expected stats not found") - } -} - -func Test_network(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - key := "This-Is-Key" - value := "this-is-value" - req, err := http.NewRequest("GET", "http://localhost:18000", nil) - require.NoError(t, err) - req.Header.Add(key, value) - req.Header.Add("Connection", "close") - require.Eventually(t, func() bool { - res, err := http.DefaultClient.Do(req) - if err != nil { - return false - } - defer res.Body.Close() - return checkMessage(stdErr.String(), []string{ - key, value, - "downstream data received", - "new connection!", - "downstream connection close!", - "upstream data received", - "connection complete!", - "remote address: 127.0.0.1:", - "upstream cluster metadata location[region]=ap-northeast-1", - "upstream cluster metadata location[cloud_provider]=aws", - "upstream cluster metadata location[az]=ap-northeast-1a", - }) - }, 10*time.Second, 100*time.Millisecond, stdErr.String()) -} - -func Test_postpone_requests(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - require.Eventually(t, func() bool { - res, err := http.Get("http://localhost:18000") - if err != nil { - return false - } - defer res.Body.Close() - return checkMessage(stdErr.String(), []string{ - "postpone request with contextID=2", - "resume request with contextID=2", - }) - }, 6*time.Second, time.Millisecond, stdErr.String()) -} - -func Test_properties(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - require.Eventually(t, func() bool { - baseUrl := "http://localhost:18000" - for _, tt := range []struct { - pathname string - authHeader [2]string - status int - }{ - {"/one", [2]string{}, http.StatusUnauthorized}, - {"/one", [2]string{"cookie", "value"}, http.StatusOK}, - {"/two", [2]string{}, http.StatusUnauthorized}, - {"/two", [2]string{"authorization", "token"}, http.StatusOK}, - {"/three", [2]string{}, http.StatusOK}, - } { - req, err := http.NewRequest("GET", baseUrl+tt.pathname, nil) - require.NoError(t, err) - if tt.authHeader[0] != "" && tt.authHeader[1] != "" { - req.Header.Add(tt.authHeader[0], tt.authHeader[1]) - } - res, err := http.DefaultClient.Do(req) - if err != nil { - return false - } - defer res.Body.Close() - - if res.StatusCode != tt.status { - return false - } - } - - return checkMessage(stdErr.String(), []string{ - "auth header is \"cookie\"", - "auth header is \"authorization\"", - "no auth header for route", - }) - }, 10*time.Second, 100*time.Millisecond, stdErr.String()) -} - -func Test_shared_data(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - var count int - require.Eventually(t, func() bool { - res, err := http.Get("http://localhost:18000") - if err != nil { - return false - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return false - } - count++ - return count == 10 - }, 10*time.Second, 100*time.Millisecond, "Endpoint not healthy.") - require.Eventually(t, func() bool { - return checkMessage(stdErr.String(), []string{fmt.Sprintf("shared value: %d", count)}) - }, 10*time.Second, 100*time.Millisecond, stdErr.String()) - fmt.Println(stdErr.String()) -} - -func Test_shared_queue(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - require.Eventually(t, func() bool { - res, err := http.Get("http://localhost:18000") - if err != nil || res.StatusCode != http.StatusOK { - return false - } - defer res.Body.Close() - - res, err = http.Get("http://localhost:18001") - if err != nil { - return false - } - defer res.Body.Close() - return res.StatusCode == http.StatusOK - }, 10*time.Second, 100*time.Millisecond, "Endpoint not healthy.") - require.Eventually(t, func() bool { - return checkMessage(stdErr.String(), []string{ - `enqueued data: {"key": ":method","value": "GET"}`, - `dequeued data from http_request_headers`, - `dequeued data from http_response_headers`, - `dequeued data from tcp_data_hashes`, - }) - }, 10*time.Second, 100*time.Millisecond, stdErr.String()) -} - -func Test_vm_plugin_configuration(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - require.Eventually(t, func() bool { - return checkMessage(stdErr.String(), []string{ - "name\": \"vm configuration", "name\": \"plugin configuration", - }) - }, 10*time.Second, 100*time.Millisecond, stdErr.String()) -} - -func Test_json_validation(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - - require.Eventually(t, func() bool { - req, _ := http.NewRequest("GET", "http://localhost:18000", nil) - res, err := http.DefaultClient.Do(req) - if err != nil { - return false - } - defer res.Body.Close() - - _, err = io.Copy(io.Discard, res.Body) - require.NoError(t, err) - require.Equal(t, http.StatusForbidden, res.StatusCode) - - jsonBody := `{"id": "abc123", "token": "xyz456"}` - - req, _ = http.NewRequest("POST", "http://localhost:18000", strings.NewReader(jsonBody)) - req.Header.Add("Content-Type", "application/json") - res, err = http.DefaultClient.Do(req) - if err != nil { - return false - } - defer res.Body.Close() - - _, err = io.Copy(io.Discard, res.Body) - require.NoError(t, err) - require.Equal(t, http.StatusOK, res.StatusCode) - - return true - }, 10*time.Second, 100*time.Millisecond, stdErr.String()) -} - -func Test_multiple_dispatches(t *testing.T) { - stdErr, kill := startEnvoy(t, 8001) - defer kill() - - require.Eventually(t, func() bool { - res, err := http.Get("http://localhost:18000") - if err != nil || res.StatusCode != http.StatusOK { - return false - } - defer res.Body.Close() - return res.StatusCode == http.StatusOK - }, 10*time.Second, 100*time.Millisecond, "Endpoint not healthy.") - - require.Eventually(t, func() bool { - return checkMessage(stdErr.String(), []string{ - "wasm log: pending dispatched requests: 9", - "wasm log: pending dispatched requests: 8", - "wasm log: pending dispatched requests: 7", - "wasm log: pending dispatched requests: 6", - "wasm log: pending dispatched requests: 5", - "wasm log: pending dispatched requests: 4", - "wasm log: pending dispatched requests: 3", - "wasm log: pending dispatched requests: 2", - "wasm log: pending dispatched requests: 1", - "wasm log: response resumed after processed 10 dispatched request", - }) - }, 10*time.Second, 100*time.Millisecond, stdErr.String()) -} diff --git a/e2e/main_test.go b/e2e/main_test.go deleted file mode 100644 index c0083f96..00000000 --- a/e2e/main_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2024 Tetrate -// -// 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 e2e - -import ( - "bytes" - "fmt" - "log" - "net/http" - "os" - "os/exec" - "strconv" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestMain(m *testing.M) { - if err := os.Chdir(".."); err != nil { - log.Fatal(err) - } - os.Exit(m.Run()) -} - -func checkMessage(str string, exps []string) bool { - for _, exp := range exps { - if !strings.Contains(str, exp) { - return false - } - } - return true -} - -func startEnvoy(t *testing.T, adminPort int) (stdErr *bytes.Buffer, kill func()) { - name := strings.TrimPrefix(t.Name(), "Test_") - cmd := exec.Command("envoy", - "--base-id", strconv.Itoa(adminPort), - "--concurrency", "1", "--component-log-level", "wasm:trace", - "-c", fmt.Sprintf("./examples/%s/envoy.yaml", name)) - - buf := new(bytes.Buffer) - cmd.Stderr = buf - require.NoError(t, cmd.Start()) - if !assert.Eventually(t, func() bool { - res, err := http.Get(fmt.Sprintf("http://localhost:%d/listeners", adminPort)) - if err != nil { - return false - } - defer res.Body.Close() - return res.StatusCode == http.StatusOK - }, 5*time.Second, 100*time.Millisecond, "Envoy has not started") { - t.Fatalf("Envoy stderr: %s", buf.String()) - } - return buf, func() { require.NoError(t, cmd.Process.Kill()) } -}