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 }