Skip to content
This repository was archived by the owner on Jul 28, 2024. It is now read-only.

Add canary routing example. #160

Merged
merged 5 commits into from
Apr 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
id: cache-built-examples
with:
path: examples
key: ${{ hashFiles('examples/**', 'proxywasm/**') }}
key: examples-${{ hashFiles('examples/**', 'proxywasm/**') }}

- name: build examples
if: steps.cache-built-examples.outputs.cache-hit != 'true'
Expand All @@ -73,7 +73,7 @@ jobs:
e2e-tests-envoy:
strategy:
matrix:
envoy-tag: [ 1.17.1, 1.18.2 ]
envoy-tag: [ 1.18.2 ]
name: e2e tests on Envoy
needs: build-examples
runs-on: ubuntu-latest
Expand All @@ -97,7 +97,7 @@ jobs:
uses: actions/cache@v2
with:
path: examples
key: ${{ hashFiles('examples/**', 'proxywasm/**') }}
key: examples-${{ hashFiles('examples/**', 'proxywasm/**') }}

- name: run e2e test
run: make test.e2e
Expand All @@ -107,7 +107,7 @@ jobs:
e2e-tests-istio:
strategy:
matrix:
istio-version: [ 1.8.5, 1.9.3 ]
istio-version: [ 1.9.3 ]
name: e2e tests on Istio
needs: build-examples
runs-on: ubuntu-latest
Expand All @@ -131,7 +131,7 @@ jobs:
uses: actions/cache@v2
with:
path: examples
key: ${{ hashFiles('examples/**', 'proxywasm/**') }}
key: examples-${{ hashFiles('examples/**', 'proxywasm/**') }}

- name: run e2e test
run: make test.e2e
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ Please follow the official instruction [here](https://tinygo.org/getting-started

| proxy-wasm-go-sdk| proxy-wasm ABI version |istio/proxyv2| Envoy upstream|
|:-------------:|:-------------:|:-------------:|:-------------:|
| main | 0.2.0| 1.8.x, 1.9.x | 1.17.x, 1.18.x |
| v0.1.1 | 0.2.0| 1.8.x, 1.9.x | 1.17.x |
| main | 0.2.0| 1.9.x | 1.18.x |
| v0.1.1 | 0.2.0| 1.8.x, 1.9.x | 1.17.x |


## Run examples
Expand Down
39 changes: 36 additions & 3 deletions e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ func Test_E2E(t *testing.T) {
staticReply: 8011,
admin: 28311,
}, dispatchCallOnTick))
t.Run("http_routing", testRunnerGetter(envoyPorts{
endpoint: 11012,
staticReply: 8012,
admin: 28312,
}, httpRouting))
}

type runner = func(t *testing.T, nps envoyPorts, stdErr *bytes.Buffer)
Expand Down Expand Up @@ -164,6 +169,34 @@ func helloworld(t *testing.T, ps envoyPorts, stdErr *bytes.Buffer) {
require.Contains(t, out, "helloworld: It's")
}

func httpRouting(t *testing.T, ps envoyPorts, stdErr *bytes.Buffer) {
var primary, canary bool
for i := 0; i < 25; i++ { // TODO: maybe flaky
req, err := http.NewRequest("GET",
fmt.Sprintf("http://localhost:%d", ps.endpoint), nil)
require.NoError(t, err)

r, err := http.DefaultClient.Do(req)
require.NoError(t, err)
raw, err := ioutil.ReadAll(r.Body)
require.NoError(t, err)
body := string(raw)
if strings.Contains(body, "canary") {
canary = true
}
if strings.Contains(body, "primary") {
primary = true
}
r.Body.Close()
fmt.Println("received body: ", body)
}

out := stdErr.String()
fmt.Println(out)
require.True(t, primary, "must be routed to primary at least once")
require.True(t, canary, "must be routed to canary at least once")
}

