diff --git a/Makefile b/Makefile index f8668c8d4e16..a9602194f2a0 100644 --- a/Makefile +++ b/Makefile @@ -283,7 +283,8 @@ install: runtime $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/discovery $(ENV_INSTALL) apisix/discovery/*.lua $(ENV_INST_LUADIR)/apisix/discovery/ - $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/discovery/{consul_kv,dns,eureka,nacos,kubernetes,tars} + $(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/discovery/{consul,consul_kv,dns,eureka,nacos,kubernetes,tars} + $(ENV_INSTALL) apisix/discovery/consul/*.lua $(ENV_INST_LUADIR)/apisix/discovery/consul $(ENV_INSTALL) apisix/discovery/consul_kv/*.lua $(ENV_INST_LUADIR)/apisix/discovery/consul_kv $(ENV_INSTALL) apisix/discovery/dns/*.lua $(ENV_INST_LUADIR)/apisix/discovery/dns $(ENV_INSTALL) apisix/discovery/eureka/*.lua $(ENV_INST_LUADIR)/apisix/discovery/eureka diff --git a/apisix/discovery/consul/init.lua b/apisix/discovery/consul/init.lua new file mode 100644 index 000000000000..dd8275e7d857 --- /dev/null +++ b/apisix/discovery/consul/init.lua @@ -0,0 +1,416 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +local require = require +local local_conf = require("apisix.core.config_local").local_conf() +local core = require("apisix.core") +local core_sleep = require("apisix.core.utils").sleep +local resty_consul = require('resty.consul') +local http = require('resty.http') +local util = require("apisix.cli.util") +local ipairs = ipairs +local error = error +local ngx = ngx +local unpack = unpack +local tonumber = tonumber +local pairs = pairs +local ngx_timer_at = ngx.timer.at +local ngx_timer_every = ngx.timer.every +local log = core.log +local json_delay_encode = core.json.delay_encode +local ngx_worker_id = ngx.worker.id + +local all_services = core.table.new(0, 5) +local default_service +local default_weight +local skip_service_map = core.table.new(0, 1) +local dump_params + +local events +local events_list +local consul_services + +local _M = { + version = 0.1, +} + + +local function discovery_consul_callback(data, event, source, pid) + all_services = data + log.notice("update local variable all_services, event is: ", event, + "source: ", source, "server pid:", pid, + ", all services: ", json_delay_encode(all_services, true)) +end + + +function _M.all_nodes() + return all_services +end + + +function _M.nodes(service_name) + 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 not resp_list then + log.error("fetch nodes failed by ", service_name, ", return default service") + return default_service and {default_service} + end + + log.info("process id: ", ngx_worker_id(), ", all_services[", service_name, "] = ", + json_delay_encode(resp_list, true)) + + return resp_list +end + + +local function parse_instance(node) + local service_name, host, port = node.Service, node.Address, node.Port + -- if exist, skip special service name + if service_name and skip_service_map[service_name] then + return false + end + -- "" means metadata of the service + return true, host, tonumber(port), "", service_name +end + + +local function update_all_services(server_name_prefix, data) + local up_services = core.table.new(0, #data) + local weight = default_weight + for _, node in pairs(data) do + local succ, ip, port, metadata, server_name = parse_instance(node) + if succ then + local nodes = up_services[server_name] + if not nodes then + nodes = core.table.new(1, 0) + up_services[server_name] = nodes + end + core.table.insert(nodes, { + host = ip, + port = port, + weight = metadata and metadata.weight or weight, + }) + end + end + + -- clean old unused data + local old_services = consul_services[server_name_prefix] or {} + for k, _ in pairs(old_services) do + all_services[k] = nil + end + core.table.clear(old_services) + + for k, v in pairs(up_services) do + all_services[k] = v + end + consul_services[server_name_prefix] = up_services + + log.info("update all services: ", json_delay_encode(all_services, true)) +end + + +local function read_dump_services() + local data, err = util.read_file(dump_params.path) + if not data then + log.error("read dump file get error: ", err) + return + end + + log.info("read dump file: ", data) + data = util.trim(data) + if #data == 0 then + log.error("dump file is empty") + return + end + + local entity, err = core.json.decode(data) + if not entity then + log.error("decoded dump data got error: ", err, ", file content: ", data) + return + end + + if not entity.services or not entity.last_update then + log.warn("decoded dump data miss fields, file content: ", data) + return + end + + local now_time = ngx.time() + log.info("dump file last_update: ", entity.last_update, ", dump_params.expire: ", + dump_params.expire, ", now_time: ", now_time) + if dump_params.expire ~= 0 and (entity.last_update + dump_params.expire) < now_time then + log.warn("dump file: ", dump_params.path, " had expired, ignored it") + return + end + + all_services = entity.services + log.info("load dump file into memory success") +end + + +local function write_dump_services() + local entity = { + services = all_services, + last_update = ngx.time(), + expire = dump_params.expire, -- later need handle it + } + local data = core.json.encode(entity) + local succ, err = util.write_file(dump_params.path, data) + if not succ then + log.error("write dump into file got error: ", err) + end +end + + +local function show_dump_file() + if not dump_params then + return 503, "dump params is nil" + end + + local data, err = util.read_file(dump_params.path) + if not data then + return 503, err + end + + return 200, data +end + +local function get_retry_delay(retry_delay) + if not retry_delay then + retry_delay = 1 + else + retry_delay = retry_delay * 4 + end + + return retry_delay +end + + +function _M.connect(premature, consul_server, retry_delay) + if premature then + return + end + + local consul_client = resty_consul:new({ + host = consul_server.host, + port = consul_server.port, + connect_timeout = consul_server.connect_timeout, + read_timeout = consul_server.read_timeout, + default_args = consul_server.default_args, + }) + + log.info("consul_server: ", json_delay_encode(consul_server, true)) + local watch_result, watch_err = consul_client:get(consul_server.consul_watch_sub_url) + local watch_error_info = (watch_err ~= nil and watch_err) + or ((watch_result ~= nil and watch_result.status ~= 200) + and watch_result.status) + if watch_error_info then + log.error("connect consul: ", consul_server.consul_server_url, + " by sub url: ", consul_server.consul_watch_sub_url, + ", got watch result: ", json_delay_encode(watch_result, true), + ", with error: ", watch_error_info) + + retry_delay = get_retry_delay(retry_delay) + log.warn("retry connecting consul after ", retry_delay, " seconds") + core_sleep(retry_delay) + + goto ERR + end + + log.info("connect consul: ", consul_server.consul_server_url, + ", watch_result status: ", watch_result.status, + ", watch_result.headers.index: ", watch_result.headers['X-Consul-Index'], + ", consul_server.index: ", consul_server.index, + ", consul_server: ", json_delay_encode(consul_server, true)) + + -- if current index different last index then update service + if consul_server.index ~= watch_result.headers['X-Consul-Index'] then + + -- fetch all services info + local result, err = consul_client:get(consul_server.consul_sub_url) + + local error_info = (err ~= nil and err) or + ((result ~= nil and result.status ~= 200) and result.status) + + if error_info then + log.error("connect consul: ", consul_server.consul_server_url, + " by sub url: ", consul_server.consul_sub_url, + ", got result: ", json_delay_encode(result, true), + ", with error: ", error_info) + + retry_delay = get_retry_delay(retry_delay) + log.warn("retry connecting consul after ", retry_delay, " seconds") + core_sleep(retry_delay) + + goto ERR + end + + consul_server.index = watch_result.headers['X-Consul-Index'] + -- only long connect type use index + if consul_server.keepalive then + consul_server.default_args.index = watch_result.headers['X-Consul-Index'] + end + -- decode body, decode json, update service, error handling + if result.body then + log.notice("server_name: ", consul_server.consul_server_url, + ", header: ", json_delay_encode(result.headers, true), + ", body: ", json_delay_encode(result.body, true)) + update_all_services(consul_server.consul_server_url, result.body) + --update events + local ok, err = events.post(events_list._source, events_list.updating, all_services) + if not ok then + log.error("post_event failure with ", events_list._source, + ", update all services error: ", err) + end + + if dump_params then + ngx_timer_at(0, write_dump_services) + end + end + end + + :: ERR :: + local keepalive = consul_server.keepalive + if keepalive then + local ok, err = ngx_timer_at(0, _M.connect, consul_server, retry_delay) + if not ok then + log.error("create ngx_timer_at got error: ", err) + return + end + end +end + + +local function format_consul_params(consul_conf) + local consul_server_list = core.table.new(0, #consul_conf.servers) + local args + + if consul_conf.keepalive == false then + args = {} + elseif consul_conf.keepalive then + args = { + wait = consul_conf.timeout.wait, --blocked wait!=0; unblocked by wait=0 + index = 0, + } + end + + for _, v in pairs(consul_conf.servers) do + local scheme, host, port, path = unpack(http.parse_uri(nil, v)) + if scheme ~= "http" then + return nil, "only support consul http schema address, eg: http://address:port" + elseif path ~= "/" or core.string.has_suffix(v, '/') then + return nil, "invalid consul server address, the valid format: http://address:port" + end + + core.table.insert(consul_server_list, { + host = host, + port = port, + connect_timeout = consul_conf.timeout.connect, + read_timeout = consul_conf.timeout.read, + consul_sub_url = "/agent/services", + consul_watch_sub_url = "/catalog/services", + consul_server_url = v .. "/v1", + weight = consul_conf.weight, + keepalive = consul_conf.keepalive, + default_args = args, + index = 0, + fetch_interval = consul_conf.fetch_interval -- fetch interval to next connect consul + }) + end + + return consul_server_list, nil +end + + +function _M.init_worker() + local consul_conf = local_conf.discovery.consul + + if consul_conf.dump then + local dump = consul_conf.dump + dump_params = dump + + if dump.load_on_init then + read_dump_services() + end + end + + events = require("resty.worker.events") + events_list = events.event_list( + "discovery_consul_update_all_services", + "updating" + ) + + if 0 ~= ngx_worker_id() then + events.register(discovery_consul_callback, events_list._source, events_list.updating) + return + end + + log.notice("consul_conf: ", json_delay_encode(consul_conf, true)) + default_weight = consul_conf.weight + -- set default service, used when the server node cannot be found + if consul_conf.default_service then + default_service = consul_conf.default_service + default_service.weight = default_weight + end + if consul_conf.skip_services then + skip_service_map = core.table.new(0, #consul_conf.skip_services) + for _, v in ipairs(consul_conf.skip_services) do + skip_service_map[v] = true + end + end + + local consul_servers_list, err = format_consul_params(consul_conf) + if err then + error(err) + end + log.info("consul_server_list: ", json_delay_encode(consul_servers_list, true)) + + consul_services = core.table.new(0, 1) + -- success or failure + for _, server in ipairs(consul_servers_list) do + local ok, err = ngx_timer_at(0, _M.connect, server) + if not ok then + error("create consul got error: " .. err) + end + + if server.keepalive == false then + ngx_timer_every(server.fetch_interval, _M.connect, server) + end + end +end + + +function _M.dump_data() + return {config = local_conf.discovery.consul, services = all_services } +end + + +function _M.control_api() + return { + { + methods = {"GET"}, + uris = {"/show_dump_file"}, + handler = show_dump_file, + } + } +end + + +return _M diff --git a/apisix/discovery/consul/schema.lua b/apisix/discovery/consul/schema.lua new file mode 100644 index 000000000000..3e998b015ce1 --- /dev/null +++ b/apisix/discovery/consul/schema.lua @@ -0,0 +1,86 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +return { + type = "object", + properties = { + servers = { + type = "array", + minItems = 1, + items = { + type = "string", + } + }, + fetch_interval = {type = "integer", minimum = 1, default = 3}, + keepalive = { + type = "boolean", + default = true + }, + weight = {type = "integer", minimum = 1, default = 1}, + timeout = { + type = "object", + properties = { + connect = {type = "integer", minimum = 1, default = 2000}, + read = {type = "integer", minimum = 1, default = 2000}, + wait = {type = "integer", minimum = 1, default = 60} + }, + default = { + connect = 2000, + read = 2000, + wait = 60, + } + }, + skip_services = { + type = "array", + minItems = 1, + items = { + type = "string", + } + }, + dump = { + type = "object", + properties = { + path = {type = "string", minLength = 1}, + load_on_init = {type = "boolean", default = true}, + expire = {type = "integer", default = 0}, + }, + required = {"path"}, + }, + default_service = { + type = "object", + properties = { + host = {type = "string"}, + port = {type = "integer"}, + metadata = { + type = "object", + properties = { + fail_timeout = {type = "integer", default = 1}, + weight = {type = "integer", default = 1}, + max_fails = {type = "integer", default = 1} + }, + default = { + fail_timeout = 1, + weight = 1, + max_fails = 1 + } + } + } + } + }, + + required = {"servers"} +} + diff --git a/ci/pod/docker-compose.first.yml b/ci/pod/docker-compose.first.yml index a13ad3cf1586..62ef7a328c16 100644 --- a/ci/pod/docker-compose.first.yml +++ b/ci/pod/docker-compose.first.yml @@ -33,7 +33,7 @@ services: restart: unless-stopped ports: - "8500:8500" - command: [ "consul", "agent", "-server", "-bootstrap-expect=1", "-client", "0.0.0.0", "-log-level", "info", "-data-dir=/consul/data" ] + command: [ "consul", "agent", "-server", "-bootstrap-expect=1", "-client", "0.0.0.0", "-log-level", "info", "-data-dir=/consul/data", "-enable-script-checks" ] networks: consul_net: @@ -42,7 +42,7 @@ services: restart: unless-stopped ports: - "8600:8500" - command: [ "consul", "agent", "-server", "-bootstrap-expect=1", "-client", "0.0.0.0", "-log-level", "info", "-data-dir=/consul/data" ] + command: [ "consul", "agent", "-server", "-bootstrap-expect=1", "-client", "0.0.0.0", "-log-level", "info", "-data-dir=/consul/data", "-enable-script-checks" ] networks: consul_net: diff --git a/conf/config-default.yaml b/conf/config-default.yaml index f14db8ef2323..59a6de6b00dd 100755 --- a/conf/config-default.yaml +++ b/conf/config-default.yaml @@ -316,6 +316,29 @@ nginx_config: # config for render the template to generate n # dump: # if you need, when registered nodes updated can dump into file # path: "logs/consul_kv.dump" # expire: 2592000 # unit sec, here is 30 day +# consul: +# servers: +# - "http://127.0.0.1:8500" +# - "http://127.0.0.1:8600" +# skip_services: # if you need to skip special services +# - "service_a" +# timeout: +# connect: 2000 # default 2000 ms +# read: 2000 # default 2000 ms +# wait: 60 # default 60 sec +# weight: 1 # default 1 +# fetch_interval: 3 # default 3 sec, only take effect for keepalive: false way +# keepalive: true # default true, use the long pull way to query consul servers +# default_service: # you can define default server when missing hit +# host: "127.0.0.1" +# port: 20999 +# metadata: +# fail_timeout: 1 # default 1 ms +# weight: 1 # default 1 +# max_fails: 1 # default 1 +# dump: # if you need, when registered nodes updated can dump into file +# path: "logs/consul.dump" +# expire: 2592000 # unit sec, here is 30 day # kubernetes: # ### kubernetes service discovery both support single-cluster and multi-cluster mode # ### applicable to the case where the service is distributed in a single or multiple kubernetes clusters. diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json index 99404960cb5d..16c31bcb7507 100644 --- a/docs/en/latest/config.json +++ b/docs/en/latest/config.json @@ -265,6 +265,7 @@ "items": [ "discovery", "discovery/dns", + "discovery/consul", "discovery/consul_kv", "discovery/nacos", "discovery/eureka", diff --git a/docs/en/latest/discovery/consul.md b/docs/en/latest/discovery/consul.md new file mode 100644 index 000000000000..17e13b00142e --- /dev/null +++ b/docs/en/latest/discovery/consul.md @@ -0,0 +1,298 @@ +--- +title: consul +--- + + + +## Summary + +APACHE APISIX supports Consul as a service discovery + +## Configuration for discovery client + +### Configuration for Consul + +First of all, we need to add following configuration in `conf/config.yaml` : + +```yaml +discovery: + consul: + servers: # make sure service name is unique in these consul servers + - "http://127.0.0.1:8500" + - "http://127.0.0.1:8600" # `http://127.0.0.1:8500` and `http://127.0.0.1:8600` are different clusters + skip_services: # if you need to skip special services + - "service_a" + timeout: + connect: 1000 # default 2000 ms + read: 1000 # default 2000 ms + wait: 60 # default 60 sec + weight: 1 # default 1 + fetch_interval: 5 # default 3 sec, only take effect for keepalive: false way + keepalive: true # default true, use the long pull way to query consul servers + default_service: # you can define default service when missing hit + host: "127.0.0.1" + port: 20999 + metadata: + fail_timeout: 1 # default 1 ms + weight: 1 # default 1 + max_fails: 1 # default 1 + dump: # if you need, when registered nodes updated can dump into file + path: "logs/consul.dump" + expire: 2592000 # unit sec, here is 30 day +``` + +And you can config it in short by default value: + +```yaml +discovery: + consul: + servers: + - "http://127.0.0.1:8500" +``` + +The `keepalive` has two optional values: + +- `true`, default and recommend value, use the long pull way to query consul servers +- `false`, not recommend, it would use the short pull way to query consul servers, then you can set the `fetch_interval` for fetch interval + +#### Dump Data + +When we need reload `apisix` online, as the `consul` module maybe loads data from CONSUL slower than load routes from ETCD, and would get the log at the moment before load successfully from consul: + +``` + http_access_phase(): failed to set upstream: no valid upstream node +``` + +So, we import the `dump` function for `consul` module. When reload, would load the dump file before from consul; when the registered nodes in consul been updated, would dump the upstream nodes into file automatically. + +The `dump` has three optional values now: + +- `path`, the dump file save path + - support relative path, eg: `logs/consul.dump` + - support absolute path, eg: `/tmp/consul.dump` + - make sure the dump file's parent path exist + - make sure the `apisix` has the dump file's read-write access permission,eg: add below config in `conf/config.yaml` + +```yaml +nginx_config: # config for render the template to generate nginx.conf + user: root # specifies the execution user of the worker process. +``` + +- `load_on_init`, default value is `true` + - if `true`, just try to load the data from the dump file before loading data from consul when starting, does not care the dump file exists or not + - if `false`, ignore loading data from the dump file + - Whether `true` or `false`, we don't need to prepare a dump file for apisix at anytime +- `expire`, unit sec, avoiding load expired dump data when load + - default `0`, it is unexpired forever + - recommend 2592000, which is 30 days(equals 3600 \* 24 \* 30) + +### Register Http API Services + +Now, register nodes into consul: + +```shell +curl -X PUT 'http://127.0.0.1:8500/v1/agent/service/register' \ +-d '{ + "ID": "service_a1", + "Name": "service_a", + "Tags": ["primary", "v1"], + "Address": "127.0.0.1", + "Port": 8000, + "Meta": { + "service_a_version": "4.0" + }, + "EnableTagOverride": false, + "Weights": { + "Passing": 10, + "Warning": 1 + } +}' + +curl -X PUT 'http://127.0.0.1:8500/v1/agent/service/register' \ +-d '{ + "ID": "service_a1", + "Name": "service_a", + "Tags": ["primary", "v1"], + "Address": "127.0.0.1", + "Port": 8002, + "Meta": { + "service_a_version": "4.0" + }, + "EnableTagOverride": false, + "Weights": { + "Passing": 10, + "Warning": 1 + } +}' +``` + +In some case, same service name exist in different consul servers. +To avoid confusion, use the full consul key url path as service name in practice. + +### Upstream setting + +Here is an example of routing a request with a URL of "/*" to a service which named "service_a" and use consul discovery client in the registry : + +```shell +$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d ' +{ + "uri": "/*", + "upstream": { + "service_name": "service_a", + "type": "roundrobin", + "discovery_type": "consul" + } +}' +``` + +The format response as below: + +```json +{ + "key": "/apisix/routes/1", + "value": { + "uri": "/*", + "priority": 0, + "id": "1", + "upstream": { + "scheme": "http", + "type": "roundrobin", + "hash_on": "vars", + "discovery_type": "consul", + "service_name": "service_a", + "pass_host": "pass" + }, + "create_time": 1669267329, + "status": 1, + "update_time": 1669267329 + } +} +``` + +You could find more usage in the `apisix/t/discovery/consul.t` file. + +## Debugging API + +It also offers control api for debugging. + +### Memory Dump API + +```shell +GET /v1/discovery/consul/dump +``` + +For example: + +```shell +# curl http://127.0.0.1:9090/v1/discovery/consul/dump | jq +{ + "config": { + "fetch_interval": 3, + "timeout": { + "wait": 60, + "connect": 6000, + "read": 6000 + }, + "weight": 1, + "servers": [ + "http://172.19.5.30:8500", + "http://172.19.5.31:8500" + ], + "keepalive": true, + "default_service": { + "host": "172.19.5.11", + "port": 8899, + "metadata": { + "fail_timeout": 1, + "weight": 1, + "max_fails": 1 + } + }, + "skip_services": [ + "service_d" + ] + }, + "services": { + "service_a": [ + { + "host": "127.0.0.1", + "port": 30513, + "weight": 1 + }, + { + "host": "127.0.0.1", + "port": 30514, + "weight": 1 + } + ], + "service_b": [ + { + "host": "172.19.5.51", + "port": 50051, + "weight": 1 + } + ], + "service_c": [ + { + "host": "127.0.0.1", + "port": 30511, + "weight": 1 + }, + { + "host": "127.0.0.1", + "port": 30512, + "weight": 1 + } + ] + } +} +``` + +### Show Dump File API + +It offers another control api for dump file view now. Maybe would add more api for debugging in future. + +```shell +GET /v1/discovery/consul/show_dump_file +``` + +For example: + +```shell +curl http://127.0.0.1:9090/v1/discovery/consul/show_dump_file | jq +{ + "services": { + "service_a": [ + { + "host": "172.19.5.12", + "port": 8000, + "weight": 120 + }, + { + "host": "172.19.5.13", + "port": 8000, + "weight": 120 + } + ] + }, + "expire": 0, + "last_update": 1615877468 +} +``` diff --git a/t/discovery/consul.t b/t/discovery/consul.t new file mode 100644 index 000000000000..39c5ab287b50 --- /dev/null +++ b/t/discovery/consul.t @@ -0,0 +1,578 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +repeat_each(1); +log_level('info'); +no_root_location(); +no_shuffle(); + + +add_block_preprocessor(sub { + my ($block) = @_; + + my $http_config = $block->http_config // <<_EOC_; + + server { + listen 20999; + + location / { + content_by_lua_block { + ngx.say("missing consul services") + } + } + } + + server { + listen 30511; + + location /hello { + content_by_lua_block { + ngx.say("server 1") + } + } + } + server { + listen 30512; + + location /hello { + content_by_lua_block { + ngx.say("server 2") + } + } + } + server { + listen 30513; + + location /hello { + content_by_lua_block { + ngx.say("server 3") + } + } + } + server { + listen 30514; + + location /hello { + content_by_lua_block { + ngx.say("server 4") + } + } + } +_EOC_ + + $block->set_value("http_config", $http_config); +}); + +our $yaml_config = <<_EOC_; +apisix: + node_listen: 1984 + enable_control: true + control: + ip: 127.0.0.1 + port: 9090 +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +discovery: + consul: + servers: + - "http://127.0.0.1:8500" + - "http://127.0.0.1:8600" + skip_services: + - "service_c" + timeout: + connect: 1000 + read: 1000 + wait: 60 + weight: 1 + fetch_interval: 1 + keepalive: true + default_service: + host: "127.0.0.1" + port: 20999 + metadata: + fail_timeout: 1 + weight: 1 + max_fails: 1 +_EOC_ + + +run_tests(); + +__DATA__ + +=== TEST 1: prepare consul catalog register nodes +--- config +location /consul1 { + rewrite ^/consul1/(.*) /v1/agent/service/$1 break; + proxy_pass http://127.0.0.1:8500; +} + +location /consul2 { + rewrite ^/consul2/(.*) /v1/agent/service/$1 break; + proxy_pass http://127.0.0.1:8600; +} +--- pipelined_requests eval +[ + "PUT /consul1/deregister/service_a1", + "PUT /consul1/deregister/service_b1", + "PUT /consul1/deregister/service_a2", + "PUT /consul1/deregister/service_b2", + "PUT /consul2/deregister/service_a1", + "PUT /consul2/deregister/service_b1", + "PUT /consul2/deregister/service_a2", + "PUT /consul2/deregister/service_b2", + "PUT /consul1/register\n" . "{\"ID\":\"service_a1\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30511,\"Meta\":{\"service_a_version\":\"4.0\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "PUT /consul1/register\n" . "{\"ID\":\"service_a2\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30512,\"Meta\":{\"service_a_version\":\"4.0\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "PUT /consul1/register\n" . "{\"ID\":\"service_b1\",\"Name\":\"service_b\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30513,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "PUT /consul1/register\n" . "{\"ID\":\"service_b2\",\"Name\":\"service_b\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30514,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", +] +--- error_code eval +[200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200] + + + +=== TEST 2: test consul server 1 +--- yaml_config eval: $::yaml_config +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service_a + discovery_type: consul + type: roundrobin +#END +--- pipelined_requests eval +[ + "GET /hello", + "GET /hello", +] +--- response_body_like eval +[ + qr/server [1-2]\n/, + qr/server [1-2]\n/, +] +--- no_error_log +[error, error] + + + +=== TEST 3: test consul server 2 +--- yaml_config eval: $::yaml_config +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service_b + discovery_type: consul + type: roundrobin +#END +--- pipelined_requests eval +[ + "GET /hello", + "GET /hello" +] +--- response_body_like eval +[ + qr/server [3-4]\n/, + qr/server [3-4]\n/, +] +--- no_error_log +[error, error] + + + +=== TEST 4: test mini consul config +--- yaml_config +apisix: + node_listen: 1984 +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +discovery: + consul: + servers: + - "http://127.0.0.1:8500" + - "http://127.0.0.1:6500" +#END +--- apisix_yaml +routes: + - + uri: /hello + upstream: + service_name: service_a + discovery_type: consul + type: roundrobin +#END +--- request +GET /hello +--- response_body_like eval +qr/server [1-2]/ +--- ignore_error_log + + + +=== TEST 5: test invalid service name sometimes the consul key maybe deleted by mistake + +--- yaml_config eval: $::yaml_config +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service_c + discovery_type: consul + type: roundrobin +#END +--- pipelined_requests eval +[ + "GET /hello_api", + "GET /hello_api" +] +--- response_body eval +[ + "missing consul services\n", + "missing consul services\n" +] +--- ignore_error_log + + + +=== TEST 6: test skip keys +skip some services, return default nodes, get response: missing consul services +--- yaml_config +apisix: + node_listen: 1984 +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +discovery: + consul: + servers: + - "http://127.0.0.1:8600" + prefix: "upstreams" + skip_services: + - "service_a" + default_service: + host: "127.0.0.1" + port: 20999 + metadata: + fail_timeout: 1 + weight: 1 + max_fails: 1 +#END +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service_a + discovery_type: consul + type: roundrobin +#END +--- request +GET /hello +--- response_body eval +"missing consul services\n" +--- ignore_error_log + + + +=== TEST 7: test register and unregister nodes +--- yaml_config eval: $::yaml_config +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service_a + discovery_type: consul + type: roundrobin +#END +--- config +location /v1/agent { + proxy_pass http://127.0.0.1:8500; +} +location /sleep { + content_by_lua_block { + local args = ngx.req.get_uri_args() + local sec = args.sec or "2" + ngx.sleep(tonumber(sec)) + ngx.say("ok") + } +} +--- timeout: 6 +--- request eval +[ + "PUT /v1/agent/service/deregister/service_a1", + "PUT /v1/agent/service/deregister/service_a2", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service_a1\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30513,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service_a2\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30514,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "GET /sleep", + + "GET /hello?random1", + "GET /hello?random2", + "GET /hello?random3", + "GET /hello?random4", + + "PUT /v1/agent/service/deregister/service_a1", + "PUT /v1/agent/service/deregister/service_a2", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service_a1\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30511,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service_a2\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30512,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "GET /sleep?sec=5", + + "GET /hello?random1", + "GET /hello?random2", + "GET /hello?random3", + "GET /hello?random4", + +] +--- response_body_like eval +[ + qr//, + qr//, + qr//, + qr//, + qr/ok\n/, + + qr/server [3-4]\n/, + qr/server [3-4]\n/, + qr/server [3-4]\n/, + qr/server [3-4]\n/, + + qr//, + qr//, + qr//, + qr//, + qr/ok\n/, + + qr/server [1-2]\n/, + qr/server [1-2]\n/, + qr/server [1-2]\n/, + qr/server [1-2]\n/ +] +--- ignore_error_log + + + +=== TEST 8: clean nodes +--- config +location /v1/agent { + proxy_pass http://127.0.0.1:8500; +} +--- request eval +[ + "PUT /v1/agent/service/deregister/service_a1", + "PUT /v1/agent/service/deregister/service_a2", +] +--- error_code eval +[200, 200] + + + +=== TEST 9: test consul short connect type +--- yaml_config +apisix: + node_listen: 1984 +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +discovery: + consul: + servers: + - "http://127.0.0.1:8500" + keepalive: false + fetch_interval: 3 + default_service: + host: "127.0.0.1" + port: 20999 +#END +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service_a + discovery_type: consul + type: roundrobin +#END +--- config +location /v1/agent { + proxy_pass http://127.0.0.1:8500; +} +location /sleep { + content_by_lua_block { + local args = ngx.req.get_uri_args() + local sec = args.sec or "2" + ngx.sleep(tonumber(sec)) + ngx.say("ok") + } +} +--- timeout: 6 +--- request eval +[ + "GET /hello", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service_a1\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30511,\"Meta\":{\"service_a_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "GET /sleep?sec=5", + "GET /hello", +] +--- response_body_like eval +[ + qr/missing consul services\n/, + qr//, + qr/ok\n/, + qr/server 1\n/ +] +--- ignore_error_log + + + +=== TEST 10: retry when Consul can't be reached (long connect type) +--- yaml_config +apisix: + node_listen: 1984 +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +discovery: + consul: + servers: + - "http://127.0.0.1:8501" + keepalive: true + fetch_interval: 3 + default_service: + host: "127.0.0.1" + port: 20999 +#END +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service_a + discovery_type: consul + type: roundrobin +#END +--- timeout: 4 +--- config +location /sleep { + content_by_lua_block { + local args = ngx.req.get_uri_args() + local sec = args.sec or "2" + ngx.sleep(tonumber(sec)) + ngx.say("ok") + } +} +--- request +GET /sleep?sec=3 +--- response_body +ok +--- grep_error_log eval +qr/retry connecting consul after \d seconds/ +--- grep_error_log_out +retry connecting consul after 1 seconds +retry connecting consul after 4 seconds + + + +=== TEST 11: prepare healthy and unhealthy nodes +--- config +location /v1/agent { + proxy_pass http://127.0.0.1:8500; +} +--- request eval +[ + "PUT /v1/agent/service/deregister/service_a1", + "PUT /v1/agent/service/deregister/service_a2", + "PUT /v1/agent/service/deregister/service_b1", + "PUT /v1/agent/service/deregister/service_b2", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service_b1\",\"Name\":\"service_b\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30513,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service_b2\",\"Name\":\"service_b\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30514,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", +] +--- error_code eval +[200, 200, 200, 200, 200, 200] + + + +=== TEST 12: test health checker +--- yaml_config eval: $::yaml_config +--- apisix_yaml +routes: + - + uris: + - /hello + upstream_id: 1 +upstreams: + - + service_name: service_b + discovery_type: consul + type: roundrobin + id: 1 + checks: + active: + http_path: "/hello" + healthy: + interval: 1 + successes: 1 + unhealthy: + interval: 1 + http_failures: 1 +#END +--- config + location /thc { + content_by_lua_block { + local json = require("toolkit.json") + local t = require("lib.test_admin") + local http = require "resty.http" + local uri = "http://127.0.0.1:" .. ngx.var.server_port .. "/hello" + local httpc = http.new() + httpc:request_uri(uri, {method = "GET"}) + ngx.sleep(3) + + local code, body, res = t.test('/v1/healthcheck', + ngx.HTTP_GET) + res = json.decode(res) + local nodes = res[1].nodes + table.sort(nodes, function(a, b) + return a.port < b.port + end) + ngx.say(json.encode(nodes)) + + local code, body, res = t.test('/v1/healthcheck/upstreams/1', + ngx.HTTP_GET) + res = json.decode(res) + nodes = res.nodes + table.sort(nodes, function(a, b) + return a.port < b.port + end) + ngx.say(json.encode(nodes)) + } + } +--- request +GET /thc +--- response_body +[{"host":"127.0.0.1","port":30513,"priority":0,"weight":1},{"host":"127.0.0.1","port":30514,"priority":0,"weight":1}] +[{"host":"127.0.0.1","port":30513,"priority":0,"weight":1},{"host":"127.0.0.1","port":30514,"priority":0,"weight":1}] +--- ignore_error_log diff --git a/t/discovery/consul_dump.t b/t/discovery/consul_dump.t new file mode 100644 index 000000000000..b229fb7a4666 --- /dev/null +++ b/t/discovery/consul_dump.t @@ -0,0 +1,453 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); +no_shuffle(); +log_level("info"); + + +add_block_preprocessor(sub { + my ($block) = @_; + + my $http_config = $block->http_config // <<_EOC_; + + server { + listen 30511; + + location /hello { + content_by_lua_block { + ngx.say("server 1") + } + } + } +_EOC_ + + $block->set_value("http_config", $http_config); +}); + +run_tests(); + +__DATA__ + +=== TEST 1: prepare nodes +--- config +location /v1/agent { + proxy_pass http://127.0.0.1:8500; +} +--- request eval +[ + "PUT /v1/agent/service/deregister/service_a1", + "PUT /v1/agent/service/deregister/service_a2", + "PUT /v1/agent/service/deregister/service_b1", + "PUT /v1/agent/service/deregister/service_b2", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service_a1\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30511,\"Meta\":{\"service_a_version\":\"4.0\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "PUT /v1/agent/service/register\n" . "{\"ID\":\"service_b1\",\"Name\":\"service_b\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":8002,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", +] +--- response_body eval +--- error_code eval +[200, 200, 200, 200, 200, 200] + + + +=== TEST 2: show dump services +--- yaml_config +apisix: + node_listen: 1984 + enable_control: true +discovery: + consul: + servers: + - "http://127.0.0.1:8500" + dump: + path: "consul.dump" + load_on_init: true +--- config + location /t { + content_by_lua_block { + local json = require("toolkit.json") + local t = require("lib.test_admin") + ngx.sleep(2) + + local code, body, res = t.test('/v1/discovery/consul/show_dump_file', + ngx.HTTP_GET) + local entity = json.decode(res) + ngx.say(json.encode(entity.services)) + } + } +--- timeout: 3 +--- request +GET /t +--- response_body +{"service_a":[{"host":"127.0.0.1","port":30511,"weight":1}],"service_b":[{"host":"127.0.0.1","port":8002,"weight":1}]} + + + +=== TEST 3: prepare dump file for next test +--- yaml_config +apisix: + node_listen: 1984 + enable_control: true +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +discovery: + consul: + servers: + - "http://127.0.0.1:8500" + dump: + path: "/tmp/consul.dump" + load_on_init: false +#END +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service_a + discovery_type: consul + type: roundrobin +#END +--- request +GET /hello +--- response_body +server 1 + + + +=== TEST 4: clean registered nodes +--- config +location /v1/agent { + proxy_pass http://127.0.0.1:8500; +} +--- request eval +[ + "PUT /v1/agent/service/deregister/service_a1", + "PUT /v1/agent/service/deregister/service_b1", +] +--- error_code eval +[200, 200] + + + +=== TEST 5: test load dump on init +Configure the invalid consul server addr, and loading the last test 3 generated /tmp/consul.dump file into memory when initializing +--- yaml_config +apisix: + node_listen: 1984 + enable_control: true +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +discovery: + consul: + servers: + - "http://127.0.0.1:38500" + dump: + path: "/tmp/consul.dump" + load_on_init: true +#END +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service_a + discovery_type: consul + type: roundrobin +#END +--- request +GET /hello +--- response_body +server 1 +--- error_log +connect consul + + + +=== TEST 6: delete dump file +--- config + location /t { + content_by_lua_block { + local util = require("apisix.cli.util") + local succ, err = util.execute_cmd("rm -f /tmp/consul.dump") + ngx.say(succ and "success" or err) + } + } +--- request +GET /t +--- response_body +success + + + +=== TEST 7: miss load dump on init +--- yaml_config +apisix: + node_listen: 1984 + enable_control: true +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +discovery: + consul: + servers: + - "http://127.0.0.1:38500" + dump: + path: "/tmp/consul.dump" + load_on_init: true +#END +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service_a + discovery_type: consul + type: roundrobin +#END +--- request +GET /hello +--- error_code: 503 +--- error_log +connect consul +fetch nodes failed +failed to set upstream + + + +=== TEST 8: prepare expired dump file +--- config + location /t { + content_by_lua_block { + local util = require("apisix.cli.util") + local json = require("toolkit.json") + + local applications = json.decode('{"service_a":[{"host":"127.0.0.1","port":30511,"weight":1}]}') + local entity = { + services = applications, + last_update = ngx.time(), + expire = 10, + } + local succ, err = util.write_file("/tmp/consul.dump", json.encode(entity)) + + ngx.sleep(2) + ngx.say(succ and "success" or err) + } + } +--- timeout: 3 +--- request +GET /t +--- response_body +success + + + +=== TEST 9: unexpired dump +test load unexpired /tmp/consul.dump file generated by upper test when initializing + when initializing +--- yaml_config +apisix: + node_listen: 1984 + enable_control: true +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +discovery: + consul: + servers: + - "http://127.0.0.1:38500" + dump: + path: "/tmp/consul.dump" + load_on_init: true + expire: 5 +#END +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service_a + discovery_type: consul + type: roundrobin +#END +--- request +GET /hello +--- response_body +server 1 +--- error_log +connect consul + + + +=== TEST 10: expired dump +test load expired ( by check: (dump_file.last_update + dump.expire) < ngx.time ) ) /tmp/consul.dump file generated by upper test when initializing + when initializing +--- yaml_config +apisix: + node_listen: 1984 + enable_control: true +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +discovery: + consul: + servers: + - "http://127.0.0.1:38500" + dump: + path: "/tmp/consul.dump" + load_on_init: true + expire: 1 +#END +--- apisix_yaml +routes: + - + uri: /* + upstream: + service_name: service_a + discovery_type: consul + type: roundrobin +#END +--- request +GET /hello +--- error_code: 503 +--- error_log +dump file: /tmp/consul.dump had expired, ignored it + + + +=== TEST 11: delete dump file +--- config + location /t { + content_by_lua_block { + local util = require("apisix.cli.util") + local succ, err = util.execute_cmd("rm -f /tmp/consul.dump") + ngx.say(succ and "success" or err) + } + } +--- request +GET /t +--- response_body +success + + + +=== TEST 12: dump file inexistence +--- yaml_config +apisix: + node_listen: 1984 + enable_control: true +discovery: + consul: + servers: + - "http://127.0.0.1:38500" + dump: + path: "/tmp/consul.dump" +#END +--- request +GET /v1/discovery/consul/show_dump_file +--- error_code: 503 +--- error_log +connect consul + + + +=== TEST 13: no dump config +--- yaml_config +apisix: + node_listen: 1984 + enable_control: true +discovery: + consul: + servers: + - "http://127.0.0.1:38500" +#END +--- request +GET /v1/discovery/consul/show_dump_file +--- error_code: 503 +--- error_log +connect consul + + + +=== TEST 14: prepare nodes with different consul clusters +--- config +location /consul1 { + rewrite ^/consul1/(.*) /v1/agent/service/$1 break; + proxy_pass http://127.0.0.1:8500; +} + +location /consul2 { + rewrite ^/consul2/(.*) /v1/agent/service/$1 break; + proxy_pass http://127.0.0.1:8600; +} +--- pipelined_requests eval +[ + "PUT /consul1/deregister/service_a1", + "PUT /consul1/deregister/service_b1", + "PUT /consul1/deregister/service_a2", + "PUT /consul1/deregister/service_b2", + "PUT /consul2/deregister/service_a1", + "PUT /consul2/deregister/service_b1", + "PUT /consul2/deregister/service_a2", + "PUT /consul2/deregister/service_b2", + "PUT /consul1/register\n" . "{\"ID\":\"service_a1\",\"Name\":\"service_a\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30511,\"Meta\":{\"service_a_version\":\"4.0\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", + "PUT /consul2/register\n" . "{\"ID\":\"service_b1\",\"Name\":\"service_b\",\"Tags\":[\"primary\",\"v1\"],\"Address\":\"127.0.0.1\",\"Port\":30517,\"Meta\":{\"service_b_version\":\"4.1\"},\"EnableTagOverride\":false,\"Weights\":{\"Passing\":10,\"Warning\":1}}", +] +--- error_code eval +[200, 200, 200, 200, 200, 200, 200, 200, 200, 200] + + + +=== TEST 15: show dump services with different consul clusters +--- yaml_config +apisix: + node_listen: 1984 + enable_control: true +discovery: + consul: + servers: + - "http://127.0.0.1:8500" + - "http://127.0.0.1:8600" + dump: + path: "consul.dump" + load_on_init: false +--- config + location /bonjour { + content_by_lua_block { + local json = require("toolkit.json") + local t = require("lib.test_admin") + ngx.sleep(2) + + local code, body, res = t.test('/v1/discovery/consul/show_dump_file', + ngx.HTTP_GET) + local entity = json.decode(res) + ngx.say(json.encode(entity.services)) + } + } +--- timeout: 3 +--- request +GET /bonjour +--- response_body +{"service_a":[{"host":"127.0.0.1","port":30511,"weight":1}],"service_b":[{"host":"127.0.0.1","port":30517,"weight":1}]}