Skip to content

Commit

Permalink
feat: initial wasm support
Browse files Browse the repository at this point in the history
Signed-off-by: spacewander <spacewanderlzx@gmail.com>
  • Loading branch information
spacewander committed Oct 20, 2021
1 parent ae08d23 commit 72da57a
Show file tree
Hide file tree
Showing 17 changed files with 985 additions and 16 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ jobs:
- name: Linux Get dependencies
run: sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3 libpcre3-dev libldap2-dev

- name: Build wasm code
if: startsWith(matrix.os_name, 'linux_openresty')
run: |
export TINYGO_VER=0.20.0
wget https://github.com/tinygo-org/tinygo/releases/download/v${TINYGO_VER}/tinygo_${TINYGO_VER}_amd64.deb 2>/dev/null
sudo dpkg -i tinygo_${TINYGO_VER}_amd64.deb
cd t/wasm && find . -type f -name "main.go" | xargs -Ip tinygo build -o p.wasm -scheduler=none -target=wasi p
- name: Linux Before install
run: sudo ./ci/${{ matrix.os_name }}_runner.sh before_install

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ build-cache/
t/fuzzing/__pycache__/
boofuzz-results/
*.pyc
*.wasm
# release tar package
*.tgz
release/*
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ A/B testing, canary release, blue-green deployment, limit rate, defense against
- **Highly scalable**
- [Custom plugins](docs/en/latest/plugin-develop.md): Allows hooking of common phases, such as `rewrite`, `access`, `header filter`, `body filter` and `log`, also allows to hook the `balancer` stage.
- [Plugin can be written in Java/Go/Python](docs/en/latest/external-plugin.md)
- [Plugin can be written with Proxy WASM SDK](docs/en/latest/wasm.md)
- Custom load balancing algorithms: You can use custom load balancing algorithms during the `balancer` phase.
- Custom routing: Support users to implement routing algorithms themselves.

Expand Down
4 changes: 4 additions & 0 deletions apisix/cli/ngx_tpl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ http {
}
{% end %}
{% if wasm then %}
wasm_vm wasmtime;
{% end %}
init_by_lua_block {
require "resty.core"
{% if lua_module_hook then %}
Expand Down
27 changes: 26 additions & 1 deletion apisix/cli/ops.lua
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,31 @@ local config_schema = {
}
}
}
}
},
wasm = {
type = "object",
properties = {
plugins = {
type = "array",
minItems = 1,
items = {
type = "object",
properties = {
name = {
type = "string"
},
file = {
type = "string"
},
priority = {
type = "integer"
}
},
required = {"name", "file", "priority"}
}
}
}
},
}
}

Expand Down Expand Up @@ -712,6 +736,7 @@ Please modify "admin_key" in conf/config.yaml .
for k,v in pairs(yaml_conf.nginx_config) do
sys_conf[k] = v
end
sys_conf["wasm"] = yaml_conf.wasm


local wrn = sys_conf["worker_rlimit_nofile"]
Expand Down
65 changes: 50 additions & 15 deletions apisix/plugin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ local require = require
local core = require("apisix.core")
local config_util = require("apisix.core.config_util")
local enable_debug = require("apisix.debug").enable_debug
local wasm = require("apisix.wasm")
local ngx_exit = ngx.exit
local pkg_loaded = package.loaded
local sort_tab = table.sort
Expand Down Expand Up @@ -67,9 +68,16 @@ local function sort_plugin(l, r)
end


local function unload_plugin(name, is_stream_plugin)
local PLUGIN_TYPE_HTTP = 1
local PLUGIN_TYPE_STREAM = 2
local PLUGIN_TYPE_HTTP_WASM = 3
local function unload_plugin(name, plugin_type)
if plugin_type == PLUGIN_TYPE_HTTP_WASM then
return
end

local pkg_name = "apisix.plugins." .. name
if is_stream_plugin then
if plugin_type == PLUGIN_TYPE_STREAM then
pkg_name = "apisix.stream.plugins." .. name
end

Expand All @@ -82,13 +90,21 @@ local function unload_plugin(name, is_stream_plugin)
end


local function load_plugin(name, plugins_list, is_stream_plugin)
local pkg_name = "apisix.plugins." .. name
if is_stream_plugin then
pkg_name = "apisix.stream.plugins." .. name
local function load_plugin(name, plugins_list, plugin_type)
local ok, plugin
if plugin_type == PLUGIN_TYPE_HTTP_WASM then
-- for wasm plugin, we pass the whole attrs instead of name
ok, plugin = wasm.require(name)
name = name.name
else
local pkg_name = "apisix.plugins." .. name
if plugin_type == PLUGIN_TYPE_STREAM then
pkg_name = "apisix.stream.plugins." .. name
end

ok, plugin = pcall(require, pkg_name)
end

local ok, plugin = pcall(require, pkg_name)
if not ok then
core.log.error("failed to load plugin [", name, "] err: ", plugin)
return
Expand Down Expand Up @@ -140,25 +156,39 @@ local function load_plugin(name, plugins_list, is_stream_plugin)
end


local function load(plugin_names)
local function load(plugin_names, wasm_plugin_names)
local processed = {}
for _, name in ipairs(plugin_names) do
if processed[name] == nil then
processed[name] = true
end
end
for _, attrs in ipairs(wasm_plugin_names) do
if processed[attrs.name] == nil then
processed[attrs.name] = attrs
end
end

core.log.warn("new plugins: ", core.json.delay_encode(processed))

for name in pairs(local_plugins_hash) do
unload_plugin(name)
for name, value in pairs(local_plugins_hash) do
local ty = PLUGIN_TYPE_HTTP
if type(value) == "table" then
ty = PLUGIN_TYPE_HTTP_WASM
end
unload_plugin(name, ty)
end

core.table.clear(local_plugins)
core.table.clear(local_plugins_hash)

for name in pairs(processed) do
load_plugin(name, local_plugins)
for name, value in pairs(processed) do
local ty = PLUGIN_TYPE_HTTP
if type(value) == "table" then
ty = PLUGIN_TYPE_HTTP_WASM
name = value
end
load_plugin(name, local_plugins, ty)
end

-- sort by plugin's priority
Expand Down Expand Up @@ -192,14 +222,14 @@ local function load_stream(plugin_names)
core.log.warn("new plugins: ", core.json.delay_encode(processed))

for name in pairs(stream_local_plugins_hash) do
unload_plugin(name, true)
unload_plugin(name, PLUGIN_TYPE_STREAM)
end

core.table.clear(stream_local_plugins)
core.table.clear(stream_local_plugins_hash)

for name in pairs(processed) do
load_plugin(name, stream_local_plugins, true)
load_plugin(name, stream_local_plugins, PLUGIN_TYPE_STREAM)
end

-- sort by plugin's priority
Expand Down Expand Up @@ -260,7 +290,12 @@ function _M.load(config)
if not http_plugin_names then
core.log.error("failed to read plugin list from local file")
else
local ok, err = load(http_plugin_names)
local wasm_plugin_names = {}
if local_conf.wasm then
wasm_plugin_names = local_conf.wasm.plugins
end

local ok, err = load(http_plugin_names, wasm_plugin_names)
if not ok then
core.log.error("failed to load plugins: ", err)
end
Expand Down
119 changes: 119 additions & 0 deletions apisix/wasm.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You 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.
--
local core = require("apisix.core")
local support_wasm, wasm = pcall(require, "resty.proxy-wasm")
local concat = table.concat


local schema = {
type = "object",
properties = {
conf = {
type = "string"
},
},
required = {"conf"}
}
local _M = {}


local function check_schema(conf)
return core.schema.check(schema, conf)
end


local get_plugin_ctx_key
do
local key_buf = {
nil,
nil,
}

function get_plugin_ctx_key(ctx)
key_buf[1] = ctx.conf_type
key_buf[2] = ctx.conf_id
return concat(key_buf, "#", 1, 2)
end
end

local function fetch_plugin_ctx(conf, ctx, plugin)
if not conf.plugin_ctxs then
conf.plugin_ctxs = {}
end

local ctxs = conf.plugin_ctxs
local key = get_plugin_ctx_key(ctx)
local plugin_ctx = ctxs[key]
local err
if not plugin_ctx then
plugin_ctx, err = wasm.on_configure(plugin, conf.conf)
if not plugin_ctx then
return nil, err
end

ctxs[key] = plugin_ctx
end

return plugin_ctx
end


local function access_wrapper(self, conf, ctx)
local plugin_ctx, err = fetch_plugin_ctx(conf, ctx, self.plugin)
if not plugin_ctx then
core.log.error("failed to init wasm plugin ctx: ", err)
return 503
end

local ok, err = wasm.on_http_request_headers(plugin_ctx)
if not ok then
core.log.error("failed to run wasm plugin: ", err)
return 503
end
end


function _M.require(attrs)
if not support_wasm then
return nil, "need to build APISIX-OpenResty to support wasm"
end

local name = attrs.name
local priority = attrs.priority
local plugin, err = wasm.load(name, attrs.file)
if not plugin then
return nil, err
end

local mod = {
version = 0.1,
name = name,
priority = priority,
schema = schema,
check_schema = check_schema,
plugin = plugin,
}
mod.access = function (conf, ctx)
return access_wrapper(mod, conf, ctx)
end

-- the returned values need to be the same as the Lua's 'require'
return true, mod
end


return _M
6 changes: 6 additions & 0 deletions conf/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,12 @@ stream_plugins: # sorted by priority
- mqtt-proxy # priority: 1000
# <- recommend to use priority (0, 100) for your custom plugins

#wasm:
#plugins:
#- name: wasm_log
#priority: 7999
#file: t/wasm/log/main.go.wasm

plugin_attr:
log-rotate:
interval: 3600 # rotate interval (unit: second)
Expand Down
4 changes: 4 additions & 0 deletions docs/en/latest/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@
"type": "doc",
"id": "external-plugin"
},
{
"type": "doc",
"id": "wasm"
},
{
"type": "doc",
"id": "plugin-interceptors"
Expand Down
Loading

0 comments on commit 72da57a

Please sign in to comment.