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