Skip to content

feat(consul): filter nodes in upstream with metadata #12448

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions apisix/discovery/consul/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,48 @@ function _M.all_nodes()
return all_services
end

local function match_metadata_filters(inst, filters)
local metadata = inst.metadata or {}
for _, f in ipairs(filters) do
local key = f.key
local allowed_vals = f.value
local val = metadata[key]
local matched = false
for _, allowed in ipairs(allowed_vals) do
if val == allowed then
matched = true
break
end
end
if not matched then
return false
end
end
return true
end

function _M.nodes(service_name)
local function match_nodes_by_metadata(nodes, filters)
local result = {}
for _, node in ipairs(nodes) do
if match_metadata_filters(node, filters) then
core.table.insert(result, node)
end
end
return result
end

function _M.nodes(service_name, discovery_args)
if not all_services then
log.error("all_services is nil, failed to fetch nodes for : ", service_name)
return
end

local resp_list = all_services[service_name]

if discovery_args.metadata_match then
resp_list = match_nodes_by_metadata(resp_list, discovery_args.metadata_match)
end

if not resp_list then
log.error("fetch nodes failed by ", service_name, ", return default service")
return default_service and {default_service}
Expand All @@ -98,7 +131,6 @@ function _M.nodes(service_name)
return resp_list
end


local function update_all_services(consul_server_url, up_services)
-- clean old unused data
local old_services = consul_services[consul_server_url] or {}
Expand Down Expand Up @@ -511,11 +543,15 @@ function _M.connect(premature, consul_server, retry_delay)
local nodes = up_services[service_name]
local nodes_uniq = {}
for _, node in ipairs(result.body) do
if not node.Service then
local service = node.Service
if not service then
goto CONTINUE
end

local svc_address, svc_port = node.Service.Address, node.Service.Port
local svc_address, svc_port, metadata = service.Address, service.Port, service.Meta
if type(metadata) ~= "table" then
metadata = nil
end
-- Handle nil or 0 port case - default to 80 for HTTP services
if not svc_port or svc_port == 0 then
svc_port = 80
Expand All @@ -532,7 +568,8 @@ function _M.connect(premature, consul_server, retry_delay)
core.table.insert(nodes, {
host = svc_address,
port = tonumber(svc_port),
weight = default_weight,
weight = metadata and metadata.weight or default_weight,
metadata = metadata
})
nodes_uniq[service_id] = true
end
Expand Down
59 changes: 59 additions & 0 deletions t/discovery/consul.t
Original file line number Diff line number Diff line change
Expand Up @@ -781,3 +781,62 @@ location /sleep {
qr//
]
--- ignore_error_log

=== TEST 16: test metadata_match with consul discovery
--- yaml_config
apisix:
node_listen: 1984
config_center: yaml
discovery:
consul:
servers:
- http://127.0.0.1:8500
routes:
-
uri: /test_metadata_match
upstream:
service_name: mock-service
type: roundrobin
discovery_type: consul
discovery_args:
metadata_match:
version:
- "v2"

--- config
location /v1/agent {
proxy_pass http://127.0.0.1:8500;
}
location /test_metadata_match {
content_by_lua_block {
local server = ngx.var.server_addr or "unknown"
ngx.say("Upstream server handling request: ", server)
}
}
--- timeout: 5
--- pipelined_requests eval
[
"PUT /v1/agent/service/register\n" . "{\"ID\":\"mock-node-v1\",\"Name\":\"mock-service\",\"Address\":\"127.0.0.1\",\"Port\":1984,\"Meta\":{\"version\":\"v1\",\"weight\":\"1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}",
"PUT /v1/agent/service/register\n" . "{\"ID\":\"mock-node-v2\",\"Name\":\"mock-service\",\"Address\":\"127.0.0.2\",\"Port\":1984,\"Meta\":{\"version\":\"v2\",\"weight\":\"2\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}",

"GET /test_metadata_match?run1",
"GET /test_metadata_match?run2",
"GET /test_metadata_match?run3",

"PUT /v1/agent/service/deregister/mock-node-v1",
"PUT /v1/agent/service/deregister/mock-node-v2"
]
--- response_body_like eval
[
qr//,
qr//,

qr/127.0.0.2/,
qr/127.0.0.2/,
qr/127.0.0.2/,

qr//,
qr//
]
--- no_error_log
[error]
Loading