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