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 2020/12/18 07:05:15 UTC

[apisix] branch master updated: feat: add control API (#3048)

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 924a30d  feat: add control API (#3048)
924a30d is described below

commit 924a30db36268ee1d7fab255ddebfc23a7110314
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Fri Dec 18 15:05:09 2020 +0800

    feat: add control API (#3048)
    
    Signed-off-by: spacewander <sp...@gmail.com>
    Co-authored-by: John Bampton <jbampton@users.noreply.github.com
---
 .../common.sh}                                     |  26 +----
 .../test_ci_only.sh}                               |  14 +--
 .travis/apisix_cli_test/test_control.sh            | 118 ++++++++++++++++++++
 .../test_main.sh}                                  |  14 +--
 .travis/linux_apisix_current_luarocks_runner.sh    |   5 +-
 apisix/cli/ngx_tpl.lua                             |  14 +++
 apisix/cli/ops.lua                                 |  19 ++++
 apisix/control/router.lua                          | 119 +++++++++++++++++++++
 apisix/control/v1.lua                              |  33 ++++++
 apisix/init.lua                                    |  12 +++
 apisix/plugins/example-plugin.lua                  |  25 +++++
 conf/config-default.yaml                           |   4 +
 doc/README.md                                      |   1 +
 doc/control-api.md                                 |  51 +++++++++
 t/APISIX.pm                                        |   6 ++
 .travis/apisix_cli_test_in_ci.sh => t/control/v1.t |  54 +++++-----
 16 files changed, 445 insertions(+), 70 deletions(-)

diff --git a/.travis/apisix_cli_test_in_ci.sh b/.travis/apisix_cli_test/common.sh
similarity index 59%
copy from .travis/apisix_cli_test_in_ci.sh
copy to .travis/apisix_cli_test/common.sh
index 87410fe..23190ba 100755
--- a/.travis/apisix_cli_test_in_ci.sh
+++ b/.travis/apisix_cli_test/common.sh
@@ -1,5 +1,3 @@
-#!/usr/bin/env bash
-
 #
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
@@ -17,33 +15,17 @@
 # limitations under the License.
 #
 
-# This file is like apisix_cli_test.sh, but requires extra dependencies which
-# you don't need them in daily development.
+# 'make init' operates scripts and related configuration files in the current directory
+# The 'apisix' command is a command in the /usr/local/apisix,
+# and the configuration file for the operation is in the /usr/local/apisix/conf
 
 set -ex
 
 clean_up() {
+    make stop || true
     git checkout conf/config.yaml
 }
 
 trap clean_up EXIT
 
 unset APISIX_PROFILE
-
-# check error handling when connecting to old etcd
-git checkout conf/config.yaml
-
-echo '
-etcd:
-  host:
-    - "http://127.0.0.1:3379"
-  prefix: "/apisix"
-' > conf/config.yaml
-
-out=$(make init 2>&1 || true)
-if ! echo "$out" | grep 'etcd cluster version 3.3.0 is less than the required version 3.4.0'; then
-    echo "failed: properly handle the error when connecting to old etcd"
-    exit 1
-fi
-
-echo "passed: properly handle the error when connecting to old etcd"
diff --git a/.travis/apisix_cli_test_in_ci.sh b/.travis/apisix_cli_test/test_ci_only.sh
similarity index 85%
copy from .travis/apisix_cli_test_in_ci.sh
copy to .travis/apisix_cli_test/test_ci_only.sh
index 87410fe..6b064a0 100755
--- a/.travis/apisix_cli_test_in_ci.sh
+++ b/.travis/apisix_cli_test/test_ci_only.sh
@@ -17,18 +17,10 @@
 # limitations under the License.
 #
 
-# This file is like apisix_cli_test.sh, but requires extra dependencies which
-# you don't need them in daily development.
+# This file is like other test_*.sh, but requires extra dependencies which
+# you don't need in daily development.
 
-set -ex
-
-clean_up() {
-    git checkout conf/config.yaml
-}
-
-trap clean_up EXIT
-
-unset APISIX_PROFILE
+. ./.travis/apisix_cli_test/common.sh
 
 # check error handling when connecting to old etcd
 git checkout conf/config.yaml
