Skip to content
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

feat: add nacos support #3820

Merged
merged 66 commits into from
Apr 21, 2021
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
baab627
add nacos support
benx203 Mar 13, 2021
e053355
add docs
benx203 Mar 15, 2021
5b14149
fix lint warnings
benx203 Mar 18, 2021
613c221
fix test
benx203 Mar 18, 2021
f0b0942
fix nacos
benx203 Mar 18, 2021
221ee3a
fix nacos test script
benx203 Mar 19, 2021
8e3e6a5
fix
benx203 Mar 19, 2021
69055a0
fix nacos - support auth
benx203 Mar 19, 2021
e412a70
fix
benx203 Mar 19, 2021
9b48317
fix
benx203 Mar 19, 2021
d8f819b
fix
benx203 Mar 19, 2021
46fe9fd
fix
benx203 Mar 20, 2021
9e51570
reindex
benx203 Mar 20, 2021
4a0dac6
fix env script
benx203 Mar 21, 2021
bbc3f1f
fix env script
benx203 Mar 21, 2021
b153240
fix env script
benx203 Mar 21, 2021
962be01
fix test script
benx203 Mar 22, 2021
26de944
fix test script
benx203 Mar 22, 2021
c1e6f00
fix
benx203 Mar 22, 2021
95ab302
fix
benx203 Mar 22, 2021
63eba52
fix
benx203 Mar 23, 2021
df1d54f
fix
benx203 Mar 23, 2021
3ea6a66
fix
benx203 Mar 29, 2021
d1f3bef
fix
benx203 Mar 29, 2021
ec0c16d
Merge branch 'master' of https://github.com/apache/apisix
benx203 Apr 1, 2021
397a197
fix
benx203 Apr 1, 2021
dcb76c2
fix
benx203 Apr 1, 2021
14de476
fix
benx203 Apr 1, 2021
b53f4e9
fix
benx203 Apr 2, 2021
dccd644
fix
benx203 Apr 2, 2021
33e7c2e
fix
benx203 Apr 2, 2021
ddff0f5
fix
benx203 Apr 2, 2021
3ebf626
fix
benx203 Apr 2, 2021
147c088
modify test
benx203 Apr 2, 2021
d55effc
fix
benx203 Apr 2, 2021
ea93f5e
fix
benx203 Apr 6, 2021
922ac50
trigger run test script
benx203 Apr 6, 2021
5a91e81
fix test
benx203 Apr 7, 2021
0d1ef67
fix
benx203 Apr 7, 2021
7398fbb
fix
benx203 Apr 7, 2021
74f5512
fix
benx203 Apr 7, 2021
b7d356c
fix
benx203 Apr 7, 2021
4fa00d7
rerun test
benx203 Apr 7, 2021
638cdc9
fix
benx203 Apr 7, 2021
e24d641
fix
benx203 Apr 7, 2021
dce8163
fix test
benx203 Apr 8, 2021
04a42a2
fix
benx203 Apr 8, 2021
846fc86
fix test
benx203 Apr 8, 2021
952aa04
fix test
benx203 Apr 8, 2021
5ea5414
fix
benx203 Apr 8, 2021
e1a9f13
Merge remote-tracking branch 'upstream/master'
benx203 Apr 8, 2021
6bbcaf1
debug test
benx203 Apr 8, 2021
f150a72
debug test
benx203 Apr 8, 2021
9ac15df
debug test
benx203 Apr 8, 2021
c97c0a2
fork the repo to prevent the original repo deleted by accident
spacewander Apr 9, 2021
4936b19
tweak
spacewander Apr 9, 2021
fb69097
WIP
spacewander Apr 9, 2021
6171c2e
Merge branch 'master' into benx203_master
spacewander Apr 14, 2021
83d7500
fix lint
spacewander Apr 14, 2021
5af1d9e
fix
benx203 Apr 19, 2021
69408be
fix
benx203 Apr 19, 2021
dfd6921
fix
benx203 Apr 19, 2021
8b196f3
fix
benx203 Apr 19, 2021
09767bd
fix
benx203 Apr 19, 2021
660d2b6
trigger test
benx203 Apr 20, 2021
407fb42
style
spacewander Apr 21, 2021
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
31 changes: 31 additions & 0 deletions .github/workflows/centos7-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,37 @@ jobs:
docker run --rm --name consul_1 -d -p 8500:8500 consul:1.7 consul agent -server -bootstrap-expect=1 -client 0.0.0.0 -log-level info -data-dir=/consul/data
docker run --rm --name consul_2 -d -p 8600:8500 consul:1.7 consul agent -server -bootstrap-expect=1 -client 0.0.0.0 -log-level info -data-dir=/consul/data

