You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2022/04/13 01:39:13 UTC
[apisix] branch master updated: feat(xRPC): add Admin API support (#6817)
This is an automated email from the ASF dual-hosted git repository.
spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 2199cdec1 feat(xRPC): add Admin API support (#6817)
2199cdec1 is described below
commit 2199cdec18e86bd8facb674bfe8f4dd0d6207cfb
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Wed Apr 13 09:39:05 2022 +0800
feat(xRPC): add Admin API support (#6817)
---
.github/workflows/build.yml | 2 +-
Makefile | 6 ++
apisix/init.lua | 11 +++
apisix/schema_def.lua | 16 ++++
apisix/stream/router/ip_port.lua | 12 +++
apisix/stream/xrpc.lua | 103 +++++++++++++++++++++
apisix/stream/xrpc/runner.lua | 26 ++++++
conf/config-default.yaml | 4 +
docs/en/latest/admin-api.md | 2 +
docs/zh/latest/admin-api.md | 2 +
t/APISIX.pm | 2 +-
t/admin/stream-routes.t | 56 ++++++++++-
.../apisix/stream/xrpc/protocols/pingpong/init.lua | 28 ++++++
.../stream/xrpc/protocols/pingpong/schema.lua | 49 ++++++++++
t/xrpc/pingpong.t | 88 ++++++++++++++++++
15 files changed, 404 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 6df741c16..a47e69741 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -29,7 +29,7 @@ jobs:
test_dir:
- t/plugin
- t/admin t/cli t/config-center-yaml t/control t/core t/debug t/discovery t/error_page t/misc
- - t/node t/router t/script t/stream-node t/utils t/wasm t/xds-library
+ - t/node t/router t/script t/stream-node t/utils t/wasm t/xds-library t/xrpc
runs-on: ${{ matrix.platform }}
timeout-minutes: 90
diff --git a/Makefile b/Makefile
index 00f39888f..708b4fba6 100644
--- a/Makefile
+++ b/Makefile
@@ -340,12 +340,18 @@ install: runtime
$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/ssl/router
$(ENV_INSTALL) apisix/ssl/router/*.lua $(ENV_INST_LUADIR)/apisix/ssl/router/
+ $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/stream
+ $(ENV_INSTALL) apisix/stream/*.lua $(ENV_INST_LUADIR)/apisix/stream/
+
$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/stream/plugins
$(ENV_INSTALL) apisix/stream/plugins/*.lua $(ENV_INST_LUADIR)/apisix/stream/plugins/
$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/stream/router
$(ENV_INSTALL) apisix/stream/router/*.lua $(ENV_INST_LUADIR)/apisix/stream/router/
+ $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/stream/xrpc
+ $(ENV_INSTALL) apisix/stream/xrpc/*.lua $(ENV_INST_LUADIR)/apisix/stream/xrpc/
+
$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/utils
$(ENV_INSTALL) apisix/utils/*.lua $(ENV_INST_LUADIR)/apisix/utils/
diff --git a/apisix/init.lua b/apisix/init.lua
index b61d43615..b4438cf0f 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -37,6 +37,7 @@ local router = require("apisix.router")
local apisix_upstream = require("apisix.upstream")
local set_upstream = apisix_upstream.set_by_route
local upstream_util = require("apisix.utils.upstream")
+local xrpc = require("apisix.stream.xrpc")
local ctxdump = require("resty.ctxdump")
local ipmatcher = require("resty.ipmatcher")
local ngx_balancer = require("ngx.balancer")
@@ -86,6 +87,8 @@ function _M.http_init(args)
core.log.error("failed to load the configuration: ", err)
end
end
+
+ xrpc.init()
end
@@ -830,6 +833,8 @@ function _M.stream_init(args)
core.log.error("failed to load the configuration: ", err)
end
end
+
+ xrpc.init()
end
@@ -845,6 +850,7 @@ function _M.stream_init_worker()
core.log.info("random stream test in [1, 10000]: ", math.random(1, 10000))
plugin.init_worker()
+ xrpc.init_worker()
router.stream_init_worker()
apisix_upstream.init_worker()
@@ -921,6 +927,11 @@ function _M.stream_preread_phase()
plugin.run_plugin("preread", plugins, api_ctx)
+ if matched_route.value.protocol then
+ xrpc.run_protocol(matched_route.value.protocol, api_ctx)
+ return
+ end
+
local code, err = set_upstream(matched_route, api_ctx)
if code then
core.log.error("failed to set upstream: ", err)
diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua
index 199bee1da..ec78a7256 100644
--- a/apisix/schema_def.lua
+++ b/apisix/schema_def.lua
@@ -796,6 +796,21 @@ _M.global_rule = {
}
+local xrpc_protocol_schema = {
+ type = "object",
+ properties = {
+ name = {
+ type = "string",
+ },
+ conf = {
+ description = "protocol-specific configuration",
+ type = "object",
+ },
+ },
+ required = {"name"}
+}
+
+
_M.stream_route = {
type = "object",
properties = {
@@ -821,6 +836,7 @@ _M.stream_route = {
upstream = upstream_schema,
upstream_id = id_schema,
plugins = plugins_schema,
+ protocol = xrpc_protocol_schema,
}
}
diff --git a/apisix/stream/router/ip_port.lua b/apisix/stream/router/ip_port.lua
index 76e999052..f762d348d 100644
--- a/apisix/stream/router/ip_port.lua
+++ b/apisix/stream/router/ip_port.lua
@@ -20,6 +20,7 @@ local config_util = require("apisix.core.config_util")
local stream_plugin_checker = require("apisix.plugin").stream_plugin_checker
local router_new = require("apisix.utils.router").new
local apisix_ssl = require("apisix.ssl")
+local xrpc = require("apisix.stream.xrpc")
local error = error
local tonumber = tonumber
local ipairs = ipairs
@@ -205,6 +206,17 @@ local function stream_route_checker(item, in_cp)
return false, "invalid server_addr: " .. item.server_addr
end
end
+
+ if item.protocol then
+ local prot_conf = item.protocol
+ if prot_conf then
+ local ok, message = xrpc.check_schema(prot_conf, false)
+ if not ok then
+ return false, message
+ end
+ end
+ end
+
return true
end
_M.stream_route_checker = stream_route_checker
diff --git a/apisix/stream/xrpc.lua b/apisix/stream/xrpc.lua
new file mode 100644
index 000000000..418ec4cf4
--- /dev/null
+++ b/apisix/stream/xrpc.lua
@@ -0,0 +1,103 @@
+--
+-- 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 require = require
+local core = require("apisix.core")
+local ipairs = ipairs
+local pairs = pairs
+local ngx_exit = ngx.exit
+
+
+local is_http = true
+local runner
+if ngx.config.subsystem ~= "http" then
+ is_http = false
+ runner = require("apisix.stream.xrpc.runner")
+end
+
+local _M = {}
+local registered_protocols = {}
+local registered_protocol_schemas = {}
+
+
+-- only need to load schema module when it is used in Admin API
+local function register_protocol(name, is_http)
+ if not is_http then
+ registered_protocols[name] = require("apisix.stream.xrpc.protocols." .. name)
+ end
+
+ registered_protocol_schemas[name] =
+ require("apisix.stream.xrpc.protocols." .. name .. ".schema")
+end
+
+
+function _M.init()
+ local local_conf = core.config.local_conf(true)
+ if not local_conf.xrpc then
+ return
+ end
+
+ local prot_conf = local_conf.xrpc.protocols
+ if not prot_conf then
+ return
+ end
+
+ if is_http and not local_conf.apisix.enable_admin then
+ -- we need to register xRPC protocols in HTTP only when Admin API is enabled
+ return
+ end
+
+ for _, prot in ipairs(prot_conf) do
+ core.log.info("register xprc protocol ", prot.name)
+ register_protocol(prot.name, is_http)
+ end
+end
+
+
+function _M.init_worker()
+ for _, prot in pairs(registered_protocols) do
+ if prot.init_worker then
+ prot.init_worker()
+ end
+ end
+end
+
+
+function _M.check_schema(item, skip_disabled_plugin)
+ local name = item.name
+ local protocol = registered_protocol_schemas[name]
+ if not protocol and not skip_disabled_plugin then
+ -- like plugins, ignore unknown plugin if the schema is checked in the DP
+ return false, "unknown protocol [" .. name .. "]"
+ end
+
+ -- check protocol-specific configuration
+ if not item.conf then
+ return true
+ end
+ return protocol.check_schema(item.conf)
+end
+
+
+function _M.run_protocol(conf, ctx)
+ local name = conf.name
+ local protocol = registered_protocols[name]
+ local code = runner.run(protocol, ctx)
+ return ngx_exit(code)
+end
+
+
+return _M
diff --git a/apisix/stream/xrpc/runner.lua b/apisix/stream/xrpc/runner.lua
new file mode 100644
index 000000000..61ce5385c
--- /dev/null
+++ b/apisix/stream/xrpc/runner.lua
@@ -0,0 +1,26 @@
+--
+-- 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 _M = {}
+
+
+function _M.run(protocol, ctx)
+ -- return non-zero code to terminal the session
+ return 200
+end
+
+
+return _M
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index 74a187eb5..4522fdc18 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -410,6 +410,10 @@ stream_plugins: # sorted by priority
#priority: 7999
#file: t/wasm/log/main.go.wasm
+#xrpc:
+ #protocols:
+ #- name: pingpong
+
plugin_attr:
log-rotate:
interval: 3600 # rotate interval (unit: second)
diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md
index 9db563774..6e59c60d6 100644
--- a/docs/en/latest/admin-api.md
+++ b/docs/en/latest/admin-api.md
@@ -960,6 +960,8 @@ Route used in the [Stream Proxy](./stream-proxy.md).
| server_addr | False | IP/CIDR | Filters Upstream forwards by matching with APISIX Server IP. | "127.0.0.1/32" or "127.0.0.1" |
| server_port | False | Integer | Filters Upstream forwards by matching with APISIX Server port. | 9090 |
| sni | False | Host | Server Name Indication. | "test.com" |
+| protocol.name | False | String | Name of the protocol proxyed by xRPC framework. | "redis" |
+| protocol.conf | False | Configuration | Protocol-specific configuration. | |
To learn more about filtering in stream proxies, check [this](./stream-proxy.md#more-route-match-options) document.
diff --git a/docs/zh/latest/admin-api.md b/docs/zh/latest/admin-api.md
index fc3829061..d2b0e2330 100644
--- a/docs/zh/latest/admin-api.md
+++ b/docs/zh/latest/admin-api.md
@@ -979,6 +979,8 @@ $ curl "http://127.0.0.1:9080/apisix/admin/plugins/key-auth" -H 'X-API-KEY:
| server_addr | 可选 | IP/CIDR | 过滤选项:如果 APISIX 服务器 IP 与 server_addr 匹配,则转发到上游 | "127.0.0.1/32" 或 "127.0.0.1" |
| server_port | 可选 | 整数 | 过滤选项:如果 APISIX 服务器 port 与 server_port 匹配,则转发到上游 | 9090 |
| sni | 可选 | Host | 服务器名称指示 | "test.com" |
+| protocol.name | 可选 | 字符串 | xRPC 框架代理的协议的名称 | "redis" |
+| protocol.conf | 可选 | 配置 | 协议特定的配置 | |
点击 [此处](./stream-proxy.md#more-route-match-options),了解更多有关过滤器如何工作的信息。
diff --git a/t/APISIX.pm b/t/APISIX.pm
index 1bd72edf3..47efd2745 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -240,7 +240,7 @@ _EOC_
}
my $lua_deps_path = $block->lua_deps_path // <<_EOC_;
- lua_package_path "$apisix_home/?.lua;$apisix_home/?/init.lua;$apisix_home/deps/share/lua/5.1/?/init.lua;$apisix_home/deps/share/lua/5.1/?.lua;$apisix_home/apisix/?.lua;$apisix_home/t/?.lua;;";
+ lua_package_path "$apisix_home/?.lua;$apisix_home/?/init.lua;$apisix_home/deps/share/lua/5.1/?/init.lua;$apisix_home/deps/share/lua/5.1/?.lua;$apisix_home/apisix/?.lua;$apisix_home/t/?.lua;$apisix_home/t/xrpc/?.lua;$apisix_home/t/xrpc/?/init.lua;;";
lua_package_cpath "$apisix_home/?.so;$apisix_home/deps/lib/lua/5.1/?.so;$apisix_home/deps/lib64/lua/5.1/?.so;;";
_EOC_
diff --git a/t/admin/stream-routes.t b/t/admin/stream-routes.t
index 85c7d9a5b..6552165b5 100644
--- a/t/admin/stream-routes.t
+++ b/t/admin/stream-routes.t
@@ -580,7 +580,6 @@ GET /t
}
}]]
)
-
if code >= 300 then
ngx.status = code
end
@@ -594,3 +593,58 @@ GET /t
{"error_msg":"unknown plugin [mqttt-proxy]"}
--- no_error_log
[error]
+
+
+
+=== TEST 16: validate protocol
+--- extra_yaml_config
+xrpc:
+ protocols:
+ - name: pingpong
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local etcd = require("apisix.core.etcd")
+ for _, case in ipairs({
+ {input = {
+ name = "xxx",
+ }},
+ {input = {
+ name = "pingpong",
+ }},
+ {input = {
+ name = "pingpong",
+ conf = {
+ faults = "a",
+ }
+ }},
+ }) do
+ local code, body = t('/apisix/admin/stream_routes/1',
+ ngx.HTTP_PUT,
+ {
+ protocol = case.input,
+ upstream = {
+ nodes = {
+ ["127.0.0.1:8080"] = 1
+ },
+ type = "roundrobin"
+ }
+ }
+ )
+ if code > 300 then
+ ngx.print(body)
+ else
+ ngx.say(body)
+ end
+ end
+ }
+ }
+--- request
+GET /t
+--- response_body
+{"error_msg":"unknown protocol [xxx]"}
+passed
+{"error_msg":"property \"faults\" validation failed: wrong type: expected array, got string"}
+--- no_error_log
+[error]
diff --git a/t/xrpc/apisix/stream/xrpc/protocols/pingpong/init.lua b/t/xrpc/apisix/stream/xrpc/protocols/pingpong/init.lua
new file mode 100644
index 000000000..fca9de366
--- /dev/null
+++ b/t/xrpc/apisix/stream/xrpc/protocols/pingpong/init.lua
@@ -0,0 +1,28 @@
+--
+-- 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 _M = {}
+
+
+function _M.init_worker()
+ core.log.info("call pingpong's init_worker")
+end
+
+
+return _M
diff --git a/t/xrpc/apisix/stream/xrpc/protocols/pingpong/schema.lua b/t/xrpc/apisix/stream/xrpc/protocols/pingpong/schema.lua
new file mode 100644
index 000000000..0e2f7256a
--- /dev/null
+++ b/t/xrpc/apisix/stream/xrpc/protocols/pingpong/schema.lua
@@ -0,0 +1,49 @@
+--
+-- 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 schema = {
+ type = "object",
+ properties = {
+ faults = {
+ type = "array",
+ minItems = 1,
+ items = {
+ type = "object",
+ properties = {
+ header_type = { type = "string" },
+ delay = {
+ type = "number",
+ description = "additional delay in seconds",
+ }
+ },
+ required = {"header_type"}
+ },
+ },
+ },
+}
+
+local _M = {}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+return _M
diff --git a/t/xrpc/pingpong.t b/t/xrpc/pingpong.t
new file mode 100644
index 000000000..4e6e527aa
--- /dev/null
+++ b/t/xrpc/pingpong.t
@@ -0,0 +1,88 @@
+#
+# 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.
+#
+use t::APISIX;
+
+my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
+my $version = eval { `$nginx_binary -V 2>&1` };
+
+if ($version !~ m/\/apisix-nginx-module/) {
+ plan(skip_all => "apisix-nginx-module not installed");
+} else {
+ plan('no_plan');
+}
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ if (!$block->extra_yaml_config) {
+ my $extra_yaml_config = <<_EOC_;
+xrpc:
+ protocols:
+ - name: pingpong
+_EOC_
+ $block->set_value("extra_yaml_config", $extra_yaml_config);
+ }
+
+ if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+ $block->set_value("no_error_log", "[error]");
+ }
+
+ $block;
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: init
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local etcd = require("apisix.core.etcd")
+ local code, body = t('/apisix/admin/stream_routes/1',
+ ngx.HTTP_PUT,
+ {
+ protocol = {
+ name = "pingpong"
+ },
+ upstream = {
+ nodes = {
+ ["127.0.0.1:1995"] = 1
+ },
+ type = "roundrobin"
+ }
+ }
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 2: hit
+--- stream_request eval
+mmm
+--- error_log
+call pingpong's init_worker