diff --git a/.travis/apisix_cli_test/test_control.sh b/.travis/apisix_cli_test/test_control.sh
new file mode 100755
index 0000000..816dd69
--- /dev/null
+++ b/.travis/apisix_cli_test/test_control.sh
@@ -0,0 +1,118 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+#
+
+. ./.travis/apisix_cli_test/common.sh
+
+# control server
+echo '
+apisix:
+  enable_control: true
+' > conf/config.yaml
+
+make init
+
+if ! grep "listen 127.0.0.1:9090;" conf/nginx.conf > /dev/null; then
+    echo "failed: find default address for control server"
+    exit 1
+fi
+
+make run
+
+sleep 0.1
+code=$(curl -v -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.1:9090/v1/schema)
+
+if [ ! $code -eq 200 ]; then
+    echo "failed: access control server"
+    exit 1
+fi
+
+code=$(curl -v -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.1:9090/v0/schema)
+
+if [ ! $code -eq 404 ]; then
+    echo "failed: handle route not found"
+    exit 1
+fi
+
+make stop
+
+echo '
+apisix:
+  enable_control: true
+  control:
+    ip: 127.0.0.2
+' > conf/config.yaml
+
+make init
+
+if ! grep "listen 127.0.0.2:9090;" conf/nginx.conf > /dev/null; then
+    echo "failed: customize address for control server"
+    exit 1
+fi
+
+make run
+
+sleep 0.1
+code=$(curl -v -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.2:9090/v1/schema)
+
+if [ ! $code -eq 200 ]; then
+    echo "failed: access control server"
+    exit 1
+fi
+
+make stop
+
+echo '
+apisix:
+  enable_control: true
+  control:
+    port: 9091
+' > conf/config.yaml
+
+make init
+
+if ! grep "listen 127.0.0.1:9091;" conf/nginx.conf > /dev/null; then
+    echo "failed: customize address for control server"
+    exit 1
+fi
+
+make run
+
+sleep 0.1
+code=$(curl -v -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.1:9091/v1/schema)
+
+if [ ! $code -eq 200 ]; then
+    echo "failed: access control server"
+    exit 1
+fi
+
+make stop
+
+echo '
+apisix:
+  enable_control: false
+' > conf/config.yaml
+
+make init
+
+if grep "listen 127.0.0.1:9090;" conf/nginx.conf > /dev/null; then
+    echo "failed: disable control server"
+    exit 1
+fi
+
+echo "pass: access control server"
diff --git a/.travis/apisix_cli_test.sh b/.travis/apisix_cli_test/test_main.sh
similarity index 99%
rename from .travis/apisix_cli_test.sh
rename to .travis/apisix_cli_test/test_main.sh
index 5b43330..a5ba0ab 100755
--- a/.travis/apisix_cli_test.sh
+++ b/.travis/apisix_cli_test/test_main.sh
@@ -21,15 +21,7 @@
 # The 'apisix' command is a command in the /usr/local/apisix,
 # and the configuration file for the operation is in the /usr/local/apisix/conf
 
-set -ex
-
-clean_up() {
-    git checkout conf/config.yaml
-}
-
-trap clean_up EXIT
-
-unset APISIX_PROFILE
+. ./.travis/apisix_cli_test/common.sh
 
 git checkout conf/config.yaml
 
@@ -546,7 +538,7 @@ if [ $count_test_access_log -eq 0 ]; then
 fi
 
 count_access_log_off=`grep -c "access_log off;" conf/nginx.conf || true`
-if [ $count_access_log_off -eq 2 ]; then
+if [ $count_access_log_off -eq 3 ]; then
     echo "failed: nginx.conf file find access_log off; when enable access log"
     exit 1
 fi
@@ -581,7 +573,7 @@ if [ $count_test_access_log -eq 1 ]; then
 fi
 
 count_access_log_off=`grep -c "access_log off;" conf/nginx.conf || true`
