You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by tz...@apache.org on 2022/02/22 13:08:11 UTC

[apisix] branch release/2.10 updated: chore: backport bugs that related etcd and graphql (#6402)

This is an automated email from the ASF dual-hosted git repository.

tzssangglass pushed a commit to branch release/2.10
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/release/2.10 by this push:
     new d926313  chore: backport bugs that related etcd and graphql (#6402)
d926313 is described below

commit d9263132058b4bf7f0615d3ff3e073a89a8f4f39
Author: tzssangglass <tz...@gmail.com>
AuthorDate: Tue Feb 22 21:07:24 2022 +0800

    chore: backport bugs that related etcd and graphql (#6402)
    
    Co-authored-by: 帅进超 <sh...@gmail.com>
---
 apisix/cli/etcd.lua            | 48 +++++++++++++++++-----------
 apisix/core/ctx.lua            | 59 ++++++++++++++++++++++++++++++++--
 apisix/core/request.lua        |  1 +
 t/cli/test_etcd_healthcheck.sh | 42 ++++++++++++++++++++++--
 t/router/graphql.t             | 72 +++++++++++++++++++++++++++++++++++++++---
 5 files changed, 194 insertions(+), 28 deletions(-)

diff --git a/apisix/cli/etcd.lua b/apisix/cli/etcd.lua
index 3cdaaa8..4595ec5 100644
--- a/apisix/cli/etcd.lua
+++ b/apisix/cli/etcd.lua
@@ -32,6 +32,8 @@ local tonumber = tonumber
 local str_format = string.format
 local str_sub = string.sub
 local table_concat = table.concat
+local table_insert = table.insert
+local io_stderr = io.stderr
 
 local _M = {}
 
@@ -187,6 +189,7 @@ function _M.init(env, args)
     end
 
     -- check the etcd cluster version
+    local etcd_healthy_hosts = {}
     for index, host in ipairs(yaml_conf.etcd.host) do
         local version_url = host .. "/version"
         local errmsg
@@ -206,29 +209,38 @@ function _M.init(env, args)
                              version_url, err, retry_time))
         end
 
-        if not res then
-            errmsg = str_format("request etcd endpoint \'%s\' error, %s\n", version_url, err)
-            util.die(errmsg)
-        end
+        if res 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 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 ", env.min_etcd_version,
+                         ", please upgrade your etcd cluster\n")
+            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 ",
-                     env.min_etcd_version,
-                     ", please upgrade your etcd cluster\n")
+            table_insert(etcd_healthy_hosts, host)
+        else
+            io_stderr:write(str_format("request etcd endpoint \'%s\' error, %s\n", version_url,
+                    err))
         end
     end
 