# start nacos server
nohup docker network rm nacos_net > /dev/null 2>&1 &
nohup docker network create nacos_net > /dev/null 2>&1 &
# nacos no auth server - for test no auth
docker run --rm -d --name nacos_no_auth --network nacos_net --hostname nacos2 --env NACOS_SERVERS="nacos1:8848 nacos2:8848" --env PREFER_HOST_MODE=hostname --env MODE=cluster --env EMBEDDED_STORAGE=embedded --env JVM_XMS=512m --env JVM_XMX=512m --env JVM_XMN=256m -p8858:8848 nacos/nacos-server:1.4.1
# nacos auth server - for test auth
docker run --rm -d --name nacos_auth --network nacos_net --hostname nacos1 --env NACOS_AUTH_ENABLE=true --env NACOS_SERVERS="nacos1:8848 nacos2:8848" --env PREFER_HOST_MODE=hostname --env MODE=cluster --env EMBEDDED_STORAGE=embedded --env JVM_XMS=512m --env JVM_XMX=512m --env JVM_XMN=256m -p8848:8848 nacos/nacos-server:1.4.1
url="127.0.0.1:8858/nacos/v1/ns/service/list?pageNo=1&pageSize=2"
until [[ "$(curl -s -o /dev/null -w ''%{http_code}'' $url)" == "200" ]]; do
echo 'wait nacos server...'
sleep 1;
done
# register nacos service
rm -rf tmp
mkdir tmp
cd tmp
wget https://raw.githubusercontent.com/benx203/nacos-test-service/main/spring-nacos-1.0-SNAPSHOT.jar
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benx203
Would you also submit the Java code to https://github.com/benx203/nacos-test-service? Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

curl https://raw.githubusercontent.com/benx203/nacos-test-service/main/Dockerfile | docker build -t nacos-test-service:1.0-SNAPSHOT -f - .
docker run -d --rm --network nacos_net --env SERVICE_NAME=APISIX-NACOS --env NACOS_ADDR=nacos2:8848 --env SUFFIX_NUM=1 -p 18001:18001 --name nacos-service1 nacos-test-service:1.0-SNAPSHOT
docker run -d --rm --network nacos_net --env SERVICE_NAME=APISIX-NACOS --env NACOS_ADDR=nacos2:8848 --env SUFFIX_NUM=2 -p 18002:18001 --name nacos-service2 nacos-test-service:1.0-SNAPSHOT
url="127.0.0.1:18002/hello"
until [[ "$(curl -s -o /dev/null -w ''%{http_code}'' $url)" == "200" ]]; do
echo 'wait nacos service...'
sleep 1;
done
until [[ $(curl -s "127.0.0.1:8858/nacos/v1/ns/service/list?pageNo=1&pageSize=2" | grep "APISIX-NACOS") ]]; do
echo 'wait nacos reg...'
sleep 1;
done
cd ..

- name: Install dependencies
run: |
docker exec centos7Instance bash -c "cd apisix && ./utils/centos7-ci.sh install_dependencies"
Expand Down
322 changes: 322 additions & 0 deletions apisix/discovery/nacos.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
--
-- 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 local_conf = require("apisix.core.config_local").local_conf()
local http = require("resty.http")
local core = require("apisix.core")
local ipairs = ipairs
local tostring = tostring
local type = type
local math = math
local math_random = math.random
local error = error
local ngx = ngx
local ngx_re = require("ngx.re")
local ngx_timer_at = ngx.timer.at
local ngx_timer_every = ngx.timer.every
local string = string
local string_sub = string.sub
local str_byte = string.byte
local str_find = core.string.find
local str_format = string.format
local log = core.log