-if [ $count_access_log_off -ne 2 ]; then
+if [ $count_access_log_off -ne 3 ]; then
     echo "failed: nginx.conf file doesn't find access_log off; when disable access log"
     exit 1
 fi
diff --git a/.travis/linux_apisix_current_luarocks_runner.sh b/.travis/linux_apisix_current_luarocks_runner.sh
index c3c64fa..70d2866 100755
--- a/.travis/linux_apisix_current_luarocks_runner.sh
+++ b/.travis/linux_apisix_current_luarocks_runner.sh
@@ -54,8 +54,9 @@ script() {
     cd ..
 
     # apisix cli test
-    sudo PATH=$PATH .travis/apisix_cli_test.sh
-    sudo PATH=$PATH .travis/apisix_cli_test_in_ci.sh
+    for f in ./.travis/apisix_cli_test/test_*.sh; do
+        sudo PATH="$PATH" "$f"
+    done
 }
 
 case_opt=$1
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index ed693d0..3dabcf0 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -255,6 +255,20 @@ http {
         apisix.http_init_worker()
     }
 
+    {% if enable_control then %}
+    server {
+        listen {* control_server_addr *};
+
+        access_log off;
+
+        location / {
+            content_by_lua_block {
+                apisix.http_control()
+            }
+        }
+    }
+    {% end %}
+
     {% if enable_admin and port_admin then %}
     server {
         {%if https_admin then%}
diff --git a/apisix/cli/ops.lua b/apisix/cli/ops.lua
index fe663b5..76d8442 100644
--- a/apisix/cli/ops.lua
+++ b/apisix/cli/ops.lua
@@ -265,6 +265,25 @@ Please modify "admin_key" in conf/config.yaml .
         sys_conf[k] = v
     end
 
+    if yaml_conf.apisix.enable_control then
+        if not yaml_conf.apisix.control then
+            sys_conf.control_server_addr = "127.0.0.1:9090"
+        else
+            local ip = yaml_conf.apisix.control.ip
+            local port = tonumber(yaml_conf.apisix.control.port)
+
+            if ip == nil then
+                ip = "127.0.0.1"
+            end
+
+            if not port then
+                port = 9090
+            end
+
+            sys_conf.control_server_addr = ip .. ":" .. port
+        end
+    end
+
     local wrn = sys_conf["worker_rlimit_nofile"]
     local wc = sys_conf["event"]["worker_connections"]
     if not wrn or wrn <= wc then
diff --git a/apisix/control/router.lua b/apisix/control/router.lua
new file mode 100644
index 0000000..221d223
--- /dev/null
+++ b/apisix/control/router.lua
@@ -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 require = require
+local router = require("resty.radixtree")
+local builtin_v1_routes = require("apisix.control.v1")
+local plugin_mod = require("apisix.plugin")
+local core = require("apisix.core")
+local str_sub = string.sub
+local ipairs = ipairs
+local type = type
+local ngx = ngx
+local get_method = ngx.req.get_method
+
+
+local _M = {}
+local current_version = 1
+
+
+local fetch_control_api_router
+do
+    local function register_api_routes(routes, api_routes)
+        for _, route in ipairs(api_routes) do
+            core.table.insert(routes, {
+                methods = route.methods,
+                -- note that it is 'uris' for control API, which is an array of strings
+                paths = route.uris,
+                handler = function (api_ctx)
+                    local code, body = route.handler(api_ctx)
+                    if code or body then
+                        if type(body) == "table" and ngx.header["Content-Type"] == nil then
+                            core.response.set_header("Content-Type", "application/json")
+                        end
+
+                        core.response.exit(code, body)
+                    end
+                end
+            })
+        end
+    end
+
+    local routes = {}
+    local v1_routes = {}
+    local function empty_func() end
+
+function fetch_control_api_router()
+    core.table.clear(v1_routes)
+
+    register_api_routes(v1_routes, builtin_v1_routes)
+
+    for _, plugin in ipairs(plugin_mod.plugins) do
+        local api_fun = plugin.control_api
+        if api_fun then
+            local api_routes = api_fun(current_version)
+            register_api_routes(v1_routes, api_routes)
+        end
+    end
+
+    local v1_router, err = router.new(v1_routes)
+    if not v1_router then
+        return nil, err
+    end
+
+    core.table.clear(routes)
+    core.table.insert(routes, {
+        paths = {"/v1/*"},
+        filter_fun = function(vars, opts, ...)
+            local uri = str_sub(vars.uri, #"/v1" + 1)
+            return v1_router:dispatch(uri, opts, ...)
+        end,
+        handler = empty_func,
+    })
+
+    return router.new(routes)
+end
+
+end -- do
+
+
+do
+    local match_opts = {}
+    local cached_version
+    local router
+
+function _M.match(uri)
+    if cached_version ~= plugin_mod.load_times then
+        local err
+        router, err = fetch_control_api_router()
+        if router == nil then
+            core.log.error("failed to fetch valid api router: ", err)
+            return false
+        end
+
+        cached_version = plugin_mod.load_times
+    end
+
+    core.table.clear(match_opts)
+    match_opts.method = get_method()
+
+    return router:dispatch(uri, match_opts)
+end
+
+end -- do
+
+
+return _M
diff --git a/apisix/control/v1.lua b/apisix/control/v1.lua
new file mode 100644
index 0000000..7ad795e
--- /dev/null
+++ b/apisix/control/v1.lua
@@ -0,0 +1,33 @@
+--
+-- 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.schema()
+    -- stub for test yet, fill it in the next PR
+    return 200, {}
+end
+
+
+return {
+    -- /v1/schema
+    {
+        methods = {"GET"},
+        uris = {"/schema"},
+        handler = _M.schema,
+    }
+}
diff --git a/apisix/init.lua b/apisix/init.lua
index 1b9c62c..01dc772 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -37,6 +37,10 @@ local ngx_now       = ngx.now
 local str_byte      = string.byte
 local str_sub       = string.sub
 local tonumber      = tonumber
+local control_api_router
+if ngx.config.subsystem == "http" then
+    control_api_router = require("apisix.control.router")
+end
 local load_balancer
 local local_conf
 local dns_resolver
@@ -810,6 +814,14 @@ end
 end -- do
 
 
+function _M.http_control()
+    local ok = control_api_router.match(get_var("uri"))
+    if not ok then
+        ngx_exit(404)
+    end
+end
+
+
 function _M.stream_init()
     core.log.info("enter stream_init")
 end
diff --git a/apisix/plugins/example-plugin.lua b/apisix/plugins/example-plugin.lua
index 0a58efd..c94a7a4 100644
--- a/apisix/plugins/example-plugin.lua
+++ b/apisix/plugins/example-plugin.lua
@@ -14,6 +14,7 @@
 -- See the License for the specific language governing permissions and
 -- limitations under the License.
 --
+local ngx = ngx
 local core = require("apisix.core")
 local plugin = require("apisix.plugin")
 local upstream = require("apisix.upstream")
@@ -109,4 +110,28 @@ function _M.access(conf, ctx)
 end
 
 
+local function hello()
+    local args = ngx.req.get_uri_args()
+    if args["json"] then
+        return 200, {msg = "world"}
+    else
+        return 200, "world\n"
+    end
+end
+
+
+function _M.control_api(ver)
+    if ver == 1 then
+        return {
+            -- /v1/plugin/example-plugin/hello
+            {
+                methods = {"GET"},
+                uris = {"/plugin/example-plugin/hello"},
+                handler = hello,
+            }
+        }
+    end
+end
+
+
 return _M
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index 6f3f2e1..dd8a931 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -115,6 +115,10 @@ apisix:
     key_encrypt_salt: "edd1c9f0985e76a2"    #  If not set, will save origin ssl key into etcd.
                                             #  If set this, must be a string of length 16. And it will encrypt ssl key with AES-128-CBC
                                             #  !!! So do not change it after saving your ssl, it can't decrypt the ssl keys have be saved if you change !!
+  enable_control: true
+  # control:
+  #   ip: "127.0.0.1"
+  #   port: 9090
 
 nginx_config:                     # config for render the template to generate nginx.conf
   error_log: "logs/error.log"
diff --git a/doc/README.md b/doc/README.md
index 9961f6a..24fa724 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -26,6 +26,7 @@
 * [Getting Started Guide](getting-started.md)
 * [How to build Apache APISIX](how-to-build.md)
 * [Admin API](admin-api.md)
+* [Control API](control-api.md)
 * [Health Check](health-check.md): Enable health check on the upstream node, and will automatically filter unhealthy nodes during load balancing to ensure system stability.
 * [Router radixtree](router-radixtree.md)
 * [Stand Alone Model](stand-alone.md): Supports to load route rules from local yaml file, it is more friendly such as under the kubernetes(k8s).
diff --git a/doc/control-api.md b/doc/control-api.md
new file mode 100644
index 0000000..c43ec7a
--- /dev/null
+++ b/doc/control-api.md
@@ -0,0 +1,51 @@
+<!--
+#
+# 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.
+#
+-->
+
+The control API can be used to
+* expose APISIX internal state
+* control the behavior of a single isolate APISIX data panel
+
+By default, the control API server is enabled and listens to `127.0.0.1:9090`. You can change it via
+the `control` section under `apisix` in `conf/config.yaml`:
+
+```yaml
+apisix:
+  ...
+  enable_control: true
+  control:
+    ip: "127.0.0.1"
+    port: 9090
+```
+
+Note that the control API server should not be configured to listen to the public traffic!
+
+## Control API Added via plugin
+
+Plugin can add its control API when it is enabled.
+If a plugin adds such a control API, please refer to each plugin's documentation for those APIs.
+
+## Plugin independent Control API
+
+Here is the supported API:
+
+### GET /v1/schema
+
+Introduced since `v2.2`.
+
+Return the jsonschema used by this APISIX instance.
diff --git a/t/APISIX.pm b/t/APISIX.pm
index 78f2ddd..2634eea 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -375,6 +375,12 @@ _EOC_
             }
         }
 
