Skip to content

Commit 4c67a31

Browse files
authored
fix(consul): tolerate null Service.Meta in fetch_services_from_server (#13258)
1 parent 3072a5e commit 4c67a31

2 files changed

Lines changed: 172 additions & 1 deletion

File tree

apisix/discovery/consul/client.lua

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,10 @@ function _M.fetch_services_from_server(consul_server, options)
412412
port = tonumber(svc_port),
413413
weight = default_weight,
414414
}
415-
if preserve_metadata and node.Service.Meta
415+
-- consul returns "Meta": null when a service is registered
416+
-- without metadata; cjson decodes that as a userdata sentinel
417+
-- (not nil), so guard with type() before calling next().
418+
if preserve_metadata and type(node.Service.Meta) == "table"
416419
and next(node.Service.Meta) then
417420
n.metadata = node.Service.Meta
418421
end

t/discovery/consul.t

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,3 +868,171 @@ v1 nodes: 2
868868
dev nodes: 0
869869
--- no_error_log
870870
[error]
871+
872+
873+
874+
=== TEST 17: fetch_services_from_server tolerates services without Meta (preserve_metadata=true)
875+
--- yaml_config
876+
apisix:
877+
node_listen: 1984
878+
deployment:
879+
role: data_plane
880+
role_data_plane:
881+
config_provider: yaml
882+
discovery:
883+
consul:
884+
servers:
885+
- "http://127.0.0.1:8500"
886+
timeout:
887+
connect: 1000
888+
read: 1000
889+
wait: 60
890+
weight: 1
891+
fetch_interval: 1
892+
keepalive: true
893+
--- apisix_yaml
894+
routes: []
895+
#END
896+
--- config
897+
location /consul1 {
898+
rewrite ^/consul1/(.*) /v1/agent/service/$1 break;
899+
proxy_pass http://127.0.0.1:8500;
900+
}
901+
location /t {
902+
content_by_lua_block {
903+
-- register a consul service WITHOUT a Meta field; consul returns
904+
-- "Meta": null which cjson decodes as a userdata sentinel.
905+
-- Before the fix, fetch_services_from_server crashed with
906+
-- "bad argument #1 to 'next' (table expected, got userdata)"
907+
-- on the very first scrape and never recovered.
908+
local httpc = require("resty.http").new()
909+
local deregister = function()
910+
local res, err = httpc:request_uri(
911+
"http://127.0.0.1:1984/consul1/deregister/svc_no_meta_1",
912+
{ method = "PUT" }
913+
)
914+
if not res or res.status ~= 200 then
915+
ngx.log(ngx.WARN, "deregister failed: ", err or res.status)
916+
end
917+
end
918+
local register = function(body)
919+
local res, err = httpc:request_uri(
920+
"http://127.0.0.1:1984/consul1/register",
921+
{ method = "PUT", body = body }
922+
)
923+
if not res or res.status ~= 200 then
924+
ngx.say("register failed: ", err or res.status)
925+
return false
926+
end
927+
return true
928+
end
929+
deregister()
930+
if not register('{"ID":"svc_no_meta_1","Name":"service_no_meta",'
931+
.. '"Address":"127.0.0.1","Port":30511}') then
932+
return
933+
end
934+
935+
local consul_client = require("apisix.discovery.consul.client")
936+
local servers = consul_client.format_consul_params({
937+
servers = {"http://127.0.0.1:8500"},
938+
timeout = {connect = 2000, read = 2000, wait = 60},
939+
weight = 1,
940+
keepalive = true,
941+
fetch_interval = 3,
942+
})
943+
944+
local up, err = consul_client.fetch_services_from_server(servers[1], {
945+
default_weight = 1,
946+
preserve_metadata = true,
947+
key_builder = function(name) return "no_meta_test/" .. name end,
948+
})
949+
if err then
950+
ngx.say("err: ", err)
951+
deregister()
952+
return
953+
end
954+
955+
local nodes = up and up["no_meta_test/service_no_meta"]
956+
if not nodes or #nodes == 0 then
957+
ngx.say("no nodes returned")
958+
deregister()
959+
return
960+
end
961+
ngx.say("nodes: ", #nodes)
962+
ngx.say("metadata: ", tostring(nodes[1].metadata))
963+
964+
deregister()
965+
}
966+
}
967+
--- request
968+
GET /t
969+
--- error_code: 200
970+
--- response_body
971+
nodes: 1
972+
metadata: nil
973+
--- no_error_log
974+
[error]
975+
976+
977+
978+
=== TEST 18: route-level E2E — consul service without Meta does not crash discovery
979+
--- yaml_config
980+
apisix:
981+
node_listen: 1984
982+
deployment:
983+
role: data_plane
984+
role_data_plane:
985+
config_provider: yaml
986+
discovery:
987+
consul:
988+
servers:
989+
- "http://127.0.0.1:8500"
990+
timeout:
991+
connect: 1000
992+
read: 1000
993+
wait: 60
994+
weight: 1
995+
fetch_interval: 3
996+
keepalive: true
997+
default_service:
998+
host: "127.0.0.1"
999+
port: 20999
1000+
--- apisix_yaml
1001+
routes:
1002+
-
1003+
uri: /hello
1004+
upstream:
1005+
service_name: service_no_meta
1006+
discovery_type: consul
1007+
type: roundrobin
1008+
#END
1009+
--- config
1010+
location /v1/agent {
1011+
proxy_pass http://127.0.0.1:8500;
1012+
}
1013+
location /sleep {
1014+
content_by_lua_block {
1015+
local args = ngx.req.get_uri_args()
1016+
local sec = args.sec or "2"
1017+
ngx.sleep(tonumber(sec))
1018+
ngx.say("ok")
1019+
}
1020+
}
1021+
--- timeout: 6
1022+
--- request eval
1023+
[
1024+
"GET /hello",
1025+
"PUT /v1/agent/service/register\n" . "{\"ID\":\"svc_no_meta_e2e\",\"Name\":\"service_no_meta\",\"Address\":\"127.0.0.1\",\"Port\":30511}",
1026+
"GET /sleep?sec=5",
1027+
"GET /hello",
1028+
"PUT /v1/agent/service/deregister/svc_no_meta_e2e",
1029+
]
1030+
--- response_body_like eval
1031+
[
1032+
qr/missing consul services\n/,
1033+
qr//,
1034+
qr/ok\n/,
1035+
qr/server 1\n/,
1036+
qr//,
1037+
]
1038+
--- ignore_error_log

0 commit comments

Comments
 (0)