local default_weight
local applications
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can initialize applications = {} so that we can save many codes like:

        if not applications then
            applications = up_apps
        end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If use applications = {},run test could fail cause maybe call _M.nodes method before finish init.

local auth_path = "auth/login"
local service_list_path = "ns/service/list?pageNo=%s&pageSize=%s"
local instance_list_path = "ns/instance/list?healthyOnly=true&serviceName="

local schema = {
type = "object",
properties = {
host = {
type = "array",
minItems = 1,
items = {
type = "string",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a pattern to limit the string to the host format

},
},
fetch_interval = {type = "integer", minimum = 1, default = 30},
prefix = {type = "string", default = "/nacos/v1/"},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

weight = {type = "integer", minimum = 1, default = 100},
timeout = {
type = "object",
properties = {
connect = {type = "integer", minimum = 1, default = 2000},
send = {type = "integer", minimum = 1, default = 2000},
read = {type = "integer", minimum = 1, default = 5000},
},
default = {
connect = 2000,
send = 2000,
read = 5000,
}
},
},
required = {"host"}
}


local _M = {
version = 0.1,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this version is useless, we can drop it

}


local function request(request_uri, path, body, method, basic_auth)
local url = request_uri .. path
log.info("request url:", url)
local headers = core.table.new(0, 0)
headers['Accept'] = 'application/json'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the string object, I think we use ", it is easier for reading.

headers["Accept"] = "application/json"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and please fix the similar points


if basic_auth then
headers['Authorization'] = basic_auth
end

if body and 'table' == type(body) then
local err
body, err = core.json.encode(body)
if not body then
return nil, 'invalid body : ' .. err
end
headers['Content-Type'] = 'application/json'
end

local httpc = http.new()
local timeout = local_conf.discovery.nacos.timeout
local connect_timeout = timeout.connect
local send_timeout = timeout.send
local read_timeout = timeout.read
log.info("connect_timeout:", connect_timeout, ", send_timeout:", send_timeout,
", read_timeout:", read_timeout)
httpc:set_timeouts(connect_timeout, send_timeout, read_timeout)
local res, err = httpc:request_uri(url, {
method = method,
headers = headers,
body = body,
ssl_verify = true,
})
if not res then
return nil, err
end

if not res.body or res.status ~= 200 then
return nil, "status = " .. res.status
end

local json_str = res.body
local data, err = core.json.decode(json_str)
if not data then
return nil, err
end
return data
end


local function get_url(request_uri, path)
return request(request_uri, path, nil, "GET", nil)
end


local function post_url(request_uri, path, body)
return request(request_uri, path, body, "POST", nil)
end


local function get_token_param(base_uri, username, password)
if username and password then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better style:

if not username or not password then
    return ""
end

...

local data, err = post_url(base_uri, auth_path .. "?username=" .. username
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should call ngx.encode_args for username and password

.. "&password=" .. password, nil)
if err then
log.error("nacos login fail:", username, " ", password, " desc:", err)
return nil, err
end
return "&accessToken=" .. data.accessToken
end
return ""
end


