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 8e1a1fd10 feat(deployment): select backend & retry (#7309)
8e1a1fd10 is described below

commit 8e1a1fd10ab55891f63f15f0ded3df88ad6f44bd
Author: 罗泽轩 <[email protected]>
AuthorDate: Fri Jun 24 09:41:37 2022 +0800

    feat(deployment): select backend & retry (#7309)
    
    Signed-off-by: spacewander <[email protected]>
---
 apisix/cli/snippet.lua     |  21 +++--
 apisix/conf_server.lua     | 195 +++++++++++++++++++++++++++++++++++++++++++++
 apisix/init.lua            |   2 +
 t/deployment/conf_server.t | 166 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 379 insertions(+), 5 deletions(-)

diff --git a/apisix/cli/snippet.lua b/apisix/cli/snippet.lua
index 191e3b0ed..2ce4a6627 100644
--- a/apisix/cli/snippet.lua
+++ b/apisix/cli/snippet.lua
@@ -46,18 +46,25 @@ function _M.generate_conf_server(env, conf)
         if not to then
             return nil, "bad etcd endpoint format"
         end
-        servers[i] = s:sub(to + 1)
     end
 
     local conf_render = template.compile([[
     upstream apisix_conf_backend {
-        {% for _, addr in ipairs(servers) do %}
-        server {* addr *};
-        {% end %}
+        server 0.0.0.0:80;
+        balancer_by_lua_block {
+            local conf_server = require("apisix.conf_server")
+            conf_server.balancer()
+        }
     }
     server {
         listen unix:{* home *}/conf/config_listen.sock;
         access_log off;
+
+        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;
@@ -70,10 +77,14 @@ function _M.generate_conf_server(env, conf)
             proxy_http_version 1.1;
             proxy_set_header Connection "";
         }
+
+        log_by_lua_block {
+            local conf_server = require("apisix.conf_server")
+            conf_server.log()
+        }
     }
     ]])
     return conf_render({
-        servers = servers,
         enable_https = enable_https,
         home = env.apisix_home or ".",
     })
diff --git a/apisix/conf_server.lua b/apisix/conf_server.lua
new file mode 100644
index 000000000..9c59b3795
--- /dev/null
+++ b/apisix/conf_server.lua
@@ -0,0 +1,195 @@
+--
+-- 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 core = require("apisix.core")
+local fetch_local_conf  = require("apisix.core.config_local").local_conf
+local picker = require("apisix.balancer.least_conn")
+local balancer = require("ngx.balancer")
+local error = error
+local ipairs = ipairs
+local ngx = ngx
+
+
+local _M = {}
+local servers = {}
+local resolved_results = {}
+local server_picker
+local has_domain = false
+
+
+local function create_resolved_result(server)
+    local host, port = core.utils.parse_addr(server)
+    return {
+        host = host,
+        port = port,
+    }
+end
+
+
+function _M.init()
+    local conf = fetch_local_conf()
+    if not (conf.deployment and conf.deployment.etcd) then
+        return
+    end
+
+    local etcd = conf.deployment.etcd
+    for i, s in ipairs(etcd.host) do
+        local _, to = core.string.find(s, "://")
+        if not to then
+            error("bad etcd endpoint format")
+        end
+
+        local addr = s:sub(to + 1)
+        local host, _, err = core.utils.parse_addr(addr)
+        if err then
+            error("failed to parse host: ".. err)
+        end
+
+        resolved_results[i] = create_resolved_result(addr)
+        servers[i] = addr
+
+        if not core.utils.parse_ipv4(host) and not core.utils.parse_ipv6(host) 
then
+            has_domain = true
+            resolved_results[i].domain = host
+        end
+    end
+
+    if #servers > 1 then
+        local nodes = {}
+        for _, s in ipairs(servers) do
+            nodes[s] = 1
+        end
+        server_picker = picker.new(nodes, {})
+    end
+end
+
+
+local function response_err(err)
+    ngx.log(ngx.ERR, "failure in conf server: ", err)
+    ngx.say(core.json.encode({error = err}))
+    ngx.exit(0)
+end
+
+
+local function resolve_servers(ctx)
+    if not has_domain then
+        return
+    end
+
+    local changed = false
+    for _, res in ipairs(resolved_results) do
+        local domain = res.domain
+        if not domain then
+            goto CONTINUE
+        end
+
+        local ip, err = core.resolver.parse_domain(domain)
+        if ip and res.host ~= ip then
+            res.host = ip
+            changed = true
+            core.log.info(domain, " is resolved to: ", ip)
+        end
+
+        if err then
+            core.log.error("dns resolver resolves domain: ", domain, " error: 
", err)
+        end
+
+        ::CONTINUE::
+    end
+
+    if not changed then
+        return
+    end
+
+    if #servers > 1 then
+        local nodes = {}
+        for _, res in ipairs(resolved_results) do
+            local s = res.host .. ":" .. res.port
+            nodes[s] = 1
+        end
+        server_picker = picker.new(nodes, {})
+    end
+end
+
+
+local function pick_node(ctx)
+    local res
+    if server_picker then
+        local server, err = server_picker.get(ctx)
+        if not server then
+            err = err or "no valid upstream node"
+            return nil, "failed to find valid upstream server, " .. err
+        end
+
+        ctx.server_picker = server_picker
+        ctx.balancer_server = server
+        res = create_resolved_result(server)
+    else
+        res = resolved_results[1]
+    end
+
+    ctx.balancer_ip = res.host
+    ctx.balancer_port = res.port
+    return true
+end
+
+
+function _M.access()
+    local ctx = ngx.ctx
+    -- Nginx's DNS resolver doesn't support search option,
+    -- so we have to use our own resolver
+    resolve_servers(ctx)
+    local ok, err = pick_node(ctx)
+    if not ok then
+        return response_err(err)
+    end
+end
+
+
+function _M.balancer()
+    local ctx = ngx.ctx
+    if not ctx.balancer_run then
+        ctx.balancer_run = true
+        local retries = #servers - 1
+        local ok, err = balancer.set_more_tries(retries)
+        if not ok then
+            core.log.error("could not set upstream retries: ", err)
+        elseif err then
+            core.log.warn("could not set upstream retries: ", err)
+        end
+    else
+        local ok, err = pick_node(ctx)
+        if not ok then
+            return response_err(err)
+        end
+    end
+
+    local ok, err = balancer.set_current_peer(ctx.balancer_ip, 
ctx.balancer_port)
+    if not ok then
+        return response_err(err)
+    end
+end
+
+
+function _M.log()
+    local ctx = ngx.ctx
+    if ctx.server_picker and ctx.server_picker.after_balance then
+        ctx.server_picker.after_balance(ctx, false)
+    end
+end
+
+
+return _M
diff --git a/apisix/init.lua b/apisix/init.lua
index 9cbe6d204..25d9d5aa2 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -27,6 +27,7 @@ require("jit.opt").start("minstitch=2", "maxtrace=4000",
 
 require("apisix.patch").patch()
 local core            = require("apisix.core")
+local conf_server     = require("apisix.conf_server")
 local plugin          = require("apisix.plugin")
 local plugin_config   = require("apisix.plugin_config")
 local script          = require("apisix.script")
@@ -95,6 +96,7 @@ function _M.http_init(args)
     end
 
     xrpc.init()
+    conf_server.init()
 end
 
 
diff --git a/t/deployment/conf_server.t b/t/deployment/conf_server.t
index 84b045055..2e89ac2ae 100644
--- a/t/deployment/conf_server.t
+++ b/t/deployment/conf_server.t
@@ -98,3 +98,169 @@ deployment:
             - 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)
+        }
+    }
+--- extra_yaml_config
+deployment:
+    role: traditional
+    role_traditional:
+        config_provider: etcd
+    etcd:
+        prefix: "/apisix"
+        host:
+            - http://127.0.0.2:2379
+            - http://localhost:2379
+            - http://[::1]:2379
+--- error_log
+dns resolve localhost, result:
+--- no_error_log
+[error]
+--- response_body
+foo
+
+
+
+=== TEST 3: resolve domain, result changed
+--- extra_init_by_lua
+    local resolver = require("apisix.core.resolver")
+    local old_f = resolver.parse_domain
+    local counter = 0
+    resolver.parse_domain = function (domain)
+        if domain == "x.com" then
+            counter = counter + 1
+            if counter % 2 == 0 then
+                return "127.0.0.2"
+            else
+                return "127.0.0.3"
+            end
+        else
+            return old_f(domain)
+        end
+    end
+--- 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)
+        }
+    }
+--- extra_yaml_config
+deployment:
+    role: traditional
+    role_traditional:
+        config_provider: etcd
+    etcd:
+        prefix: "/apisix"
+        host:
+            - http://x.com:2379
+--- response_body
+foo
+--- error_log
+x.com is resolved to: 127.0.0.3
+x.com is resolved to: 127.0.0.2
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: update balancer if the DNS result changed
+--- extra_init_by_lua
+    local resolver = require("apisix.core.resolver")
+    local old_f = resolver.parse_domain
+    package.loaded.counter = 0
+    resolver.parse_domain = function (domain)
+        if domain == "x.com" then
+            local counter = package.loaded.counter
+            package.loaded.counter = counter + 1
+            if counter % 2 == 0 then
+                return "127.0.0.2"
+            else
+                return "127.0.0.3"
+            end
+        else
+            return old_f(domain)
+        end
+    end
+
+    local picker = require("apisix.balancer.least_conn")
+    package.loaded.n_picker = 0
+    local old_f = picker.new
+    picker.new = function (nodes, upstream)
+        package.loaded.n_picker = package.loaded.n_picker + 1
+        return old_f(nodes, upstream)
+    end
+--- 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)
+            local counter = package.loaded.counter
+            local n_picker = package.loaded.n_picker
+            if counter == n_picker then
+                ngx.say("OK")
+            else
+                ngx.say(counter, " ", n_picker)
+            end
+        }
+    }
+--- extra_yaml_config
+deployment:
+    role: traditional
+    role_traditional:
+        config_provider: etcd
+    etcd:
+        prefix: "/apisix"
+        host:
+            - http://127.0.0.1:2379
+            - http://x.com:2379
+--- response_body
+foo
+OK
+--- error_log
+x.com is resolved to: 127.0.0.3
+x.com is resolved to: 127.0.0.2
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: retry
+--- 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)
+        }
+    }
+--- extra_yaml_config
+deployment:
+    role: traditional
+    role_traditional:
+        config_provider: etcd
+    etcd:
+        prefix: "/apisix"
+        host:
+            - http://127.0.0.1:1979
+            - http://[::1]:1979
+            - http://localhost:2379
+--- error_log
+connect() failed
+--- response_body
+foo

Reply via email to