+        location /v1/ {
+            content_by_lua_block {
+                apisix.http_control()
+            }
+        }
+
         location / {
             set \$upstream_mirror_host        '';
             set \$upstream_upgrade            '';
diff --git a/.travis/apisix_cli_test_in_ci.sh b/t/control/v1.t
old mode 100755
new mode 100644
similarity index 53%
rename from .travis/apisix_cli_test_in_ci.sh
rename to t/control/v1.t
index 87410fe..d1e4a17
--- a/.travis/apisix_cli_test_in_ci.sh
+++ b/t/control/v1.t
@@ -1,5 +1,3 @@
-#!/usr/bin/env bash
-
 #
 # Licensed to the Apache Software Foundation (ASF) under one or more
 # contributor license agreements.  See the NOTICE file distributed with
@@ -16,34 +14,42 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+no_shuffle();
+log_level("info");
 
-# This file is like apisix_cli_test.sh, but requires extra dependencies which
-# you don't need them in daily development.
+add_block_preprocessor(sub {
+    my ($block) = @_;
 
-set -ex
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
 
-clean_up() {
-    git checkout conf/config.yaml
-}
+    if (!$block->no_error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
 
-trap clean_up EXIT
+run_tests;
 
-unset APISIX_PROFILE
+__DATA__
 
-# check error handling when connecting to old etcd
-git checkout conf/config.yaml
+=== TEST 1: sanity
+--- request
+GET /v1/plugin/example-plugin/hello
+--- response_body
+world
 
-echo '
-etcd:
-  host:
-    - "http://127.0.0.1:3379"
-  prefix: "/apisix"
-' > conf/config.yaml
 
-out=$(make init 2>&1 || true)
-if ! echo "$out" | grep 'etcd cluster version 3.3.0 is less than the required version 3.4.0'; then
-    echo "failed: properly handle the error when connecting to old etcd"
-    exit 1
-fi
 
-echo "passed: properly handle the error when connecting to old etcd"
+=== TEST 2: set Content-Type for table response
+--- request
+GET /v1/plugin/example-plugin/hello?json
+--- response_body
+{"msg":"world"}
+--- response_headers
+Content-Type: application/json