Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial wasm support #5288

Merged
merged 2 commits into from
Oct 22, 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
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/*
4 changes: 2 additions & 2 deletions .licenserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ header:
- '**/*.log'
# Exclude test toolkit files
- 't/toolkit'
- 't/chaos/go.mod'
- 't/chaos/go.sum'
- 'go.mod'
- 'go.sum'
# Exclude non-Apache licensed files
- 'apisix/balancer/ewma.lua'
# Exclude plugin-specific configuration files
Expand Down
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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lack of a reasonable length range?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to have a limitation of the length. Otherwise people will ask they can't set a xxx with length yyy.

},
file = {
type = "string"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditoo

},
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, plugin in pairs(local_plugins_hash) do
local ty = PLUGIN_TYPE_HTTP
if plugin.type == "wasm" 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
120 changes: 120 additions & 0 deletions apisix/wasm.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
--
-- 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: ctx.conf_type .. '#' .. ctx.conf_id is better

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current way is faster

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"
nic-chen marked this conversation as resolved.
Show resolved Hide resolved
end

local name = attrs.name
local priority = attrs.priority
local plugin, err = wasm.load(name, attrs.file)
if not plugin then
return nil, err
nic-chen marked this conversation as resolved.
Show resolved Hide resolved
end

local mod = {
version = 0.1,
name = name,
priority = priority,
schema = schema,
check_schema = check_schema,
plugin = plugin,
type = "wasm",
}
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