This is an automated email from the ASF dual-hosted git repository.

spacewander 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 8757080  feat: add nacos support (#3820)
8757080 is described below

commit 8757080697d04071ee17e0d43417b9649b8e2b48
Author: _chg_ <[email protected]>
AuthorDate: Wed Apr 21 17:48:56 2021 +0800

    feat: add nacos support (#3820)
    
    Co-authored-by: spacewander <[email protected]>
---
 .github/workflows/centos7-ci.yml    |  31 ++++
 README.md                           |   2 +-
 apisix/discovery/nacos.lua          | 329 ++++++++++++++++++++++++++++++++++++
 ci/linux_openresty_common_runner.sh |  31 ++++
 ci/linux_tengine_runner.sh          |  31 ++++
 docs/en/latest/config.json          |   1 +
 docs/en/latest/discovery.md         |   4 +-
 docs/en/latest/discovery/nacos.md   | 101 +++++++++++
 docs/zh/latest/README.md            |   2 +-
 docs/zh/latest/discovery.md         |   2 +
 t/discovery/nacos.t                 | 253 +++++++++++++++++++++++++++
 11 files changed, 784 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/centos7-ci.yml b/.github/workflows/centos7-ci.yml
index 17c0bc9..0d1b899 100644
--- a/.github/workflows/centos7-ci.yml
+++ b/.github/workflows/centos7-ci.yml
@@ -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/api7/nacos-test-service/main/spring-nacos-1.0-SNAPSHOT.jar
+        curl 
https://raw.githubusercontent.com/api7/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 && ./ci/centos7-ci.sh 
install_dependencies"
diff --git a/README.md b/README.md
index f2e94f9..8272281 100644
--- a/README.md
+++ b/README.md
@@ -117,7 +117,7 @@ A/B testing, canary release, blue-green deployment, limit 
rate, defense against
 - **OPS friendly**
 
   - OpenTracing: support [Apache 
Skywalking](docs/en/latest/plugins/skywalking.md) and 
[Zipkin](docs/en/latest/plugins/zipkin.md)
-  - works with external service discovery:In addition to the built-in etcd, it 
also supports `Consul` and `Nacos` [DNS discovery 
mode](https://github.com/apache/apisix/issues/1731#issuecomment-646392129), and 
[Eureka](docs/en/latest/discovery.md)
+  - works with external service discovery:In addition to the built-in etcd, it 
also supports [Consul](docs/en/latest/discovery/consul_kv.md) and 
[Nacos](docs/en/latest/discovery/nacos.md), and 
[Eureka](docs/en/latest/discovery.md)
   - Monitoring And Metrics: [Prometheus](docs/en/latest/plugins/prometheus.md)
   - Clustering: APISIX nodes are stateless, creates clustering of the 
configuration center, please refer to [etcd Clustering 
Guide](https://etcd.io/docs/v3.4.0/op-guide/clustering/).
   - High availability: Support to configure multiple etcd addresses in the 
same cluster.
diff --git a/apisix/discovery/nacos.lua b/apisix/discovery/nacos.lua
new file mode 100644
index 0000000..cbb0bb6
--- /dev/null
+++ b/apisix/discovery/nacos.lua
@@ -0,0 +1,329 @@
+--
+-- 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 http               = require('resty.http')
+local core               = require('apisix.core')
+local ipairs             = ipairs
+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 log                = core.log
+
+local default_weight
+local applications
+local auth_path = 'auth/login'
+local instance_list_path = 'ns/instance/list?healthyOnly=true&serviceName='
+
+local host_pattern = [[^http(s)?:\/\/[a-zA-Z0-9-_.:\@]+$]]
+local prefix_pattern = [[^[\/a-zA-Z0-9-_.]+$]]
+local schema = {
+    type = 'object',
+    properties = {
+        host = {
+            type = 'array',
+            minItems = 1,
+            items = {
+                type = 'string',
+                pattern = host_pattern,
+                minLength = 2,
+                maxLength = 100,
+            },
+        },
+        fetch_interval = {type = 'integer', minimum = 1, default = 30},
+        prefix = {
+            type = 'string',
+            pattern = prefix_pattern,
+            maxLength = 100,
+            default = '/nacos/v1/'
+        },
+        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 = {}
+
+
+local function request(request_uri, path, body, method, basic_auth)
+    local url = request_uri .. path
+    log.info('request url:', url)
+    local headers = {}
+    headers['Accept'] = 'application/json'
+
+    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 not username or not password then
+        return ''
+    end
+
+    local args = { username = username, password = password}
+    local data, err = post_url(base_uri, auth_path .. '?' .. 
ngx.encode_args(args), nil)
+    if err then
+        log.error('nacos login fail:', username, ' ', password, ' desc:', err)
+        return nil, err
+    end
+    return '&accessToken=' .. data.accessToken
+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, '@')
+    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 iter_and_add_service(services, values)
+    if not values then
+        return
+    end
+
+    for _, value in core.config_util.iterate_values(values) do
+        local conf = value.value
+        if not conf then
+            goto CONTINUE
+        end
+
+        local up
+        if conf.upstream then
+            up = conf.upstream
+        else
+            up = conf
+        end
+
+        if up.discovery_type == 'nacos' then
+            core.table.insert(services, up.service_name)
+        end
+        ::CONTINUE::
+    end
+end
+
+
+local function get_nacos_services()
+    local services = {}
+
+    -- here we use lazy load to work around circle dependency
+    local get_upstreams = require('apisix.upstream').upstreams
+    local get_routes = require('apisix.router').http_routes
+    local get_services = require('apisix.http.service').services
+
+    local values = get_upstreams()
+    iter_and_add_service(services, values)
+    values = get_routes()
+    iter_and_add_service(services, values)
+    values = get_services()
+    iter_and_add_service(services, values)
+    return services
+end
+
+
+local function fetch_full_registry(premature)
+    if premature then
+        return
+    end
+
+    local up_apps = {}
+    local base_uri, username, password = get_base_uri()
+    local token_param, err = get_token_param(base_uri, username, password)
+    if err then
+        log.error('get_token_param error:', err)
+        if not applications then
+            applications = up_apps
+        end
+        return
+    end
+
+    local infos = get_nacos_services()
+    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)
+        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
+            local nodes = up_apps[service_name]
+            if not nodes then
+                nodes = {}
+                up_apps[service_name] = nodes
+            end
+            core.table.insert(nodes, {
+                host = host.ip,
+                port = host.port,
+                weight = host.weight or default_weight,
+            })
+        end
+    end
+    applications = up_apps
+end
+
+
+function _M.nodes(service_name)
+    local logged = false
+    -- maximum waiting time: 5 seconds
+    local waiting_time = 5
+    local step = 0.1
+    while not applications and waiting_time > 0 do
+        if not logged then
+            log.warn('wait init')
+            logged = true
+        end
+        ngx.sleep(step)
+        waiting_time = waiting_time - step
+    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
diff --git a/ci/linux_openresty_common_runner.sh 
b/ci/linux_openresty_common_runner.sh
index 34c3ecd..0716806 100755
--- a/ci/linux_openresty_common_runner.sh
+++ b/ci/linux_openresty_common_runner.sh
@@ -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/api7/nacos-test-service/main/spring-nacos-1.0-SNAPSHOT.jar
+    curl 
https://raw.githubusercontent.com/api7/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() {
diff --git a/ci/linux_tengine_runner.sh b/ci/linux_tengine_runner.sh
index b8f6a27..f48d90b 100755
--- a/ci/linux_tengine_runner.sh
+++ b/ci/linux_tengine_runner.sh
@@ -40,6 +40,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/api7/nacos-test-service/main/spring-nacos-1.0-SNAPSHOT.jar
+    curl 
https://raw.githubusercontent.com/api7/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 ..
 }
 
 tengine_install() {
diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json
index 599a556..2721df5 100644
--- a/docs/en/latest/config.json
+++ b/docs/en/latest/config.json
@@ -158,6 +158,7 @@
             "discovery",
             "discovery/dns",
             "discovery/consul_kv",
+            "discovery/nacos",
             "discovery/eureka"
           ]
         },
diff --git a/docs/en/latest/discovery.md b/docs/en/latest/discovery.md
index 97e511d..2abaf22 100644
--- a/docs/en/latest/discovery.md
+++ b/docs/en/latest/discovery.md
@@ -35,12 +35,14 @@ Common registries: Eureka, Etcd, Consul, Zookeeper, Nacos 
etc.
 
 ## Supported discovery registries
 
-Currently we support Eureka/Consul and service discovery via DNS.
+Currently we support Eureka/Consul/Nacos and service discovery via DNS.
 
 For service discovery via DNS, see [service discovery via 
DNS](discovery/dns.md).
 
 For Consul, see [service discovery via Consul](discovery/consul_kv.md)
 
+For Nacos, see [service discovery via Nacos](discovery/nacos.md)
+
 For Eureka, see below.
 
 ## How to extend the discovery client?
diff --git a/docs/en/latest/discovery/nacos.md 
b/docs/en/latest/discovery/nacos.md
new file mode 100644
index 0000000..5d5ea2b
--- /dev/null
+++ b/docs/en/latest/discovery/nacos.md
@@ -0,0 +1,101 @@
+---
+title: nacos
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+## Service discovery via Nacos
+
+This is experimental discovery module for Nacos.
+
+The performance of this module needs to be improved:
+
+1. avoid synchroning configuration in each workers. You can refer the 
implementation in `consul_kv.lua`.
+2. send the request parallelly.
+
+### Configuration for Nacos
+
+Add following configuration in `conf/config.yaml` :
+
+```yaml
+discovery:
+  nacos:
+    host:
+      - "http://${username}:${password}@${host1}:${port1}";
+    prefix: "/nacos/v1/"
+    fetch_interval: 30    # default 30 sec
+    weight: 100           # default 100
+    timeout:
+      connect: 2000       # default 2000 ms
+      send: 2000          # default 2000 ms
+      read: 5000          # default 5000 ms
+```
+
+And you can config it in short by default value:
+
+```yaml
+discovery:
+  nacos:
+    host:
+      - "http://192.168.33.1:8848";
+```
+
+### Upstream setting
+
+Here is an example of routing a request with a URL of "/nacos/*" to a service 
which named 
"http://192.168.33.1:8848/nacos/v1/ns/instance/list?serviceName=APISIX-NACOS"; 
and use nacos discovery client in the registry :
+
+```shell
+$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
+{
+    "uri": "/nacos/*",
+    "upstream": {
+        "service_name": "APISIX-NACOS",
+        "type": "roundrobin",
+        "discovery_type": "nacos"
+    }
+}'
+```
+
+The format response as below:
+
+```json
+{
+  "node": {
+    "key": "\/apisix\/routes\/1",
+    "value": {
+      "id": "1",
+      "create_time": 1615796097,
+      "status": 1,
+      "update_time": 1615799165,
+      "upstream": {
+        "hash_on": "vars",
+        "pass_host": "pass",
+        "scheme": "http",
+        "service_name": "APISIX-NACOS",
+        "type": "roundrobin",
+        "discovery_type": "nacos"
+      },
+      "priority": 0,
+      "uri": "\/nacos\/*"
+    }
+  },
+  "action": "set"
+}
+```
diff --git a/docs/zh/latest/README.md b/docs/zh/latest/README.md
index 40b93ef..641dcba 100644
--- a/docs/zh/latest/README.md
+++ b/docs/zh/latest/README.md
@@ -117,7 +117,7 @@ A/B 测试、金丝雀发布(灰度发布)、蓝绿部署、限流限速、抵
 - **运维友好**
 
   - OpenTracing 可观测性: 支持 [Apache Skywalking](plugins/skywalking.md) 和 
[Zipkin](plugins/zipkin.md)。
-  - 对接外部服务发现:除了内置的 etcd 外,还支持 `Consul` 和 `Nacos` 的 [DNS 
发现模式](https://github.com/apache/apisix/issues/1731#issuecomment-646392129),以及 
[Eureka](discovery.md)。
+  - 对接外部服务发现:除了内置的 etcd 外,还支持 [Consul](../../en/latest/discovery/consul_kv.md) 
和 [Nacos](../../en/latest/discovery/nacos.md),以及 [Eureka](discovery.md)。
   - 监控和指标: [Prometheus](plugins/prometheus.md)
   - 集群:APISIX 节点是无状态的,创建配置中心集群请参考 [etcd Clustering 
Guide](https://etcd.io/docs/v3.4.0/op-guide/clustering/)。
   - 高可用:支持配置同一个集群内的多个 etcd 地址。
diff --git a/docs/zh/latest/discovery.md b/docs/zh/latest/discovery.md
index 261f242..20105e1 100644
--- a/docs/zh/latest/discovery.md
+++ b/docs/zh/latest/discovery.md
@@ -53,6 +53,8 @@ title: 集成服务发现注册中心
 
 Consul 的支持见 [基于 Consul 的服务支持发现](../../en/latest/discovery/consul_kv.md)
 
+Nacos 的支持见 [基于 Nacos 的服务支持发现](../../en/latest/discovery/nacos.md)
+
 Eureka 的支持方式见下文。
 
 ## 如何扩展注册中心?
diff --git a/t/discovery/nacos.t b/t/discovery/nacos.t
new file mode 100644
index 0000000..60f60cb
--- /dev/null
+++ b/t/discovery/nacos.t
@@ -0,0 +1,253 @@
+#
+# 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();
+
+our $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  nacos:
+      host:
+        - "http://127.0.0.1:8858";
+      prefix: "/nacos/v1/"
+      fetch_interval: 1
+      weight: 1
+      timeout:
+        connect: 2000
+        send: 2000
+        read: 5000
+
+_EOC_
+
+our $yaml_auth_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  nacos:
+      host:
+        - "http://nacos:nacos\@127.0.0.1:8848";
+      prefix: "/nacos/v1/"
+      fetch_interval: 1
+      weight: 1
+      timeout:
+        connect: 2000
+        send: 2000
+        read: 5000
+
+_EOC_
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: get APISIX-NACOS info from NACOS - no auth
+--- yaml_config eval: $::yaml_config
+--- apisix_yaml
+routes:
+  -
+    uri: /hello
+    upstream:
+      service_name: APISIX-NACOS
+      discovery_type: nacos
+      type: roundrobin
+#END
+--- pipelined_requests eval
+[
+    "GET /hello",
+    "GET /hello",
+]
+--- response_body_like eval
+[
+    qr/server [1-2]/,
+    qr/server [1-2]/,
+]
+--- no_error_log
+[error, error]
+
+
+
+=== TEST 2: error service_name name - no auth
+--- yaml_config eval: $::yaml_config
+--- apisix_yaml
+routes:
+  -
+    uri: /hello
+    upstream:
+      service_name: APISIX-NACOS-DEMO
+      discovery_type: nacos
+      type: roundrobin
+
+#END
+--- request
+GET /hello
+--- error_code: 503
+
+
+
+=== TEST 3: get APISIX-NACOS info from NACOS - auth
+--- yaml_config eval: $::yaml_auth_config
+--- apisix_yaml
+routes:
+  -
+    uri: /hello
+    upstream:
+      service_name: APISIX-NACOS
+      discovery_type: nacos
+      type: roundrobin
+
+#END
+--- pipelined_requests eval
+[
+    "GET /hello",
+    "GET /hello",
+]
+--- response_body_like eval
+[
+    qr/server [1-2]/,
+    qr/server [1-2]/,
+]
+--- no_error_log
+[error, error]
+
+
+
+=== TEST 4: error service_name name - auth
+--- yaml_config eval: $::yaml_auth_config
+--- apisix_yaml
+routes:
+  -
+    uri: /hello
+    upstream:
+      service_name: APISIX-NACOS-DEMO
+      discovery_type: nacos
+      type: roundrobin
+
+#END
+--- request
+GET /hello
+--- error_code: 503
+
+
+
+=== TEST 5: get APISIX-NACOS info from NACOS - configured in services
+--- yaml_config eval: $::yaml_config
+--- apisix_yaml
+routes:
+  -
+    uri: /hello
+    service_id: 1
+services:
+  -
+    id: 1
+    upstream:
+      service_name: APISIX-NACOS
+      discovery_type: nacos
+      type: roundrobin
+#END
+--- pipelined_requests eval
+[
+    "GET /hello",
+    "GET /hello",
+]
+--- response_body_like eval
+[
+    qr/server [1-2]/,
+    qr/server [1-2]/,
+]
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: get APISIX-NACOS info from NACOS - configured in upstreams + etcd
+--- extra_yaml_config
+discovery:
+  nacos:
+      host:
+        - "http://127.0.0.1:8858";
+      fetch_interval: 1
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/upstreams/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "service_name": "APISIX-NACOS",
+                    "discovery_type": "nacos",
+                    "type": "roundrobin"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "uri": "/hello",
+                    "upstream_id": 1
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: hit
+--- extra_yaml_config
+discovery:
+  nacos:
+      host:
+        - "http://127.0.0.1:8858";
+      fetch_interval: 1
+--- pipelined_requests eval
+[
+    "GET /hello",
+    "GET /hello",
+]
+--- response_body_like eval
+[
+    qr/server [1-2]/,
+    qr/server [1-2]/,
+]
+--- no_error_log
+[error]

Reply via email to