github.com/tetratelabs/proxy-wasm-go-sdk@v0.23.1-0.20240517021853-021aa9cf78e8/e2e/e2e_test.go (about)

     1  // Copyright 2020-2021 Tetrate
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package e2e
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"net/http"
    22  	"strings"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/stretchr/testify/require"
    27  )
    28  
    29  func Test_dispatch_call_on_tick(t *testing.T) {
    30  	stdErr, kill := startEnvoy(t, 8001)
    31  	defer kill()
    32  	var count int = 1
    33  	require.Eventually(t, func() bool {
    34  		if checkMessage(stdErr.String(), []string{
    35  			fmt.Sprintf("called %d for contextID=1", count),
    36  			fmt.Sprintf("called %d for contextID=2", count),
    37  			":status: 200", ":status: 503",
    38  		}) {
    39  			count++
    40  		}
    41  		return count == 6
    42  	}, 5*time.Second, 10*time.Millisecond, stdErr.String())
    43  }
    44  
    45  func Test_foreign_call_on_tick(t *testing.T) {
    46  	stdErr, kill := startEnvoy(t, 8001)
    47  	defer kill()
    48  	var count int = 1
    49  	require.Eventually(t, func() bool {
    50  		if strings.Contains(stdErr.String(), fmt.Sprintf("foreign function (compress) called: %d", count)) {
    51  			count++
    52  		}
    53  		return count == 6
    54  	}, 5*time.Second, 10*time.Millisecond, stdErr.String())
    55  }
    56  
    57  func Test_helloworld(t *testing.T) {
    58  	stdErr, kill := startEnvoy(t, 8001)
    59  	defer kill()
    60  	require.Eventually(t, func() bool {
    61  		return checkMessage(stdErr.String(), []string{
    62  			"OnPluginStart from Go!",
    63  			"It's",
    64  		})
    65  	}, 10*time.Second, 100*time.Millisecond, stdErr.String())
    66  }
    67  
    68  func Test_http_auth_random(t *testing.T) {
    69  	stdErr, kill := startEnvoy(t, 8001)
    70  	defer kill()
    71  	key := "this-is-key"
    72  	value := "this-is-value"
    73  	req, err := http.NewRequest("GET", "http://localhost:18000/uuid", nil)
    74  	require.NoError(t, err)
    75  	req.Header.Add(key, value)
    76  	require.Eventually(t, func() bool {
    77  		res, err := http.DefaultClient.Do(req)
    78  		if err != nil {
    79  			return false
    80  		}
    81  		defer res.Body.Close()
    82  		return checkMessage(stdErr.String(), []string{
    83  			"access forbidden",
    84  			"access granted",
    85  			"response header from httpbin: :status: 200",
    86  		})
    87  	}, 10*time.Second, 100*time.Millisecond, stdErr.String())
    88  }
    89  
    90  func Test_http_body(t *testing.T) {
    91  	stdErr, kill := startEnvoy(t, 8001)
    92  	defer kill()
    93  
    94  	for _, mode := range []string{
    95  		"request",
    96  		"response",
    97  	} {
    98  		t.Run(mode, func(t *testing.T) {
    99  			for _, tc := range []struct {
   100  				op, expBody string
   101  			}{
   102  				{op: "append", expBody: `[original body][this is appended body]`},
   103  				{op: "prepend", expBody: `[this is prepended body][original body]`},
   104  				{op: "replace", expBody: `[this is replaced body]`},
   105  				// Should fall back to to the replace.
   106  				{op: "invalid", expBody: `[this is replaced body]`},
   107  			} {
   108  				tc := tc
   109  				t.Run(tc.op, func(t *testing.T) {
   110  					require.Eventually(t, func() bool {
   111  						req, err := http.NewRequest("PUT", "http://localhost:18000/anything",
   112  							bytes.NewBuffer([]byte(`[original body]`)))
   113  						require.NoError(t, err)
   114  						req.Header.Add("buffer-replace-at", mode)
   115  						req.Header.Add("buffer-operation", tc.op)
   116  						res, err := http.DefaultClient.Do(req)
   117  						if err != nil {
   118  							return false
   119  						}
   120  						defer res.Body.Close()
   121  						body, err := io.ReadAll(res.Body)
   122  						require.NoError(t, err)
   123  						return tc.expBody == string(body) && checkMessage(stdErr.String(), []string{
   124  							fmt.Sprintf(`original %s body: [original body]`, mode)},
   125  						)
   126  					}, 5*time.Second, 500*time.Millisecond)
   127  				})
   128  			}
   129  		})
   130  	}
   131  }
   132  
   133  func Test_http_headers(t *testing.T) {
   134  	stdErr, kill := startEnvoy(t, 8001)
   135  	defer kill()
   136  	req, err := http.NewRequest("GET", "http://localhost:18000/uuid", nil)
   137  	require.NoError(t, err)
   138  	key := "this-is-key"
   139  	value := "this-is-value"
   140  	req.Header.Add(key, value)
   141  	require.Eventually(t, func() bool {
   142  		res, err := http.DefaultClient.Do(req)
   143  		if err != nil {
   144  			return false
   145  		}
   146  		defer res.Body.Close()
   147  		require.Equal(t, res.Header.Get("x-wasm-header"), "demo-wasm")
   148  		require.Equal(t, res.Header.Get("x-proxy-wasm-go-sdk-example"), "http_headers")
   149  		return checkMessage(stdErr.String(), []string{
   150  			key, value, "server: envoy", "x-wasm-header", "x-proxy-wasm-go-sdk-example",
   151  		})
   152  	}, 10*time.Second, 100*time.Millisecond, stdErr.String())
   153  }
   154  
   155  func Test_http_routing(t *testing.T) {
   156  	stdErr, kill := startEnvoy(t, 8001)
   157  	defer kill()
   158  	var primary, canary bool
   159  	require.Eventually(t, func() bool {
   160  		res, err := http.Get("http://localhost:18000")
   161  		if err != nil {
   162  			return false
   163  		}
   164  		raw, err := io.ReadAll(res.Body)
   165  		require.NoError(t, err)
   166  		defer res.Body.Close()
   167  		body := string(raw)
   168  		if strings.Contains(body, "canary") {
   169  			canary = true
   170  		}
   171  		if strings.Contains(body, "primary") {
   172  			primary = true
   173  		}
   174  		return primary && canary
   175  	}, 10*time.Second, 100*time.Millisecond, stdErr.String())
   176  }
   177  
   178  func Test_metrics(t *testing.T) {
   179  	_, kill := startEnvoy(t, 8001)
   180  	defer kill()
   181  
   182  	const customHeaderKey = "my-custom-header"
   183  	customHeaderToExpectedCounts := map[string]int{
   184  		"foo": 3,
   185  		"bar": 5,
   186  	}
   187  	for headerValue, expCount := range customHeaderToExpectedCounts {
   188  		var actualCount int
   189  		require.Eventually(t, func() bool {
   190  			req, err := http.NewRequest("GET", "http://localhost:18000", nil)
   191  			require.NoError(t, err)
   192  			req.Header.Add(customHeaderKey, headerValue)
   193  			res, err := http.DefaultClient.Do(req)
   194  			if err != nil {
   195  				return false
   196  			}
   197  			defer res.Body.Close()
   198  			if res.StatusCode != http.StatusOK {
   199  				return false
   200  			}
   201  			actualCount++
   202  			return actualCount == expCount
   203  		}, 10*time.Second, 100*time.Millisecond, "Endpoint not healthy.")
   204  	}
   205  
   206  	for headerValue, expCount := range customHeaderToExpectedCounts {
   207  		expectedMetric := fmt.Sprintf("custom_header_value_counts{value=\"%s\",reporter=\"wasmgosdk\"} %d", headerValue, expCount)
   208  		require.Eventually(t, func() bool {
   209  			res, err := http.Get("http://localhost:8001/stats/prometheus")
   210  			if err != nil {
   211  				return false
   212  			}
   213  			defer res.Body.Close()
   214  			raw, err := io.ReadAll(res.Body)
   215  			require.NoError(t, err)
   216  			return checkMessage(string(raw), []string{expectedMetric})
   217  		}, 10*time.Second, 100*time.Millisecond, "Expected stats not found")
   218  	}
   219  }
   220  
   221  func Test_network(t *testing.T) {
   222  	stdErr, kill := startEnvoy(t, 8001)
   223  	defer kill()
   224  	key := "This-Is-Key"
   225  	value := "this-is-value"
   226  	req, err := http.NewRequest("GET", "http://localhost:18000", nil)
   227  	require.NoError(t, err)
   228  	req.Header.Add(key, value)
   229  	req.Header.Add("Connection", "close")
   230  	require.Eventually(t, func() bool {
   231  		res, err := http.DefaultClient.Do(req)
   232  		if err != nil {
   233  			return false
   234  		}
   235  		defer res.Body.Close()
   236  		return checkMessage(stdErr.String(), []string{
   237  			key, value,
   238  			"downstream data received",
   239  			"new connection!",
   240  			"downstream connection close!",
   241  			"upstream data received",
   242  			"connection complete!",
   243  			"remote address: 127.0.0.1:",
   244  			"upstream cluster metadata location[region]=ap-northeast-1",
   245  			"upstream cluster metadata location[cloud_provider]=aws",
   246  			"upstream cluster metadata location[az]=ap-northeast-1a",
   247  		})
   248  	}, 10*time.Second, 100*time.Millisecond, stdErr.String())
   249  }
   250  
   251  func Test_postpone_requests(t *testing.T) {
   252  	stdErr, kill := startEnvoy(t, 8001)
   253  	defer kill()
   254  	require.Eventually(t, func() bool {
   255  		res, err := http.Get("http://localhost:18000")
   256  		if err != nil {
   257  			return false
   258  		}
   259  		defer res.Body.Close()
   260  		return checkMessage(stdErr.String(), []string{
   261  			"postpone request with contextID=2",
   262  			"resume request with contextID=2",
   263  		})
   264  	}, 6*time.Second, time.Millisecond, stdErr.String())
   265  }
   266  
   267  func Test_properties(t *testing.T) {
   268  	stdErr, kill := startEnvoy(t, 8001)
   269  	defer kill()
   270  	require.Eventually(t, func() bool {
   271  		baseUrl := "http://localhost:18000"
   272  		for _, tt := range []struct {
   273  			pathname   string
   274  			authHeader [2]string
   275  			status     int
   276  		}{
   277  			{"/one", [2]string{}, http.StatusUnauthorized},
   278  			{"/one", [2]string{"cookie", "value"}, http.StatusOK},
   279  			{"/two", [2]string{}, http.StatusUnauthorized},
   280  			{"/two", [2]string{"authorization", "token"}, http.StatusOK},
   281  			{"/three", [2]string{}, http.StatusOK},
   282  		} {
   283  			req, err := http.NewRequest("GET", baseUrl+tt.pathname, nil)
   284  			require.NoError(t, err)
   285  			if tt.authHeader[0] != "" && tt.authHeader[1] != "" {
   286  				req.Header.Add(tt.authHeader[0], tt.authHeader[1])
   287  			}
   288  			res, err := http.DefaultClient.Do(req)
   289  			if err != nil {
   290  				return false
   291  			}
   292  			defer res.Body.Close()
   293  
   294  			if res.StatusCode != tt.status {
   295  				return false
   296  			}
   297  		}
   298  
   299  		return checkMessage(stdErr.String(), []string{
   300  			"auth header is \"cookie\"",
   301  			"auth header is \"authorization\"",
   302  			"no auth header for route",
   303  		})
   304  	}, 10*time.Second, 100*time.Millisecond, stdErr.String())
   305  }
   306  
   307  func Test_shared_data(t *testing.T) {
   308  	stdErr, kill := startEnvoy(t, 8001)
   309  	defer kill()
   310  	var count int
   311  	require.Eventually(t, func() bool {
   312  		res, err := http.Get("http://localhost:18000")
   313  		if err != nil {
   314  			return false
   315  		}
   316  		defer res.Body.Close()
   317  		if res.StatusCode != http.StatusOK {
   318  			return false
   319  		}
   320  		count++
   321  		return count == 10
   322  	}, 10*time.Second, 100*time.Millisecond, "Endpoint not healthy.")
   323  	require.Eventually(t, func() bool {
   324  		return checkMessage(stdErr.String(), []string{fmt.Sprintf("shared value: %d", count)})
   325  	}, 10*time.Second, 100*time.Millisecond, stdErr.String())
   326  	fmt.Println(stdErr.String())
   327  }
   328  
   329  func Test_shared_queue(t *testing.T) {
   330  	stdErr, kill := startEnvoy(t, 8001)
   331  	defer kill()
   332  	require.Eventually(t, func() bool {
   333  		res, err := http.Get("http://localhost:18000")
   334  		if err != nil || res.StatusCode != http.StatusOK {
   335  			return false
   336  		}
   337  		defer res.Body.Close()
   338  
   339  		res, err = http.Get("http://localhost:18001")
   340  		if err != nil {
   341  			return false
   342  		}
   343  		defer res.Body.Close()
   344  		return res.StatusCode == http.StatusOK
   345  	}, 10*time.Second, 100*time.Millisecond, "Endpoint not healthy.")
   346  	require.Eventually(t, func() bool {
   347  		return checkMessage(stdErr.String(), []string{
   348  			`enqueued data: {"key": ":method","value": "GET"}`,
   349  			`dequeued data from http_request_headers`,
   350  			`dequeued data from http_response_headers`,
   351  			`dequeued data from tcp_data_hashes`,
   352  		})
   353  	}, 10*time.Second, 100*time.Millisecond, stdErr.String())
   354  }
   355  
   356  func Test_vm_plugin_configuration(t *testing.T) {
   357  	stdErr, kill := startEnvoy(t, 8001)
   358  	defer kill()
   359  	require.Eventually(t, func() bool {
   360  		return checkMessage(stdErr.String(), []string{
   361  			"name\": \"vm configuration", "name\": \"plugin configuration",
   362  		})
   363  	}, 10*time.Second, 100*time.Millisecond, stdErr.String())
   364  }
   365  
   366  func Test_json_validation(t *testing.T) {
   367  	stdErr, kill := startEnvoy(t, 8001)
   368  	defer kill()
   369  
   370  	require.Eventually(t, func() bool {
   371  		req, _ := http.NewRequest("GET", "http://localhost:18000", nil)
   372  		res, err := http.DefaultClient.Do(req)
   373  		if err != nil {
   374  			return false
   375  		}
   376  		defer res.Body.Close()
   377  
   378  		_, err = io.Copy(io.Discard, res.Body)
   379  		require.NoError(t, err)
   380  		require.Equal(t, http.StatusForbidden, res.StatusCode)
   381  
   382  		jsonBody := `{"id": "abc123", "token": "xyz456"}`
   383  
   384  		req, _ = http.NewRequest("POST", "http://localhost:18000", strings.NewReader(jsonBody))
   385  		req.Header.Add("Content-Type", "application/json")
   386  		res, err = http.DefaultClient.Do(req)
   387  		if err != nil {
   388  			return false
   389  		}
   390  		defer res.Body.Close()
   391  
   392  		_, err = io.Copy(io.Discard, res.Body)
   393  		require.NoError(t, err)
   394  		require.Equal(t, http.StatusOK, res.StatusCode)
   395  
   396  		return true
   397  	}, 10*time.Second, 100*time.Millisecond, stdErr.String())
   398  }
   399  
   400  func Test_multiple_dispatches(t *testing.T) {
   401  	stdErr, kill := startEnvoy(t, 8001)
   402  	defer kill()
   403  
   404  	require.Eventually(t, func() bool {
   405  		res, err := http.Get("http://localhost:18000")
   406  		if err != nil || res.StatusCode != http.StatusOK {
   407  			return false
   408  		}
   409  		defer res.Body.Close()
   410  		return res.StatusCode == http.StatusOK
   411  	}, 10*time.Second, 100*time.Millisecond, "Endpoint not healthy.")
   412  
   413  	require.Eventually(t, func() bool {
   414  		return checkMessage(stdErr.String(), []string{
   415  			"wasm log: pending dispatched requests: 9",
   416  			"wasm log: pending dispatched requests: 8",
   417  			"wasm log: pending dispatched requests: 7",
   418  			"wasm log: pending dispatched requests: 6",
   419  			"wasm log: pending dispatched requests: 5",
   420  			"wasm log: pending dispatched requests: 4",
   421  			"wasm log: pending dispatched requests: 3",
   422  			"wasm log: pending dispatched requests: 2",
   423  			"wasm log: pending dispatched requests: 1",
   424  			"wasm log: response resumed after processed 10 dispatched request",
   425  		})
   426  	}, 10*time.Second, 100*time.Millisecond, stdErr.String())
   427  }