You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2022/03/21 09:22:53 UTC

[apisix] branch master updated: feat: add tars discovery (#6599)

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

spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 5b21d31  feat: add tars discovery (#6599)
5b21d31 is described below

commit 5b21d31c9eb5ad1d873066c8eee9c4837b8d565d
Author: zhixiongdu <ro...@libssl.com>
AuthorDate: Mon Mar 21 17:22:45 2022 +0800

    feat: add tars discovery (#6599)
---
 .github/workflows/tars-ci.yml    |  61 +++++
 Makefile                         |   3 +-
 apisix/cli/ngx_tpl.lua           |   4 +
 apisix/discovery/tars/init.lua   | 357 ++++++++++++++++++++++++++
 apisix/discovery/tars/schema.lua |  45 ++++
 ci/tars-ci.sh                    |  33 +++
 conf/config-default.yaml         |   1 +
 t/APISIX.pm                      |   1 +
 t/tars/conf/tars.sql             | 539 +++++++++++++++++++++++++++++++++++++++
 t/tars/discovery/tars.t          | 393 ++++++++++++++++++++++++++++
 10 files changed, 1436 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/tars-ci.yml b/.github/workflows/tars-ci.yml
new file mode 100644
index 0000000..cc376e8
--- /dev/null
+++ b/.github/workflows/tars-ci.yml
@@ -0,0 +1,61 @@
+name: CI Tars
+
+on:
+  push:
+    branches: [ master, 'release/**' ]
+    paths-ignore:
+      - 'docs/**'
+      - '**/*.md'
+  pull_request:
+    branches: [ master, 'release/**' ]
+    paths-ignore:
+      - 'docs/**'
+      - '**/*.md'
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/master' && github.run_number || github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  tars:
+    strategy:
+      fail-fast: false
+      matrix:
+        platform:
+          - ubuntu-18.04
+        os_name:
+          - linux_openresty
+          - linux_openresty_1_17
+
+    runs-on: ${{ matrix.platform }}
+    timeout-minutes: 15
+    env:
+      SERVER_NAME: ${{ matrix.os_name }}
+      OPENRESTY_VERSION: default
+
+    steps:
+      - name: Check out code
+        uses: actions/checkout@v2.4.0
+        with:
+          submodules: recursive
+
+      - name: Extract branch name
+        if: ${{ startsWith(github.ref, 'refs/heads/release/') }}
+        id: branch_env
+        shell: bash
+        run: |
+          echo "##[set-output name=version;]$(echo ${GITHUB_REF##*/})"
+
+      - name: Setup Tars MySql
+        run: |
+          docker run -d -p 3306:3306 -v $PWD/t/tars/conf/tars.sql:/docker-entrypoint-initdb.d/tars.sql -e MYSQL_ROOT_PASSWORD=tars2022 mysql:5.7
+
+      - name: Linux Install
+        run: |
+          sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3 libpcre3-dev libldap2-dev
+          sudo cpanm --notest Test::Nginx >build.log 2>&1 || (cat build.log && exit 1)
+          sudo --preserve-env=OPENRESTY_VERSION ./ci/${{ matrix.os_name }}_runner.sh do_install
+
+      - name: Run test cases
+        run: |
+          ./ci/tars-ci.sh run_case
diff --git a/Makefile b/Makefile
index f7546bf..36f659c 100644
--- a/Makefile
+++ b/Makefile
@@ -284,12 +284,13 @@ install: runtime
 
 	$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/discovery
 	$(ENV_INSTALL) apisix/discovery/*.lua $(ENV_INST_LUADIR)/apisix/discovery/
-	$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/discovery/{consul_kv,dns,eureka,nacos,kubernetes}
+	$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/discovery/{consul_kv,dns,eureka,nacos,kubernetes,tars}
 	$(ENV_INSTALL) apisix/discovery/consul_kv/*.lua $(ENV_INST_LUADIR)/apisix/discovery/consul_kv
 	$(ENV_INSTALL) apisix/discovery/dns/*.lua $(ENV_INST_LUADIR)/apisix/discovery/dns
 	$(ENV_INSTALL) apisix/discovery/eureka/*.lua $(ENV_INST_LUADIR)/apisix/discovery/eureka
 	$(ENV_INSTALL) apisix/discovery/nacos/*.lua $(ENV_INST_LUADIR)/apisix/discovery/nacos
 	$(ENV_INSTALL) apisix/discovery/kubernetes/*.lua $(ENV_INST_LUADIR)/apisix/discovery/kubernetes
+	$(ENV_INSTALL) apisix/discovery/tars/*.lua $(ENV_INST_LUADIR)/apisix/discovery/tars
 
 	$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/http
 	$(ENV_INSTALL) apisix/http/*.lua $(ENV_INST_LUADIR)/apisix/http/
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index b1ec2bb..7af9646 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -194,6 +194,10 @@ http {
     lua_shared_dict kubernetes {* http.lua_shared_dict["kubernetes"] *};
     {% end %}
 
+    {% if enabled_discoveries["tars"] then %}
+    lua_shared_dict tars {* http.lua_shared_dict["tars"] *};
+    {% end %}
+
     {% if enabled_plugins["limit-conn"] then %}
     lua_shared_dict plugin-limit-conn {* http.lua_shared_dict["plugin-limit-conn"] *};
     {% end %}
diff --git a/apisix/discovery/tars/init.lua b/apisix/discovery/tars/init.lua
new file mode 100644
index 0000000..f621791
--- /dev/null
+++ b/apisix/discovery/tars/init.lua
@@ -0,0 +1,357 @@
+--
+-- 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
+
+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)
+
+    --[[
+    fetch_full function will:
+         1: call endpoint_dict:flush_all()
+         2: setup servant:nodes pairs into endpoint_dict
+         3: call endpoint_dict:flush_expired()
+
+    get_endpoint may be called during the 2 step of the fetch_full function,
+    so we must use endpoint_dict:get_stale() to get value instead endpoint_dict:get()
+    --]]
+
+    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()
+
+    return nil
+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
+
+    return nil
+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 lua_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.delay_encode(conf))
+    local backtrack_time = conf.incremental_fetch_interval + 5
+    incremental_query_sql = format(incremental_query_sql, backtrack_time, backtrack_time)
+
+    ngx.timer.at(0, fetch_endpoint, conf)
+    ngx.timer.every(conf.incremental_fetch_interval, fetch_endpoint, conf)
+end
+
+
+return _M
diff --git a/apisix/discovery/tars/schema.lua b/apisix/discovery/tars/schema.lua
new file mode 100644
index 0000000..01d44d1
--- /dev/null
+++ b/apisix/discovery/tars/schema.lua
@@ -0,0 +1,45 @@
+--
+-- 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 host_pattern = [[^([a-zA-Z0-9-_.]+:.+\@)?[a-zA-Z0-9-_.:]+$]]
+
+return {
+    type = 'object',
+    properties = {
+        db_conf = {
+            type = 'object',
+            properties = {
+                host = { type = 'string', minLength = 1, maxLength = 500, pattern = host_pattern },
+                port = { type = 'integer', minimum = 1, maximum = 65535, default = 3306 },
+                database = { type = 'string', minLength = 1, maxLength = 64 },
+                user = { type = 'string', minLength = 1, maxLength = 64 },
+                password = { type = 'string', minLength = 1, maxLength = 64 },
+            },
+            required = { 'host', 'database', 'user', 'password' }
+        },
+        full_fetch_interval = {
+            type = 'integer', minimum = 90, maximum = 3600, default = 300,
+        },
+        incremental_fetch_interval = {
+            type = 'integer', minimum = 5, maximum = 60, default = 15,
+        },
+        default_weight = {
+            type = 'integer', minimum = 0, maximum = 100, default = 100,
+        },
+    },
+    required = { 'db_conf' }
+}
diff --git a/ci/tars-ci.sh b/ci/tars-ci.sh
new file mode 100755
index 0000000..c160db7
--- /dev/null
+++ b/ci/tars-ci.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+. ./ci/common.sh
+
+run_case() {
+    export_or_prefix
+    export PERL5LIB=.:$PERL5LIB
+    prove -Itest-nginx/lib -I./ -r t/tars | tee test-result
+    rerun_flaky_tests test-result
+}
+
+case_opt=$1
+case $case_opt in
+    (run_case)
+        run_case
+        ;;
+esac
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index d42ff21..f96b839 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -265,6 +265,7 @@ nginx_config:                     # config for render the template to generate n
       access-tokens: 1m
       ext-plugin: 1m
       kubernetes: 1m
+      tars: 1m
 
 etcd:
   host:                           # it's possible to define multiple etcd hosts addresses of the same etcd cluster.
diff --git a/t/APISIX.pm b/t/APISIX.pm
index b9f7089..5de300d 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -508,6 +508,7 @@ _EOC_
     lua_shared_dict etcd-cluster-health-check 10m; # etcd health check
     lua_shared_dict ext-plugin 1m;
     lua_shared_dict kubernetes 1m;
+    lua_shared_dict tars 1m;
 
     proxy_ssl_name \$upstream_host;
     proxy_ssl_server_name on;
diff --git a/t/tars/conf/tars.sql b/t/tars/conf/tars.sql
new file mode 100644
index 0000000..166bfb9
--- /dev/null
+++ b/t/tars/conf/tars.sql
@@ -0,0 +1,539 @@
+--
+-- 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.
+--
+
+-- MySQL dump 10.13  Distrib 5.6.26, for Linux (x86_64)
+--
+-- Host: 172.25.0.2    Database: db_tars
+-- ------------------------------------------------------
+-- Server version	5.6.51
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
+/*!40103 SET TIME_ZONE='+00:00' */;
+/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+--
+-- Current Database: `db_tars`
+--
+
+/*!40000 DROP DATABASE IF EXISTS `db_tars`*/;
+
+CREATE DATABASE /*!32312 IF NOT EXISTS*/ `db_tars` /*!40100 DEFAULT CHARACTER SET latin1 */;
+
+USE `db_tars`;
+
+--
+-- Table structure for table `t_adapter_conf`
+--
+
+DROP TABLE IF EXISTS `t_adapter_conf`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_adapter_conf` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `application` varchar(50) DEFAULT '',
+  `server_name` varchar(128) DEFAULT '',
+  `node_name` varchar(50) DEFAULT '',
+  `adapter_name` varchar(100) DEFAULT '',
+  `registry_timestamp` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+  `thread_num` int(11) DEFAULT '1',
+  `endpoint` varchar(128) DEFAULT '',
+  `max_connections` int(11) DEFAULT '1000',
+  `allow_ip` varchar(255) NOT NULL DEFAULT '',
+  `servant` varchar(128) DEFAULT '',
+  `queuecap` int(11) DEFAULT NULL,
+  `queuetimeout` int(11) DEFAULT NULL,
+  `posttime` datetime DEFAULT NULL,
+  `lastuser` varchar(30) DEFAULT NULL,
+  `protocol` varchar(64) DEFAULT 'tars',
+  `handlegroup` varchar(64) DEFAULT '',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `application` (`application`,`server_name`,`node_name`,`adapter_name`),
+  KEY `adapter_conf_endpoint_index` (`endpoint`),
+  KEY `index_regtime_1` (`registry_timestamp`),
+  KEY `index_regtime` (`registry_timestamp`)
+) ENGINE=InnoDB AUTO_INCREMENT=72 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_ats_cases`
+--
+
+DROP TABLE IF EXISTS `t_ats_cases`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_ats_cases` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `casename` varchar(20) DEFAULT NULL,
+  `retvalue` text,
+  `paramvalue` text,
+  `interfaceid` int(11) DEFAULT NULL,
+  `posttime` datetime DEFAULT NULL,
+  `lastuser` varchar(30) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_ats_interfaces`
+--
+
+DROP TABLE IF EXISTS `t_ats_interfaces`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_ats_interfaces` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `objname` varchar(150) DEFAULT NULL,
+  `funcname` varchar(150) DEFAULT NULL,
+  `retype` text,
+  `paramtype` text,
+  `outparamtype` text,
+  `interfaceid` int(11) DEFAULT NULL,
+  `postime` datetime DEFAULT NULL,
+  `lastuser` varchar(30) DEFAULT NULL,
+  `request_charset` varchar(16) NOT NULL,
+  `response_charset` varchar(16) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `objname` (`objname`,`funcname`),
+  UNIQUE KEY `objname_idx` (`objname`,`funcname`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_config_files`
+--
+
+DROP TABLE IF EXISTS `t_config_files`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_config_files` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `server_name` varchar(128) DEFAULT '',
+  `set_name` varchar(16) NOT NULL DEFAULT '',
+  `set_area` varchar(16) NOT NULL DEFAULT '',
+  `set_group` varchar(16) NOT NULL DEFAULT '',
+  `host` varchar(20) NOT NULL DEFAULT '',
+  `filename` varchar(128) DEFAULT NULL,
+  `config` longtext,
+  `posttime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `lastuser` varchar(50) DEFAULT NULL,
+  `level` int(11) DEFAULT '2',
+  `config_flag` int(10) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `application` (`server_name`,`filename`,`host`,`level`,`set_name`,`set_area`,`set_group`)
+) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_config_history_files`
+--
+
+DROP TABLE IF EXISTS `t_config_history_files`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_config_history_files` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `configid` int(11) DEFAULT NULL,
+  `reason` varchar(128) DEFAULT '',
+  `reason_select` varchar(20) NOT NULL DEFAULT '',
+  `content` longtext,
+  `posttime` datetime DEFAULT NULL,
+  `lastuser` varchar(50) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_config_references`
+--
+
+DROP TABLE IF EXISTS `t_config_references`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_config_references` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `config_id` int(11) DEFAULT NULL,
+  `reference_id` int(11) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `config_id` (`config_id`,`reference_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_group_priority`
+--
+
+DROP TABLE IF EXISTS `t_group_priority`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_group_priority` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(128) DEFAULT '',
+  `group_list` text,
+  `list_order` int(11) DEFAULT '0',
+  `station` varchar(128) NOT NULL DEFAULT '',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_machine_tars_info`
+--
+
+DROP TABLE IF EXISTS `t_machine_tars_info`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_machine_tars_info` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `application` varchar(100) NOT NULL DEFAULT '',
+  `server_name` varchar(100) NOT NULL DEFAULT '',
+  `app_server_name` varchar(50) NOT NULL DEFAULT '',
+  `node_name` varchar(50) NOT NULL DEFAULT '',
+  `location` varchar(255) NOT NULL DEFAULT '',
+  `machine_type` varchar(50) NOT NULL DEFAULT '',
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `update_person` varchar(64) NOT NULL DEFAULT '',
+  PRIMARY KEY (`application`,`server_name`,`node_name`),
+  UNIQUE KEY `id` (`id`),
+  UNIQUE KEY `tmachine_key` (`application`,`node_name`,`server_name`),
+  KEY `tmachine_i_2` (`node_name`,`server_name`),
+  KEY `tmachine_idx` (`node_name`,`server_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_node_info`
+--
+
+DROP TABLE IF EXISTS `t_node_info`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_node_info` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `node_name` varchar(128) DEFAULT '',
+  `node_obj` varchar(128) DEFAULT '',
+  `endpoint_ip` varchar(16) DEFAULT '',
+  `endpoint_port` int(11) DEFAULT '0',
+  `data_dir` varchar(128) DEFAULT '',
+  `load_avg1` float DEFAULT '0',
+  `load_avg5` float DEFAULT '0',
+  `load_avg15` float DEFAULT '0',
+  `last_reg_time` datetime DEFAULT '1970-01-01 00:08:00',
+  `last_heartbeat` datetime DEFAULT '1970-01-01 00:08:00',
+  `setting_state` enum('active','inactive') DEFAULT 'inactive',
+  `present_state` enum('active','inactive') DEFAULT 'inactive',
+  `tars_version` varchar(128) NOT NULL DEFAULT '',
+  `template_name` varchar(128) NOT NULL DEFAULT '',
+  `modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `group_id` int(11) DEFAULT '-1',
+  `label` text,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `node_name` (`node_name`),
+  KEY `indx_node_info_1` (`last_heartbeat`),
+  KEY `indx_node_info` (`last_heartbeat`)
+) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_profile_template`
+--
+
+DROP TABLE IF EXISTS `t_profile_template`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_profile_template` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `template_name` varchar(128) DEFAULT '',
+  `parents_name` varchar(128) DEFAULT '',
+  `profile` text NOT NULL,
+  `posttime` datetime DEFAULT NULL,
+  `lastuser` varchar(30) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `template_name` (`template_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_registry_info`
+--
+
+DROP TABLE IF EXISTS `t_registry_info`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_registry_info` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `locator_id` varchar(128) NOT NULL DEFAULT '',
+  `servant` varchar(128) NOT NULL DEFAULT '',
+  `endpoint` varchar(128) NOT NULL DEFAULT '',
+  `last_heartbeat` datetime DEFAULT '1970-01-01 00:08:00',
+  `present_state` enum('active','inactive') DEFAULT 'inactive',
+  `tars_version` varchar(128) NOT NULL DEFAULT '',
+  `modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `enable_group` char(1) DEFAULT 'N',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `locator_id` (`locator_id`,`servant`)
+) ENGINE=InnoDB AUTO_INCREMENT=4576264 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_server_conf`
+--
+
+DROP TABLE IF EXISTS `t_server_conf`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_server_conf` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `application` varchar(128) DEFAULT '',
+  `server_name` varchar(128) DEFAULT '',
+  `node_group` varchar(50) NOT NULL DEFAULT '',
+  `node_name` varchar(50) NOT NULL DEFAULT '',
+  `registry_timestamp` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+  `base_path` varchar(128) DEFAULT '',
+  `exe_path` varchar(128) NOT NULL DEFAULT '',
+  `template_name` varchar(128) NOT NULL DEFAULT '',
+  `bak_flag` int(11) NOT NULL DEFAULT '0',
+  `setting_state` enum('active','inactive') NOT NULL DEFAULT 'inactive',
+  `present_state` enum('active','inactive','activating','deactivating','destroyed') NOT NULL DEFAULT 'inactive',
+  `process_id` int(11) NOT NULL DEFAULT '0',
+  `patch_version` varchar(128) NOT NULL DEFAULT '',
+  `patch_time` datetime NOT NULL DEFAULT '2021-12-22 10:35:56',
+  `patch_user` varchar(128) NOT NULL DEFAULT '',
+  `tars_version` varchar(128) NOT NULL DEFAULT '',
+  `posttime` datetime DEFAULT NULL,
+  `lastuser` varchar(30) DEFAULT NULL,
+  `server_type` enum('tars_cpp','not_tars','tars_java','tars_nodejs','tars_php','tars_go') DEFAULT NULL,
+  `start_script_path` varchar(128) DEFAULT NULL,
+  `stop_script_path` varchar(128) DEFAULT NULL,
+  `monitor_script_path` varchar(128) DEFAULT NULL,
+  `enable_group` char(1) DEFAULT 'N',
+  `enable_set` char(1) NOT NULL DEFAULT 'N',
+  `set_name` varchar(16) DEFAULT NULL,
+  `set_area` varchar(16) DEFAULT NULL,
+  `set_group` varchar(64) DEFAULT NULL,
+  `ip_group_name` varchar(64) DEFAULT NULL,
+  `profile` text,
+  `config_center_port` int(11) NOT NULL DEFAULT '0',
+  `async_thread_num` int(11) DEFAULT '3',
+  `server_important_type` enum('0','1','2','3','4','5') DEFAULT '0',
+  `remote_log_reserve_time` varchar(32) NOT NULL DEFAULT '65',
+  `remote_log_compress_time` varchar(32) NOT NULL DEFAULT '2',
+  `remote_log_type` int(1) NOT NULL DEFAULT '0',
+  `flow_state` enum('active','inactive') NOT NULL DEFAULT 'active',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `application` (`application`,`server_name`,`node_name`),
+  KEY `node_name` (`node_name`),
+  KEY `index_i_3` (`setting_state`,`server_type`,`application`,`server_name`,`node_name`),
+  KEY `index_regtime` (`registry_timestamp`),
+  KEY `index_i` (`setting_state`,`server_type`,`application`,`server_name`,`node_name`)
+) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_server_group_relation`
+--
+
+DROP TABLE IF EXISTS `t_server_group_relation`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_server_group_relation` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `application` varchar(90) NOT NULL DEFAULT '',
+  `server_group` varchar(50) DEFAULT '',
+  `server_name` varchar(50) DEFAULT '',
+  `create_time` datetime DEFAULT NULL,
+  `creator` varchar(30) DEFAULT '',
+  PRIMARY KEY (`id`),
+  KEY `f_unique` (`application`,`server_group`,`server_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_server_group_rule`
+--
+
+DROP TABLE IF EXISTS `t_server_group_rule`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_server_group_rule` (
+  `group_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `ip_order` enum('allow_denny','denny_allow') NOT NULL DEFAULT 'denny_allow',
+  `allow_ip_rule` text,
+  `denny_ip_rule` text,
+  `lastuser` varchar(50) DEFAULT NULL,
+  `modify_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `group_name` varchar(128) DEFAULT '',
+  `group_name_cn` varchar(128) DEFAULT '',
+  PRIMARY KEY (`group_id`),
+  UNIQUE KEY `group_name_index` (`group_name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_server_notifys`
+--
+
+DROP TABLE IF EXISTS `t_server_notifys`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_server_notifys` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `application` varchar(128) DEFAULT '',
+  `server_name` varchar(128) DEFAULT NULL,
+  `container_name` varchar(128) DEFAULT '',
+  `node_name` varchar(128) NOT NULL DEFAULT '',
+  `set_name` varchar(16) DEFAULT NULL,
+  `set_area` varchar(16) DEFAULT NULL,
+  `set_group` varchar(16) DEFAULT NULL,
+  `server_id` varchar(100) DEFAULT NULL,
+  `thread_id` varchar(20) DEFAULT NULL,
+  `command` varchar(50) DEFAULT NULL,
+  `result` text,
+  `notifytime` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `index_name` (`server_name`),
+  KEY `servernoticetime_i_1` (`notifytime`),
+  KEY `indx_1_server_id` (`server_id`),
+  KEY `query_index` (`application`,`server_name`,`node_name`,`set_name`,`set_area`,`set_group`),
+  KEY `servernoticetime_i` (`notifytime`),
+  KEY `indx_server_id` (`server_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=21962 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_server_patchs`
+--
+
+DROP TABLE IF EXISTS `t_server_patchs`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_server_patchs` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `server` varchar(50) DEFAULT NULL,
+  `version` varchar(1000) DEFAULT '',
+  `tgz` varchar(255) DEFAULT NULL,
+  `update_text` varchar(255) DEFAULT NULL,
+  `reason_select` varchar(255) DEFAULT NULL,
+  `document_complate` varchar(30) DEFAULT NULL,
+  `is_server_group` int(2) NOT NULL DEFAULT '0',
+  `publish` int(3) DEFAULT NULL,
+  `publish_time` datetime DEFAULT NULL,
+  `publish_user` varchar(30) DEFAULT NULL,
+  `upload_time` datetime DEFAULT NULL,
+  `upload_user` varchar(30) DEFAULT NULL,
+  `posttime` datetime DEFAULT NULL,
+  `lastuser` varchar(30) DEFAULT NULL,
+  `is_release_version` enum('true','false') DEFAULT 'true',
+  `package_type` int(4) DEFAULT '0',
+  `group_id` varchar(64) NOT NULL DEFAULT '',
+  `default_version` int(4) DEFAULT '0',
+  `md5` varchar(40) DEFAULT NULL,
+  `svn_version` varchar(50) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `server_patchs_server_index` (`server`),
+  KEY `index_patchs_i1` (`server`),
+  KEY `index_i_2` (`tgz`(50)),
+  KEY `index_i` (`tgz`)
+) ENGINE=InnoDB AUTO_INCREMENT=170 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_task`
+--
+
+DROP TABLE IF EXISTS `t_task`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_task` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `task_no` varchar(40) DEFAULT NULL,
+  `serial` int(1) DEFAULT NULL,
+  `user_name` varchar(20) DEFAULT NULL,
+  `create_time` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `f_task` (`task_no`),
+  CONSTRAINT `t_task_ibfk_1` FOREIGN KEY (`task_no`) REFERENCES `t_task_item` (`task_no`) ON DELETE SET NULL ON UPDATE CASCADE
+) ENGINE=InnoDB AUTO_INCREMENT=119 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_task_item`
+--
+
+DROP TABLE IF EXISTS `t_task_item`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_task_item` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `task_no` varchar(40) DEFAULT NULL,
+  `item_no` varchar(40) DEFAULT NULL,
+  `application` varchar(30) DEFAULT NULL,
+  `server_name` varchar(50) DEFAULT NULL,
+  `node_name` varchar(20) DEFAULT NULL,
+  `command` varchar(20) DEFAULT NULL,
+  `parameters` text,
+  `start_time` datetime DEFAULT NULL,
+  `end_time` datetime DEFAULT NULL,
+  `status` int(11) DEFAULT NULL,
+  `set_name` varchar(20) DEFAULT NULL,
+  `log` text,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `f_uniq` (`item_no`,`task_no`),
+  KEY `f_task_no` (`task_no`),
+  KEY `f_index` (`application`,`server_name`,`command`)
+) ENGINE=InnoDB AUTO_INCREMENT=120 DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `t_web_release_conf`
+--
+
+DROP TABLE IF EXISTS `t_web_release_conf`;
+/*!40101 SET @saved_cs_client     = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `t_web_release_conf` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `server` varchar(100) NOT NULL DEFAULT '',
+  `path` varchar(200) NOT NULL DEFAULT '',
+  `server_dir` varchar(200) NOT NULL DEFAULT '',
+  `is_server_group` int(2) NOT NULL DEFAULT '0',
+  `enable_batch` int(2) NOT NULL DEFAULT '0',
+  `user` varchar(200) NOT NULL DEFAULT '*',
+  `posttime` datetime DEFAULT NULL,
+  `lastuser` varchar(60) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `server` (`server`,`is_server_group`),
+  KEY `web_release_conf_server_index` (`server`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
+
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed
diff --git a/t/tars/discovery/tars.t b/t/tars/discovery/tars.t
new file mode 100644
index 0000000..da85fb3
--- /dev/null
+++ b/t/tars/discovery/tars.t
@@ -0,0 +1,393 @@
+#
+# 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: 3
+    incremental_fetch_interval: 1
+_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 $extra_init_by_lua = <<_EOC_;
+        -- reduce incremental_fetch_interval,full_fetch_interval
+        local core = require("apisix.core")
+        local schema = require("apisix.discovery.tars.schema")
+        schema.properties.incremental_fetch_interval.minimum=1
+        schema.properties.incremental_fetch_interval.default=1
+        schema.properties.full_fetch_interval.minimum = 3
+        schema.properties.full_fetch_interval.default = 3
+_EOC_
+
+    $block->set_value("extra_init_by_lua", $extra_init_by_lua);
+
+    my $config = $block->config // <<_EOC_;
+        location /count {
+            content_by_lua_block {
+              local core = require("apisix.core")
+              local d = require("apisix.discovery.tars")
+
+              ngx.sleep(2)
+
+              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(2)
+
+              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);
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: create initial server and servant
+--- timeout: 3
+--- 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",
+]
+
+
+
+=== TEST 2: add servers on different nodes
+--- timeout: 3
+--- request eval
+[
+"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.2', now(), 'taf-cpp', 'active', 'active', 'tars_cpp'),
+       ('B', 'BServer', '172.16.2.2', now(), 'taf-cpp', 'active', 'active', 'tars_cpp'),
+       ('C', 'CServer', '172.16.3.2', now(), 'taf-cpp', 'active', 'active', 'tars_cpp')",
+
+"GET /count
+[\"A.AServer.FirstObj\",\"B.BServer.FirstObj\", \"C.CServer.FirstObj\"]",
+
+]
+--- response_body eval
+[
+    "DONE\n",
+    "{ 1 1 1 }\n",
+]
+
+
+
+=== TEST 3: add servant
+--- timeout: 3
+--- request eval
+[
+"POST /sql
+insert into t_adapter_conf(application, server_name, node_name, adapter_name, endpoint, servant)
+values ('A', 'AServer', '172.16.1.2', 'A.AServer.FirstObjAdapter',
+        'tcp -h 172.16.1.2 -p 10001 -e 0 -t 6000', 'A.AServer.FirstObj'),
+       ('A', 'AServer', '172.16.1.2', 'A.AServer.SecondObjAdapter',
+        'tcp -p 10002 -h 172.16.1.2 -e 0 -t 6000', 'A.AServer.SecondObj')",
+
+"GET /count
+[\"A.AServer.FirstObj\", \"A.AServer.SecondObj\", \"B.BServer.FirstObj\", \"C.CServer.FirstObj\"]",
+
+]
+--- response_body eval
+[
+    "DONE\n",
+    "{ 2 1 1 1 }\n",
+]
+
+
+
+=== TEST 4: update servant, update setting_state
+--- timeout: 3
+--- request eval
+[
+"POST /sql
+update t_server_conf set setting_state='inactive'
+where application = 'A' and server_name = 'AServer' and node_name = '172.16.1.2'",
+
+"GET /count
+[\"A.AServer.FirstObj\", \"A.AServer.SecondObj\", \"B.BServer.FirstObj\", \"C.CServer.FirstObj\"]",
+
+]
+--- response_body eval
+[
+    "DONE\n",
+    "{ 1 0 1 1 }\n",
+]
+
+
+
+=== TEST 5: update server setting_state
+--- timeout: 3
+--- request eval
+[
+"POST /sql
+update t_server_conf set setting_state='active', present_state='inactive'
+where application = 'A' and server_name = 'AServer' and node_name = '172.16.1.2'",
+
+"GET /count
+[\"A.AServer.FirstObj\", \"A.AServer.SecondObj\", \"B.BServer.FirstObj\", \"C.CServer.FirstObj\"]",
+
+]
+--- response_body eval
+[
+    "DONE\n",
+    "{ 1 0 1 1 }\n",
+]
+
+
+
+=== TEST 6: update server present_state
+--- timeout: 3
+--- request eval
+[
+"POST /sql
+update t_server_conf set setting_state='active', present_state='active'
+where application = 'A' and server_name = 'AServer' and node_name = '172.16.1.2'",
+
+"GET /count
+[\"A.AServer.FirstObj\", \"A.AServer.SecondObj\", \"B.BServer.FirstObj\", \"C.CServer.FirstObj\"]",
+
+]
+--- response_body eval
+[
+    "DONE\n",
+    "{ 2 1 1 1 }\n",
+]
+
+
+
+=== TEST 7: update servant endpoint
+--- timeout: 3
+--- request eval
+[
+"GET /nodes
+A.AServer.SecondObj",
+
+"POST /sql
+update t_adapter_conf set endpoint='tcp -h 172.16.1.2 -p 10003 -e 0 -t 3000'
+where application = 'A' and server_name = 'AServer'
+and node_name = '172.16.1.2' and servant='A.AServer.SecondObj'",
+
+"GET /nodes
+A.AServer.SecondObj",
+
+]
+--- response_body eval
+[
+    "{172.16.1.2:10002,}\n",
+    "DONE\n",
+    "{172.16.1.2:10003,}\n",
+]
+
+
+
+=== TEST 8: delete servant
+--- request eval
+[
+"POST /sql
+delete from t_adapter_conf where application = 'A' and server_name = 'AServer'
+and node_name = '172.16.1.2' and servant = 'A.AServer.SecondObj'",
+
+]
+--- response_body eval
+[
+    "DONE\n",
+]
+
+
+
+=== TEST 9: count after delete servant
+--- timeout: 4
+--- wait: 3
+--- request eval
+[
+"GET /count
+[\"A.AServer.FirstObj\", \"A.AServer.SecondObj\", \"B.BServer.FirstObj\", \"C.CServer.FirstObj\"]",
+
+]
+--- response_body eval
+[
+    "{ 2 0 1 1 }\n",
+]
+
+
+
+=== TEST 10: delete server
+--- request eval
+[
+"POST /sql
+delete from t_server_conf
+where application = 'A' and server_name = 'AServer' and node_name = '172.16.1.1'",
+
+]
+--- response_body eval
+[
+    "DONE\n",
+]
+
+
+
+=== TEST 11: count after delete
+--- timeout: 4
+--- wait: 3
+--- request eval
+[
+"GET /count
+[\"A.AServer.FirstObj\", \"A.AServer.SecondObj\", \"B.BServer.FirstObj\", \"C.CServer.FirstObj\"]",
+
+]
+--- response_body eval
+[
+    "{ 1 0 1 1 }\n",
+]