You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by GitBox <gi...@apache.org> on 2022/03/13 07:45:09 UTC

[GitHub] [apisix] zhixiongdu027 opened a new pull request #6599: feat: add tars discovery

zhixiongdu027 opened a new pull request #6599:
URL: https://github.com/apache/apisix/pull/6599


   ### What this PR does / why we need it:
   <!--- Why is this change required? What problem does it solve? -->
   <!--- If it fixes an open issue, please link to the issue here. -->
   
   ### Pre-submission checklist:
   
   <!--
   Please follow the PR manners:
   1. Use Draft if the PR is not ready to be reviewed
   2. Test is required for the feat/fix PR, unless you have a good reason
   3. Doc is required for the feat PR
   4. Use a new commit to resolve review instead of `push -f`
   5. If you need to resolve merge conflicts after the PR is reviewed, please merge master but do not rebase
   6. Use "request review" to notify the reviewer once you have resolved the review
   7. Only reviewer can click "Resolve conversation" to mark the reviewer's review resolved
   -->
   
   * [ ] Did you explain what problem does this PR solve? Or what new features have been added?
   * [ ] Have you added corresponding test cases?
   * [ ] Have you modified the corresponding document?
   * [ ] Is this PR backward compatible? **If it is not backward compatible, please discuss on the [mailing list](https://github.com/apache/apisix/tree/master#community) first**
   
   
   #6570 
   
   Hello everyone:
   
     This is a PR for the tars discovery module, which is part of "tars-proxy".
     
     Work on "tars-proxy" continues,  but maybe we can finish tars discovery first
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r830632628



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,340 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local endpoint_dict = ngx.shared.tars
+if not endpoint_dict then
+    error("failed to get nginx shared dict: tars, please check your APISIX version")
+end
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we extract host and port value via endpoints_pattern
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    --[[
+    res format is as follows:
+    {
+        {
+            servant = "A.AServer.FirstObj",
+            endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+        },
+        {
+            servant = "A.AServer.SecondObj",
+            endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+        },
+    }
+
+    if current endpoint_dict is as follows:
+      key1:nodes1, key2:nodes2, key3:nodes3
+
+    then fetch_full get follow results:
+      key1:nodes1, key4:nodes4, key5:nodes5
+
+    at this time, we need
+      1: setup key4:nodes4, key5:nodes5
+      2: delete key2:nodes2, key3:nodes3
+
+    to achieve goals, we should:
+      1: before setup results, execute endpoint_dict:flush_all()
+      2:  after setup results, execute endpoint_dict:flush_expired()
+    --]]
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()

Review comment:
       > Please add some comment about the expiration operation, thanks.
   
   Sorry, I add comment in function fetch_full and forget function get_endpoint()
   
   I will update




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tzssangglass commented on pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#issuecomment-1073424457


   hi @zhixiongdu027 , file conflict, you need to merge master branch to your PR.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829769074



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do

Review comment:
       I do not think so. 
   Because the result of query_result is the return by db_cli, there is no type other than table.
   But you reminded me that query_result maybe is {}




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r827679023



##########
File path: conf/config-default.yaml
##########
@@ -266,6 +266,7 @@ nginx_config:                     # config for render the template to generate n
       introspection: 10m
       access-tokens: 1m
       ext-plugin: 1m
+      tars: 1m

Review comment:
       > Need to handle this.
   
   Sorry,I did not get your point
   Your means we can keep using this db hanlder event get last_db_error ?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tzssangglass commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829722817



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict

Review comment:
       ```suggestion
   local endpoint_dict = ngx.shared.tars
   ```
   just declare use `ngx.shared.DICT` here directly?
   

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do

Review comment:
       Is there a risk of a dead-end cycle here?

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_db_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_db_error = fetch_full(db_cli)
+    else
+        last_db_error = fetch_incremental(db_cli)
+    end
+
+    if not last_db_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    endpoint_dict = ngx.shared.tars
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: tars, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = conf.default_weight
+
+    core.log.info("conf ", core.json.encode(conf, true))

Review comment:
       ```suggestion
       core.log.info("conf ", core.json.delay_encode(conf, true))
   ```

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_db_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_db_error = fetch_full(db_cli)
+    else
+        last_db_error = fetch_incremental(db_cli)
+    end
+
+    if not last_db_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    endpoint_dict = ngx.shared.tars
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: tars, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = conf.default_weight

Review comment:
       I have some concerns about this place. By declaring `default_weight` as a module-level variable, its lifecycle extends beyond that of `local_conf`. Can we just get the `default_weight` directly from the `local_conf` module when we use it?

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do

Review comment:
       should check if `query_result` is a table(array)?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r828720055



##########
File path: conf/config-default.yaml
##########
@@ -266,6 +266,7 @@ nginx_config:                     # config for render the template to generate n
       introspection: 10m
       access-tokens: 1m
       ext-plugin: 1m
+      tars: 1m

Review comment:
       You mean we need to decide whether to close or not based on the value of error ?
   
   Currently the case of error=='again' has been handled
   But So Sorry for I don't know what other error values do not need to be closed.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r827676423



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       Fitst: 
         All servant:nodes pairs are from SQL queries and have no expiration time in the cache sense
   
   Next:
       When we read the results of fectch_full the logic is as follows:
   ```lua
   1. endpoint_dict:flush_all() 
   2. for (result : results )
       { 
            endpoint_dict[servant]=nodes 
       } 
   3. endpoint_dict:flush_expired()
   ```
   get_endpoint may happen at 2, we have to use endpoint_dict:get_stale
   
   @spacewander 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander merged pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
spacewander merged pull request #6599:
URL: https://github.com/apache/apisix/pull/6599


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r830640039



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,283 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_fetch_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into discovery DICT failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into discovery DICT failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function create_endpoint_lrucache(servant)
+    local endpoint_content = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content from discovery DICT, servant: ", servant)
+        return nil
+    end
+
+    local endpoint = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode endpoint content failed, content: ", endpoint_content)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version then
+        return nil
+    end
+
+    return endpoint_lrucache(servant, endpoint_version, create_endpoint_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_fetch_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_fetch_error = fetch_full(db_cli)
+    else
+        last_fetch_error = fetch_incremental(db_cli)
+    end
+
+    if not last_fetch_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end

Review comment:
       > Need `close` if err?
   
   Sorry, I think this place doesn't actively call db:close() should be acceptable,
   [this link](https://forum.openresty.us/d/2151-f9a6aa2bb662b5ab77ee279e6dbd3ed6/4) provides agentzh's view




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#issuecomment-1073009862


   @spacewander @tzssangglass 
   Hi , I have submitted a new submission,
   Please help to review, thank you very much


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r830606894



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,283 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_fetch_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into discovery DICT failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into discovery DICT failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function create_endpoint_lrucache(servant)
+    local endpoint_content = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content from discovery DICT, servant: ", servant)
+        return nil
+    end
+
+    local endpoint = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode endpoint content failed, content: ", endpoint_content)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version then
+        return nil
+    end
+
+    return endpoint_lrucache(servant, endpoint_version, create_endpoint_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_fetch_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_fetch_error = fetch_full(db_cli)
+    else
+        last_fetch_error = fetch_incremental(db_cli)
+    end
+
+    if not last_fetch_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end

Review comment:
       @zhixiongdu027 
   Please add close to the db_cli, thanks.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r830609058



##########
File path: conf/config-default.yaml
##########
@@ -266,6 +266,7 @@ nginx_config:                     # config for render the template to generate n
       introspection: 10m
       access-tokens: 1m
       ext-plugin: 1m
+      tars: 1m

Review comment:
       According to the example, we need to close if we don't keepalive?
   https://github.com/openresty/lua-resty-mysql#synopsis
   ```
   				-- put it into the connection pool of size 100,
                   -- with 10 seconds max idle timeout
                   local ok, err = db:set_keepalive(10000, 100)
                   if not ok then
                       ngx.say("failed to set keepalive: ", err)
                       return
                   end
   
                   -- or just close the connection right away:
                   -- local ok, err = db:close()
                   -- if not ok then
                   --     ngx.say("failed to close: ", err)
                   --     return
                   -- end
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r830632152



##########
File path: conf/config-default.yaml
##########
@@ -266,6 +266,7 @@ nginx_config:                     # config for render the template to generate n
       introspection: 10m
       access-tokens: 1m
       ext-plugin: 1m
+      tars: 1m

Review comment:
       Sorry,  I  think this place doesn't actively call db:close() should be acceptable, 
   [this link](https://forum.openresty.us/d/2151-f9a6aa2bb662b5ab77ee279e6dbd3ed6/4)  provides agentzh's view
   @spacewander 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r827676423



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       Fitst: 
         All servant:nodes pairs are from SQL queries and have no expiration time in the cache sense
   
   Next:
       When we read the results of fectch_full the logic is as follows:
   ```lua
   1. endpoint_dict:flush_all() 
   2. for (result : results )
       { 
            endpoint_dict[servant]=nodes 
       } 
   3. endpoint_dict:flush_expired()
   ```
   get_endpoint may happen at 2, we have to use endpoint_dict:get_stale
   




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r828675182



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       We don't set any expiration for the shdict's data, right? So why do we need to use get_stale & flush_expired?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r828705494



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       Consider the following situation:
   
   If the current endpoint is as follows:
   k1: v1, k2: v2, k3: v3
   
   Then we full fetch to get the following results
   k1: v1, k4: v4, k5: v5
   
   At this time, we need
   1: add k4:v4, k5:v5
   2: delete k2:v2, k3:v3
   
   flush_all and flush_expired can achieve this




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829792265



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_db_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_db_error = fetch_full(db_cli)
+    else
+        last_db_error = fetch_incremental(db_cli)
+    end
+
+    if not last_db_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    endpoint_dict = ngx.shared.tars
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: tars, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = conf.default_weight

Review comment:
       I get your point.
   But other discovery plugin is also used like this
   https://github.com/apache/apisix/blob/c7755543ae2e097942da4bbc5a9c6136de90fd78/apisix/discovery/eureka/init.lua#L209
   https://github.com/apache/apisix/blob/c7755543ae2e097942da4bbc5a9c6136de90fd78/apisix/discovery/nacos/init.lua#L379




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829653704



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       Thanks for your explanation. Let's enrich the comment in the code.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829769074



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do

Review comment:
       I do not think so. 
   Because the result of query_result is the return by db_cli, there is no type other than table.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829776803



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do

Review comment:
       Actually I don't know much about this db:read_result
   This code is copied from lua_resty_mysql
   
   And according to the code, as long as res == nil , loop will break.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#issuecomment-1073473662


   > hi @zhixiongdu027 , file conflict, you need to merge master branch to your PR.
   
   Done


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r828705494



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       > We don't set any expiration for the shdict's data, right? 
   
   We call  endpoint_dict:flush_all()  to marks all the existing items as expired before read full query results.
   
   Why:
   ```
   Consider the following situation:
   If the current endpoint is as follows:
   k1:v1, k2:v2, k3:v3
   
   Then we full query get the following results
   k1:v1, k4:v4, k5:v5
   
   At this time, we need
   1: add k4:v4, k5:v5
   2: delete k2:v2, k3:v3
   
   flush_all and flush_expired can achieve this
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r828678657



##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,381 @@
+#
+# 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('warn');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90

Review comment:
       Maybe we can reduce the min in the schema? A sane user may not use a low value.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tzssangglass commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r830566309



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict

Review comment:
       Sorry for my mistake. The original way here was fine. Because when we don't use tars as a discovery type, we can do without the declared ngx.shared.tars.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r830632334



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       Sorry, I add comment in function fetch_full and forget  function get_endpoint()
   
   I will update




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829654527



##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,381 @@
+#
+# 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('warn');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90

Review comment:
       What about rewriting schema in the test?
   Like https://github.com/apache/apisix/blob/9d450d7fe3169a77727df28696d083809b93977a/t/plugin/skywalking.t#L34-L35




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829663300



##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,381 @@
+#
+# 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('warn');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90

Review comment:
       ok, I will try

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do

Review comment:
       I do not think so. 
   Because the result of query_result is the return by db_cli, there is no type other than table.
   But you reminded me that query_result maybe is {}

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do

Review comment:
       I do not think so. 
   Because the result of query_result is the return by db_cli, there is no type other than table.

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do

Review comment:
       Actually I don't know much about this db:read_result
   This code is copied from lua_resty_mysql
   
   And according to the code, as long as res == nil , loop will break.

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_db_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_db_error = fetch_full(db_cli)
+    else
+        last_db_error = fetch_incremental(db_cli)
+    end
+
+    if not last_db_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    endpoint_dict = ngx.shared.tars
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: tars, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = conf.default_weight
+
+    core.log.info("conf ", core.json.encode(conf, true))

Review comment:
       I will update

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_db_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_db_error = fetch_full(db_cli)
+    else
+        last_db_error = fetch_incremental(db_cli)
+    end
+
+    if not last_db_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    endpoint_dict = ngx.shared.tars
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: tars, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = conf.default_weight
+
+    core.log.info("conf ", core.json.encode(conf, true))

Review comment:
       > 
   I will update
   

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_db_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_db_error = fetch_full(db_cli)
+    else
+        last_db_error = fetch_incremental(db_cli)
+    end
+
+    if not last_db_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    endpoint_dict = ngx.shared.tars
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: tars, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = conf.default_weight

Review comment:
       I get your point.
   But other discovery plugin is also used like this
   https://github.com/apache/apisix/blob/c7755543ae2e097942da4bbc5a9c6136de90fd78/apisix/discovery/eureka/init.lua#L209
   https://github.com/apache/apisix/blob/c7755543ae2e097942da4bbc5a9c6136de90fd78/apisix/discovery/nacos/init.lua#L379




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829653704



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       Thanks for your explanation. Let's enrich the comment in the code.

##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,381 @@
+#
+# 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('warn');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90

Review comment:
       What about rewriting schema in the test?
   Like https://github.com/apache/apisix/blob/9d450d7fe3169a77727df28696d083809b93977a/t/plugin/skywalking.t#L34-L35




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r830607932



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,340 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local endpoint_dict = ngx.shared.tars
+if not endpoint_dict then
+    error("failed to get nginx shared dict: tars, please check your APISIX version")
+end
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we extract host and port value via endpoints_pattern
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    --[[
+    res format is as follows:
+    {
+        {
+            servant = "A.AServer.FirstObj",
+            endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+        },
+        {
+            servant = "A.AServer.SecondObj",
+            endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+        },
+    }
+
+    if current endpoint_dict is as follows:
+      key1:nodes1, key2:nodes2, key3:nodes3
+
+    then fetch_full get follow results:
+      key1:nodes1, key4:nodes4, key5:nodes5
+
+    at this time, we need
+      1: setup key4:nodes4, key5:nodes5
+      2: delete key2:nodes2, key3:nodes3
+
+    to achieve goals, we should:
+      1: before setup results, execute endpoint_dict:flush_all()
+      2:  after setup results, execute endpoint_dict:flush_expired()
+    --]]
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()

Review comment:
       For style, let's add return nil at the end

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       @zhixiongdu027 
   Please add some comment about the expiration operation, thanks. 

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,340 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local endpoint_dict = ngx.shared.tars
+if not endpoint_dict then
+    error("failed to get nginx shared dict: tars, please check your APISIX version")
+end
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we extract host and port value via endpoints_pattern
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    --[[
+    res format is as follows:
+    {
+        {
+            servant = "A.AServer.FirstObj",
+            endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+        },
+        {
+            servant = "A.AServer.SecondObj",
+            endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+        },
+    }
+
+    if current endpoint_dict is as follows:
+      key1:nodes1, key2:nodes2, key3:nodes3
+
+    then fetch_full get follow results:
+      key1:nodes1, key4:nodes4, key5:nodes5
+
+    at this time, we need
+      1: setup key4:nodes4, key5:nodes5
+      2: delete key2:nodes2, key3:nodes3
+
+    to achieve goals, we should:
+      1: before setup results, execute endpoint_dict:flush_all()
+      2:  after setup results, execute endpoint_dict:flush_expired()
+    --]]
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    --[[
+    res is as follows:
+    {
+        {
+            activated=1,
+            servant = "A.AServer.FirstObj",
+            endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+        },
+        {
+            activated=0,
+            servant = "A.AServer.FirstObj",
+            endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+        },
+        {
+            activated=0,
+            servant = "B.BServer.FirstObj",
+            endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+        },
+    }
+
+    for each item:
+      if activated==1, setup
+      if activated==0, if there is a other item had same servant and activate==1, ignore
+      if activated==0, and there is no other item had same servant, delete
+    --]]
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end

Review comment:
       For style, let's add `return nil` at the end




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r830640039



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,283 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_fetch_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into discovery DICT failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into discovery DICT failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function create_endpoint_lrucache(servant)
+    local endpoint_content = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content from discovery DICT, servant: ", servant)
+        return nil
+    end
+
+    local endpoint = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode endpoint content failed, content: ", endpoint_content)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version then
+        return nil
+    end
+
+    return endpoint_lrucache(servant, endpoint_version, create_endpoint_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_fetch_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_fetch_error = fetch_full(db_cli)
+    else
+        last_fetch_error = fetch_incremental(db_cli)
+    end
+
+    if not last_fetch_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end

Review comment:
       > Need `close` if err?
   
   Sorry, I think this place doesn't actively call db:close() should be acceptable,
   [Here](https://forum.openresty.us/d/2151-f9a6aa2bb662b5ab77ee279e6dbd3ed6/4) provides agentzh's view




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r828708565



##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,381 @@
+#
+# 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('warn');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90

Review comment:
       > A sane user may not use a low value.
   
   Yes, I agree with this
   
   But I think the meaning of scheame's existence is to ensure that this value is within a reasonable range,
   
   Is it not appropriate to modify the schema if it is only for testing .




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829777573



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_db_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_db_error = fetch_full(db_cli)
+    else
+        last_db_error = fetch_incremental(db_cli)
+    end
+
+    if not last_db_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    endpoint_dict = ngx.shared.tars
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: tars, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = conf.default_weight
+
+    core.log.info("conf ", core.json.encode(conf, true))

Review comment:
       I will update




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r828705494



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       > We don't set any expiration for the shdict's data, right? 
   
   We call  endpoint_dict:flush_all()  to marks all the existing items as expired before read full query results.
   
   Why:
   ```
   Consider the following situation:
   If the current endpoint is as follows:
   k1: v1, k2: v2, k3: v3
   
   Then we full fetch to get the following results
   k1: v1, k4: v4, k5: v5
   
   At this time, we need
   1: add k4:v4, k5:v5
   2: delete k2:v2, k3:v3
   
   flush_all and flush_expired can achieve this
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tzssangglass commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829722817



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict

Review comment:
       ```suggestion
   local endpoint_dict = ngx.shared.tars
   ```
   just declare use `ngx.shared.DICT` here directly?
   

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do

Review comment:
       Is there a risk of a dead-end cycle here?

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_db_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_db_error = fetch_full(db_cli)
+    else
+        last_db_error = fetch_incremental(db_cli)
+    end
+
+    if not last_db_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    endpoint_dict = ngx.shared.tars
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: tars, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = conf.default_weight
+
+    core.log.info("conf ", core.json.encode(conf, true))

Review comment:
       ```suggestion
       core.log.info("conf ", core.json.delay_encode(conf, true))
   ```

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_db_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_db_error = fetch_full(db_cli)
+    else
+        last_db_error = fetch_incremental(db_cli)
+    end
+
+    if not last_db_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    endpoint_dict = ngx.shared.tars
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: tars, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = conf.default_weight

Review comment:
       I have some concerns about this place. By declaring `default_weight` as a module-level variable, its lifecycle extends beyond that of `local_conf`. Can we just get the `default_weight` directly from the `local_conf` module when we use it?

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do

Review comment:
       should check if `query_result` is a table(array)?

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_db_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_db_error = fetch_full(db_cli)
+    else
+        last_db_error = fetch_incremental(db_cli)
+    end
+
+    if not last_db_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    endpoint_dict = ngx.shared.tars
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: tars, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = conf.default_weight

Review comment:
       Thanks for the reminder, I will think about whether it is appropriate.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r825449599



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,283 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_fetch_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into discovery DICT failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into discovery DICT failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function create_endpoint_lrucache(servant)
+    local endpoint_content = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content from discovery DICT, servant: ", servant)
+        return nil
+    end
+
+    local endpoint = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode endpoint content failed, content: ", endpoint_content)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version then
+        return nil
+    end
+
+    return endpoint_lrucache(servant, endpoint_version, create_endpoint_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_fetch_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_fetch_error = fetch_full(db_cli)
+    else
+        last_fetch_error = fetch_incremental(db_cli)
+    end
+
+    if not last_fetch_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    -- TODO: maybe we can read dict name from discovery config
+    endpoint_dict = ngx.shared.discovery
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: discovery, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = local_conf.discovery.tars.default_weight or 100

Review comment:
       ```suggestion
       default_weight = conf.default_weight
   ```
   The default weight is set already.

##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,409 @@
+#
+# 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('debug');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90
+    incremental_fetch_interval: 5
+_EOC_
+
+    $block->set_value("yaml_config", $yaml_config);
+
+    my $apisix_yaml = $block->apisix_yaml // <<_EOC_;
+routes: []
+#END
+_EOC_
+
+    $block->set_value("apisix_yaml", $apisix_yaml);
+
+    my $config = $block->config // <<_EOC_;
+        location /count {
+            content_by_lua_block {
+              local core = require("apisix.core")
+              local d = require("apisix.discovery.tars")
+
+              ngx.sleep(11)
+
+              ngx.req.read_body()
+              local request_body = ngx.req.get_body_data()
+              local queries = core.json.decode(request_body)
+              local response_body = "{"
+              for _,query in ipairs(queries) do
+                local nodes = d.nodes(query)
+                if nodes==nil or #nodes==0 then
+                    response_body=response_body.." "..0
+                else
+                    response_body=response_body.." "..#nodes
+                end
+              end
+              ngx.say(response_body.." }")
+            }
+        }
+
+        location /nodes {
+            content_by_lua_block {
+              local core = require("apisix.core")
+              local d = require("apisix.discovery.tars")
+
+              ngx.sleep(11)
+
+              ngx.req.read_body()
+              local servant = ngx.req.get_body_data()
+              local response=""
+              local nodes = d.nodes(servant)
+              response="{"
+              for _,node in ipairs(nodes or {}) do
+                response=response..node.host..":"..node.port..","
+              end
+              response=response.."}"
+              ngx.say(response)
+            }
+        }
+
+        location /sql {
+            content_by_lua_block {
+                local mysql = require("resty.mysql")
+                local core = require("apisix.core")
+                local ipairs = ipairs
+
+                ngx.req.read_body()
+                local sql = ngx.req.get_body_data()
+                core.log.info("get sql ", sql)
+
+                local db_conf= {
+                  host="127.0.0.1",
+                  port=3306,
+                  database="db_tars",
+                  user="root",
+                  password="tars2022",
+                }
+
+                local db_cli, err = mysql:new()
+                if not db_cli then
+                  core.log.error("failed to instantiate mysql: ", err)
+                  return
+                end
+                db_cli:set_timeout(3000)
+
+                local ok, err, errcode, sqlstate = db_cli:connect(db_conf)
+                if not ok then
+                  core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+                  return
+                end
+
+                local res, err, errcode, sqlstate = db_cli:query(sql)
+                if not res then
+                   ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")
+                   return
+                end
+                ngx.say("DONE")
+            }
+        }
+_EOC_
+
+    $block->set_value("config", $config);
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: create initial server and servant
+--- timeout: 12
+--- yaml_config eval: $::yaml_config

Review comment:
       We already set the yaml_config?

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,283 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_fetch_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into discovery DICT failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into discovery DICT failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function create_endpoint_lrucache(servant)
+    local endpoint_content = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content from discovery DICT, servant: ", servant)
+        return nil
+    end
+
+    local endpoint = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode endpoint content failed, content: ", endpoint_content)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       Could you tell me why get_statle is used instead of get?

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,283 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_fetch_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into discovery DICT failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into discovery DICT failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function create_endpoint_lrucache(servant)

Review comment:
       ```suggestion
   local function add_endpoint_to_lrucache(servant)
   ```

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,283 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_fetch_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+local endpoints_pattern = core.table.concat(

Review comment:
       Could you show us the data which is matched by this regex?

##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,409 @@
+#
+# 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('debug');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90
+    incremental_fetch_interval: 5
+_EOC_
+
+    $block->set_value("yaml_config", $yaml_config);
+
+    my $apisix_yaml = $block->apisix_yaml // <<_EOC_;
+routes: []
+#END
+_EOC_
+
+    $block->set_value("apisix_yaml", $apisix_yaml);
+
+    my $config = $block->config // <<_EOC_;
+        location /count {
+            content_by_lua_block {
+              local core = require("apisix.core")
+              local d = require("apisix.discovery.tars")
+
+              ngx.sleep(11)
+
+              ngx.req.read_body()
+              local request_body = ngx.req.get_body_data()
+              local queries = core.json.decode(request_body)
+              local response_body = "{"
+              for _,query in ipairs(queries) do
+                local nodes = d.nodes(query)
+                if nodes==nil or #nodes==0 then
+                    response_body=response_body.." "..0
+                else
+                    response_body=response_body.." "..#nodes
+                end
+              end
+              ngx.say(response_body.." }")
+            }
+        }
+
+        location /nodes {
+            content_by_lua_block {
+              local core = require("apisix.core")
+              local d = require("apisix.discovery.tars")
+
+              ngx.sleep(11)
+
+              ngx.req.read_body()
+              local servant = ngx.req.get_body_data()
+              local response=""
+              local nodes = d.nodes(servant)
+              response="{"
+              for _,node in ipairs(nodes or {}) do
+                response=response..node.host..":"..node.port..","
+              end
+              response=response.."}"
+              ngx.say(response)
+            }
+        }
+
+        location /sql {
+            content_by_lua_block {
+                local mysql = require("resty.mysql")
+                local core = require("apisix.core")
+                local ipairs = ipairs
+
+                ngx.req.read_body()
+                local sql = ngx.req.get_body_data()
+                core.log.info("get sql ", sql)
+
+                local db_conf= {
+                  host="127.0.0.1",
+                  port=3306,
+                  database="db_tars",
+                  user="root",
+                  password="tars2022",
+                }
+
+                local db_cli, err = mysql:new()
+                if not db_cli then
+                  core.log.error("failed to instantiate mysql: ", err)
+                  return
+                end
+                db_cli:set_timeout(3000)
+
+                local ok, err, errcode, sqlstate = db_cli:connect(db_conf)
+                if not ok then
+                  core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+                  return
+                end
+
+                local res, err, errcode, sqlstate = db_cli:query(sql)
+                if not res then
+                   ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")
+                   return
+                end
+                ngx.say("DONE")
+            }
+        }
+_EOC_
+
+    $block->set_value("config", $config);
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: create initial server and servant
+--- timeout: 12
+--- yaml_config eval: $::yaml_config
+--- request eval
+[
+"POST /sql
+truncate table t_server_conf",
+
+"POST /sql
+truncate table t_adapter_conf",
+
+"POST /sql
+insert into t_server_conf(application, server_name, node_name, registry_timestamp,
+                          template_name, setting_state, present_state, server_type)
+values ('A', 'AServer', '172.16.1.1', now(), 'taf-cpp', 'active', 'active', 'tars_cpp'),
+       ('B', 'BServer', '172.16.2.1', now(), 'taf-cpp', 'active', 'active', 'tars_cpp'),
+       ('C', 'CServer', '172.16.3.1', now(), 'taf-cpp', 'active', 'active', 'tars_cpp')",
+
+"POST /sql
+insert into t_adapter_conf(application, server_name, node_name, adapter_name, endpoint, servant)
+values ('A', 'AServer', '172.16.1.1', 'A.AServer.FirstObjAdapter',
+        'tcp -h 172.16.1.1 -p 10001 -e 0 -t 6000', 'A.AServer.FirstObj'),
+       ('B', 'BServer', '172.16.2.1', 'B.BServer.FirstObjAdapter',
+        'tcp -p 10001 -h 172.16.2.1 -e 0 -t 6000', 'B.BServer.FirstObj'),
+       ('C', 'CServer', '172.16.3.1', 'C.CServer.FirstObjAdapter',
+        'tcp -e 0 -h 172.16.3.1 -t 6000 -p 10001 ', 'C.CServer.FirstObj')",
+
+"GET /count
+[\"A.AServer.FirstObj\",\"B.BServer.FirstObj\", \"C.CServer.FirstObj\"]",
+
+]
+--- response_body eval
+[
+    "DONE\n",
+    "DONE\n",
+    "DONE\n",
+    "DONE\n",
+    "{ 1 1 1 }\n",
+]
+--- no_error_log

Review comment:
       Let's save the no_error_log like https://github.com/apache/apisix/blob/1ac7166e80fdd502cc6a8a60ef58f415d3c0e56c/t/plugin/gzip.t#L40 

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,283 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_fetch_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into discovery DICT failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into discovery DICT failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function create_endpoint_lrucache(servant)
+    local endpoint_content = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content from discovery DICT, servant: ", servant)
+        return nil
+    end
+
+    local endpoint = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode endpoint content failed, content: ", endpoint_content)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version then
+        return nil
+    end
+
+    return endpoint_lrucache(servant, endpoint_version, create_endpoint_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_fetch_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_fetch_error = fetch_full(db_cli)
+    else
+        last_fetch_error = fetch_incremental(db_cli)
+    end
+
+    if not last_fetch_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    -- TODO: maybe we can read dict name from discovery config
+    endpoint_dict = ngx.shared.discovery

Review comment:
       Let's use a new shdict instead of hijacking authz-keycloak's

##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,409 @@
+#
+# 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('debug');

Review comment:
       Is it necessary to use debug level?

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,283 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_fetch_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into discovery DICT failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into discovery DICT failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function create_endpoint_lrucache(servant)
+    local endpoint_content = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content from discovery DICT, servant: ", servant)
+        return nil
+    end
+
+    local endpoint = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode endpoint content failed, content: ", endpoint_content)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version then
+        return nil
+    end
+
+    return endpoint_lrucache(servant, endpoint_version, create_endpoint_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_fetch_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_fetch_error = fetch_full(db_cli)
+    else
+        last_fetch_error = fetch_incremental(db_cli)
+    end
+
+    if not last_fetch_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end

Review comment:
       Need `close` if err?

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,283 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_fetch_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into discovery DICT failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into discovery DICT failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function create_endpoint_lrucache(servant)
+    local endpoint_content = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content from discovery DICT, servant: ", servant)
+        return nil
+    end
+
+    local endpoint = core.json.decode(endpoint_content)

Review comment:
       Let's get the err and put it in the msg




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r827586821



##########
File path: conf/config-default.yaml
##########
@@ -266,6 +266,7 @@ nginx_config:                     # config for render the template to generate n
       introspection: 10m
       access-tokens: 1m
       ext-plugin: 1m
+      tars: 1m

Review comment:
       Let's make it configurable in the next PR.

##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,381 @@
+#
+# 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('warn');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90

Review comment:
       Could we use a shorter interval instead of waiting > 90 in the test?

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       Why use get_stale when we don't have an expiration time?

##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,381 @@
+#
+# 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('warn');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90
+    incremental_fetch_interval: 5
+_EOC_
+
+    $block->set_value("yaml_config", $yaml_config);
+
+    my $apisix_yaml = $block->apisix_yaml // <<_EOC_;
+routes: []
+#END
+_EOC_
+
+    $block->set_value("apisix_yaml", $apisix_yaml);
+
+    my $config = $block->config // <<_EOC_;
+        location /count {
+            content_by_lua_block {
+              local core = require("apisix.core")
+              local d = require("apisix.discovery.tars")
+
+              ngx.sleep(11)

Review comment:
       Ditto

##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,283 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_fetch_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into discovery DICT failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into discovery DICT failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function create_endpoint_lrucache(servant)
+    local endpoint_content = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content from discovery DICT, servant: ", servant)
+        return nil
+    end
+
+    local endpoint = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode endpoint content failed, content: ", endpoint_content)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version then
+        return nil
+    end
+
+    return endpoint_lrucache(servant, endpoint_version, create_endpoint_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+                return err
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_fetch_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_fetch_error = fetch_full(db_cli)
+    else
+        last_fetch_error = fetch_incremental(db_cli)
+    end
+
+    if not last_fetch_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end

Review comment:
       Need to handle this.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#issuecomment-1068645969


   @spacewander 
   Hi, all comment issues have been taken care of. please help review. 
   Many TKS.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#issuecomment-1068676548


   ![image](https://user-images.githubusercontent.com/4032302/158506746-4ddd0d50-7697-4ba1-857b-a8187d81e8f8.png)
   
   @spacewander @membphis 
   
   Sorry, I don't understand how this failure happened. 
   Please give a hand. TKS


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r828708565



##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,381 @@
+#
+# 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('warn');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90

Review comment:
       > A sane user may not use a low value.
   Yes, I agree with this
   
   But I think the meaning of scheame's existence is to ensure that this value is within a reasonable range,
   
   Is it not appropriate to modify the schema if it is only for testing




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] spacewander commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
spacewander commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r828672726



##########
File path: conf/config-default.yaml
##########
@@ -266,6 +266,7 @@ nginx_config:                     # config for render the template to generate n
       introspection: 10m
       access-tokens: 1m
       ext-plugin: 1m
+      tars: 1m

Review comment:
       We need to close the handler when getting last_db_error?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829663300



##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,381 @@
+#
+# 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('warn');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90

Review comment:
       ok, I will try




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829778026



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_db_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_db_error = fetch_full(db_cli)
+    else
+        last_db_error = fetch_incremental(db_cli)
+    end
+
+    if not last_db_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    endpoint_dict = ngx.shared.tars
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: tars, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = conf.default_weight
+
+    core.log.info("conf ", core.json.encode(conf, true))

Review comment:
       > 
   I will update
   




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r830632152



##########
File path: conf/config-default.yaml
##########
@@ -266,6 +266,7 @@ nginx_config:                     # config for render the template to generate n
       introspection: 10m
       access-tokens: 1m
       ext-plugin: 1m
+      tars: 1m

Review comment:
       Sorry,  I  think this place doesn't actively call db:close() should be acceptable, 
   [this link](https://forum.openresty.us/d/2151-f9a6aa2bb662b5ab77ee279e6dbd3ed6/4)  provides agentzh's view 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r830582815



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict

Review comment:
       OK,I will update




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] tzssangglass commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
tzssangglass commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r829845591



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")
+    if not endpoint_version  then
+        if err then
+            core.log.error("get empty endpoint version, servant: ", servant, ", err: ", err)
+        end
+        return nil
+    end
+    return endpoint_lrucache(servant, endpoint_version, add_endpoint_to_lrucache, servant)
+end
+
+
+local function extract_endpoint(query_result)
+    for _, p in ipairs(query_result) do
+        repeat
+            local servant = p.servant
+
+            if servant == ngx.null then
+                break
+            end
+
+            if p.activated == 1 then
+                activated_buffer[servant] = ngx.null
+            elseif p.activated == 0 then
+                if activated_buffer[servant] == nil then
+                    delete_endpoint(servant)
+                end
+                break
+            end
+
+            core.table.clear(nodes_buffer)
+            local iterator = ngx.re.gmatch(p.endpoints, endpoints_pattern, "jao")
+            while true do
+                local captures, err = iterator()
+                if err then
+                    core.log.error("gmatch failed, error: ", err, " , endpoints: ", p.endpoints)
+                    break
+                end
+
+                if not captures then
+                    break
+                end
+
+                local host, port
+                if captures[3] == "h" or captures[3] == "H" then
+                    host = captures[4]
+                    port = tonumber(captures[8])
+                else
+                    host = captures[8]
+                    port = tonumber(captures[4])
+                end
+
+                core.table.insert(nodes_buffer, {
+                    host = host,
+                    port = port,
+                    weight = default_weight,
+                })
+            end
+            update_endpoint(servant, nodes_buffer)
+        until true
+    end
+end
+
+--[[
+result of full_query_sql is as follows:
+{
+    {
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        servant = "A.AServer.SecondObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+between two fetch_full(), some servant may be deleted, and cannot be reflected in the result
+so :
+  before read result, we should to execute endpoint_dict:flush_all()
+  after read result, we should to execute endpoint_dict:flush_expired()
+
+--]]
+local function fetch_full(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(full_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    endpoint_dict:flush_all()
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+    endpoint_dict:flush_expired()
+end
+
+
+--[[
+result of incremental_query_sql is as follows:
+{
+    {
+        activated=1,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -h 172.16.1.1 -p 10001 -e 0 -t 3000,tcp -p 10002 -h 172.16.1.2 -t 3000"
+    },
+    {
+        activated=0,
+        servant = "A.AServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10001 -h 172.16.1.3"
+    },
+    {
+        activated=0,
+        servant = "B.BServer.FirstObj",
+        endpoints = "tcp -t 3000 -p 10002 -h 172.16.1.2"
+    },
+}
+
+for each item:
+    if activated==1 , set into endpoints_dict
+    if activated==0 , there is a item had same servant and activate==1, ignore
+    if activated==0 , there is no item had same servant, delete
+--]]
+local function fetch_incremental(db_cli)
+    local res, err, errcode, sqlstate = db_cli:query(incremental_query_sql)
+    if not res then
+        core.log.error("query failed, error: ", err, ", ", errcode, " ", sqlstate)
+        return err
+    end
+
+    core.table.clear(activated_buffer)
+    extract_endpoint(res)
+
+    while err == "again" do
+        res, err, errcode, sqlstate = db_cli:read_result()
+        if not res then
+            if err then
+                core.log.error("read result failed, error: ", err, ", ", errcode, " ", sqlstate)
+            end
+            return err
+        end
+        extract_endpoint(res)
+    end
+end
+
+
+local function fetch_endpoint(premature, conf)
+    if premature then
+        return
+    end
+
+    local db_cli, err = mysql:new()
+    if not db_cli then
+        core.log.error("failed to instantiate mysql: ", err)
+        return
+    end
+    db_cli:set_timeout(3000)
+
+    local ok, err, errcode, sqlstate = db_cli:connect(conf.db_conf)
+    if not ok then
+        core.log.error("failed to connect mysql: ", err, ", ", errcode, ", ", sqlstate)
+        return
+    end
+
+    local now = ngx.time()
+
+    if last_db_error or last_fetch_full_time + conf.full_fetch_interval <= now then
+        last_fetch_full_time = now
+        last_db_error = fetch_full(db_cli)
+    else
+        last_db_error = fetch_incremental(db_cli)
+    end
+
+    if not last_db_error then
+        db_cli:set_keepalive(120 * 1000, 1)
+    end
+end
+
+
+function _M.nodes(servant)
+    return get_endpoint(servant)
+end
+
+
+function _M.init_worker()
+    endpoint_dict = ngx.shared.tars
+    if not endpoint_dict then
+        error("failed to get nginx shared dict: tars, please check your APISIX version")
+    end
+
+    if process.type() ~= "privileged agent" then
+        return
+    end
+
+    local conf = local_conf.discovery.tars
+    default_weight = conf.default_weight

Review comment:
       Thanks for the reminder, I will think about whether it is appropriate.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r827606069



##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,381 @@
+#
+# 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('warn');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90

Review comment:
       this value Is check by schema ,
   can we use an fake schema.lua  int test ?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r827676423



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       from SQL queries and have no expiration time in the cache sense.
   
   Next:
   When we read the results of fectch_full the logic is as follows:
   
   1. endpoint_dict:flush_all() 2. for (result : results ){ endpoint_dict[servant]=nodes } 3. endpoint_dict: flush_expired()
   
   get_endpoint may happen at 2,
   we have to use endpoint_dict:get_stale
   
   




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r828705494



##########
File path: apisix/discovery/tars/init.lua
##########
@@ -0,0 +1,332 @@
+--
+-- 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 ngx = ngx
+local format = string.format
+local ipairs = ipairs
+local error = error
+local tonumber = tonumber
+local local_conf = require("apisix.core.config_local").local_conf()
+local core = require("apisix.core")
+local mysql = require("resty.mysql")
+local process = require("ngx.process")
+
+local full_query_sql = [[ select servant, group_concat(endpoint order by endpoint) as endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where setting_state = 'active' and present_state = 'active'
+group by servant ]]
+
+local incremental_query_sql = [[
+select servant, (setting_state = 'active' and present_state = 'active') activated,
+group_concat(endpoint order by endpoint) endpoints
+from t_server_conf left join t_adapter_conf tac using (application, server_name, node_name)
+where (application, server_name) in
+(
+select application, server_name from t_server_conf
+where registry_timestamp > now() - interval %d second
+union
+select application, server_name from t_adapter_conf
+where registry_timestamp > now() - interval %d second
+)
+group by servant, activated order by activated desc ]]
+
+local _M = {
+    version = 0.1,
+}
+
+local endpoint_dict
+local default_weight
+
+local last_fetch_full_time = 0
+local last_db_error
+
+local endpoint_lrucache = core.lrucache.new({
+    ttl = 300,
+    count = 1024
+})
+
+local activated_buffer = core.table.new(10, 0)
+local nodes_buffer = core.table.new(0, 5)
+
+
+--[[
+endpoints format as follows:
+  tcp -h 172.16.1.1 -p 11 -t 6000 -e 0,tcp -e 0 -p 12 -h 172.16.1.1,tcp -p 13 -h 172.16.1.1
+we need extract host and port value via regex
+--]]
+local endpoints_pattern = core.table.concat(
+        { [[tcp(\s*-[te]\s*(\S+)){0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+))]],
+          [[{0,2}\s*-([hpHP])\s*(\S+)(\s*-[teTE]\s*(\S+)){0,2}\s*(,|$)]] }
+)
+
+
+local function update_endpoint(servant, nodes)
+    local endpoint_content = core.json.encode(nodes, true)
+    local endpoint_version = ngx.crc32_long(endpoint_content)
+    core.log.debug("set servant ", servant, endpoint_content)
+    local _, err
+    _, err = endpoint_dict:safe_set(servant .. "#version", endpoint_version)
+    if err then
+        core.log.error("set endpoint version into nginx shared dict failed, ", err)
+        return
+    end
+    _, err = endpoint_dict:safe_set(servant, endpoint_content)
+    if err then
+        core.log.error("set endpoint into nginx shared dict failed, ", err)
+        endpoint_dict:delete(servant .. "#version")
+    end
+end
+
+
+local function delete_endpoint(servant)
+    core.log.info("delete servant ", servant)
+    endpoint_dict:delete(servant .. "#version")
+    endpoint_dict:delete(servant)
+end
+
+
+local function add_endpoint_to_lrucache(servant)
+    local endpoint_content, err = endpoint_dict:get_stale(servant)
+    if not endpoint_content then
+        core.log.error("get empty endpoint content, servant: ", servant, ", err: ", err)
+        return nil
+    end
+
+    local endpoint, err = core.json.decode(endpoint_content)
+    if not endpoint then
+        core.log.error("decode json failed, content: ", endpoint_content, ", err: ", err)
+        return nil
+    end
+
+    return endpoint
+end
+
+
+local function get_endpoint(servant)
+    local endpoint_version, err = endpoint_dict:get_stale(servant .. "#version")

Review comment:
       Consider the following situation:
   
   If the current endpoint is as follows:
   k1: v1, k2: v2, k3: v3 ,k4: v4
   
   Then we full fetch to get the following results
   k1: v1, k4: v4, k5: v5
   
   At this time, we need
   1: add k4:v4, k5:v5
   2: delete k2:v2, k3:v3
   
   flush_all and flush_expired can achieve this




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on a change in pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on a change in pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#discussion_r828708565



##########
File path: t/tars/discovery/tars.t
##########
@@ -0,0 +1,381 @@
+#
+# 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('warn');
+no_root_location();
+no_shuffle();
+workers(4);
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $yaml_config = <<_EOC_;
+apisix:
+  node_listen: 1984
+  config_center: yaml
+  enable_admin: false
+discovery:
+  tars:
+    db_conf:
+      host: 127.0.0.1
+      port: 3306
+      database: db_tars
+      user: root
+      password: tars2022
+    full_fetch_interval: 90

Review comment:
       > A sane user may not use a low value.
   Yes, I agree with this
   
   But I think the meaning of scheame's existence is to ensure that this value is within a reasonable range,
   
   Is it not appropriate to modify the schema if it is only for testing .




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix] zhixiongdu027 commented on pull request #6599: feat: add tars discovery

Posted by GitBox <gi...@apache.org>.
zhixiongdu027 commented on pull request #6599:
URL: https://github.com/apache/apisix/pull/6599#issuecomment-1070153436


   @spacewander
   
   Hi, I still have some questions to ask, please take a look at the comments I wrote.
   thank you very much.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org