This is an automated email from the ASF dual-hosted git repository.
AlinsRan 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 bc54a21c0 fix(control): always report healthcheck nodes as a JSON
array (#13891)
bc54a21c0 is described below
commit bc54a21c05a4a7911add6ee3398ee9d232aee370
Author: AlinsRan <[email protected]>
AuthorDate: Mon Aug 31 11:54:24 2026 +0800
fix(control): always report healthcheck nodes as a JSON array (#13891)
---
apisix/control/v1.lua | 9 ++++++-
docs/en/latest/control-api.md | 12 ++++-----
docs/zh/latest/control-api.md | 8 +++---
t/control/healthcheck.t | 59 ++++++++++++++++++++++++++++++++++++++++++-
4 files changed, 75 insertions(+), 13 deletions(-)
diff --git a/apisix/control/v1.lua b/apisix/control/v1.lua
index d49bb7c89..b224b6838 100644
--- a/apisix/control/v1.lua
+++ b/apisix/control/v1.lua
@@ -27,6 +27,7 @@ local get_upstreams = upstream_mod.upstreams
local collectgarbage = collectgarbage
local ipairs = ipairs
local pcall = pcall
+local setmetatable = setmetatable
local str_format = string.format
local ngx = ngx
local ngx_var = ngx.var
@@ -86,6 +87,11 @@ local function extra_checker_info(value)
if err then
core.log.error("healthcheck.get_target_list failed: ", err)
end
+ if nodes then
+ -- the checker has no target registered until the upstream is first
used,
+ -- so keep the field a JSON array to report `[]` instead of `{}` then
+ setmetatable(nodes, core.json.array_mt)
+ end
return {
name = value.key,
nodes = nodes,
@@ -184,7 +190,8 @@ end
local function _get_health_checkers()
- local infos = {}
+ -- same as the nodes field: report `[]` rather than `{}` when nothing is
checked
+ local infos = setmetatable({}, core.json.array_mt)
local routes = get_routes()
iter_and_add_healthcheck_info(infos, routes)
local stream_routes = get_stream_routes()
diff --git a/docs/en/latest/control-api.md b/docs/en/latest/control-api.md
index ae7f59d0e..6f594c402 100644
--- a/docs/en/latest/control-api.md
+++ b/docs/en/latest/control-api.md
@@ -134,7 +134,9 @@ Each of the returned objects contain the following fields:
* name: resource id, where the health checker is reporting from.
* type: health check type: `["http", "https", "tcp"]`.
-* nodes: target nodes of the health checker.
+* nodes: target nodes of the health checker. It is an empty array (`[]`) until
the
+ health checker registers its targets, which happens the first time the
upstream
+ serves a request.
* nodes[i].ip: ip address.
* nodes[i].port: port number.
* nodes[i].status: health check result: `["healthy", "unhealthy",
"mostly_healthy", "mostly_unhealthy"]`.
@@ -181,11 +183,9 @@ For example, `GET /v1/healthcheck/upstreams/1` returns:
:::note
-Only when one upstream is satisfied by the conditions below,
-its status is shown in the result list:
-
-* The upstream is configured with a health checker
-* The upstream has served requests in any worker process
+An upstream is shown in the result list as soon as it is configured with a
health
+checker. Its `nodes` stay empty until the upstream has served requests in any
+worker process, because the health checker is only created then.
:::
diff --git a/docs/zh/latest/control-api.md b/docs/zh/latest/control-api.md
index eeb61b5b8..9040ce0e8 100644
--- a/docs/zh/latest/control-api.md
+++ b/docs/zh/latest/control-api.md
@@ -132,7 +132,7 @@ APISIX 中一些插件添加了自己的 control API。如果你对他们感兴
* name: 资源 ID,健康检查的报告对象。
* type: 健康检查类型,取值为 `["http", "https", "tcp"]`。
-* nodes: 检查节点列表。
+* nodes: 检查节点列表。在健康检查器注册检查目标之前该字段为空数组(`[]`),健康检查器在上游第一次处理请求时才会创建。
* nodes[i].ip: IP 地址。
* nodes[i].port: 端口。
* nodes[i].status: 状态:`["healthy", "unhealthy", "mostly_healthy",
"mostly_unhealthy"]`。
@@ -179,10 +179,8 @@ APISIX 中一些插件添加了自己的 control API。如果你对他们感兴
:::note
-只有一个上游满足以下条件时,它的健康检查状态才会出现在结果里面:
-
-* 上游配置了健康检查。
-* 上游在任何一个 worker 进程处理过客户端请求。
+上游只要配置了健康检查,就会出现在结果里面。在上游于任意一个 worker 进程处理过客户端请求之前,
+健康检查器尚未创建,此时它的 `nodes` 为空数组。
:::
diff --git a/t/control/healthcheck.t b/t/control/healthcheck.t
index 79f8eb120..e9bdcfdde 100644
--- a/t/control/healthcheck.t
+++ b/t/control/healthcheck.t
@@ -293,7 +293,7 @@ unhealthy TCP increment (2/2) for
'127.0.0.1(127.0.0.1:1988)'
}
}
--- response_body
-{}
+[]
--- timeout: 5
@@ -405,3 +405,60 @@ unhealthy HTTP increment (2/3) for
'127.0.0.1(127.0.0.1:1980)'
unhealthy HTTP increment (3/3) for '127.0.0.1(127.0.0.1:1980)'
--- response_body
{"name":"/routes/1","nodes":[{"counter":{"http_failure":3,"success":0,"tcp_failure":0,"timeout_failure":0},"hostname":"127.0.0.1","ip":"127.0.0.1","port":1980,"status":"unhealthy"},{"counter":{"http_failure":3,"success":0,"tcp_failure":0,"timeout_failure":0},"hostname":"127.0.0.2","ip":"127.0.0.2","port":1980,"status":"unhealthy"}],"type":"http"}
+
+
+
+=== TEST 8: nodes stays a JSON array before the health checker is created
+--- yaml_config
+apisix:
+ node_listen: 1984
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uris:
+ - /hello
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+ checks:
+ active:
+ http_path: "/status"
+ healthy:
+ interval: 1
+ successes: 1
+ unhealthy:
+ interval: 1
+ http_failures: 1
+#END
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin")
+
+ -- no request is proxied, so the checker is not created yet and the
+ -- target list is empty
+ local code, _, res = t.test('/v1/healthcheck', ngx.HTTP_GET)
+ assert(code == ngx.HTTP_OK,
+ "/v1/healthcheck returned HTTP " .. tostring(code))
+ assert(res:find("^%s*%["),
+ "/v1/healthcheck: result is not a JSON array: " .. res)
+ assert(res:find('"nodes":[]', 1, true),
+ "/v1/healthcheck: nodes is not an array: " .. res)
+
+ local code, _, res = t.test('/v1/healthcheck/routes/1',
ngx.HTTP_GET)
+ assert(code == ngx.HTTP_OK,
+ "/v1/healthcheck/routes/1 returned HTTP " .. tostring(code))
+ assert(res:find('"nodes":[]', 1, true),
+ "/v1/healthcheck/routes/1: nodes is not an array: " .. res)
+
+ ngx.say("passed")
+ }
+ }
+--- response_body
+passed