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 5822eca9a feat: sync conf via gRPC (#8450)
5822eca9a is described below

commit 5822eca9a28ff7b92dc7fc0431281eb62dd60ba8
Author: 罗泽轩 <[email protected]>
AuthorDate: Wed Dec 7 17:38:03 2022 +0800

    feat: sync conf via gRPC (#8450)
---
 .gitignore                        |   2 +
 apisix/cli/env.lua                |  12 ++
 apisix/cli/ngx_tpl.lua            |  16 +--
 apisix/cli/ops.lua                |  11 +-
 apisix/cli/snippet.lua            | 169 ++++++++++++----------
 apisix/core/config_etcd.lua       | 124 ++++++++++++++--
 apisix/core/etcd.lua              | 104 +++++++++++---
 apisix/init.lua                   |  12 +-
 rockspec/apisix-master-0.rockspec |   2 +-
 t/APISIX.pm                       |   2 +
 t/certs/mtls_ca.crt               |  36 ++---
 t/certs/mtls_ca.key               |  27 ++++
 t/certs/mtls_client.crt           |  85 +++--------
 t/certs/mtls_client.key           |  50 +++----
 t/certs/mtls_server.crt           |  86 +++--------
 t/certs/mtls_server.key           |  50 +++----
 t/cli/test_etcd_grpc_mtls.sh      | 181 +++++++++++++++++++++++
 t/cli/test_etcd_grpc_tls.sh       |  78 ++++++++++
 t/core/config_etcd.t              |   1 +
 t/core/etcd-auth-fail.t           |   2 +-
 t/core/etcd-grpc-mtls.t           | 292 ++++++++++++++++++++++++++++++++++++++
 t/core/etcd-sync.t                |  21 +--
 t/core/etcd.t                     |  10 ++
 t/deployment/conf_server.t        |   4 +-
 t/deployment/grpc/conf_server.t   | 165 +++++++++++++++++++++
 t/deployment/grpc/mtls.t          | 118 +++++++++++++++
 t/discovery/nacos.t               |   1 +
 t/node/ewma.t                     |   1 +
 t/node/grpc-proxy-mtls.t          |   8 +-
 t/node/healthcheck-stop-checker.t |  34 ++++-
 t/plugin/grpc-transcode2.t        |  10 +-
 t/stream-node/random.t            |   1 +
 32 files changed, 1366 insertions(+), 349 deletions(-)

diff --git a/.gitignore b/.gitignore
index bd0b1dbb1..49dbbfe57 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,8 @@ luac.out
 *.orig
 *.rej
 t/servroot
+t/certs/*.csr
+t/certs/*.srl
 t/xds-library/libxds.h
 conf/apisix.uid
 conf/nginx.conf
diff --git a/apisix/cli/env.lua b/apisix/cli/env.lua
index f0e1a36e7..363148364 100644
--- a/apisix/cli/env.lua
+++ b/apisix/cli/env.lua
@@ -89,12 +89,24 @@ return function (apisix_home, pkg_cpath_org, pkg_path_org)
     local openresty_args = openresty_path_abs .. [[ -p ]] .. apisix_home .. [[ 
-c ]]
                            .. apisix_home .. [[/conf/nginx.conf]]
 
+    local or_info, err = util.execute_cmd("openresty -V 2>&1")
+    if not or_info then
+        error("failed to exec cmd \'openresty -V 2>&1\', err: " .. err)
+    end
+
+    local use_apisix_base = true
+    if not or_info:find("apisix-nginx-module", 1, true) then
+        use_apisix_base = false
+    end
+
     local min_etcd_version = "3.4.0"
 
     return {
         apisix_home = apisix_home,
         is_root_path = is_root_path,
         openresty_args = openresty_args,
+        openresty_info = or_info,
+        use_apisix_base = use_apisix_base,
         pkg_cpath_org = pkg_cpath_org,
         pkg_path_org = pkg_path_org,
         min_etcd_version = min_etcd_version,
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index b578ec27f..c1e46220a 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -57,7 +57,7 @@ env {*name*};
 {% end %}
 {% end %}
 
-{% if use_apisix_openresty then %}
+{% if use_apisix_base then %}
 thread_pool grpc-client-nginx-module threads=1;
 
 lua {
@@ -90,7 +90,7 @@ http {
     }
 
     server {
-        {% if use_apisix_openresty then %}
+        {% if use_apisix_base then %}
             listen {* prometheus_server_addr *} 
enable_process=privileged_agent;
         {% else %}
             listen {* prometheus_server_addr *};
@@ -215,7 +215,7 @@ stream {
 
         proxy_pass apisix_backend;
 
-        {% if use_apisix_openresty then %}
+        {% if use_apisix_base then %}
         set $upstream_sni "apisix_backend";
         proxy_ssl_server_name on;
         proxy_ssl_name $upstream_sni;
@@ -395,7 +395,7 @@ http {
     upstream apisix_backend {
         server 0.0.0.1;
 
-        {% if use_apisix_openresty then %}
+        {% if use_apisix_base then %}
         keepalive {* http.upstream.keepalive *};
         keepalive_requests {* http.upstream.keepalive_requests *};
         keepalive_timeout {* http.upstream.keepalive_timeout *};
@@ -431,7 +431,7 @@ http {
     }
     {% end %}
 
-    {% if use_apisix_openresty then %}
+    {% if use_apisix_base then %}
     apisix_delay_client_max_body_check on;
     apisix_mirror_on_demand on;
     {% end %}
@@ -483,7 +483,7 @@ http {
 
     {% if enabled_plugins["prometheus"] and prometheus_server_addr then %}
     server {
-        {% if use_apisix_openresty then %}
+        {% if use_apisix_base then %}
             listen {* prometheus_server_addr *} 
enable_process=privileged_agent;
         {% else %}
             listen {* prometheus_server_addr *};
@@ -746,7 +746,7 @@ http {
                 apisix.grpc_access_phase()
             }
 
-            {% if use_apisix_openresty then %}
+            {% if use_apisix_base then %}
             # For servers which obey the standard, when `:authority` is 
missing,
             # `host` will be used instead. When used with apisix-base, we can 
do
             # better by setting `:authority` directly
@@ -799,7 +799,7 @@ http {
         location = /proxy_mirror {
             internal;
 
-            {% if not use_apisix_openresty then %}
+            {% if not use_apisix_base then %}
             if ($upstream_mirror_uri = "") {
                 return 200;
             }
diff --git a/apisix/cli/ops.lua b/apisix/cli/ops.lua
index 93bd17bf9..ef069f815 100644
--- a/apisix/cli/ops.lua
+++ b/apisix/cli/ops.lua
@@ -259,17 +259,12 @@ Please modify "admin_key" in conf/config.yaml .
         util.die("openresty version must >=", need_ver, " current ", or_ver, 
"\n")
     end
 
-    local or_info = util.execute_cmd("openresty -V 2>&1")
-    if or_info and not or_info:find("http_stub_status_module", 1, true) then
+    local or_info = env.openresty_info
+    if not or_info:find("http_stub_status_module", 1, true) then
         util.die("'http_stub_status_module' module is missing in ",
                  "your openresty, please check it out.\n")
     end
 
-    local use_apisix_openresty = true
-    if or_info and not or_info:find("apisix-nginx-module", 1, true) then
-        use_apisix_openresty = false
-    end
-
     local enable_http = true
     if not yaml_conf.apisix.enable_admin and yaml_conf.apisix.stream_proxy and
         yaml_conf.apisix.stream_proxy.only ~= false
@@ -543,7 +538,7 @@ Please modify "admin_key" in conf/config.yaml .
         os_name = util.trim(util.execute_cmd("uname")),
         apisix_lua_home = env.apisix_home,
         deployment_role = env.deployment_role,
-        use_apisix_openresty = use_apisix_openresty,
+        use_apisix_base = env.use_apisix_base,
         error_log = {level = "warn"},
         enable_http = enable_http,
         enabled_discoveries = enabled_discoveries,
diff --git a/apisix/cli/snippet.lua b/apisix/cli/snippet.lua
index 3b5eb3232..6225c213f 100644
--- a/apisix/cli/snippet.lua
+++ b/apisix/cli/snippet.lua
@@ -21,6 +21,95 @@ local ipairs = ipairs
 
 -- this module provide methods to generate snippets which will be used in the 
nginx.conf template
 local _M = {}
+local conf_server_tpl = [[
+upstream apisix_conf_backend {
+    server 0.0.0.0:80;
+    balancer_by_lua_block {
+        local conf_server = require("apisix.conf_server")
+        conf_server.balancer()
+    }
+}
+
+{% if trusted_ca_cert then %}
+lua_ssl_trusted_certificate {* trusted_ca_cert *};
+{% end %}
+
+server {
+    {% if control_plane then %}
+    listen {* control_plane.listen *} ssl;
+    ssl_certificate {* control_plane.cert *};
+    ssl_certificate_key {* control_plane.cert_key *};
+
+    {% if control_plane.client_ca_cert then %}
+    ssl_verify_client on;
+    ssl_client_certificate {* control_plane.client_ca_cert *};
+    {% end %}
+
+    {% else %}
+    listen unix:{* home *}/conf/config_listen.sock;
+    {% end %}
+
+    access_log off;
+
+    set $upstream_host '';
+
+    access_by_lua_block {
+        local conf_server = require("apisix.conf_server")
+        conf_server.access()
+    }
+
+    location / {
+        {% if enable_https then %}
+        {* directive_prefix *}_pass {* scheme_name *}s://apisix_conf_backend;
+        {* directive_prefix *}_ssl_protocols TLSv1.2 TLSv1.3;
+        {* directive_prefix *}_ssl_server_name on;
+
+        {% if etcd_tls_verify then %}
+        {* directive_prefix *}_ssl_verify on;
+        {* directive_prefix *}_ssl_trusted_certificate {* 
ssl_trusted_certificate *};
+        {% end %}
+
+        {% if sni then %}
+        {* directive_prefix *}_ssl_name {* sni *};
+        {% else %}
+        {* directive_prefix *}_ssl_name $upstream_host;
+        {% end %}
+
+        {% if client_cert then %}
+        {* directive_prefix *}_ssl_certificate {* client_cert *};
+        {* directive_prefix *}_ssl_certificate_key {* client_cert_key *};
+        {% end %}
+
+        {% else %}
+        {* directive_prefix *}_pass {* scheme_name *}://apisix_conf_backend;
+        {% end %}
+
+        {% if scheme_name == "http"  then %}
+        proxy_http_version 1.1;
+        proxy_set_header Connection "";
+        {% end %}
+
+        {* directive_prefix *}_set_header Host $upstream_host;
+        {* directive_prefix *}_next_upstream error timeout non_idempotent
+            http_500 http_502 http_503 http_504;
+    }
+
+    log_by_lua_block {
+        local conf_server = require("apisix.conf_server")
+        conf_server.log()
+    }
+}
+]]
+
+
+local function is_grpc_used(env, etcd)
+    local is_grpc_available = env.use_apisix_base
+    if etcd.user then
+        -- TODO: support user/password
+        is_grpc_available = false
+    end
+    return is_grpc_available and etcd.use_grpc
+end
 
 
 function _M.generate_conf_server(env, conf)
@@ -69,87 +158,13 @@ function _M.generate_conf_server(env, conf)
         end
     end
 
-    local conf_render = template.compile([[
-    upstream apisix_conf_backend {
-        server 0.0.0.0:80;
-        balancer_by_lua_block {
-            local conf_server = require("apisix.conf_server")
-            conf_server.balancer()
-        }
-    }
-
-    {% if trusted_ca_cert then %}
-    lua_ssl_trusted_certificate {* trusted_ca_cert *};
-    {% end %}
-
-    server {
-        {% if control_plane then %}
-        listen {* control_plane.listen *} ssl;
-        ssl_certificate {* control_plane.cert *};
-        ssl_certificate_key {* control_plane.cert_key *};
-
-        {% if control_plane.client_ca_cert then %}
-        ssl_verify_client on;
-        ssl_client_certificate {* control_plane.client_ca_cert *};
-        {% end %}
-
-        {% else %}
-        listen unix:{* home *}/conf/config_listen.sock;
-        {% end %}
-
-        access_log off;
-
-        set $upstream_host '';
-
-        access_by_lua_block {
-            local conf_server = require("apisix.conf_server")
-            conf_server.access()
-        }
-
-        location / {
-            {% if enable_https then %}
-            proxy_pass https://apisix_conf_backend;
-            proxy_ssl_protocols TLSv1.2 TLSv1.3;
-            proxy_ssl_server_name on;
-
-            {% if etcd_tls_verify then %}
-            proxy_ssl_verify on;
-            proxy_ssl_trusted_certificate {* ssl_trusted_certificate *};
-            {% end %}
-
-            {% if sni then %}
-            proxy_ssl_name {* sni *};
-            {% else %}
-            proxy_ssl_name $upstream_host;
-            {% end %}
-
-            {% if client_cert then %}
-            proxy_ssl_certificate {* client_cert *};
-            proxy_ssl_certificate_key {* client_cert_key *};
-            {% end %}
-
-            {% else %}
-            proxy_pass http://apisix_conf_backend;
-            {% end %}
-
-            proxy_http_version 1.1;
-            proxy_set_header Connection "";
-            proxy_set_header Host $upstream_host;
-            proxy_next_upstream error timeout non_idempotent http_500 http_502 
http_503 http_504;
-        }
-
-        log_by_lua_block {
-            local conf_server = require("apisix.conf_server")
-            conf_server.log()
-        }
-    }
-    ]])
-
+    local conf_render = template.compile(conf_server_tpl)
     local tls = etcd.tls
     local client_cert
     local client_cert_key
     local ssl_trusted_certificate
     local etcd_tls_verify
+    local use_grpc = is_grpc_used(env, etcd)
     if tls then
         if tls.cert then
             client_cert = pl_path.abspath(tls.cert)
@@ -175,6 +190,8 @@ function _M.generate_conf_server(env, conf)
         trusted_ca_cert = trusted_ca_cert,
         etcd_tls_verify = etcd_tls_verify,
         ssl_trusted_certificate = ssl_trusted_certificate,
+        scheme_name = use_grpc and "grpc" or "http",
+        directive_prefix = use_grpc and "grpc" or "proxy",
     })
 end
 
diff --git a/apisix/core/config_etcd.lua b/apisix/core/config_etcd.lua
index cff2cdc1e..efec28e8e 100644
--- a/apisix/core/config_etcd.lua
+++ b/apisix/core/config_etcd.lua
@@ -26,12 +26,14 @@ local log          = require("apisix.core.log")
 local json         = require("apisix.core.json")
 local etcd_apisix  = require("apisix.core.etcd")
 local core_str     = require("apisix.core.string")
+local core_tab     = require("apisix.core.table")
 local new_tab      = require("table.new")
 local check_schema = require("apisix.core.schema").check
 local exiting      = ngx.worker.exiting
 local insert_tab   = table.insert
 local type         = type
 local ipairs       = ipairs
+local pairs        = pairs
 local setmetatable = setmetatable
 local ngx_sleep    = require("apisix.core.utils").sleep
 local ngx_timer_at = ngx.timer.at
@@ -49,6 +51,8 @@ local health_check = require("resty.etcd.health_check")
 
 
 local is_http = ngx.config.subsystem == "http"
+local err_etcd_grpc_engine_timeout = "context deadline exceeded"
+local err_etcd_grpc_ngx_timeout = "timeout"
 local err_etcd_unhealthy_all = "has no healthy etcd endpoint available"
 local health_check_shm_name = "etcd-cluster-health-check"
 if not is_http then
@@ -120,11 +124,46 @@ local function readdir(etcd_cli, key, formatter)
     return res
 end
 
-local function waitdir(etcd_cli, key, modified_index, timeout)
-    if not etcd_cli then
-        return nil, nil, "not inited"
+
+local function grpc_waitdir(self, etcd_cli, key, modified_index, timeout)
+    local watching_stream = self.watching_streams[key]
+    if not watching_stream then
+        local attr = {}
+        attr.start_revision = modified_index
+        local opts = {}
+        opts.timeout = timeout
+
+        local st, err = etcd_cli:create_grpc_watch_stream(key, attr, opts)
+        if not st then
+            log.error("create watch stream failed: ", err)
+            return nil, err
+        end
+
+        log.info("create watch stream for key: ", key, ", modified_index: ", 
modified_index)
+
+        self.watching_streams[key] = st
+        watching_stream = st
     end
 
+    return etcd_cli:read_grpc_watch_stream(watching_stream)
+end
+
+
+local function flush_watching_streams(self)
+    local etcd_cli = self.etcd_cli
+    if not etcd_cli.use_grpc then
+        return
+    end
+
+    for _, st in pairs(self.watching_streams) do
+        st:close()
+    end
+
+    core_tab.clear(self.watching_streams)
+end
+
+
+local function http_waitdir(etcd_cli, key, modified_index, timeout)
     local opts = {}
     opts.start_revision = modified_index
     opts.timeout = timeout
@@ -151,7 +190,6 @@ local function waitdir(etcd_cli, key, modified_index, 
timeout)
     end
 
     if not res then
-        -- log.error("failed to get key from etcd: ", err)
         return nil, err
     end
 
@@ -162,6 +200,33 @@ local function waitdir(etcd_cli, key, modified_index, 
timeout)
         end
         return nil, err
     end
+
+    return res, err
+end
+
+
+local function waitdir(self)
+    local etcd_cli = self.etcd_cli
+    local key = self.key
+    local modified_index = self.prev_index + 1
+    local timeout = self.timeout
+
+    if not etcd_cli then
+        return nil, "not inited"
+    end
+
+    local res, err
+    if etcd_cli.use_grpc then
+        res, err = grpc_waitdir(self, etcd_cli, key, modified_index, timeout)
+    else
+        res, err = http_waitdir(etcd_cli, key, modified_index, timeout)
+    end
+
+    if not res then
+        -- log.error("failed to get key from etcd: ", err)
+        return nil, err
+    end
+
     return etcd_apisix.watch_format(res)
 end
 
@@ -301,6 +366,8 @@ local function sync_data(self)
     end
 
     if self.need_reload then
+        flush_watching_streams(self)
+
         local res, err = readdir(self.etcd_cli, self.key)
         if not res then
             return false, err
@@ -327,9 +394,9 @@ local function sync_data(self)
         return true
     end
 
-    local dir_res, err = waitdir(self.etcd_cli, self.key, self.prev_index + 1, 
self.timeout)
+    local dir_res, err = waitdir(self)
     log.info("waitdir key: ", self.key, " prev_index: ", self.prev_index + 1)
-    log.info("res: ", json.delay_encode(dir_res, true))
+    log.info("res: ", json.delay_encode(dir_res, true), ", err: ", err)
 
     if not dir_res then
         if err == "compacted" then
@@ -507,7 +574,7 @@ do
         end
 
         local _, err
-        etcd_cli, _, err = etcd_apisix.switch_proxy(true)
+        etcd_cli, _, err = etcd_apisix.get_etcd_syncer()
         return etcd_cli, err
     end
 end
@@ -547,7 +614,13 @@ local function _automatic_fetch(premature, self)
 
             local ok, err = sync_data(self)
             if err then
-                if string.find(err, err_etcd_unhealthy_all) then
+                if core_str.find(err, err_etcd_grpc_engine_timeout) or
+                   core_str.find(err, err_etcd_grpc_ngx_timeout)
+                then
+                    err = "timeout"
+                end
+
+                if core_str.find(err, err_etcd_unhealthy_all) then
                     local reconnected = false
                     while err and not reconnected and i <= 32 do
                         local backoff_duration, backoff_factor, backoff_step = 
1, 2, 6
@@ -600,6 +673,7 @@ local function _automatic_fetch(premature, self)
     end
 
     if not exiting() and self.running then
+        flush_watching_streams(self)
         ngx_timer_at(0, _automatic_fetch, self)
     end
 end
@@ -668,6 +742,7 @@ function _M.new(key, opts)
         conf_version = 0,
         values = nil,
         need_reload = true,
+        watching_streams = {},
         routes_hash = nil,
         prev_index = 0,
         last_err = nil,
@@ -799,6 +874,39 @@ function _M.init()
         return true
     end
 
+    if local_conf.etcd.use_grpc then
+        return true
+    end
+
+    -- don't go through proxy during start because the proxy is not available
+    local etcd_cli, prefix, err = etcd_apisix.new_without_proxy()
+    if not etcd_cli then
+        return nil, "failed to start a etcd instance: " .. err
+    end
+
+    local res, err = readdir(etcd_cli, prefix, create_formatter(prefix))
+    if not res then
+        return nil, err
+    end
+
+    return true
+end
+
+
+function _M.init_worker()
+    local local_conf, err = config_local.local_conf()
+    if not local_conf then
+        return nil, err
+    end
+
+    if table.try_read_attr(local_conf, "apisix", 
"disable_sync_configuration_during_start") then
+        return true
+    end
+
+    if not local_conf.etcd.use_grpc then
+        return true
+    end
+
     -- don't go through proxy during start because the proxy is not available
     local etcd_cli, prefix, err = etcd_apisix.new_without_proxy()
     if not etcd_cli then
diff --git a/apisix/core/etcd.lua b/apisix/core/etcd.lua
index a90775845..9e6786c86 100644
--- a/apisix/core/etcd.lua
+++ b/apisix/core/etcd.lua
@@ -74,7 +74,7 @@ local function _new(etcd_conf)
 
     if etcd_conf.use_grpc then
         -- TODO: let lua-resty-etcd support more use cases
-        if etcd.user or ngx_get_phase() == "init" or ngx_get_phase() == 
"init_worker" then
+        if etcd.user or ngx_get_phase() == "init" then
             etcd_conf.use_grpc = false
         else
             local ok = pcall(require, "resty.grpc")
@@ -108,26 +108,31 @@ local function new_without_proxy()
     end
 
     local etcd_conf = clone_tab(local_conf.etcd)
+
+    if local_conf.apisix.ssl and local_conf.apisix.ssl.ssl_trusted_certificate 
then
+        etcd_conf.trusted_ca = local_conf.apisix.ssl.ssl_trusted_certificate
+    end
+
     return _new(etcd_conf)
 end
 _M.new_without_proxy = new_without_proxy
 
 
-local function new(use_http)
+local function new()
     local local_conf, err = fetch_local_conf()
     if not local_conf then
         return nil, nil, err
     end
 
     local etcd_conf = clone_tab(local_conf.etcd)
-    if use_http then
-        etcd_conf.use_grpc = false
-    end
     if etcd_conf.use_grpc then
-        -- TODO: add grpc proxy support later
         return new_without_proxy()
     end
 
+    if local_conf.apisix.ssl and local_conf.apisix.ssl.ssl_trusted_certificate 
then
+        etcd_conf.trusted_ca = local_conf.apisix.ssl.ssl_trusted_certificate
+    end
+
     local proxy_by_conf_server = false
 
     if local_conf.deployment then
@@ -173,6 +178,10 @@ local function new(use_http)
                 etcd_conf.tls.key = cert_key
             end
         end
+
+        if local_conf.deployment.certs and 
local_conf.deployment.certs.trusted_ca_cert then
+            etcd_conf.trusted_ca = local_conf.deployment.certs.trusted_ca_cert
+        end
     end
 
     -- if an unhealthy etcd node is selected in a single admin read/write etcd 
operation,
@@ -193,13 +202,8 @@ end
 _M.new = new
 
 
--- TODO: add grpc proxy support later, then we can remove "use_http" scaffold
-local function switch_proxy(use_http)
-    if ngx_get_phase() == "init" or ngx_get_phase() == "init_worker" then
-        return new_without_proxy()
-    end
-
-    local etcd_cli, prefix, err = new(use_http)
+local function switch_proxy()
+    local etcd_cli, prefix, err = new()
     if not etcd_cli or err then
         return etcd_cli, prefix, err
     end
@@ -215,7 +219,7 @@ local function switch_proxy(use_http)
 
     return etcd_cli, prefix, err
 end
-_M.switch_proxy = switch_proxy
+_M.get_etcd_syncer = switch_proxy
 
 -- convert ETCD v3 entry to v2 one
 local function kvs_to_node(kvs)
@@ -329,8 +333,61 @@ function _M.watch_format(v3res)
 end
 
 
+local get_etcd_cli
+do
+    local prefix
+    local etcd_cli_init_phase
+    local etcd_cli
+    local tmp_etcd_cli
+
+    function get_etcd_cli()
+        local err
+        if ngx_get_phase() == "init" or ngx_get_phase() == "init_worker" then
+            if etcd_cli_init_phase == nil then
+                tmp_etcd_cli, prefix, err = new_without_proxy()
+                if not tmp_etcd_cli then
+                    return nil, nil, err
+                end
+
+                if tmp_etcd_cli.use_grpc then
+                    etcd_cli_init_phase = tmp_etcd_cli
+                end
+
+                return tmp_etcd_cli, prefix
+            end
+
+            return etcd_cli_init_phase, prefix
+        end
+
+        if etcd_cli_init_phase ~= nil then
+            -- we can't share the etcd instance created in init* phase
+            -- they have different configuration
+            etcd_cli_init_phase:close()
+            etcd_cli_init_phase = nil
+        end
+
+        if etcd_cli == nil then
+            tmp_etcd_cli, prefix, err = switch_proxy()
+            if not tmp_etcd_cli then
+                return nil, nil, err
+            end
+
+            if tmp_etcd_cli.use_grpc then
+                etcd_cli = tmp_etcd_cli
+            end
+
+            return tmp_etcd_cli, prefix
+        end
+
+        return etcd_cli, prefix
+    end
+end
+-- export it so we can mock the etcd cli in test
+_M.get_etcd_cli = get_etcd_cli
+
+
 function _M.get(key, is_dir)
-    local etcd_cli, prefix, err = switch_proxy()
+    local etcd_cli, prefix, err = get_etcd_cli()
     if not etcd_cli then
         return nil, err
     end
@@ -349,7 +406,7 @@ end
 
 
 local function set(key, value, ttl)
-    local etcd_cli, prefix, err = switch_proxy()
+    local etcd_cli, prefix, err = get_etcd_cli()
     if not etcd_cli then
         return nil, err
     end
@@ -361,7 +418,12 @@ local function set(key, value, ttl)
         if not data then
             return nil, grant_err
         end
+
         res, err = etcd_cli:set(prefix .. key, value, {prev_kv = true, lease = 
data.body.ID})
+        if not res then
+            return nil, err
+        end
+
         res.body.lease_id = data.body.ID
     else
         res, err = etcd_cli:set(prefix .. key, value, {prev_kv = true})
@@ -393,7 +455,7 @@ _M.set = set
 
 
 function _M.atomic_set(key, value, ttl, mod_revision)
-    local etcd_cli, prefix, err = switch_proxy()
+    local etcd_cli, prefix, err = get_etcd_cli()
     if not etcd_cli then
         return nil, err
     end
@@ -452,7 +514,7 @@ end
 
 
 function _M.push(key, value, ttl)
-    local etcd_cli, _, err = switch_proxy()
+    local etcd_cli, _, err = get_etcd_cli()
     if not etcd_cli then
         return nil, err
     end
@@ -484,7 +546,7 @@ end
 
 
 function _M.delete(key)
-    local etcd_cli, prefix, err = switch_proxy()
+    local etcd_cli, prefix, err = get_etcd_cli()
     if not etcd_cli then
         return nil, err
     end
@@ -522,7 +584,7 @@ end
 -- --   etcdserver = "3.5.0"
 -- -- }
 function _M.server_version()
-    local etcd_cli, _, err = switch_proxy()
+    local etcd_cli, _, err = get_etcd_cli()
     if not etcd_cli then
         return nil, err
     end
@@ -532,7 +594,7 @@ end
 
 
 function _M.keepalive(id)
-    local etcd_cli, _, err = switch_proxy()
+    local etcd_cli, _, err = get_etcd_cli()
     if not etcd_cli then
         return nil, err
     end
diff --git a/apisix/init.lua b/apisix/init.lua
index fe23f84a0..d968a1244 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -894,15 +894,19 @@ function _M.stream_init_worker()
     -- for testing only
     core.log.info("random stream test in [1, 10000]: ", math.random(1, 10000))
 
+    if core.config.init_worker then
+        local ok, err = core.config.init_worker()
+        if not ok then
+            core.log.error("failed to init worker process of ", 
core.config.type,
+                           " config center, err: ", err)
+        end
+    end
+
     plugin.init_worker()
     xrpc.init_worker()
     router.stream_init_worker()
     apisix_upstream.init_worker()
 
-    if core.config == require("apisix.core.config_yaml") then
-        core.config.init_worker()
-    end
-
     load_balancer = require("apisix.balancer")
 
     local_conf = core.config.local_conf()
diff --git a/rockspec/apisix-master-0.rockspec 
b/rockspec/apisix-master-0.rockspec
index d42396634..5f558b82a 100644
--- a/rockspec/apisix-master-0.rockspec
+++ b/rockspec/apisix-master-0.rockspec
@@ -34,7 +34,7 @@ dependencies = {
     "lua-resty-ctxdump = 0.1-0",
     "lua-resty-dns-client = 6.0.2",
     "lua-resty-template = 2.0",
-    "lua-resty-etcd = 1.10.0",
+    "lua-resty-etcd = 1.10.1",
     "api7-lua-resty-http = 0.2.0",
     "lua-resty-balancer = 0.04",
     "lua-resty-ngxvar = 0.5.2",
diff --git a/t/APISIX.pm b/t/APISIX.pm
index 9223df0ae..22e143ba2 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -416,6 +416,7 @@ _EOC_
 _EOC_
 
     my $stream_extra_init_by_lua = $block->stream_extra_init_by_lua // "";
+    my $stream_extra_init_worker_by_lua = 
$block->stream_extra_init_worker_by_lua // "";
 
     $stream_config .= <<_EOC_;
     init_by_lua_block {
@@ -424,6 +425,7 @@ _EOC_
     }
     init_worker_by_lua_block {
         apisix.stream_init_worker()
+        $stream_extra_init_worker_by_lua
     }
 
     $extra_stream_config
diff --git a/t/certs/mtls_ca.crt b/t/certs/mtls_ca.crt
index b57e39084..f5895b676 100644
--- a/t/certs/mtls_ca.crt
+++ b/t/certs/mtls_ca.crt
@@ -1,20 +1,20 @@
 -----BEGIN CERTIFICATE-----
-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
+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
+j4wg2QntJBQWmSc=
 -----END CERTIFICATE-----
diff --git a/t/certs/mtls_ca.key b/t/certs/mtls_ca.key
new file mode 100644
index 000000000..94672d79b
--- /dev/null
+++ b/t/certs/mtls_ca.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAukEbWytamYNYFLmlVp9DPIcO9x9+tIR+hLx7RIpfJnp4t1tN
+NB6QrIvyXQ0q97Q+mfPDu8dCE6Dwk9DbAFgNAdiytaLIRZIsiF4quAloH8DPaneR
+iMAO9VlMyeiZKpM9K9zN/6IKNPme0SqCopbwYp6lY02YUaXetw4OXC64DwShRlAT
+H0T5ikoMD0ln1VfOUrh5c6XIHCtPTR4Ei7tg5RRivpVxlkyy7K2+BdBgAMC/5ZOJ
+KZXBytCGQsXySabl8p5qDpoFinGXB4qYL+Pi2OvvCvvC4fRRemVzrlu0S8E2NcKC
+zCitruNHxuAtCFnMofr804rwqye9qUgt62Bl+QIDAQABAoIBABfHXiW6mDuHIESt
+GuW/OYdNuuRj+foz/C8YHSi3/cPc2PKXznh7+n5883lbyAON2HwxOekMXGxDHNPS
+U1Ns6mQ09UPpP2ZabiMO2qdaVBfRtulh0IvD8WTzfLE+Z+eemq2x5/7eAi2XPOZ5
+Zeo6GQCOPpE6A9tQsOlv+vdb45XPCsDdyC2WukeUfB2wsFBFEgMmcW7HLuzMYp9W
+JUuQ6OcOf52ePkGnmEuXIK8Wc3RXitivHlkPLO49Y1LG8t4f61HfG9b2pvgNbHn2
+YdkzemCkBMSjzV4oAoIuG56IWBwuqXETRj2xduVcoxd0t2vLK9qlPADBMCa0hcnW
+uCBqGM0CgYEA3ZjZQuQqer7XrA0qVbXdOdNXal/efUpqfxdxlwMug5sFqt3rSwVF
+S9ZbbtGFeQZp4UAG41o2bWvXuWg59QnKOkKleVQ8zlQdPPjHSOz/7YwXcf/gNYqN
+WHSaT8MqaDYY2m0bo3t9SwSMIBxas05guYuBenisOh3jjcaRPYJELucCgYEA1yuY
+vlIVksPGN+vx6RBaOyY86B8MuXyvi0jVf96UvnL7soUIZDDWojGZvlq3r7ol8Ljj
+LF4Wqdg3MxoOfmUDNWCnSZPjsFo1AUFP/2MJiyCDq75yO0CoVj54QsVYkDTnTosp
+at3CKWTueIeOOtXUAKTObMjrWi9A7cF4rS+siB8CgYBOO2UQcX7xwKhhjHBSvBbz
+EEK/QkNJFlmMrtkiSDRGsBcLILetz5mMUYwMDppBhNsic7k60KGAdd8+DKbRdHhZ
+oyfKMswYx6de3DF29HzR/3BThdNA87486UWFPVCeY+LYUka8q58rOdrCh2AaB2Ss
+fKzkcO/UwLKSXfTusyuhJwKBgEIEslzSuqPJRawqzJKB3e2AEff2buUKiKHnuvn8
+xQ6aIPfpMWXsRi6FoXJySyGzr6hoUetvAu0h1e3r9L57J7zc5vcAVT/qrZCxBWaK
+cIcrdrrfOBVOBVhQ2n1CJ6Y3VTEYKaEMYWJqAXEhxlXu/Zkk9+EQ1IVbMkTAs9IP
+apRpAoGBALd3ZhaP9WvAUVVFs489CKFEP14dZi+SRM6pCsqaQ+zxkW7Cc3Vt72rt
+d+CwIiUMJlac/Efk2Za8MKn/ctMngvRrym8SxGjxJq9jhGo/jSOkHnTy9W//hE/X
+SqlmnJHek5EXl+w0HjgFdukyIo8athDj0jS2ch3OPG4tgavRpKAI
+-----END RSA PRIVATE KEY-----
diff --git a/t/certs/mtls_client.crt b/t/certs/mtls_client.crt
index 847a544ed..378041f9d 100644
--- a/t/certs/mtls_client.crt
+++ b/t/certs/mtls_client.crt
@@ -1,69 +1,20 @@
-Certificate:
-    Data:
-        Version: 1 (0x0)
-        Serial Number: 64207 (0xfacf)
-    Signature Algorithm: sha256WithRSAEncryption
-        Issuer: C=cn, ST=GuangDong, L=ZhuHai, O=api7, OU=ops, CN=ca.apisix.dev
-        Validity
-            Not Before: Jun 20 13:15:00 2020 GMT
-            Not After : Jul  8 13:15:00 2030 GMT
-        Subject: C=cn, ST=GuangDong, O=api7, L=ZhuHai, CN=client.apisix.dev
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                Public-Key: (2048 bit)
-                Modulus:
-                    00:9f:28:8f:2e:88:41:ff:89:f6:62:91:29:d1:6b:
-                    7f:c4:d8:1e:28:85:55:91:c2:3a:3f:23:1c:83:11:
-                    6a:26:81:1e:2d:2e:4d:69:48:98:4f:ff:84:82:2d:
-                    6b:8c:41:31:56:4d:b4:aa:b7:52:05:63:2e:19:6d:
-                    54:87:1f:21:a8:34:f9:89:1a:b1:d1:24:21:84:fa:
-                    c8:29:7f:39:f4:1a:35:78:95:74:0f:24:3d:24:e8:
-                    64:75:09:7d:8c:a3:54:d6:74:5a:92:27:f1:dc:e4:
-                    04:30:71:01:67:3d:fa:0b:03:0b:01:cb:8c:aa:ae:
-                    59:9f:f7:a6:40:53:2b:65:ff:b6:64:8d:fe:0f:ee:
-                    62:64:24:7b:4c:fd:68:12:47:4a:46:86:36:53:00:
-                    64:5f:e4:32:56:a0:ee:75:92:2d:e2:dc:92:3e:d7:
-                    99:8e:86:69:e7:0a:99:e4:b2:71:95:3d:f9:7d:da:
-                    af:76:1f:3f:f8:bf:78:aa:13:e5:13:84:f6:11:a5:
-                    c1:9b:9d:d7:73:32:f3:da:09:78:9a:be:0f:01:fe:
-                    ed:8b:55:b9:f8:97:46:9d:6a:6a:90:19:ea:4e:02:
-                    30:ff:d7:1a:da:39:53:f6:5b:6d:96:d0:fc:ed:0d:
-                    72:78:ac:b7:be:71:aa:4d:4b:8a:06:b9:25:1f:90:
-                    81:0d
-                Exponent: 65537 (0x10001)
-    Signature Algorithm: sha256WithRSAEncryption
-         72:a7:1f:15:21:ba:4f:e7:2f:64:a0:e5:40:7c:e0:ea:09:7b:
-         95:cf:80:d0:6f:54:c2:8d:d1:cf:cd:00:f2:95:20:f9:e2:9e:
-         f5:1c:1b:f9:87:78:a7:b1:3f:31:34:b0:c8:1a:44:da:2c:ef:
-         93:76:d7:df:44:5f:27:6a:51:cb:09:f2:32:f4:70:db:50:da:
-         4e:49:41:75:e0:d2:7b:4d:0b:8b:6e:0a:02:0a:00:e9:ce:f3:
-         bf:72:e6:14:86:df:a7:b9:ef:09:80:a1:52:a7:69:b8:23:7a:
-         3d:3d:cc:6d:64:91:7b:c0:9a:98:2a:a3:17:95:0a:ee:e1:ed:
-         f2:be:02:ea:cb:6e:c1:82:4d:a1:e8:03:9a:46:d6:d7:07:0f:
-         12:50:7e:95:5c:6c:17:f0:40:34:81:5b:74:90:8e:24:6a:5f:
-         8e:77:ff:4d:67:c3:a9:1b:39:e2:ca:62:b6:89:ca:c6:86:f1:
-         95:36:2b:cf:96:a5:6e:89:0e:e6:dc:88:78:f0:7d:09:e9:53:
-         65:35:e9:72:a2:be:1c:5e:b8:a6:2b:57:f2:0d:2f:4b:31:8f:
-         f7:d9:ad:a3:58:12:bb:c9:5b:38:79:96:5b:c8:74:d2:e6:79:
-         23:e6:bd:be:74:25:42:2c:fa:50:ea:9f:53:28:6d:35:f3:0e:
-         9b:82:15:70
 -----BEGIN CERTIFICATE-----
-MIIDOjCCAiICAwD6zzANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJjbjESMBAG
-A1UECAwJR3VhbmdEb25nMQ8wDQYDVQQHDAZaaHVIYWkxDTALBgNVBAoMBGFwaTcx
-DDAKBgNVBAsMA29wczEWMBQGA1UEAwwNY2EuYXBpc2l4LmRldjAeFw0yMDA2MjAx
-MzE1MDBaFw0zMDA3MDgxMzE1MDBaMF0xCzAJBgNVBAYTAmNuMRIwEAYDVQQIDAlH
-dWFuZ0RvbmcxDTALBgNVBAoMBGFwaTcxDzANBgNVBAcMBlpodUhhaTEaMBgGA1UE
-AwwRY2xpZW50LmFwaXNpeC5kZXYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
-AoIBAQCfKI8uiEH/ifZikSnRa3/E2B4ohVWRwjo/IxyDEWomgR4tLk1pSJhP/4SC
-LWuMQTFWTbSqt1IFYy4ZbVSHHyGoNPmJGrHRJCGE+sgpfzn0GjV4lXQPJD0k6GR1
-CX2Mo1TWdFqSJ/Hc5AQwcQFnPfoLAwsBy4yqrlmf96ZAUytl/7Zkjf4P7mJkJHtM
-/WgSR0pGhjZTAGRf5DJWoO51ki3i3JI+15mOhmnnCpnksnGVPfl92q92Hz/4v3iq
-E+UThPYRpcGbnddzMvPaCXiavg8B/u2LVbn4l0adamqQGepOAjD/1xraOVP2W22W
-0PztDXJ4rLe+capNS4oGuSUfkIENAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHKn
-HxUhuk/nL2Sg5UB84OoJe5XPgNBvVMKN0c/NAPKVIPninvUcG/mHeKexPzE0sMga
-RNos75N2199EXydqUcsJ8jL0cNtQ2k5JQXXg0ntNC4tuCgIKAOnO879y5hSG36e5
-7wmAoVKnabgjej09zG1kkXvAmpgqoxeVCu7h7fK+AurLbsGCTaHoA5pG1tcHDxJQ
-fpVcbBfwQDSBW3SQjiRqX453/01nw6kbOeLKYraJysaG8ZU2K8+WpW6JDubciHjw
-fQnpU2U16XKivhxeuKYrV/INL0sxj/fZraNYErvJWzh5llvIdNLmeSPmvb50JUIs
-+lDqn1MobTXzDpuCFXA=
+MIIDUzCCAjugAwIBAgIURw+Rc5FSNUQWdJD+quORtr9KaE8wDQYJKoZIhvcNAQEN
+BQAwWDELMAkGA1UEBhMCY24xEjAQBgNVBAgMCUd1YW5nRG9uZzEPMA0GA1UEBwwG
+Wmh1SGFpMRYwFAYDVQQDDA1jYS5hcGlzaXguZGV2MQwwCgYDVQQLDANvcHMwHhcN
+MjIxMjAxMTAxOTU3WhcNNDIwODE4MTAxOTU3WjBOMQswCQYDVQQGEwJjbjESMBAG
+A1UECAwJR3VhbmdEb25nMQ8wDQYDVQQHDAZaaHVIYWkxGjAYBgNVBAMMEWNsaWVu
+dC5hcGlzaXguZGV2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzypq
+krsJ8MaqpS0kr2SboE9aRKOJzd6mY3AZLq3tFpio5cK5oIHkQLfeaaLcd4ycFcZw
+FTpxc+Eth6I0X9on+j4tEibc5IpDnRSAQlzHZzlrOG6WxcOza4VmfcrKqj27oodr
+oqXv05r/5yIoRrEN9ZXfA8n2OnjhkP+C3Q68L6dBtPpv+e6HaAuw8MvcsEo+MQwu
+cTZyWqWT2UzKVzToW29dHRW+yZGuYNWRh15X09VSvx+E0s+uYKzN0Cyef2C6VtBJ
+KmJ3NtypAiPqw7Ebfov2Ym/zzU9pyWPi3P1mYPMKQqUT/FpZSXm4iSy0a5qTYhkF
+rFdV1YuYYZL5YGl9aQIDAQABox8wHTAbBgNVHREEFDASghBhZG1pbi5hcGlzaXgu
+ZGV2MA0GCSqGSIb3DQEBDQUAA4IBAQBepRpwWdckZ6QdL5EuufYwU7p5SIqkVL/+
+N4/l5YSjPoAZf/M6XkZu/PsLI9/kPZN/PX4oxjZSDH14dU9ON3JjxtSrebizcT8V
+aQ13TeW9KSv/i5oT6qBmj+V+RF2YCUhyzXdYokOfsSVtSlA1qMdm+cv0vkjYcImV
+l3L9nVHRPq15dY9sbmWEtFBWvOzqNSuQYax+iYG+XEuL9SPaYlwKRC6eS/dbXa1T
+PPWDQad2X/WmhxPzEHvjSl2bsZF1u0GEdKyhXWMOLCLiYIJo15G7bMz8cTUvkDN3
+6WaWBd6bd2g13Ho/OOceARpkR/ND8PU78Y8cq+zHoOSqH+1aly5H
 -----END CERTIFICATE-----
diff --git a/t/certs/mtls_client.key b/t/certs/mtls_client.key
index d939c62b1..d7e381de3 100644
--- a/t/certs/mtls_client.key
+++ b/t/certs/mtls_client.key
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----
diff --git a/t/certs/mtls_server.crt b/t/certs/mtls_server.crt
index 14f48ef7e..965008230 100644
--- a/t/certs/mtls_server.crt
+++ b/t/certs/mtls_server.crt
@@ -1,69 +1,21 @@
-Certificate:
-    Data:
-        Version: 1 (0x0)
-        Serial Number: 64206 (0xface)
-    Signature Algorithm: sha256WithRSAEncryption
-        Issuer: C=cn, ST=GuangDong, L=ZhuHai, O=api7, OU=ops, CN=ca.apisix.dev
-        Validity
-            Not Before: Jun 20 13:14:34 2020 GMT
-            Not After : Jun 18 13:14:34 2030 GMT
-        Subject: C=cn, ST=GuangDong, O=api7, L=ZhuHai, CN=admin.apisix.dev
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                Public-Key: (2048 bit)
-                Modulus:
-                    00:9b:45:2a:e1:c9:6e:a7:af:af:bd:46:5c:5e:5f:
-                    72:66:02:78:69:16:fd:f9:69:8e:47:68:0f:8d:35:
-                    92:c4:14:40:5c:cf:57:3d:41:ea:13:7b:f4:de:c8:
-                    ab:e8:62:56:1e:60:61:f6:38:65:5f:30:b5:91:25:
-                    79:07:12:45:ce:24:31:86:1f:2c:a6:cb:1d:8b:4b:
-                    9e:5f:1f:c7:b6:f3:e8:98:ee:b3:70:c7:9e:5d:10:
-                    ce:29:e4:22:68:69:9e:df:ae:f6:bb:11:e8:b8:f1:
-                    07:bf:2d:d5:57:f2:e4:07:8a:da:d2:7b:8a:53:d1:
-                    b4:f4:42:19:9a:14:98:01:3e:23:27:3a:0f:ad:d0:
-                    1d:c5:31:9a:ee:ae:df:7f:fb:2e:34:0b:51:ca:b4:
-                    8c:59:ae:86:5f:95:69:2b:4a:c6:2d:a5:ae:04:46:
-                    7a:93:09:15:72:0a:78:ef:98:7d:00:b5:b4:b2:f2:
-                    e2:a9:2e:04:fb:de:84:ad:da:8e:a3:31:53:3a:d5:
-                    91:cd:77:f5:b8:ea:eb:14:aa:d9:62:d1:12:79:87:
-                    08:27:6d:c1:b9:e3:7d:f1:07:52:3c:a3:34:6a:c1:
-                    96:cf:a2:84:cc:14:50:49:40:0b:38:3c:3b:1e:df:
-                    57:6f:f2:05:35:92:9b:4f:b1:21:0b:f7:62:3a:2d:
-                    83:c7
-                Exponent: 65537 (0x10001)
-    Signature Algorithm: sha256WithRSAEncryption
-         7a:1c:a3:d8:d4:97:5d:91:d2:c8:31:c4:40:ef:f1:38:ac:5c:
-         b9:74:66:81:94:4f:71:02:38:49:5a:0d:7b:10:17:73:a5:96:
-         3e:de:0e:a4:75:8c:1b:c7:51:f9:f6:eb:9d:f4:bd:4c:1c:92:
-         41:d0:16:c6:73:c1:f9:7c:b6:71:7d:16:53:13:fa:70:90:c0:
-         95:e3:a3:51:30:96:02:f2:32:32:fe:a9:d1:ef:c5:7e:04:58:
-         ca:20:ef:d0:43:8c:52:8d:52:3a:71:ed:0f:87:4e:8b:c6:28:
-         51:56:13:fd:71:81:10:cc:2f:2c:aa:8d:6a:93:d7:52:34:08:
-         23:7b:2b:a7:a4:3e:6b:8f:c3:af:59:b9:1c:b8:d8:6c:a3:88:
-         c7:bd:b5:e1:eb:6b:6a:f2:7d:a3:89:c6:b0:21:f8:1b:9a:dc:
-         bf:ef:d6:21:91:7f:65:99:4d:f4:49:24:ab:46:09:a0:c9:a1:
-         64:14:f4:56:73:ce:1b:22:dd:b7:1f:58:0f:29:ae:6a:6e:41:
-         6e:b4:5c:90:97:4e:59:4e:cf:e3:a1:89:d1:5a:65:a3:68:2f:
-         b9:97:82:6f:4c:21:cb:f6:9b:7d:fd:d8:07:70:14:cd:10:fb:
-         bf:03:70:fa:51:7c:56:4c:1b:a5:87:d3:1b:18:5c:22:87:6f:
-         04:08:59:53
 -----BEGIN CERTIFICATE-----
-MIIDOTCCAiECAwD6zjANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJjbjESMBAG
-A1UECAwJR3VhbmdEb25nMQ8wDQYDVQQHDAZaaHVIYWkxDTALBgNVBAoMBGFwaTcx
-DDAKBgNVBAsMA29wczEWMBQGA1UEAwwNY2EuYXBpc2l4LmRldjAeFw0yMDA2MjAx
-MzE0MzRaFw0zMDA2MTgxMzE0MzRaMFwxCzAJBgNVBAYTAmNuMRIwEAYDVQQIDAlH
-dWFuZ0RvbmcxDTALBgNVBAoMBGFwaTcxDzANBgNVBAcMBlpodUhhaTEZMBcGA1UE
-AwwQYWRtaW4uYXBpc2l4LmRldjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
-ggEBAJtFKuHJbqevr71GXF5fcmYCeGkW/flpjkdoD401ksQUQFzPVz1B6hN79N7I
-q+hiVh5gYfY4ZV8wtZEleQcSRc4kMYYfLKbLHYtLnl8fx7bz6Jjus3DHnl0Qzink
-Imhpnt+u9rsR6LjxB78t1Vfy5AeK2tJ7ilPRtPRCGZoUmAE+Iyc6D63QHcUxmu6u
-33/7LjQLUcq0jFmuhl+VaStKxi2lrgRGepMJFXIKeO+YfQC1tLLy4qkuBPvehK3a
-jqMxUzrVkc139bjq6xSq2WLREnmHCCdtwbnjffEHUjyjNGrBls+ihMwUUElACzg8
-Ox7fV2/yBTWSm0+xIQv3Yjotg8cCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAehyj
-2NSXXZHSyDHEQO/xOKxcuXRmgZRPcQI4SVoNexAXc6WWPt4OpHWMG8dR+fbrnfS9
-TBySQdAWxnPB+Xy2cX0WUxP6cJDAleOjUTCWAvIyMv6p0e/FfgRYyiDv0EOMUo1S
-OnHtD4dOi8YoUVYT/XGBEMwvLKqNapPXUjQII3srp6Q+a4/Dr1m5HLjYbKOIx721
-4etravJ9o4nGsCH4G5rcv+/WIZF/ZZlN9Ekkq0YJoMmhZBT0VnPOGyLdtx9YDymu
-am5BbrRckJdOWU7P46GJ0Vplo2gvuZeCb0why/abff3YB3AUzRD7vwNw+lF8Vkwb
-pYfTGxhcIodvBAhZUw==
+MIIDYDCCAkigAwIBAgIURw+Rc5FSNUQWdJD+quORtr9KaE4wDQYJKoZIhvcNAQEN
+BQAwWDELMAkGA1UEBhMCY24xEjAQBgNVBAgMCUd1YW5nRG9uZzEPMA0GA1UEBwwG
+Wmh1SGFpMRYwFAYDVQQDDA1jYS5hcGlzaXguZGV2MQwwCgYDVQQLDANvcHMwHhcN
+MjIxMjAxMTAxNzI0WhcNNDIwODE4MTAxNzI0WjBbMQswCQYDVQQGEwJjbjESMBAG
+A1UECAwJR3VhbmdEb25nMQ8wDQYDVQQHDAZaaHVIYWkxGTAXBgNVBAMMEGFkbWlu
+LmFwaXNpeC5kZXYxDDAKBgNVBAsMA29wczCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBAONlOihn9AtXWay72aPDFbwm2zJOe+5ngV1D3B4f2q+KlkAnjAPx
+1GWO1buRDEALL8g5NfbZF3gU9v+wjsEsFK/Sn8ejtziVwJUFVmvRr+PCm/2DEARN
+Re1cp0cwRVQJAKLVFpy19ALlSSTQRoCAjLVXC8tEsIxrvN3DVVet9g6AxnPPd4oR
+LosDGQ+p+qbriQdx20gg5MHmjZX+/ByZq4BIQkshmQW2LnwxAS3xOpqPmFHmdn56
+RXw8JlyvYS3KRiGU3z59uph4wnIic4r/11Puj1LoGd+YtFJash6ZRU/rM6JSdPS7
+b53m8HdRcjGdBG+EnsqN67qZUWbGBntmu2cCAwEAAaMfMB0wGwYDVR0RBBQwEoIQ
+YWRtaW4uYXBpc2l4LmRldjANBgkqhkiG9w0BAQ0FAAOCAQEAMAxCZmKwWEDHuAzW
+PHJUyrdK1eos2UExmeq9TY8c7IluIMClTxS8ui3+7+oc6ZqffyrZL4jx7oe8Ua6V
+bat75H+OF05f9ziGJjJY5PM3wxOP1QvR7HePSkwBkNPhGOQf3bi7rQEhBuqhpRfr
+GfPmzKejaUm9m8IiHnFKxjTfQ7pm3hR8/+P9LKDO21i5Ua3ec+rKv0Y1jsCuv3t/
+APMN7MTDsFqxudqbOG3dufOSe1E7qs16/ygTRvYpIe+kz4rldGWmo0joOrrti43T
+Oi1BAGaC3znJe3aaihr08c37NZ/A6WHiX+h5wBEdboOJc4Htytkicd8jBvU2Svjq
+dZS3wQ==
 -----END CERTIFICATE-----
diff --git a/t/certs/mtls_server.key b/t/certs/mtls_server.key
index 5f2c75b98..a6ed4486e 100644
--- a/t/certs/mtls_server.key
+++ b/t/certs/mtls_server.key
@@ -1,27 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEAm0Uq4clup6+vvUZcXl9yZgJ4aRb9+WmOR2gPjTWSxBRAXM9X
-PUHqE3v03sir6GJWHmBh9jhlXzC1kSV5BxJFziQxhh8spssdi0ueXx/HtvPomO6z
-cMeeXRDOKeQiaGme3672uxHouPEHvy3VV/LkB4ra0nuKU9G09EIZmhSYAT4jJzoP
-rdAdxTGa7q7ff/suNAtRyrSMWa6GX5VpK0rGLaWuBEZ6kwkVcgp475h9ALW0svLi
-qS4E+96ErdqOozFTOtWRzXf1uOrrFKrZYtESeYcIJ23BueN98QdSPKM0asGWz6KE
-zBRQSUALODw7Ht9Xb/IFNZKbT7EhC/diOi2DxwIDAQABAoIBAC3NJW0dAissw+ZN
-Twn3lcNJj0NQqPJdlL6zj4LT/ssgPiwibVWAkA/XTNA62ZrfBxBG1h7PW/fMYoLC
-TwUq+rRoMMOjhoRc/gYM9FaTBVKOeFpEb2IhQDGrt2TcCtpJ7beF4PolukRztRlL
-59bdqy4eY5YbIx6+iWZT6UFuObiDqi7i4SLWEgK+/P4Uk8/SmhVqIWcj1m3SPK6I
-YbzsgXiT64fNd7/O06ISKia1UzvUCtH7tbxWxCvsqw+PqQT+YuEmNY1pOQGYp0dU
-4ndzvrP0Ajuu3xH7aYP/Kilkz69PPMLygwNey4HRIAuUqw/HBfTR0/ccRSuhrYxb
-9QaOP0ECgYEAyuqLo/tjWrFiJnDbhK3z2qcydktFS58da2QitSRYlQ6AQXjZ3+v7
-buL1QV59aXzIGTZz3gjO+omdpfIagBI47YnWIUtj+NylNROWv+aZXQwgC7ayQWTg
-eBu8L2YXBvAR9TgHhqj3Fl4YcuipVE3XFVjjvLjrbE1nssMmaJqi95kCgYEAw+O7
-Zdj/NedbI2GtnZv31YlLmrMdtmeAmU2x8eC5v30Kx3GCU9DdZzImsaYxxjfSL+6c
-eP/DF8JHWIGo9GQPcMSijHsaNMIwgv6+5rx+Lp/zsjwRApJsVQeoff2ZdWjnFsi3
-rRHE8QZfWMqcnOsr4io7xfVd3t4tV22BBrnt8l8CgYEAncU3xcxUN9PryI+/Xq4S
-CFQvvCJSQrX4neNByS31Yg/gUQex/5Tv7guxPZ5GTJqkylW4SU73/3y4gqp3SFTi
-xm6Be2mu1XRZT6dnctXNMLeYwwLOHmJc1YZbD0+FX/ORQuTJlT4Sv+VxhQa5gb70
-GLkAeWAeTBrzId7yIir5wyECgYAw2iJqC+vZrZD1Ce8aV0M/ZbYNJo5KZxWTJeUy
-xTCNqMl/Y7d036nXipJLy36uSE2K1p7/LgmhazoPwIY6LJoNLXy8PBcVATjH8m/5
-axis2AcWdBRp58pMilRi11PmC/tVm0jzSHMtCMHOivjzyVJwXMf7Xm3CnvX/z7dV
-zhihUQKBgHWtWfNk/svgLp6w8T6aMgyAb9ud5pX/CbNZhGNRqhPhJkss1tFr6/Mv
-bJiZoEP3C0sDdA1JRuMkXm5EE60xyhzCNmv5H0cQ3C2Y9Q9ly89ggwIXNiNfKWpP
-VrdvXQ3NkP/RaDy83B9dN2Jb6lUpcNQnB5Q5yAlsYaYgsGBedcvc
+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
 -----END RSA PRIVATE KEY-----
diff --git a/t/cli/test_etcd_grpc_mtls.sh b/t/cli/test_etcd_grpc_mtls.sh
new file mode 100755
index 000000000..8f37a7112
--- /dev/null
+++ b/t/cli/test_etcd_grpc_mtls.sh
@@ -0,0 +1,181 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+#
+
+. ./t/cli/common.sh
+
+exit_if_not_customed_nginx
+
+# The 'admin.apisix.dev' is injected by ci/common.sh@set_coredns
+
+# etcd mTLS verify
+echo '
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    use_grpc: true
+    host:
+      - "https://admin.apisix.dev:22379";
+    prefix: "/apisix"
+    tls:
+      cert: t/certs/mtls_client.crt
+      key: t/certs/mtls_client.key
+      verify: false
+  ' > conf/config.yaml
+
+out=$(make init 2>&1 || echo "ouch")
+if echo "$out" | grep "bad certificate"; then
+    echo "failed: apisix should not echo \"bad certificate\""
+    exit 1
+fi
+
+echo "passed: certificate verify success expectedly"
+
+echo '
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    use_grpc: true
+    host:
+      - "https://admin.apisix.dev:22379";
+    prefix: "/apisix"
+    tls:
+      verify: false
+  ' > conf/config.yaml
+
+out=$(make init 2>&1 || echo "ouch")
+if ! echo "$out" | grep "bad certificate"; then
+    echo "failed: apisix should echo \"bad certificate\""
+    exit 1
+fi
+
+echo "passed: certificate verify fail expectedly"
+
+# etcd mTLS verify with CA
+echo '
+apisix:
+  ssl:
+    ssl_trusted_certificate: t/certs/mtls_ca.crt
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    use_grpc: true
+    host:
+      - "https://admin.apisix.dev:22379";
+    prefix: "/apisix"
+    tls:
+      cert: t/certs/mtls_client.crt
+      key: t/certs/mtls_client.key
+  ' > conf/config.yaml
+
+out=$(make init 2>&1 || echo "ouch")
+if echo "$out" | grep "certificate verify failed"; then
+    echo "failed: apisix should not echo \"certificate verify failed\""
+    exit 1
+fi
+
+if echo "$out" | grep "ouch"; then
+    echo "failed: apisix should not fail"
+    exit 1
+fi
+
+echo "passed: certificate verify with CA success expectedly"
+
+# etcd mTLS in stream subsystem
+echo '
+apisix:
+  stream_proxy:
+    tcp:
+      - addr: 9100
+  ssl:
+    ssl_trusted_certificate: t/certs/mtls_ca.crt
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    use_grpc: true
+    host:
+      - "https://admin.apisix.dev:22379";
+    prefix: "/apisix"
+    tls:
+      cert: t/certs/mtls_client.crt
+      key: t/certs/mtls_client.key
+  ' > conf/config.yaml
+
+out=$(make init 2>&1 || echo "ouch")
+if echo "$out" | grep "certificate verify failed"; then
+    echo "failed: apisix should not echo \"certificate verify failed\""
+    exit 1
+fi
+
+if echo "$out" | grep "ouch"; then
+    echo "failed: apisix should not fail"
+    exit 1
+fi
+
+rm logs/error.log || true
+make run
+sleep 1
+make stop
+
+if grep "\[error\]" logs/error.log; then
+    echo "failed: veirfy etcd certificate during sync should not fail"
+fi
+
+echo "passed: certificate verify in stream subsystem successfully"
+
+# use host in etcd.host as sni by default
+git checkout conf/config.yaml
+echo '
+apisix:
+  ssl:
+    ssl_trusted_certificate: t/certs/mtls_ca.crt
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    use_grpc: true
+    host:
+      - "https://127.0.0.2:22379";
+    prefix: "/apisix"
+    tls:
+      cert: t/certs/mtls_client.crt
+      key: t/certs/mtls_client.key
+  ' > conf/config.yaml
+
+rm logs/error.log || true
+make init
+make run
+sleep 1
+make stop
+
+if ! grep -E "cannot validate certificate for 127.0.0.2 because it doesn't 
contain any IP SANs" logs/error.log; then
+    echo "failed: should got certificate host mismatch when use host in 
etcd.host as sni"
+    exit 1
+fi
+
+
+echo "passed: use host in etcd.host as sni by default"
diff --git a/t/cli/test_etcd_grpc_tls.sh b/t/cli/test_etcd_grpc_tls.sh
new file mode 100755
index 000000000..9e429e41b
--- /dev/null
+++ b/t/cli/test_etcd_grpc_tls.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+#
+
+# 'make init' operates scripts and related configuration files in the current 
directory
+# The 'apisix' command is a command in the /usr/local/apisix,
+# and the configuration file for the operation is in the /usr/local/apisix/conf
+
+. ./t/cli/common.sh
+
+exit_if_not_customed_nginx
+
+# Check etcd tls verify failure
+git checkout conf/config.yaml
+
+echo '
+apisix:
+  ssl:
+    ssl_trusted_certificate: t/certs/mtls_ca.crt
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    use_grpc: true
+    host:
+      - "https://127.0.0.1:12379";
+    prefix: "/apisix"
+  ' > conf/config.yaml
+
+out=$(make init 2>&1 || true)
+if ! echo "$out" | grep "certificate verify failed"; then
+    echo "failed: apisix should echo \"certificate verify failed\""
+    exit 1
+fi
+
+echo "passed: Show certificate verify failed info successfully"
+
+
+# Check etcd tls without verification
+git checkout conf/config.yaml
+
+echo '
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    use_grpc: true
+    host:
+      - "https://127.0.0.1:12379";
+    prefix: "/apisix"
+    tls:
+      verify: false
+  ' > conf/config.yaml
+
+out=$(make init 2>&1 || true)
+if echo "$out" | grep "certificate verify failed"; then
+    echo "failed: apisix should not echo \"certificate verify failed\""
+    exit 1
+fi
+
+echo "passed: Certificate verification successfully"
diff --git a/t/core/config_etcd.t b/t/core/config_etcd.t
index 825e0f225..cdd99d8e3 100644
--- a/t/core/config_etcd.t
+++ b/t/core/config_etcd.t
@@ -337,6 +337,7 @@ qr/healthy check use round robin
             config_etcd.test_automatic_fetch(false, {
                 running = true,
                 resync_delay = 1,
+                watching_streams = {},
             })
             ngx.say("passed")
         }
diff --git a/t/core/etcd-auth-fail.t b/t/core/etcd-auth-fail.t
index c85f660dc..b7f9374db 100644
--- a/t/core/etcd-auth-fail.t
+++ b/t/core/etcd-auth-fail.t
@@ -63,7 +63,7 @@ __DATA__
 --- request
 GET /t
 --- error_log eval
-qr /insufficient credentials code: 401/
+qr /(insufficient credentials code: 401|etcdserver: user name is empty)/
 
 
 
diff --git a/t/core/etcd-grpc-mtls.t b/t/core/etcd-grpc-mtls.t
new file mode 100644
index 000000000..4e9ca306e
--- /dev/null
+++ b/t/core/etcd-grpc-mtls.t
@@ -0,0 +1,292 @@
+#
+# 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;
+
+my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
+my $version = eval { `$nginx_binary -V 2>&1` };
+
+if ($version !~ m/\/apisix-nginx-module/) {
+    plan(skip_all => "apisix-nginx-module not installed");
+} else {
+    plan('no_plan');
+}
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: run etcd in init_worker phase
+--- yaml_config
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    use_grpc: true
+    host:
+      - "https://127.0.0.1:22379";
+    prefix: "/apisix"
+    tls:
+      cert: t/certs/mtls_client.crt
+      key: t/certs/mtls_client.key
+      verify: false
+--- extra_init_worker_by_lua
+    if ngx.worker.id() ~= 0 then
+        return
+    end
+
+    local etcd = require("apisix.core.etcd")
+    assert(etcd.set("/a", "ab"))
+
+    local out = ""
+    local res, err = etcd.get("/a")
+    if not res then
+        ngx.log(ngx.ERR, err)
+        return
+    end
+    out = out .. res.body.node.value
+
+    local res, err = etcd.delete("/a")
+    if not res then
+        ngx.log(ngx.ERR, err)
+        return
+    end
+    out = out .. res.status
+
+    local res, err = etcd.get("/a")
+    if not res then
+        ngx.log(ngx.ERR, err)
+        return
+    end
+    out = out .. res.status
+    ngx.log(ngx.WARN, out)
+--- config
+    location /t {
+        return 200;
+    }
+--- request
+GET /t
+--- grep_error_log eval
+qr/init_worker_by_lua:\d+: [^,]+/
+--- grep_error_log_out
+init_worker_by_lua:31: ab200404
+
+
+
+=== TEST 2: run etcd in init phase (stream)
+--- yaml_config
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    use_grpc: true
+    host:
+      - "https://127.0.0.1:22379";
+    prefix: "/apisix"
+    tls:
+      cert: t/certs/mtls_client.crt
+      key: t/certs/mtls_client.key
+      verify: false
+--- stream_extra_init_worker_by_lua
+    if ngx.worker.id() ~= 0 then
+        return
+    end
+
+    local etcd = require("apisix.core.etcd")
+    assert(etcd.set("/a", "ab"))
+
+    local out = ""
+    local res, err = etcd.get("/a")
+    if not res then
+        ngx.log(ngx.ERR, err)
+        return
+    end
+    out = out .. res.body.node.value
+
+    local res, err = etcd.delete("/a")
+    if not res then
+        ngx.log(ngx.ERR, err)
+        return
+    end
+    out = out .. res.status
+
+    local res, err = etcd.get("/a")
+    if not res then
+        ngx.log(ngx.ERR, err)
+        return
+    end
+    out = out .. res.status
+    ngx.log(ngx.WARN, out)
+--- stream_server_config
+    content_by_lua_block {
+        ngx.say("ok")
+    }
+--- stream_enable
+--- grep_error_log eval
+qr/init_worker_by_lua:\d+: \S+/
+--- grep_error_log_out
+init_worker_by_lua:31: ab200404,
+
+
+
+=== TEST 3: sync
+--- extra_yaml_config
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    use_grpc: true
+    host:
+      - "https://127.0.0.1:22379";
+    prefix: "/apisix"
+    tls:
+      cert: t/certs/mtls_client.crt
+      key: t/certs/mtls_client.key
+      verify: false
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            local consumers, _ = core.config.new("/consumers", {
+                automatic = true,
+                item_schema = core.schema.consumer,
+            })
+
+            ngx.sleep(0.6)
+            local idx = consumers.prev_index
+
+            local code, body = t('/apisix/admin/consumers',
+                ngx.HTTP_PUT,
+                [[{
+                    "username": "jobs",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "jobs",
+                            "password": "678901"
+                        }
+                    }
+                }]])
+
+            ngx.sleep(2)
+            local new_idx = consumers.prev_index
+            if new_idx > idx then
+                ngx.say("prev_index updated")
+            else
+                ngx.say("prev_index not update")
+            end
+        }
+    }
+--- request
+GET /t
+--- response_body
+prev_index updated
+--- error_log
+waitdir key
+
+
+
+=== TEST 4: sync (stream)
+--- extra_yaml_config
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    use_grpc: true
+    host:
+      - "https://127.0.0.1:22379";
+    prefix: "/apisix"
+    tls:
+      cert: t/certs/mtls_client.crt
+      key: t/certs/mtls_client.key
+      verify: false
+--- stream_server_config
+    content_by_lua_block {
+        local core = require("apisix.core")
+
+        local sr, _ = core.config.new("/stream_routes", {
+            automatic = true,
+            item_schema = core.schema.stream_routes,
+        })
+
+        ngx.sleep(0.6)
+        local idx = sr.prev_index
+
+        assert(core.etcd.set("/stream_routes/1",
+            {
+                plugins = {
+                }
+            }))
+
+        ngx.sleep(2)
+        local new_idx = sr.prev_index
+        if new_idx > idx then
+            ngx.say("prev_index updated")
+        else
+            ngx.say("prev_index not update")
+        end
+        }
+--- stream_enable
+--- stream_response
+prev_index updated
+--- error_log
+waitdir key
+
+
+
+=== TEST 5: ssl_trusted_certificate
+--- yaml_config
+apisix:
+  ssl:
+    ssl_trusted_certificate: t/certs/mtls_ca.crt
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    use_grpc: true
+    host:
+      - "https://admin.apisix.dev:22379";
+    prefix: "/apisix"
+    tls:
+      cert: t/certs/mtls_client.crt
+      key: t/certs/mtls_client.key
+--- extra_init_worker_by_lua
+    if ngx.worker.id() ~= 0 then
+        return
+    end
+
+    local etcd = require("apisix.core.etcd")
+    assert(etcd.set("/a", "ab"))
+    local res, err = etcd.get("/a")
+    if not res then
+        ngx.log(ngx.ERR, err)
+        return
+    end
+    ngx.log(ngx.WARN, res.body.node.value)
+--- config
+    location /t {
+        return 200;
+    }
+--- request
+GET /t
+--- error_log
+init_worker_by_lua:14: ab
diff --git a/t/core/etcd-sync.t b/t/core/etcd-sync.t
index 47e372a79..e74ae19ec 100644
--- a/t/core/etcd-sync.t
+++ b/t/core/etcd-sync.t
@@ -23,14 +23,17 @@ run_tests;
 __DATA__
 
 === TEST 1: minus timeout to watch repeatedly
---- extra_yaml_config
+--- yaml_config
 deployment:
-  role: traditional
-  role_traditional:
-    config_provider: etcd
-  etcd:
-    host:
-      - "http://127.0.0.1:2379";
+    role: traditional
+    role_traditional:
+        config_provider: etcd
+    etcd:
+        # this test requires the HTTP long pull as the gRPC stream is shared 
and can't change
+        # default timeout in the fly
+        use_grpc: false
+    admin:
+        admin_key: null
 --- config
     location /t {
         content_by_lua_block {
@@ -72,8 +75,8 @@ deployment:
 GET /t
 --- response_body
 prev_index updated
---- error_log
-cancel watch connection success
+--- error_log eval
+qr/(create watch stream for key|cancel watch connection success)/
 
 
 
diff --git a/t/core/etcd.t b/t/core/etcd.t
index 091fba02e..eaa425ec6 100644
--- a/t/core/etcd.t
+++ b/t/core/etcd.t
@@ -405,10 +405,20 @@ init_by_lua:26: 404
 
 
 === TEST 8: error handling in server_version
+--- yaml_config
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    host:
+      - "http://127.0.0.1:2379";
+    prefix: "/apisix"
 --- config
     location /t {
         content_by_lua_block {
             local etcd_lib = require("resty.etcd")
+            -- the mock won't take effect when using gRPC because the 
connection will be cached
             etcd_lib.new = function()
                 return nil, "ouch"
             end
diff --git a/t/deployment/conf_server.t b/t/deployment/conf_server.t
index 367d0af8b..2e15ed8bd 100644
--- a/t/deployment/conf_server.t
+++ b/t/deployment/conf_server.t
@@ -16,6 +16,8 @@
 #
 use t::APISIX 'no_plan';
 
+worker_connections(256);
+
 add_block_preprocessor(sub {
     my ($block) = @_;
 
@@ -159,7 +161,7 @@ localhost is resolved to: 127.0.0.2
 === TEST 4: update balancer if the DNS result changed
 --- extra_init_by_lua
     local etcd = require("apisix.core.etcd")
-    etcd.switch_proxy = function ()
+    etcd.get_etcd_syncer = function ()
         return etcd.new()
     end
 
diff --git a/t/deployment/grpc/conf_server.t b/t/deployment/grpc/conf_server.t
new file mode 100644
index 000000000..a6ac47a59
--- /dev/null
+++ b/t/deployment/grpc/conf_server.t
@@ -0,0 +1,165 @@
+#
+# 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;
+
+my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
+my $version = eval { `$nginx_binary -V 2>&1` };
+
+if ($version !~ m/\/apisix-nginx-module/) {
+    plan(skip_all => "apisix-nginx-module not installed");
+} else {
+    plan('no_plan');
+}
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sync in https
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            local consumers, _ = core.config.new("/consumers", {
+                automatic = true,
+                item_schema = core.schema.consumer,
+            })
+
+            ngx.sleep(0.6)
+            local idx = consumers.prev_index
+
+            local code, body = t('/apisix/admin/consumers',
+                ngx.HTTP_PUT,
+                [[{
+                    "username": "jobs",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "jobs",
+                            "password": "678901"
+                        }
+                    }
+                }]])
+
+            ngx.sleep(2)
+            local new_idx = consumers.prev_index
+            if new_idx > idx then
+                ngx.say("prev_index updated")
+            else
+                ngx.say("prev_index not update")
+            end
+        }
+    }
+--- response_body
+prev_index updated
+--- yaml_config
+deployment:
+    role: traditional
+    role_traditional:
+        config_provider: etcd
+    admin:
+        admin_key: ~
+    etcd:
+        use_grpc: true
+        prefix: "/apisix"
+        host:
+            - https://127.0.0.1:12379
+        tls:
+            verify: false
+
+
+
+=== TEST 2: mix ip & domain
+--- config
+    location /t {
+        content_by_lua_block {
+            local etcd = require("apisix.core.etcd")
+            assert(etcd.set("/apisix/test", "foo"))
+            local res = assert(etcd.get("/apisix/test"))
+            ngx.say(res.body.node.value)
+        }
+    }
+--- yaml_config
+deployment:
+    role: traditional
+    role_traditional:
+        config_provider: etcd
+    etcd:
+        use_grpc: true
+        prefix: "/apisix"
+        host:
+            - http://127.0.0.2:2379
+            - http://localhost:2379
+            - http://[::1]:2379
+--- response_body
+foo
+
+
+
+=== TEST 3: check default SNI
+--- http_config
+server {
+    listen 12345 http2 ssl;
+    ssl_certificate             cert/apisix.crt;
+    ssl_certificate_key         cert/apisix.key;
+
+    ssl_certificate_by_lua_block {
+        local ngx_ssl = require "ngx.ssl"
+        ngx.log(ngx.WARN, "Receive SNI: ", ngx_ssl.server_name())
+    }
+
+    location / {
+        grpc_pass grpc://127.0.0.1:2379;
+    }
+}
+--- config
+    location /t {
+        content_by_lua_block {
+            local etcd = require("apisix.core.etcd")
+            assert(etcd.set("/apisix/test", "foo"))
+            local res = assert(etcd.get("/apisix/test"))
+            ngx.say(res.body.node.value)
+        }
+    }
+--- response_body
+foo
+--- yaml_config
+deployment:
+    role: traditional
+    role_traditional:
+        config_provider: etcd
+    etcd:
+        use_grpc: true
+        prefix: "/apisix"
+        host:
+            - https://127.0.0.1:12379
+            - https://localhost:12345
+        timeout: 1
+        tls:
+            verify: false
+--- error_log
+Receive SNI: localhost
diff --git a/t/deployment/grpc/mtls.t b/t/deployment/grpc/mtls.t
new file mode 100644
index 000000000..abe0ea385
--- /dev/null
+++ b/t/deployment/grpc/mtls.t
@@ -0,0 +1,118 @@
+#
+# 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;
+
+my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
+my $version = eval { `$nginx_binary -V 2>&1` };
+
+if ($version !~ m/\/apisix-nginx-module/) {
+    plan(skip_all => "apisix-nginx-module not installed");
+} else {
+    plan('no_plan');
+}
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: mTLS for control plane
+--- exec
+curl --cert t/certs/mtls_client.crt --key t/certs/mtls_client.key -k 
https://localhost:12345/version
+--- response_body eval
+qr/"etcdserver":/
+--- yaml_config
+deployment:
+    role: control_plane
+    role_control_plane:
+        config_provider: etcd
+        conf_server:
+            listen: 0.0.0.0:12345
+            cert: t/certs/mtls_server.crt
+            cert_key: t/certs/mtls_server.key
+            client_ca_cert: t/certs/mtls_ca.crt
+    etcd:
+        use_grpc: true
+        prefix: "/apisix"
+        host:
+            - http://127.0.0.1:2379
+    certs:
+        cert: t/certs/mtls_client.crt
+        cert_key: t/certs/mtls_client.key
+        trusted_ca_cert: t/certs/mtls_ca.crt
+
+
+
+=== TEST 2: no client certificate
+--- exec
+curl -k https://localhost:12345/version
+--- response_body eval
+qr/No required SSL certificate was sent/
+--- yaml_config
+deployment:
+    role: control_plane
+    role_control_plane:
+        config_provider: etcd
+        conf_server:
+            listen: 0.0.0.0:12345
+            cert: t/certs/mtls_server.crt
+            cert_key: t/certs/mtls_server.key
+            client_ca_cert: t/certs/mtls_ca.crt
+    etcd:
+        use_grpc: true
+        prefix: "/apisix"
+        host:
+            - http://127.0.0.1:2379
+    certs:
+        cert: t/certs/mtls_client.crt
+        cert_key: t/certs/mtls_client.key
+        trusted_ca_cert: t/certs/mtls_ca.crt
+
+
+
+=== TEST 3: wrong client certificate
+--- exec
+curl --cert t/certs/apisix.crt --key t/certs/apisix.key -k 
https://localhost:12345/version
+--- response_body eval
+qr/The SSL certificate error/
+--- yaml_config
+deployment:
+    role: control_plane
+    role_control_plane:
+        config_provider: etcd
+        conf_server:
+            listen: 0.0.0.0:12345
+            cert: t/certs/mtls_server.crt
+            cert_key: t/certs/mtls_server.key
+            client_ca_cert: t/certs/mtls_ca.crt
+    etcd:
+        use_grpc: true
+        prefix: "/apisix"
+        host:
+            - http://127.0.0.1:2379
+    certs:
+        cert: t/certs/mtls_client.crt
+        cert_key: t/certs/mtls_client.key
+        trusted_ca_cert: t/certs/mtls_ca.crt
diff --git a/t/discovery/nacos.t b/t/discovery/nacos.t
index 39a6717b3..9af1ee14a 100644
--- a/t/discovery/nacos.t
+++ b/t/discovery/nacos.t
@@ -18,6 +18,7 @@ use t::APISIX 'no_plan';
 
 repeat_each(1);
 log_level('info');
+worker_connections(256);
 no_root_location();
 no_shuffle();
 workers(4);
diff --git a/t/node/ewma.t b/t/node/ewma.t
index c32dbc01a..776a6512a 100644
--- a/t/node/ewma.t
+++ b/t/node/ewma.t
@@ -20,6 +20,7 @@ repeat_each(1);
 #no_long_string();
 no_root_location();
 log_level('info');
+worker_connections(256);
 run_tests;
 
 __DATA__
diff --git a/t/node/grpc-proxy-mtls.t b/t/node/grpc-proxy-mtls.t
index 3c974fed1..b238431e2 100644
--- a/t/node/grpc-proxy-mtls.t
+++ b/t/node/grpc-proxy-mtls.t
@@ -56,8 +56,8 @@ routes:
     upstream:
       scheme: grpcs
       tls:
-        client_cert: "-----BEGIN 
CERTIFICATE-----\nMIIDOjCCAiICAwD6zzANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJjbjESMBAG\nA1UECAwJR3VhbmdEb25nMQ8wDQYDVQQHDAZaaHVIYWkxDTALBgNVBAoMBGFwaTcx\nDDAKBgNVBAsMA29wczEWMBQGA1UEAwwNY2EuYXBpc2l4LmRldjAeFw0yMDA2MjAx\nMzE1MDBaFw0zMDA3MDgxMzE1MDBaMF0xCzAJBgNVBAYTAmNuMRIwEAYDVQQIDAlH\ndWFuZ0RvbmcxDTALBgNVBAoMBGFwaTcxDzANBgNVBAcMBlpodUhhaTEaMBgGA1UE\nAwwRY2xpZW50LmFwaXNpeC5kZXYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQCfKI8uiEH/ifZikSnRa3/E2B4ohVWRwjo/IxyDE
 [...]
-        client_key: "-----BEGIN RSA PRIVATE 
KEY-----\nMIIEpAIBAAKCAQEAnyiPLohB/4n2YpEp0Wt/xNgeKIVVkcI6PyMcgxFqJoEeLS5N\naUiYT/+Egi1rjEExVk20qrdSBWMuGW1Uhx8hqDT5iRqx0SQhhPrIKX859Bo1eJV0\nDyQ9JOhkdQl9jKNU1nRakifx3OQEMHEBZz36CwMLAcuMqq5Zn/emQFMrZf+2ZI3+\nD+5iZCR7TP1oEkdKRoY2UwBkX+QyVqDudZIt4tySPteZjoZp5wqZ5LJxlT35fdqv\ndh8/+L94qhPlE4T2EaXBm53XczLz2gl4mr4PAf7ti1W5+JdGnWpqkBnqTgIw/9ca\n2jlT9lttltD87Q1yeKy3vnGqTUuKBrklH5CBDQIDAQABAoIBAHDe5bPdQ9jCcW3z\nfpGax/DER5b6//UvpfkSoGy/E+Wcmdb2yEVLC2FoVw
 [...]
+        client_cert: "-----BEGIN 
CERTIFICATE-----\nMIIDUzCCAjugAwIBAgIURw+Rc5FSNUQWdJD+quORtr9KaE8wDQYJKoZIhvcNAQEN\nBQAwWDELMAkGA1UEBhMCY24xEjAQBgNVBAgMCUd1YW5nRG9uZzEPMA0GA1UEBwwG\nWmh1SGFpMRYwFAYDVQQDDA1jYS5hcGlzaXguZGV2MQwwCgYDVQQLDANvcHMwHhcN\nMjIxMjAxMTAxOTU3WhcNNDIwODE4MTAxOTU3WjBOMQswCQYDVQQGEwJjbjESMBAG\nA1UECAwJR3VhbmdEb25nMQ8wDQYDVQQHDAZaaHVIYWkxGjAYBgNVBAMMEWNsaWVu\ndC5hcGlzaXguZGV2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzypq\nkrsJ8MaqpS0kr2SboE9aRKOJzd6mY3AZLq3tFpio5cK5o
 [...]
+        client_key: "-----BEGIN RSA PRIVATE 
KEY-----\nMIIEpAIBAAKCAQEAzypqkrsJ8MaqpS0kr2SboE9aRKOJzd6mY3AZLq3tFpio5cK5\noIHkQLfeaaLcd4ycFcZwFTpxc+Eth6I0X9on+j4tEibc5IpDnRSAQlzHZzlrOG6W\nxcOza4VmfcrKqj27oodroqXv05r/5yIoRrEN9ZXfA8n2OnjhkP+C3Q68L6dBtPpv\n+e6HaAuw8MvcsEo+MQwucTZyWqWT2UzKVzToW29dHRW+yZGuYNWRh15X09VSvx+E\n0s+uYKzN0Cyef2C6VtBJKmJ3NtypAiPqw7Ebfov2Ym/zzU9pyWPi3P1mYPMKQqUT\n/FpZSXm4iSy0a5qTYhkFrFdV1YuYYZL5YGl9aQIDAQABAoIBAD7tUG//lnZnsj/4\nJXONaORaFj5ROrOpFPuRemS+egzqFCuuaXpC2lV6RH
 [...]
       nodes:
         "127.0.0.1:50053": 1
       type: roundrobin
@@ -85,8 +85,8 @@ routes:
     upstream:
       scheme: grpcs
       tls:
-        client_cert: "-----BEGIN 
CERTIFICATE-----\nMIIDOjCCAiICAwD6zzANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJjbjESMBAG\nA1UECAwJR3VhbmdEb25nMQ8wDQYDVQQHDAZaaHVIYWkxDTALBgNVBAoMBGFwaTcx\nDDAKBgNVBAsMA29wczEWMBQGA1UEAwwNY2EuYXBpc2l4LmRldjAeFw0yMDA2MjAx\nMzE1MDBaFw0zMDA3MDgxMzE1MDBaMF0xCzAJBgNVBAYTAmNuMRIwEAYDVQQIDAlH\ndWFuZ0RvbmcxDTALBgNVBAoMBGFwaTcxDzANBgNVBAcMBlpodUhhaTEaMBgGA1UE\nAwwRY2xpZW50LmFwaXNpeC5kZXYwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQCfKI8uiEH/ifZikSnRa3/E2B4ohVWRwjo/IxyDE
 [...]
-        client_key: "-----BEGIN RSA PRIVATE 
KEY-----\nMIIEpAIBAAKCAQEAnyiPLohB/4n2YpEp0Wt/xNgeKIVVkcI6PyMcgxFqJoEeLS5N\naUiYT/+Egi1rjEExVk20qrdSBWMuGW1Uhx8hqDT5iRqx0SQhhPrIKX859Bo1eJV0\nDyQ9JOhkdQl9jKNU1nRakifx3OQEMHEBZz36CwMLAcuMqq5Zn/emQFMrZf+2ZI3+\nD+5iZCR7TP1oEkdKRoY2UwBkX+QyVqDudZIt4tySPteZjoZp5wqZ5LJxlT35fdqv\ndh8/+L94qhPlE4T2EaXBm53XczLz2gl4mr4PAf7ti1W5+JdGnWpqkBnqTgIw/9ca\n2jlT9lttltD87Q1yeKy3vnGqTUuKBrklH5CBDQIDAQABAoIBAHDe5bPdQ9jCcW3z\nfpGax/DER5b6//UvpfkSoGy/E+Wcmdb2yEVLC2FoVw
 [...]
+        client_cert: "-----BEGIN 
CERTIFICATE-----\nMIIDUzCCAjugAwIBAgIURw+Rc5FSNUQWdJD+quORtr9KaE8wDQYJKoZIhvcNAQEN\nBQAwWDELMAkGA1UEBhMCY24xEjAQBgNVBAgMCUd1YW5nRG9uZzEPMA0GA1UEBwwG\nWmh1SGFpMRYwFAYDVQQDDA1jYS5hcGlzaXguZGV2MQwwCgYDVQQLDANvcHMwHhcN\nMjIxMjAxMTAxOTU3WhcNNDIwODE4MTAxOTU3WjBOMQswCQYDVQQGEwJjbjESMBAG\nA1UECAwJR3VhbmdEb25nMQ8wDQYDVQQHDAZaaHVIYWkxGjAYBgNVBAMMEWNsaWVu\ndC5hcGlzaXguZGV2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzypq\nkrsJ8MaqpS0kr2SboE9aRKOJzd6mY3AZLq3tFpio5cK5o
 [...]
+        client_key: "-----BEGIN RSA PRIVATE 
KEY-----\nMIIEpAIBAAKCAQEAzypqkrsJ8MaqpS0kr2SboE9aRKOJzd6mY3AZLq3tFpio5cK5\noIHkQLfeaaLcd4ycFcZwFTpxc+Eth6I0X9on+j4tEibc5IpDnRSAQlzHZzlrOG6W\nxcOza4VmfcrKqj27oodroqXv05r/5yIoRrEN9ZXfA8n2OnjhkP+C3Q68L6dBtPpv\n+e6HaAuw8MvcsEo+MQwucTZyWqWT2UzKVzToW29dHRW+yZGuYNWRh15X09VSvx+E\n0s+uYKzN0Cyef2C6VtBJKmJ3NtypAiPqw7Ebfov2Ym/zzU9pyWPi3P1mYPMKQqUT\n/FpZSXm4iSy0a5qTYhkFrFdV1YuYYZL5YGl9aQIDAQABAoIBAD7tUG//lnZnsj/4\nJXONaORaFj5ROrOpFPuRemS+egzqFCuuaXpC2lV6RH
 [...]
       nodes:
         "127.0.0.1:50053": 1
       type: roundrobin
diff --git a/t/node/healthcheck-stop-checker.t 
b/t/node/healthcheck-stop-checker.t
index b8a47dccb..1a5eaf286 100644
--- a/t/node/healthcheck-stop-checker.t
+++ b/t/node/healthcheck-stop-checker.t
@@ -32,6 +32,30 @@ no_root_location();
 no_shuffle();
 worker_connections(256);
 
+# the healthcheck stop test requires exiting worker to keep watching etcd for 
a while,
+# which is not the case when using gRPC.
+my $yaml_config = <<_EOC_;
+deployment:
+  role: traditional
+  role_traditional:
+    config_provider: etcd
+  etcd:
+    prefix: "/apisix"
+    host:
+      - "http://127.0.0.1:2379";
+    use_grpc: false
+  admin:
+    admin_key: null
+_EOC_
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!$block->yaml_config) {
+        $block->set_value("yaml_config", $yaml_config);
+    }
+});
+
 run_tests();
 
 __DATA__
@@ -141,6 +165,15 @@ create new checker: table: 0x
         content_by_lua_block {
             local t = require("lib.test_admin").test
 
+            -- release the clean handler of previous test
+            local code, _, body = t('/apisix/admin/routes/1', "DELETE")
+
+            if code > 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
             local code, _, body = t('/apisix/admin/upstreams/stopchecker',
                 "PUT",
                 
[[{"type":"roundrobin","nodes":{"127.0.0.1:1980":1,"127.0.0.1:1981":1},"checks":{"active":{"http_path":"/status","healthy":{"interval":1,"successes":1},"unhealthy":{"interval":1,"http_failures":2}}}}]]
@@ -152,7 +185,6 @@ create new checker: table: 0x
                 return
             end
 
-            -- release the clean handler of previous test
             local code, _, body = t('/apisix/admin/routes/1',
                 "PUT",
                 [[{"uri":"/server_port","upstream_id":"stopchecker"}]]
diff --git a/t/plugin/grpc-transcode2.t b/t/plugin/grpc-transcode2.t
index 7dc42789a..0f7cc8d0b 100644
--- a/t/plugin/grpc-transcode2.t
+++ b/t/plugin/grpc-transcode2.t
@@ -565,8 +565,9 @@ qr/request log: \{.*body":\"\{\\"result\\":3}/
     local pb = require("pb")
     local old_f = pb.option
     pb.option = function(o)
-        if o ~= "int64_as_string" then
-            -- filter out options set by other components
+        if o ~= "int64_as_string" and o ~= "int64_as_number" then
+            -- filter out options set by other components.
+            -- we can still test some options like enum_as_name
             ngx.log(ngx.WARN, "set protobuf option: ", o)
         end
         return old_f(o)
@@ -675,7 +676,6 @@ set protobuf option: enum_as_name
 set protobuf option: auto_default_values
 set protobuf option: disable_hooks
 set protobuf option: enum_as_name
-set protobuf option: int64_as_number
 set protobuf option: enum_as_name
 
 
@@ -685,8 +685,9 @@ set protobuf option: enum_as_name
     local pb = require("pb")
     local old_f = pb.option
     pb.option = function(o)
-        if o ~= "int64_as_string" then
+        if o ~= "int64_as_string" and o ~= "int64_as_number" then
             -- filter out options set by other components
+            -- we can still test some options like enum_as_name
             ngx.log(ngx.WARN, "set protobuf option: ", o)
         end
         return old_f(o)
@@ -793,4 +794,3 @@ qr/set protobuf option: \w+/
 set protobuf option: auto_default_values
 set protobuf option: disable_hooks
 set protobuf option: enum_as_name
-set protobuf option: int64_as_number
diff --git a/t/stream-node/random.t b/t/stream-node/random.t
index 3df62d340..dfe939c3a 100644
--- a/t/stream-node/random.t
+++ b/t/stream-node/random.t
@@ -18,6 +18,7 @@ use t::APISIX 'no_plan';
 
 workers(4);
 log_level('info');
+worker_connections(256);
 repeat_each(1);
 no_long_string();
 no_root_location();

Reply via email to