+    if #etcd_healthy_hosts <= 0 then
+        util.die("all etcd nodes are unavailable\n")
+    end
+
+    if (#etcd_healthy_hosts / host_count * 100) <= 50 then
+        util.die("the etcd cluster needs at least 50% and above healthy nodes\n")
+    end
+
     local etcd_ok = false
-    for index, host in ipairs(yaml_conf.etcd.host) do
+    for index, host in ipairs(etcd_healthy_hosts) do
         local is_success = true
 
         local errmsg
@@ -358,7 +370,7 @@ function _M.init(env, args)
     end
 
     if not etcd_ok then
-        util.die("none of the configured etcd works well")
+        util.die("none of the configured etcd works well\n")
     end
 end
 
diff --git a/apisix/core/ctx.lua b/apisix/core/ctx.lua
index 872a8f6..a9c2913 100644
--- a/apisix/core/ctx.lua
+++ b/apisix/core/ctx.lua
@@ -18,6 +18,7 @@ local core_str     = require("apisix.core.string")
 local core_tab     = require("apisix.core.table")
 local request      = require("apisix.core.request")
 local log          = require("apisix.core.log")
+local json         = require("apisix.core.json")
 local config_local = require("apisix.core.config_local")
 local tablepool    = require("tablepool")
 local get_var      = require("resty.ngxvar").fetch
@@ -36,7 +37,52 @@ local pcall        = pcall
 
 
 local _M = {version = 0.2}
-local GRAPHQL_DEFAULT_MAX_SIZE = 1048576               -- 1MiB
+local GRAPHQL_DEFAULT_MAX_SIZE       = 1048576               -- 1MiB
+local GRAPHQL_REQ_DATA_KEY           = "query"
+local GRAPHQL_REQ_METHOD_HTTP_GET    = "GET"
+local GRAPHQL_REQ_METHOD_HTTP_POST   = "POST"
+local GRAPHQL_REQ_MIME_JSON          = "application/json"
+
+
+local fetch_graphql_data = {
+    [GRAPHQL_REQ_METHOD_HTTP_GET] = function(ctx, max_size)
+        local body = request.get_uri_args(ctx)[GRAPHQL_REQ_DATA_KEY]
+        if not body then
+            return nil, "failed to read graphql data, args[" ..
+                        GRAPHQL_REQ_DATA_KEY .. "] is nil"
+        end
+
+        if type(body) == "table" then
+            body = body[1]
+        end
+
+        return body
+    end,
+
+    [GRAPHQL_REQ_METHOD_HTTP_POST] = function(ctx, max_size)
+        local body, err = request.get_body(max_size, ctx)
+        if not body then
+            return nil, "failed to read graphql data, " .. (err or "request body has zero size")
+        end
+
+        if request.header(ctx, "Content-Type") == GRAPHQL_REQ_MIME_JSON then
+            local res
+            res, err = json.decode(body)
+            if not res then
+                return nil, "failed to read graphql data, " .. err
+            end
+
+            if not res[GRAPHQL_REQ_DATA_KEY] then
+                return nil, "failed to read graphql data, json body[" ..
+                            GRAPHQL_REQ_DATA_KEY .. "] is nil"
+            end
+
+            body = res[GRAPHQL_REQ_DATA_KEY]
+        end
+
+        return body
+    end
+}
 
 
 local function parse_graphql(ctx)
@@ -51,9 +97,16 @@ local function parse_graphql(ctx)
         max_size = size
     end
 
-    local body, err = request.get_body(max_size, ctx)
+    local method = request.get_method()
+    local func = fetch_graphql_data[method]
+    if not func then
+        return nil, "graphql not support `" .. method .. "` request"
+    end
+
+    local body
+    body, err = func(ctx, max_size)
     if not body then
-        return nil, "failed to read graphql body: " .. err
+        return nil, err
     end
 
     local ok, res = pcall(gq_parse, body)
diff --git a/apisix/core/request.lua b/apisix/core/request.lua
index e43a647..4adac0a 100644
--- a/apisix/core/request.lua
+++ b/apisix/core/request.lua
@@ -258,6 +258,7 @@ function _M.get_port(ctx)
     return tonumber(ctx.var.server_port)
 end
 
+_M.get_method = ngx.req.get_method
 
 function _M.get_http_version()
     return ngx.req.http_version()
diff --git a/t/cli/test_etcd_healthcheck.sh b/t/cli/test_etcd_healthcheck.sh
index f94b8f6..b98cd40 100755
--- a/t/cli/test_etcd_healthcheck.sh
+++ b/t/cli/test_etcd_healthcheck.sh
@@ -40,7 +40,7 @@ etcd:
 
 docker-compose -f ./t/cli/docker-compose-etcd-cluster.yaml up -d
 
-# Check apisix not got effected when one etcd node disconnected
+# case 1: Check apisix not got effected when one etcd node disconnected
 make init && make run
 
 docker stop ${ETCD_NAME_0}
@@ -63,7 +63,7 @@ make stop
 
 echo "passed: apisix not got effected when one etcd node disconnected"
 
-# Check when all etcd nodes disconnected, apisix trying to reconnect with backoff, and could successfully recover when reconnected
+# case 2: Check when all etcd nodes disconnected, apisix trying to reconnect with backoff, and could successfully recover when reconnected
 make init && make run
 
 docker stop ${ETCD_NAME_0} && docker stop ${ETCD_NAME_1} && docker stop ${ETCD_NAME_2}
@@ -78,7 +78,7 @@ fi
 
 docker start ${ETCD_NAME_0} && docker start ${ETCD_NAME_1} && docker start ${ETCD_NAME_2}
 
-# sleep till etcd health check try to check again
+# case 3: sleep till etcd health check try to check again
 current_time=$(date +%s)
 sleep_seconds=$(( $sleep_till - $current_time + 3))
 if [ "$sleep_seconds" -gt 0 ]; then
@@ -96,3 +96,39 @@ fi
 make stop
 
 echo "passed: when all etcd nodes disconnected, apisix trying to reconnect with backoff, and could successfully recover when reconnected"
+
+# case 4: stop one etcd node (result: start successful)
+docker stop ${ETCD_NAME_0}
+
+out=$(make init 2>&1)
+if echo "$out" | grep "23790" | grep "connection refused"; then
+    echo "passed: APISIX successfully to start, stop only one etcd node"
+else
+    echo "failed: stop only one etcd node APISIX should start normally"
+    exit 1
+fi
+
+# case 5: stop two etcd nodes (result: start failure)
+docker stop ${ETCD_NAME_1}
+
+out=$(make init 2>&1 || true)
+if echo "$out" | grep "23791" | grep "connection refused"; then
+    echo "passed: APISIX failed to start, etcd cluster must have two or more healthy nodes"
+else
+    echo "failed: two etcd nodes have been stopped, APISIX should fail to start"
+    exit 1
+fi
+
+# case 6: stop all etcd nodes (result: start failure)
+docker stop ${ETCD_NAME_2}
+
+out=$(make init 2>&1 || true)
+if echo "$out" | grep "23792" | grep "connection refused"; then
+    echo "passed: APISIX failed to start, all etcd nodes have stopped"
+else
+    echo "failed: all etcd nodes have stopped, APISIX should not be able to start"
+    exit 1
+fi
+
+# stop etcd docker container
+docker-compose -f ./t/cli/docker-compose-etcd-cluster.yaml down
diff --git a/t/router/graphql.t b/t/router/graphql.t
index d608403..b6936fc 100644
--- a/t/router/graphql.t
+++ b/t/router/graphql.t
@@ -232,7 +232,7 @@ query {
 }
 --- error_code: 404
 --- error_log
-failed to read graphql body
+failed to read graphql data
 
 
 
@@ -244,7 +244,7 @@ failed to read graphql body
             local code, body = t('/apisix/admin/routes/1',
                  ngx.HTTP_PUT,
                  [=[{
-                        "methods": ["POST"],
+                        "methods": ["POST", "GET"],
                         "upstream": {
                             "nodes": {
                                 "127.0.0.1:1980": 1
@@ -283,7 +283,62 @@ hello world
 
 
 
-=== TEST 13: multiple root fields
+=== TEST 13: test send http post json data
+--- request
+POST /hello
+{"query":"query{owner{name}}"}
+--- more_headers
+Content-Type: application/json
+--- response_body
+hello world
+
+
+
+=== TEST 14: test send http get query data
+--- request
+GET /hello?query=query{owner{name}}
+--- response_body
+hello world
+
+
+
+=== TEST 15: test send http get multiple query data success
+--- request
+GET /hello?query=query{owner{name}}&query=query{repo{name}}
+--- response_body
+hello world
+
+
+
+=== TEST 16: test send http get multiple query data failure
+--- request
+GET /hello?query=query{repo{name}}&query=query{owner{name}}
+--- error_code: 404
+
+
+
+=== TEST 17: no body (HTTP GET)
+--- request
+GET /hello
+--- error_code: 404
+--- error_log
+failed to read graphql data, args[query] is nil
+
+
+
+=== TEST 18: no body (HTTP POST JSON)
+--- request
+POST /hello
+{}
+--- more_headers
+Content-Type: application/json
+--- error_code: 404
+--- error_log
+failed to read graphql data, json body[query] is nil
+
+
+
+=== TEST 19: multiple root fields
 --- request
 POST /hello
 query {
@@ -299,7 +354,7 @@ hello world
 
 
 
-=== TEST 14: root fields mismatch
+=== TEST 20: root fields mismatch
 --- request
 POST /hello
 query {
@@ -308,3 +363,12 @@ query {
     }
 }
 --- error_code: 404
+
+
+
+=== TEST 21: no body
+--- request
+POST /hello
+--- error_code: 404
+--- error_log
+failed to read graphql data, request body has zero size