func httpAuthRandom(t *testing.T, ps envoyPorts, stdErr *bytes.Buffer) {
key := "this-is-key"
value := "this-is-value"
Expand Down Expand Up @@ -241,7 +274,7 @@ func network(t *testing.T, ps envoyPorts, stdErr *bytes.Buffer) {
require.NoError(t, err)
r.Body.Close()

time.Sleep(time.Second)
time.Sleep(time.Second * 5)

out := stdErr.String()
fmt.Println(out)
Expand Down Expand Up @@ -307,7 +340,7 @@ func sharedQueue(t *testing.T, ps envoyPorts, stdErr *bytes.Buffer) {
r.Body.Close()
}

time.Sleep(time.Second * 2)
time.Sleep(time.Second * 5)

out := stdErr.String()
fmt.Println(out)
Expand Down Expand Up @@ -352,7 +385,7 @@ func accessLogger(t *testing.T, ps envoyPorts, stdErr *bytes.Buffer) {
}

func dispatchCallOnTick(t *testing.T, ps envoyPorts, stdErr *bytes.Buffer) {
time.Sleep(3 * time.Second)
time.Sleep(5 * time.Second)
out := stdErr.String()
fmt.Println(out)
for i := 1; i < 6; i++ {
Expand Down
11 changes: 11 additions & 0 deletions examples/http_routing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## http_routing

this example proxies http requests and randomly route them to primary/canary clusters by manipulating :authorty header.

```
$ curl localhost:18000
hello from primary!

$ curl localhost:18000
hello from canary!
```
144 changes: 144 additions & 0 deletions examples/http_routing/envoy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
static_resources:
listeners:
- name: main
address:
socket_address:
address: 0.0.0.0
port_value: 18000
filter_chains:
- filters:
- name: envoy.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: auto
route_config:
name: local_route
virtual_hosts:
- name: canary_route
domains:
- "*-canary"
routes:
- match:
prefix: "/"
route:
cluster: canary
- name: primary_route
domains:
- "*"
routes:
- match:
prefix: "/"
route:
cluster: primary
http_filters:
- name: envoy.filters.http.wasm
typed_config:
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
value:
config:
name: "my_plugin"
root_id: "my_root_id"
vm_config:
vm_id: "my_vm_id"
runtime: "envoy.wasm.runtime.v8"
code:
local:
filename: "./examples/http_routing/main.go.wasm"
- name: envoy.filters.http.router
typed_config: {}

- name: staticreply
address:
socket_address:
address: 127.0.0.1
port_value: 8099
filter_chains:
- filters:
- name: envoy.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: auto
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains:
- "*"
routes:
- match:
prefix: "/"
direct_response:
status: 200
body:
inline_string: "hello from primary!\n"
http_filters:
- name: envoy.filters.http.router
typed_config: {}

- name: staticreply_canary
address:
socket_address:
address: 127.0.0.1
port_value: 31000
filter_chains:
- filters:
- name: envoy.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: auto
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains:
- "*"
routes:
- match:
prefix: "/"
direct_response:
status: 200
body:
inline_string: "hello from canary!\n"
http_filters:
- name: envoy.filters.http.router
typed_config: {}

clusters:
- name: primary
connect_timeout: 0.25s
type: STATIC
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: primary
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 8099

- name: canary
connect_timeout: 0.25s
type: STATIC
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: canary
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 31000

admin:
access_log_path: "/dev/null"
address:
socket_address:
address: 0.0.0.0
port_value: 8001
74 changes: 74 additions & 0 deletions examples/http_routing/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2020-2021 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 main

import (
"math/rand"
"time"

"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
)

func main() {
proxywasm.SetNewRootContext(newRootContext)
}

type rootContext struct {
// You'd better embed the default root context
// so that you don't need to reimplement all the methods by yourself.
proxywasm.DefaultRootContext
}

func newRootContext(uint32) proxywasm.RootContext { return &rootContext{} }

// Override DefaultRootContext.
func (*rootContext) NewHttpContext(contextID uint32) proxywasm.HttpContext {
return &httpRouting{}
}

type httpRouting struct {
// You'd better embed the default root context
// so that you don't need to reimplement all the methods by yourself.
proxywasm.DefaultHttpContext
}

// Unittest purpose.
var now = func() int {
rand.Seed(time.Now().UnixNano())
return rand.Int()
}

// Override DefaultHttpContext.
func (ctx *httpRouting) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
// Randomly routing to the canary cluster.
dice := now()
proxywasm.LogInfof("dice: %d\n", dice)
if dice%2 == 0 {
const authorityKey = ":authority"
value, err := proxywasm.GetHttpRequestHeader(authorityKey)
if err != nil {
proxywasm.LogCritical("failed to get request header: ':authority'")
return types.ActionPause
}
// Append "-canary" suffix to route this request to the canary cluster.
value += "-canary"
if err := proxywasm.SetHttpRequestHeader(":authority", value); err != nil {
proxywasm.LogCritical("failed to set request header: test")
return types.ActionPause
}
}
return types.ActionContinue
}
Loading