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

Reply via email to