From 83df02bdb8fb20411b2d4c1d2959f21407919a96 Mon Sep 17 00:00:00 2001 From: starsz Date: Tue, 22 Dec 2020 23:48:11 +0800 Subject: [PATCH 1/7] feat: use luasocket instead of curl in etcd.lua (#2818) --- .travis/apisix_cli_test/test_main.sh | 53 +++++++++++++++ apisix/cli/etcd.lua | 97 +++++++++++++++++----------- rockspec/apisix-master-0.rockspec | 1 + 3 files changed, 113 insertions(+), 38 deletions(-) diff --git a/.travis/apisix_cli_test/test_main.sh b/.travis/apisix_cli_test/test_main.sh index a5ba0abe7043..4001efbd8170 100755 --- a/.travis/apisix_cli_test/test_main.sh +++ b/.travis/apisix_cli_test/test_main.sh @@ -925,3 +925,56 @@ if ! echo "$out" | grep "Admin API can only be used with etcd config_center"; th fi echo "passed: Admin API can only be used with etcd config_center" + +# Check etcd connect refused +git checkout conf/config.yaml + +echo ' +etcd: + host: + - "http://127.0.0.1:2389" + prefix: "/apisix" +' > conf/config.yaml + +out=$(make init 2>&1 || true) +if ! echo "$out" | grep "connection refused"; then + echo "failed: apisix should echo \"connection refused\"" + exit 1 +fi + +echo "passed: Show connection refused info successfully" + +# check etcd auth error +git checkout conf/config.yaml + +export ETCDCTL_API=3 +etcdctl version +etcdctl --endpoints=127.0.0.1:2379 user add "root:apache-api6" +etcdctl --endpoints=127.0.0.1:2379 role add root +etcdctl --endpoints=127.0.0.1:2379 user grant-role root root +etcdctl --endpoints=127.0.0.1:2379 user get root +etcdctl --endpoints=127.0.0.1:2379 auth enable +etcdctl --endpoints=127.0.0.1:2379 --user=root:apache-api6 del /apisix --prefix + +echo ' +etcd: + host: + - "http://127.0.0.1:2379" + prefix: "/apisix" + timeout: 30 + user: root + password: apache-api7 +' > conf/config.yaml + +out=$(make init 2>&1 || true) +if ! echo "$out" | grep "invalid user ID or password"; then + echo "failed: should echo \"invalid user ID or password\"" + exit 1 +fi + +echo "passed: show password error successfully" + +# clean etcd auth +etcdctl --endpoints=127.0.0.1:2379 --user=root:apache-api6 auth disable +etcdctl --endpoints=127.0.0.1:2379 role delete root +etcdctl --endpoints=127.0.0.1:2379 user delete root \ No newline at end of file diff --git a/apisix/cli/etcd.lua b/apisix/cli/etcd.lua index 10e5c8722ca4..ed38fccd68c3 100644 --- a/apisix/cli/etcd.lua +++ b/apisix/cli/etcd.lua @@ -19,15 +19,20 @@ local base64_encode = require("base64").encode local dkjson = require("dkjson") local util = require("apisix.cli.util") local file = require("apisix.cli.file") +local http = require("socket.http") +local ltn12 = require("ltn12") local type = type local ipairs = ipairs local print = print local tonumber = tonumber local str_format = string.format +local table_concat = table.concat local _M = {} +-- Timeout for all I/O operations +http.TIMEOUT = 3 local function parse_semantic_version(ver) local errmsg = "invalid semantic version: " .. ver @@ -106,9 +111,6 @@ function _M.init(env, show_output) local etcd_conf = yaml_conf.etcd - local timeout = etcd_conf.timeout or 3 - local uri - -- convert old single etcd config to multiple etcd config if type(yaml_conf.etcd.host) == "string" then yaml_conf.etcd.host = {yaml_conf.etcd.host} @@ -132,22 +134,23 @@ function _M.init(env, show_output) -- check the etcd cluster version for index, host in ipairs(yaml_conf.etcd.host) do - uri = host .. "/version" - local cmd = str_format("curl -s -m %d %s", timeout * 2, uri) - local res = util.execute_cmd(cmd) - local errmsg = str_format("got malformed version message: \"%s\" from etcd\n", - res) + local version_url = host .. "/version" + local errmsg - local body, _, err = dkjson.decode(res) - if err then + local res, err = http.request(version_url) + if err and type(err) == "string" then + errmsg = str_format("request etcd endpoint \'%s\' error, %s\n", version_url, err) util.die(errmsg) end - local cluster_version = body["etcdcluster"] - if not cluster_version then + local body, _, err = dkjson.decode(res) + if err or (body and not body["etcdcluster"]) then + errmsg = str_format("got malformed version message: \"%s\" from etcd \"%s\"\n", res, + version_url) util.die(errmsg) end + local cluster_version = body["etcdcluster"] if compare_semantic_version(cluster_version, env.min_etcd_version) then util.die("etcd cluster version ", cluster_version, " is less than the required version ", @@ -160,31 +163,38 @@ function _M.init(env, show_output) for index, host in ipairs(yaml_conf.etcd.host) do local is_success = true - local token_head = "" + local errmsg + local auth_token local user = yaml_conf.etcd.user local password = yaml_conf.etcd.password if user and password then - local uri_auth = host .. "/v3/auth/authenticate" + local auth_url = host .. "/v3/auth/authenticate" local json_auth = { name = etcd_conf.user, password = etcd_conf.password } - local post_json_auth = dkjson.encode(json_auth) - local cmd_auth = "curl -s " .. uri_auth .. " -X POST -d '" .. - post_json_auth .. "' --connect-timeout " .. timeout - .. " --max-time " .. timeout * 2 .. " --retry 1 2>&1" - local res_auth = util.execute_cmd(cmd_auth) - local body_auth, _, err_auth = dkjson.decode(res_auth) - if err_auth then - util.die(cmd_auth, "\n", res_auth) + local post_json_auth = dkjson.encode(json_auth) + local response_body = {} + local _, err = http.request{url = auth_url, method = "POST", + source = ltn12.source.string(post_json_auth), + sink = ltn12.sink.table(response_body), + headers = {["Content-Length"] = #post_json_auth}} + -- err is string type + if err and type(err) == "string" then + errmsg = str_format("request etcd endpoint \"%s\" error, %s\n", auth_url, err) + util.die(errmsg) end - if not body_auth.token then - util.die(cmd_auth, "\n", res_auth) + local res_auth = table_concat(response_body) + local body_auth, _, err_auth = dkjson.decode(res_auth) + if err_auth or (body_auth and not body_auth["token"]) then + errmsg = str_format("got malformed auth message: \"%s\" from etcd \"%s\"\n", + res_auth, auth_url) + util.die(errmsg) end - token_head = " -H 'Authorization: " .. body_auth.token .. "'" + auth_token = body_auth.token end @@ -195,31 +205,42 @@ function _M.init(env, show_output) local key = (etcd_conf.prefix or "") .. dir_name .. "/" - local uri = host .. "/v3/kv/put" + local put_url = host .. "/v3/kv/put" local post_json = '{"value":"' .. base64_encode("init_dir") .. '", "key":"' .. base64_encode(key) .. '"}' - local cmd = "curl " .. uri .. token_head .. " -X POST -d '" .. post_json - .. "' --connect-timeout " .. timeout - .. " --max-time " .. timeout * 2 .. " --retry 1 2>&1" - - local res = util.execute_cmd(cmd) - if res:find("404 page not found", 1, true) then - util.die("gRPC gateway is not enabled in your etcd cluster, ", - "which is required by Apache APISIX.", "\n") + local response_body = {} + local headers = {["Content-Length"] = #post_json} + if auth_token then + headers["Authorization"] = auth_token end - if res:find("error", 1, true) then + local _, err = http.request{url = put_url, method = "POST", + source = ltn12.source.string(post_json), + sink = ltn12.sink.table(response_body), + headers = headers} + if err and type(err) == "string" then + errmsg = str_format("request etcd endpoint \"%s\" error, %s\n", put_url, err) + util.die(errmsg) + end + + local res_put = table_concat(response_body) + if res_put:find("404 page not found", 1, true) then + errmsg = str_format("gRPC gateway is not enabled in etcd cluster \"%s\"," + "which is required by Apache APISIX\n") + + if res_put:find("error", 1, true) then is_success = false if (index == host_count) then - util.die(cmd, "\n", res) + errmsg = str_format("got malformed key-put message: \"%s\" from etcd \"%s\"\n", + res_put, put_url) + util.die(errmsg) end break end if show_output then - print(cmd) - print(res) + print(res_put) end end diff --git a/rockspec/apisix-master-0.rockspec b/rockspec/apisix-master-0.rockspec index bf2912a17c09..4f088b2c40a5 100644 --- a/rockspec/apisix-master-0.rockspec +++ b/rockspec/apisix-master-0.rockspec @@ -58,6 +58,7 @@ dependencies = { "resty-redis-cluster = 1.02-4", "lua-resty-expr = 1.0.0", "graphql = 0.0.2", + "luasocket = 3.0rc1-2", } build = { From edd1a31f4aa474cee18fe4351029b7b50ab617ac Mon Sep 17 00:00:00 2001 From: starsz Date: Wed, 23 Dec 2020 08:57:40 +0800 Subject: [PATCH 2/7] update: fix ci --- apisix/cli/etcd.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apisix/cli/etcd.lua b/apisix/cli/etcd.lua index ed38fccd68c3..7747adb3d3e0 100644 --- a/apisix/cli/etcd.lua +++ b/apisix/cli/etcd.lua @@ -225,7 +225,7 @@ function _M.init(env, show_output) local res_put = table_concat(response_body) if res_put:find("404 page not found", 1, true) then - errmsg = str_format("gRPC gateway is not enabled in etcd cluster \"%s\"," + errmsg = str_format("gRPC gateway is not enabled in etcd cluster \"%s\",", "which is required by Apache APISIX\n") if res_put:find("error", 1, true) then From d731433efcdb001e9083b2ef0cee3647d98d1c8a Mon Sep 17 00:00:00 2001 From: starsz Date: Wed, 23 Dec 2020 11:39:50 +0800 Subject: [PATCH 3/7] update: fix test --- apisix/cli/etcd.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/apisix/cli/etcd.lua b/apisix/cli/etcd.lua index 7747adb3d3e0..6aa6d897c343 100644 --- a/apisix/cli/etcd.lua +++ b/apisix/cli/etcd.lua @@ -227,6 +227,7 @@ function _M.init(env, show_output) if res_put:find("404 page not found", 1, true) then errmsg = str_format("gRPC gateway is not enabled in etcd cluster \"%s\",", "which is required by Apache APISIX\n") + end if res_put:find("error", 1, true) then is_success = false From 74296574e044e85ddf9e5adf85facb1ce9e25a3f Mon Sep 17 00:00:00 2001 From: starsz Date: Wed, 23 Dec 2020 14:34:32 +0800 Subject: [PATCH 4/7] update: fix test --- apisix/cli/etcd.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/apisix/cli/etcd.lua b/apisix/cli/etcd.lua index 6aa6d897c343..b08512a41e0a 100644 --- a/apisix/cli/etcd.lua +++ b/apisix/cli/etcd.lua @@ -227,6 +227,7 @@ function _M.init(env, show_output) if res_put:find("404 page not found", 1, true) then errmsg = str_format("gRPC gateway is not enabled in etcd cluster \"%s\",", "which is required by Apache APISIX\n") + util.die(errmsg) end if res_put:find("error", 1, true) then From 02cc565e48f9fe7c44fe460876de870adc6ca801 Mon Sep 17 00:00:00 2001 From: starsz Date: Wed, 23 Dec 2020 15:41:12 +0800 Subject: [PATCH 5/7] update: fix test --- .travis/apisix_cli_test/test_main.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis/apisix_cli_test/test_main.sh b/.travis/apisix_cli_test/test_main.sh index 4001efbd8170..ab0f8903802f 100755 --- a/.travis/apisix_cli_test/test_main.sh +++ b/.travis/apisix_cli_test/test_main.sh @@ -977,4 +977,5 @@ echo "passed: show password error successfully" # clean etcd auth etcdctl --endpoints=127.0.0.1:2379 --user=root:apache-api6 auth disable etcdctl --endpoints=127.0.0.1:2379 role delete root -etcdctl --endpoints=127.0.0.1:2379 user delete root \ No newline at end of file +etcdctl --endpoints=127.0.0.1:2379 user delete root + From 2ffba5684e20bb24bbac1c9d8fb11c6ce2e5a932 Mon Sep 17 00:00:00 2001 From: starsz Date: Thu, 24 Dec 2020 22:32:28 +0800 Subject: [PATCH 6/7] update: enrich request comment --- apisix/cli/etcd.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apisix/cli/etcd.lua b/apisix/cli/etcd.lua index b08512a41e0a..12a6d1b61869 100644 --- a/apisix/cli/etcd.lua +++ b/apisix/cli/etcd.lua @@ -138,6 +138,9 @@ function _M.init(env, show_output) local errmsg local res, err = http.request(version_url) + -- In case of failure, request returns nil followed by an error message. + -- Else the first return value is the response body + -- and followed by the response status code. if err and type(err) == "string" then errmsg = str_format("request etcd endpoint \'%s\' error, %s\n", version_url, err) util.die(errmsg) @@ -180,7 +183,9 @@ function _M.init(env, show_output) source = ltn12.source.string(post_json_auth), sink = ltn12.sink.table(response_body), headers = {["Content-Length"] = #post_json_auth}} - -- err is string type + -- In case of failure, request returns nil followed by an error message. + -- Else the first return value is just the number 1 + -- and followed by the response status code. if err and type(err) == "string" then errmsg = str_format("request etcd endpoint \"%s\" error, %s\n", auth_url, err) util.die(errmsg) From 2f9106b4963551b18a81c4611c7eb008c59a0e6a Mon Sep 17 00:00:00 2001 From: starsz Date: Fri, 25 Dec 2020 15:37:20 +0800 Subject: [PATCH 7/7] update: fix etcd error handle --- apisix/cli/etcd.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apisix/cli/etcd.lua b/apisix/cli/etcd.lua index 12a6d1b61869..ae3cb9094e28 100644 --- a/apisix/cli/etcd.lua +++ b/apisix/cli/etcd.lua @@ -141,7 +141,7 @@ function _M.init(env, show_output) -- In case of failure, request returns nil followed by an error message. -- Else the first return value is the response body -- and followed by the response status code. - if err and type(err) == "string" then + if not res then errmsg = str_format("request etcd endpoint \'%s\' error, %s\n", version_url, err) util.die(errmsg) end @@ -179,14 +179,14 @@ function _M.init(env, show_output) local post_json_auth = dkjson.encode(json_auth) local response_body = {} - local _, err = http.request{url = auth_url, method = "POST", + local res, err = http.request{url = auth_url, method = "POST", source = ltn12.source.string(post_json_auth), sink = ltn12.sink.table(response_body), headers = {["Content-Length"] = #post_json_auth}} -- In case of failure, request returns nil followed by an error message. -- Else the first return value is just the number 1 -- and followed by the response status code. - if err and type(err) == "string" then + if not res then errmsg = str_format("request etcd endpoint \"%s\" error, %s\n", auth_url, err) util.die(errmsg) end @@ -219,11 +219,11 @@ function _M.init(env, show_output) headers["Authorization"] = auth_token end - local _, err = http.request{url = put_url, method = "POST", + local res, err = http.request{url = put_url, method = "POST", source = ltn12.source.string(post_json), sink = ltn12.sink.table(response_body), headers = headers} - if err and type(err) == "string" then + if not res then errmsg = str_format("request etcd endpoint \"%s\" error, %s\n", put_url, err) util.die(errmsg) end