local function get_base_uri()
local host = local_conf.discovery.nacos.host
-- TODO Add health check to get healthy nodes.
local url = host[math_random(#host)]
local auth_idx = str_find(url, "@")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we'd better use the plain text model, better performance.

https://stackoverflow.com/questions/18973019/is-there-a-lua-string-find-without-pattern

local username, password
if auth_idx then
local protocol_idx = str_find(url, "://")
local protocol = string_sub(url, 1, protocol_idx + 2)
local user_and_password = string_sub(url, protocol_idx + 3, auth_idx - 1)
local arr = ngx_re.split(user_and_password, ":")
if #arr == 2 then
username = arr[1]
password = arr[2]
end
local other = string_sub(url, auth_idx + 1)
url = protocol .. other
end

if local_conf.discovery.nacos.prefix then
url = url .. local_conf.discovery.nacos.prefix
end

if str_byte(url, #url) ~= str_byte("/") then
url = url .. "/"
end

return url, username, password
end


local function get_page_service(infos, base_uri, token_param, page_num)
-- TODO Hardcode:page size=100,will rewrite by spacewander after merge
local path = str_format(service_list_path, page_num, 100) .. token_param
local data, err = get_url(base_uri, path)
if err then
return data, err, path
end

for _, service_name in ipairs(data.doms) do
core.table.insert(infos, service_name)
end
return data, err, path
end


local function iter_and_add_service_info(infos, base_uri, token_param)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Emm... I mean to iterate the upstream configurations and collect the service list from those that have discovery_type set to nacos. There is no need to ask nacos for the service list, we already know it via the upstream configuration.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can take a look at

for _, value in core.config_util.iterate_values(values) do
if value.value.id == src_id then
if not value.checker then
return nil, str_format("no checker for %s[%s]", src_type, src_id)
end
return extra_checker_info(value, src_type)
end

for how we iterate the upstream configurations and collect the health check info.
It is similar to collect the service_name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If import these in nacos.lua:
local upstream_mod = require("apisix.upstream")
local get_upstreams = upstream_mod.upstreams

When run test file,will throw error:
attempt to index local 'upstream_mod' (a number value)

It's not right import?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have mentioned it before: #3820 (comment)

You might need some lazy load trick to break the dependent cycle.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you give me some demo code?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't worry, I can update it for you once you have solved the other issues.

local data, err, path = get_page_service(infos, base_uri, token_param, 1)
if err then
log.error("get_url:", path, " err:", err)
return
end

-- TODO Hardcode:page size=100,will rewrite by spacewander after merge
local maxPage = math.ceil(data.count / 100)
if maxPage == 0 then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magic number, need some comments

return
end

-- more than 1 page,continue fetch other pages
if maxPage > 1 then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

for i = 2, maxPage do
get_page_service(infos, base_uri, token_param, i)
end
end
end


local function get_services(base_uri, token_param)
local infos = core.table.new(0, 0)
iter_and_add_service_info(infos, base_uri, token_param)
return infos
end


local function fetch_full_registry(premature)
if premature then
return
end

local up_apps = core.table.new(0, 0)
local base_uri, username, password = get_base_uri()
local token_param, err = get_token_param(base_uri, username, password)
if err then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to writhe this error message to error log

log.error("get_token_param error:", err)
if not applications then
applications = up_apps
end
return
end

local infos = get_services(base_uri, token_param)
if #infos == 0 then
applications = up_apps
return
end

local data, err
for _, service_name in ipairs(infos) do
data, err = get_url(base_uri, instance_list_path .. service_name .. token_param)
spacewander marked this conversation as resolved.
Show resolved Hide resolved
if err then
log.error("get_url:", instance_list_path, " err:", err)
if not applications then
applications = up_apps
end
return
end

for _, host in ipairs(data.hosts) do
if tostring(host.valid) == 'true' and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I set up the environment and debug it myself.
Here is the host: {"enabled":true,"instanceHeartBeatTimeOut":15000,"ipDeleteTimeout":30000,"instanceHeartBeatInterval":5000,"metadata":{"preserved.register.source":"SPRING_CLOUD"},"port":18001,"serviceName":"DEFAULT_GROUP@@APISIX-NACOS","instanceId":"192.168.65.3#18001#DEFAULT#DEFAULT_GROUP@@APISIX-NACOS","healthy":true,"ip":"192.168.65.3","ephemeral":true,"clusterName":"DEFAULT","weight":1}

There is not valid field. The response doesn't match their API doc: https://nacos.io/en-us/docs/open-api.html

I am not sure if it is because the latest Nacos has changed the API. Maybe we need to use the tagged nacos image 2.0.0 instead of the latest one?

tostring(host.healthy) == 'true' and
tostring(host.enabled) == 'true' then
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more question: why we need to filter the result according to those fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If query all records,need to filter.
But now should not need any more.

local nodes = up_apps[service_name]
if not nodes then
nodes = core.table.new(0, 0)
up_apps[service_name] = nodes
end
core.table.insert(nodes, {
host = host.ip,
port = host.port,
weight = host.weight or default_weight,
})
end
end
end
applications = up_apps
end


function _M.nodes(service_name)
local logged = false
while not applications do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to set a maximum waiting time , eg: 5 seconds

if not logged then
log.warn('wait init')
logged = true
end
ngx.sleep(0.1)
end
return applications[service_name]
end


function _M.init_worker()
if not local_conf.discovery.nacos or
not local_conf.discovery.nacos.host or #local_conf.discovery.nacos.host == 0 then
error("do not set nacos.host")
return
end

local ok, err = core.schema.check(schema, local_conf.discovery.nacos)
if not ok then
error("invalid nacos configuration: " .. err)
return
end
default_weight = local_conf.discovery.nacos.weight
log.info("default_weight:", default_weight)
local fetch_interval = local_conf.discovery.nacos.fetch_interval
log.info("fetch_interval:", fetch_interval)
ngx_timer_at(0, fetch_full_registry)
ngx_timer_every(fetch_interval, fetch_full_registry)
end


function _M.dump_data()
return {config = local_conf.discovery.nacos, services = applications or {}}
end


return _M
31 changes: 31 additions & 0 deletions ci/linux_openresty_common_runner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,37 @@ before_install() {
# start consul servers
docker run --rm --name consul_1 -d -p 8500:8500 consul:1.7 consul agent -server -bootstrap-expect=1 -client 0.0.0.0 -log-level info -data-dir=/consul/data
docker run --rm --name consul_2 -d -p 8600:8500 consul:1.7 consul agent -server -bootstrap-expect=1 -client 0.0.0.0 -log-level info -data-dir=/consul/data

# start nacos server
nohup docker network rm nacos_net > /dev/null 2>&1 &
nohup docker network create nacos_net > /dev/null 2>&1 &
# nacos no auth server - for test no auth
docker run --rm -d --name nacos_no_auth --network nacos_net --hostname nacos2 --env NACOS_SERVERS="nacos1:8848 nacos2:8848" --env PREFER_HOST_MODE=hostname --env MODE=cluster --env EMBEDDED_STORAGE=embedded --env JVM_XMS=512m --env JVM_XMX=512m --env JVM_XMN=256m -p8858:8848 nacos/nacos-server:1.4.1
# nacos auth server - for test auth
docker run --rm -d --name nacos_auth --network nacos_net --hostname nacos1 --env NACOS_AUTH_ENABLE=true --env NACOS_SERVERS="nacos1:8848 nacos2:8848" --env PREFER_HOST_MODE=hostname --env MODE=cluster --env EMBEDDED_STORAGE=embedded --env JVM_XMS=512m --env JVM_XMX=512m --env JVM_XMN=256m -p8848:8848 nacos/nacos-server:1.4.1
url="127.0.0.1:8858/nacos/v1/ns/service/list?pageNo=1&pageSize=2"
until [[ "$(curl -s -o /dev/null -w ''%{http_code}'' $url)" == "200" ]]; do
echo 'wait nacos server...'
sleep 1;
done
# register nacos service
rm -rf tmp
mkdir tmp
cd tmp
wget https://raw.githubusercontent.com/benx203/nacos-test-service/main/spring-nacos-1.0-SNAPSHOT.jar
curl https://raw.githubusercontent.com/benx203/nacos-test-service/main/Dockerfile | docker build -t nacos-test-service:1.0-SNAPSHOT -f - .
docker run -d --rm --network nacos_net --env SERVICE_NAME=APISIX-NACOS --env NACOS_ADDR=nacos2:8848 --env SUFFIX_NUM=1 -p 18001:18001 --name nacos-service1 nacos-test-service:1.0-SNAPSHOT
docker run -d --rm --network nacos_net --env SERVICE_NAME=APISIX-NACOS --env NACOS_ADDR=nacos2:8848 --env SUFFIX_NUM=2 -p 18002:18001 --name nacos-service2 nacos-test-service:1.0-SNAPSHOT
url="127.0.0.1:18002/hello"
until [[ "$(curl -s -o /dev/null -w ''%{http_code}'' $url)" == "200" ]]; do
echo 'wait nacos service...'
sleep 1;
done
until [[ $(curl -s "127.0.0.1:8858/nacos/v1/ns/service/list?pageNo=1&pageSize=2" | grep "APISIX-NACOS") ]]; do
echo 'wait nacos reg...'
sleep 1;
done
cd ..
}

do_install() {
Expand Down
Loading