From 46d11ddac44ede4d12f5c780a182bed88a22e4df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandor=20Sz=C3=BCcs?= Date: Mon, 19 Feb 2024 17:37:09 +0100 Subject: [PATCH 1/3] super basic wasm filter implementation using wazero MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sandor Szücs --- filters/builtin/builtin.go | 2 + filters/filters.go | 1 + filters/wasm/testdata/add.go | 15 +++++ filters/wasm/testdata/add.wasm | Bin 0 -> 82721 bytes filters/wasm/wasm.go | 104 +++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + 7 files changed, 125 insertions(+) create mode 100644 filters/wasm/testdata/add.go create mode 100755 filters/wasm/testdata/add.wasm create mode 100644 filters/wasm/wasm.go diff --git a/filters/builtin/builtin.go b/filters/builtin/builtin.go index d5c9e34f24..0247fd9cb7 100644 --- a/filters/builtin/builtin.go +++ b/filters/builtin/builtin.go @@ -21,6 +21,7 @@ import ( "github.com/zalando/skipper/filters/tee" "github.com/zalando/skipper/filters/tls" "github.com/zalando/skipper/filters/tracing" + "github.com/zalando/skipper/filters/wasm" "github.com/zalando/skipper/filters/xforward" "github.com/zalando/skipper/script" ) @@ -230,6 +231,7 @@ func Filters() []filters.Spec { consistenthash.NewConsistentHashKey(), consistenthash.NewConsistentHashBalanceFactor(), tls.New(), + wasm.NewWASM(), } } diff --git a/filters/filters.go b/filters/filters.go index a43c4b19e4..519a4f7931 100644 --- a/filters/filters.go +++ b/filters/filters.go @@ -352,6 +352,7 @@ const ( OpaServeResponseName = "opaServeResponse" OpaServeResponseWithReqBodyName = "opaServeResponseWithReqBody" TLSName = "tlsPassClientCertificates" + WASMName = "wasm" // Undocumented filters HealthCheckName = "healthcheck" diff --git a/filters/wasm/testdata/add.go b/filters/wasm/testdata/add.go new file mode 100644 index 0000000000..4d4d0e5ecd --- /dev/null +++ b/filters/wasm/testdata/add.go @@ -0,0 +1,15 @@ +package main + +//export request +func request(x, y uint32) uint32 { + return x + y +} + +//export response +func response(x, y uint32) uint32 { + return x - y +} + +// main is required for the `wasi` target, even if it isn't used. +// See https://wazero.io/languages/tinygo/#why-do-i-have-to-define-main +func main() {} diff --git a/filters/wasm/testdata/add.wasm b/filters/wasm/testdata/add.wasm new file mode 100755 index 0000000000000000000000000000000000000000..f8232bf42215ddd7fc7c3d51edecefd23b267ac5 GIT binary patch literal 82721 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 O618$ZuOY93y7<3*kakW0 literal 0 HcmV?d00001 diff --git a/filters/wasm/wasm.go b/filters/wasm/wasm.go new file mode 100644 index 0000000000..9e0d5b4337 --- /dev/null +++ b/filters/wasm/wasm.go @@ -0,0 +1,104 @@ +package wasm + +import ( + "context" + "net/url" + "os" + + "github.com/sirupsen/logrus" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + "github.com/zalando/skipper/filters" +) + +type wasmSpec struct{} + +type wasm struct { + code []byte + mod api.Module + request api.Function + response api.Function +} + +func NewWASM() filters.Spec { + return &wasmSpec{} +} + +// Name implements filters.Spec. +func (*wasmSpec) Name() string { + return filters.WASMName +} + +// CreateFilter implements filters.Spec. +func (*wasmSpec) CreateFilter(args []interface{}) (filters.Filter, error) { + if len(args) != 1 { + return nil, filters.ErrInvalidFilterParameters + } + src, ok := args[0].(string) + if !ok { + return nil, filters.ErrInvalidFilterParameters + } + u, err := url.Parse(src) + if err != nil { + return nil, filters.ErrInvalidFilterParameters + } + + var code []byte + + switch u.Scheme { + case "file": + code, err = os.ReadFile(u.Path) + if err != nil { + logrus.Errorf("Failed to load file %q: %v", u.Path, err) + return nil, filters.ErrInvalidFilterParameters + } + case "https": + panic("not implemented") + default: + return nil, filters.ErrInvalidFilterParameters + } + + ctx := context.Background() + r := wazero.NewRuntime(ctx) // TODO: needs r.Close() + // Instantiate WASI, which implements host functions needed for TinyGo to + // implement `panic`. + wasi_snapshot_preview1.MustInstantiate(ctx, r) + + // Instantiate the guest Wasm into the same runtime. It exports the `add` + // function, implemented in WebAssembly. + mod, err := r.Instantiate(ctx, code) + if err != nil { + logrus.Fatalf("failed to instantiate module: %v", err) + } + request := mod.ExportedFunction("request") + response := mod.ExportedFunction("response") + + return &wasm{ + code: code, + mod: mod, + request: request, + response: response, + }, nil +} + +// Request implements filters.Filter. +func (w *wasm) Request(filters.FilterContext) { + + result, err := w.request.Call(context.Background(), 2, 3) + if err != nil { + logrus.Errorf("failed to call add: %v", err) + } + logrus.Infof("request result: %v", result) + +} + +// Response implements filters.Filter. +func (w *wasm) Response(filters.FilterContext) { + result, err := w.response.Call(context.Background(), 3, 2) + if err != nil { + logrus.Errorf("failed to call add: %v", err) + } + logrus.Infof("response result: %v", result) + +} diff --git a/go.mod b/go.mod index c949222f46..133f70a434 100644 --- a/go.mod +++ b/go.mod @@ -146,6 +146,7 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d // indirect github.com/tchap/go-patricia/v2 v2.3.1 // indirect + github.com/tetratelabs/wazero v1.6.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect diff --git a/go.sum b/go.sum index 7349368ebb..5ad35b0327 100644 --- a/go.sum +++ b/go.sum @@ -407,6 +407,8 @@ github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BG github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/testcontainers/testcontainers-go v0.27.0 h1:IeIrJN4twonTDuMuBNQdKZ+K97yd7VrmNGu+lDpYcDk= github.com/testcontainers/testcontainers-go v0.27.0/go.mod h1:+HgYZcd17GshBUZv9b+jKFJ198heWPQq3KQIp2+N+7U= +github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= +github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= From 7b5b8ce0846db09559f507f6378c2f41969330bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandor=20Sz=C3=BCcs?= Date: Thu, 22 Feb 2024 16:29:03 +0100 Subject: [PATCH 2/3] fix: close runtime if filter is destroyed feature: use compilation cache refactor: pass context from request to wasm call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sandor Szücs --- filters/wasm/wasm.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/filters/wasm/wasm.go b/filters/wasm/wasm.go index 9e0d5b4337..c9be08af1b 100644 --- a/filters/wasm/wasm.go +++ b/filters/wasm/wasm.go @@ -16,9 +16,13 @@ type wasmSpec struct{} type wasm struct { code []byte + runtime wazero.Runtime mod api.Module request api.Function response api.Function + + cache wazero.CompilationCache + config wazero.RuntimeConfig } func NewWASM() filters.Spec { @@ -60,7 +64,11 @@ func (*wasmSpec) CreateFilter(args []interface{}) (filters.Filter, error) { } ctx := context.Background() - r := wazero.NewRuntime(ctx) // TODO: needs r.Close() + + cache := wazero.NewCompilationCache() + config := wazero.NewRuntimeConfig().WithCompilationCache(cache) + r := wazero.NewRuntimeWithConfig(ctx, config) + // Instantiate WASI, which implements host functions needed for TinyGo to // implement `panic`. wasi_snapshot_preview1.MustInstantiate(ctx, r) @@ -76,16 +84,18 @@ func (*wasmSpec) CreateFilter(args []interface{}) (filters.Filter, error) { return &wasm{ code: code, + runtime: r, mod: mod, request: request, response: response, + cache: cache, + config: config, }, nil } -// Request implements filters.Filter. -func (w *wasm) Request(filters.FilterContext) { +func (w *wasm) Request(ctx filters.FilterContext) { - result, err := w.request.Call(context.Background(), 2, 3) + result, err := w.request.Call(ctx.Request().Context(), 2, 3) if err != nil { logrus.Errorf("failed to call add: %v", err) } @@ -93,8 +103,7 @@ func (w *wasm) Request(filters.FilterContext) { } -// Response implements filters.Filter. -func (w *wasm) Response(filters.FilterContext) { +func (w *wasm) Response(ctx filters.FilterContext) { result, err := w.response.Call(context.Background(), 3, 2) if err != nil { logrus.Errorf("failed to call add: %v", err) @@ -102,3 +111,7 @@ func (w *wasm) Response(filters.FilterContext) { logrus.Infof("response result: %v", result) } + +func (w *wasm) Close() error { + return w.runtime.Close(context.Background()) +} From 1120752a8dae2daf9ae9b0daefe5cc6f7c8fabc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandor=20Sz=C3=BCcs?= Date: Mon, 4 Mar 2024 14:49:28 +0100 Subject: [PATCH 3/3] feature: wasm compile cache config as option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sandor Szücs --- filters/builtin/builtin.go | 2 - filters/wasm/wasm.go | 142 +++++++++++++++++++++++++++++++------ skipper.go | 8 +++ 3 files changed, 129 insertions(+), 23 deletions(-) diff --git a/filters/builtin/builtin.go b/filters/builtin/builtin.go index 0247fd9cb7..d5c9e34f24 100644 --- a/filters/builtin/builtin.go +++ b/filters/builtin/builtin.go @@ -21,7 +21,6 @@ import ( "github.com/zalando/skipper/filters/tee" "github.com/zalando/skipper/filters/tls" "github.com/zalando/skipper/filters/tracing" - "github.com/zalando/skipper/filters/wasm" "github.com/zalando/skipper/filters/xforward" "github.com/zalando/skipper/script" ) @@ -231,7 +230,6 @@ func Filters() []filters.Spec { consistenthash.NewConsistentHashKey(), consistenthash.NewConsistentHashBalanceFactor(), tls.New(), - wasm.NewWASM(), } } diff --git a/filters/wasm/wasm.go b/filters/wasm/wasm.go index c9be08af1b..7a57c98569 100644 --- a/filters/wasm/wasm.go +++ b/filters/wasm/wasm.go @@ -2,31 +2,73 @@ package wasm import ( "context" + "fmt" "net/url" "os" - "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" "github.com/zalando/skipper/filters" ) -type wasmSpec struct{} +type WASMOpts struct { + Typ string + CacheDir string +} + +const ( + memoryLimitPages uint32 = 8 // 8*2^16 +) + +type cache int + +type compilationCache cache + +const ( + none cache = iota + 1 + inmemory + filesystem +) + +type wasmSpec struct { + typ cache + cacheDir string +} +// TODO(sszuecs): think about: +// +// 1) If we want to provide internal Go functions to support our wasm +// modules, we can use +// https://pkg.go.dev/github.com/tetratelabs/wazero#HostFunctionBuilder, +// such that WASM binary can import and use these functions. +// see also https://pkg.go.dev/github.com/tetratelabs/wazero#HostModuleBuilder type wasm struct { code []byte runtime wazero.Runtime mod api.Module request api.Function response api.Function - - cache wazero.CompilationCache - config wazero.RuntimeConfig } -func NewWASM() filters.Spec { - return &wasmSpec{} +func NewWASM(o WASMOpts) filters.Spec { + typ := none + switch o.Typ { + case "none": + typ = none + case "in-memory": + typ = inmemory + case "fs": + typ = filesystem + default: + log.Errorf("Failed to find compilation cache type %q, available values 'none', 'in-memory' and 'fs'", typ) + } + + return &wasmSpec{ + typ: typ, + cacheDir: o.CacheDir, + } } // Name implements filters.Spec. @@ -35,10 +77,11 @@ func (*wasmSpec) Name() string { } // CreateFilter implements filters.Spec. -func (*wasmSpec) CreateFilter(args []interface{}) (filters.Filter, error) { +func (ws *wasmSpec) CreateFilter(args []interface{}) (filters.Filter, error) { if len(args) != 1 { return nil, filters.ErrInvalidFilterParameters } + src, ok := args[0].(string) if !ok { return nil, filters.ErrInvalidFilterParameters @@ -54,8 +97,7 @@ func (*wasmSpec) CreateFilter(args []interface{}) (filters.Filter, error) { case "file": code, err = os.ReadFile(u.Path) if err != nil { - logrus.Errorf("Failed to load file %q: %v", u.Path, err) - return nil, filters.ErrInvalidFilterParameters + return nil, fmt.Errorf("failed to load file %q: %v", u.Path, filters.ErrInvalidFilterParameters) } case "https": panic("not implemented") @@ -65,19 +107,79 @@ func (*wasmSpec) CreateFilter(args []interface{}) (filters.Filter, error) { ctx := context.Background() - cache := wazero.NewCompilationCache() - config := wazero.NewRuntimeConfig().WithCompilationCache(cache) - r := wazero.NewRuntimeWithConfig(ctx, config) + var r wazero.Runtime + switch ws.typ { + // in general, we likely do not need compilation + // cache, but likely we want to use Pre-/PostProcessor + // to not recreate the filter and check in + // CreateFilter to not compile the WASM code again and + // again + case none: + // we could try to use NewRuntimeConfigCompiler for + // GOARCH specific asm for optimal performance as + // stated in + // https://pkg.go.dev/github.com/tetratelabs/wazero#NewRuntimeConfigCompiler + config := wazero.NewRuntimeConfig().WithMemoryLimitPages(memoryLimitPages) + r = wazero.NewRuntimeWithConfig(ctx, config) + + case inmemory: + // TODO(sszuecs): unclear if we hit the bug described in https://pkg.go.dev/github.com/tetratelabs/wazero#RuntimeConfig for WithCompilationCache(): + + // Cached files are keyed on the version of wazero. This is obtained from go.mod of your application, + // and we use it to verify the compatibility of caches against the currently-running wazero. + // However, if you use this in tests of a package not named as `main`, then wazero cannot obtain the correct + // version of wazero due to the known issue of debug.BuildInfo function: https://github.com/golang/go/issues/33976. + // As a consequence, your cache won't contain the correct version information and always be treated as `dev` version. + // To avoid this issue, you can pass -ldflags "-X github.com/tetratelabs/wazero/internal/version.version=foo" when running tests. + + cache := wazero.NewCompilationCache() + config := wazero.NewRuntimeConfig().WithCompilationCache(cache) + config = config.WithMemoryLimitPages(memoryLimitPages) + r = wazero.NewRuntimeWithConfig(ctx, config) + + case filesystem: + cache, err := wazero.NewCompilationCacheWithDir(ws.cacheDir) + if err != nil { + return nil, fmt.Errorf("failed to create compilation cache dir with %q as directory: %w", ws.cacheDir, filters.ErrInvalidFilterParameters) + } + + config := wazero.NewRuntimeConfig().WithCompilationCache(cache) + config = config.WithMemoryLimitPages(memoryLimitPages) + r = wazero.NewRuntimeWithConfig(ctx, config) + + default: + return nil, fmt.Errorf("failed to create wazero runtime typ %q: %w", ws.typ, filters.ErrInvalidFilterParameters) + } // Instantiate WASI, which implements host functions needed for TinyGo to // implement `panic`. - wasi_snapshot_preview1.MustInstantiate(ctx, r) + // see also https://github.com/tetratelabs/wazero/blob/main/imports/README.md + // and https://wazero.io/languages/ + // + // we do not need the closer because of https://pkg.go.dev/github.com/tetratelabs/wazero@v1.6.0/imports/wasi_snapshot_preview1#hdr-Notes + // "Closing the wazero.Runtime has the same effect as closing the result." + _, err = wasi_snapshot_preview1.Instantiate(ctx, r) + if err != nil { + return nil, fmt.Errorf("failed to wasi_snapshot_preview1: %w: %w", err, filters.ErrInvalidFilterParameters) + } + // TODO(sszuecs): create modules to be used from user wasm code + // cmod, err := r.CompileModule(ctx, []byte("")) + // if err != nil { + // return nil, fmt.Errorf("failed to compile module: %w: %w", err, filters.ErrInvalidFilterParameters) + // } + // r.InstantiateModule(ctx) + // // Instantiate the guest Wasm into the same runtime. It exports the `add` // function, implemented in WebAssembly. + // mod, err := r.Instantiate(ctx, cmod, moduleConfig) + // if err != nil { + // return nil, fmt.Errorf("failed to instantiate module: %w: %w", err, filters.ErrInvalidFilterParameters) + // } + mod, err := r.Instantiate(ctx, code) if err != nil { - logrus.Fatalf("failed to instantiate module: %v", err) + return nil, fmt.Errorf("failed to instantiate module: %w: %w", err, filters.ErrInvalidFilterParameters) } request := mod.ExportedFunction("request") response := mod.ExportedFunction("response") @@ -88,8 +190,6 @@ func (*wasmSpec) CreateFilter(args []interface{}) (filters.Filter, error) { mod: mod, request: request, response: response, - cache: cache, - config: config, }, nil } @@ -97,18 +197,18 @@ func (w *wasm) Request(ctx filters.FilterContext) { result, err := w.request.Call(ctx.Request().Context(), 2, 3) if err != nil { - logrus.Errorf("failed to call add: %v", err) + log.Errorf("failed to call add: %v", err) } - logrus.Infof("request result: %v", result) + log.Infof("request result: %v", result) } func (w *wasm) Response(ctx filters.FilterContext) { result, err := w.response.Call(context.Background(), 3, 2) if err != nil { - logrus.Errorf("failed to call add: %v", err) + log.Errorf("failed to call add: %v", err) } - logrus.Infof("response result: %v", result) + log.Infof("response result: %v", result) } diff --git a/skipper.go b/skipper.go index 3457ffcbf7..ce5dcc99b6 100644 --- a/skipper.go +++ b/skipper.go @@ -42,6 +42,7 @@ import ( ratelimitfilters "github.com/zalando/skipper/filters/ratelimit" "github.com/zalando/skipper/filters/shedder" teefilters "github.com/zalando/skipper/filters/tee" + "github.com/zalando/skipper/filters/wasm" "github.com/zalando/skipper/loadbalancer" "github.com/zalando/skipper/logging" "github.com/zalando/skipper/metrics" @@ -1524,6 +1525,13 @@ func run(o Options, sig chan os.Signal, idleConnsCH chan struct{}) error { return err } + o.CustomFilters = append(o.CustomFilters, + wasm.NewWASM(wasm.WASMOpts{ + Typ: "none", + CacheDir: "", + }), + ) + // tee filters override with initialized tracer o.CustomFilters = append(o.CustomFilters, // tee()