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 6accc8173 feat: V3 features from the next branch (#7585)
6accc8173 is described below

commit 6accc8173fd33d63f0eabe0de3081690baa8509b
Author: tzssangglass <[email protected]>
AuthorDate: Wed Aug 3 16:59:50 2022 +0800

    feat: V3 features from the next branch (#7585)
    
    * ci: add next branche to CI coverage (#7359)
    
    * feat: adjust some list uris of V3 API (#7364)
    
    * feat: response body format (#7366)
    
    * feat: support paging to get a list of objects  (#7379)
    
    * feat: filter fields supported by all objects (#7391)
    
    * resolve code review
---
 apisix/admin/consumers.lua     |   2 +
 apisix/admin/global_rules.lua  |   2 +
 apisix/admin/init.lua          |   2 +
 apisix/admin/plugin_config.lua |   2 +
 apisix/admin/proto.lua         |   2 +
 apisix/admin/routes.lua        |   2 +
 apisix/admin/services.lua      |   2 +
 apisix/admin/ssl.lua           |   2 +
 apisix/admin/stream_routes.lua |   2 +
 apisix/admin/upstreams.lua     |   2 +
 apisix/admin/utils.lua         |   4 +-
 apisix/admin/v3_adapter.lua    | 189 +++++++++++
 apisix/core/etcd.lua           |  12 +-
 conf/config-default.yaml       |   2 +
 t/admin/filter.t               | 754 +++++++++++++++++++++++++++++++++++++++++
 t/admin/protos.t               |  82 +++++
 t/admin/response_body_format.t | 237 +++++++++++++
 t/admin/ssls.t                 |  79 +++++
 18 files changed, 1372 insertions(+), 7 deletions(-)

diff --git a/apisix/admin/consumers.lua b/apisix/admin/consumers.lua
index 46b23de09..e9456a31b 100644
--- a/apisix/admin/consumers.lua
+++ b/apisix/admin/consumers.lua
@@ -18,6 +18,7 @@ local core    = require("apisix.core")
 local plugins = require("apisix.admin.plugins")
 local utils   = require("apisix.admin.utils")
 local plugin  = require("apisix.plugin")
+local v3_adapter = require("apisix.admin.v3_adapter")
 local pairs   = pairs
 
 local _M = {
@@ -102,6 +103,7 @@ function _M.get(consumer_name)
     end
 
     utils.fix_count(res.body, consumer_name)
+    v3_adapter.filter(res.body)
     return res.status, res.body
 end
 
diff --git a/apisix/admin/global_rules.lua b/apisix/admin/global_rules.lua
index c4dd4ca93..5cec0604f 100644
--- a/apisix/admin/global_rules.lua
+++ b/apisix/admin/global_rules.lua
@@ -17,6 +17,7 @@
 local core = require("apisix.core")
 local utils = require("apisix.admin.utils")
 local schema_plugin = require("apisix.admin.plugins").check_schema
+local v3_adapter = require("apisix.admin.v3_adapter")
 local type = type
 local tostring = tostring
 
@@ -97,6 +98,7 @@ function _M.get(id)
     end
 
     utils.fix_count(res.body, id)
+    v3_adapter.filter(res.body)
     return res.status, res.body
 end
 
diff --git a/apisix/admin/init.lua b/apisix/admin/init.lua
index 318348ecd..a7f6d1044 100644
--- a/apisix/admin/init.lua
+++ b/apisix/admin/init.lua
@@ -47,8 +47,10 @@ local resources = {
     consumers       = require("apisix.admin.consumers"),
     schema          = require("apisix.admin.schema"),
     ssl             = require("apisix.admin.ssl"),
+    ssls            = require("apisix.admin.ssl"),
     plugins         = require("apisix.admin.plugins"),
     proto           = require("apisix.admin.proto"),
+    protos          = require("apisix.admin.proto"),
     global_rules    = require("apisix.admin.global_rules"),
     stream_routes   = require("apisix.admin.stream_routes"),
     plugin_metadata = require("apisix.admin.plugin_metadata"),
diff --git a/apisix/admin/plugin_config.lua b/apisix/admin/plugin_config.lua
index bcf199fcd..06c100cdf 100644
--- a/apisix/admin/plugin_config.lua
+++ b/apisix/admin/plugin_config.lua
@@ -18,6 +18,7 @@ local core = require("apisix.core")
 local get_routes = require("apisix.router").http_routes
 local utils = require("apisix.admin.utils")
 local schema_plugin = require("apisix.admin.plugins").check_schema
+local v3_adapter = require("apisix.admin.v3_adapter")
 local type = type
 local tostring = tostring
 local ipairs = ipairs
@@ -97,6 +98,7 @@ function _M.get(id)
     end
 
     utils.fix_count(res.body, id)
+    v3_adapter.filter(res.body)
     return res.status, res.body
 end
 
diff --git a/apisix/admin/proto.lua b/apisix/admin/proto.lua
index 132db68a1..abe161f07 100644
--- a/apisix/admin/proto.lua
+++ b/apisix/admin/proto.lua
@@ -21,6 +21,7 @@ local utils = require("apisix.admin.utils")
 local get_routes = require("apisix.router").http_routes
 local get_services = require("apisix.http.service").services
 local compile_proto = 
require("apisix.plugins.grpc-transcode.proto").compile_proto
+local v3_adapter = require("apisix.admin.v3_adapter")
 local tostring = tostring
 
 
@@ -99,6 +100,7 @@ function _M.get(id)
     end
 
     utils.fix_count(res.body, id)
+    v3_adapter.filter(res.body)
     return res.status, res.body
 end
 
diff --git a/apisix/admin/routes.lua b/apisix/admin/routes.lua
index 877f6cf5e..ccfe0bf95 100644
--- a/apisix/admin/routes.lua
+++ b/apisix/admin/routes.lua
@@ -19,6 +19,7 @@ local core = require("apisix.core")
 local apisix_upstream = require("apisix.upstream")
 local schema_plugin = require("apisix.admin.plugins").check_schema
 local utils = require("apisix.admin.utils")
+local v3_adapter = require("apisix.admin.v3_adapter")
 local tostring = tostring
 local type = type
 local loadstring = loadstring
@@ -203,6 +204,7 @@ function _M.get(id)
     end
 
     utils.fix_count(res.body, id)
+    v3_adapter.filter(res.body)
     return res.status, res.body
 end
 
diff --git a/apisix/admin/services.lua b/apisix/admin/services.lua
index 59c53eec3..1872d5bb7 100644
--- a/apisix/admin/services.lua
+++ b/apisix/admin/services.lua
@@ -19,6 +19,7 @@ local get_routes = require("apisix.router").http_routes
 local apisix_upstream = require("apisix.upstream")
 local schema_plugin = require("apisix.admin.plugins").check_schema
 local utils = require("apisix.admin.utils")
+local v3_adapter = require("apisix.admin.v3_adapter")
 local tostring = tostring
 local ipairs = ipairs
 local type = type
@@ -146,6 +147,7 @@ function _M.get(id)
     end
 
     utils.fix_count(res.body, id)
+    v3_adapter.filter(res.body)
     return res.status, res.body
 end
 
diff --git a/apisix/admin/ssl.lua b/apisix/admin/ssl.lua
index 9a73107c9..70d868dc9 100644
--- a/apisix/admin/ssl.lua
+++ b/apisix/admin/ssl.lua
@@ -17,6 +17,7 @@
 local core              = require("apisix.core")
 local utils             = require("apisix.admin.utils")
 local apisix_ssl        = require("apisix.ssl")
+local v3_adapter        = require("apisix.admin.v3_adapter")
 local tostring          = tostring
 local type              = type
 
@@ -107,6 +108,7 @@ function _M.get(id)
     end
 
     utils.fix_count(res.body, id)
+    v3_adapter.filter(res.body)
     return res.status, res.body
 end
 
diff --git a/apisix/admin/stream_routes.lua b/apisix/admin/stream_routes.lua
index 6770830ac..625911c04 100644
--- a/apisix/admin/stream_routes.lua
+++ b/apisix/admin/stream_routes.lua
@@ -17,6 +17,7 @@
 local core = require("apisix.core")
 local utils = require("apisix.admin.utils")
 local stream_route_checker = 
require("apisix.stream.router.ip_port").stream_route_checker
+local v3_adapter = require("apisix.admin.v3_adapter")
 local tostring = tostring
 
 
@@ -114,6 +115,7 @@ function _M.get(id)
     end
 
     utils.fix_count(res.body, id)
+    v3_adapter.filter(res.body)
     return res.status, res.body
 end
 
diff --git a/apisix/admin/upstreams.lua b/apisix/admin/upstreams.lua
index 5aec65269..d262f3977 100644
--- a/apisix/admin/upstreams.lua
+++ b/apisix/admin/upstreams.lua
@@ -19,6 +19,7 @@ local get_routes = require("apisix.router").http_routes
 local get_services = require("apisix.http.service").services
 local apisix_upstream = require("apisix.upstream")
 local utils = require("apisix.admin.utils")
+local v3_adapter = require("apisix.admin.v3_adapter")
 local tostring = tostring
 local ipairs = ipairs
 local type = type
@@ -99,6 +100,7 @@ function _M.get(id)
     end
 
     utils.fix_count(res.body, id)
+    v3_adapter.filter(res.body)
     return res.status, res.body
 end
 
diff --git a/apisix/admin/utils.lua b/apisix/admin/utils.lua
index 3ff695a47..db73dda67 100644
--- a/apisix/admin/utils.lua
+++ b/apisix/admin/utils.lua
@@ -24,8 +24,8 @@ local _M = {}
 
 local function inject_timestamp(conf, prev_conf, patch_conf)
     if not conf.create_time then
-        if prev_conf and prev_conf.node.value.create_time then
-            conf.create_time = prev_conf.node.value.create_time
+        if prev_conf and (prev_conf.node or prev_conf.list).value.create_time 
then
+            conf.create_time = (prev_conf.node or 
prev_conf.list).value.create_time
         else
             -- As we don't know existent data's create_time, we have to pretend
             -- they are created now.
diff --git a/apisix/admin/v3_adapter.lua b/apisix/admin/v3_adapter.lua
new file mode 100644
index 000000000..aa9226a83
--- /dev/null
+++ b/apisix/admin/v3_adapter.lua
@@ -0,0 +1,189 @@
+--
+-- 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 fetch_local_conf  = require("apisix.core.config_local").local_conf
+local try_read_attr     = require("apisix.core.table").try_read_attr
+local log               = require("apisix.core.log")
+local request           = require("apisix.core.request")
+local response          = require("apisix.core.response")
+local table             = require("apisix.core.table")
+local tonumber          = tonumber
+local re_find           = ngx.re.find
+local pairs             = pairs
+
+local _M = {}
+
+
+local admin_api_version
+local function enable_v3()
+    if admin_api_version then
+        if admin_api_version == "v3" then
+            return true
+        end
+
+        if admin_api_version == "default" then
+            return false
+        end
+    end
+
+    local local_conf, err = fetch_local_conf()
+    if not local_conf then
+        admin_api_version = "default"
+        log.error("failed to fetch local conf: ", err)
+        return false
+    end
+
+    local api_ver = try_read_attr(local_conf, "apisix", "admin_api_version")
+    if api_ver ~= "v3" then
+        admin_api_version = "default"
+        return false
+    end
+
+    admin_api_version = api_ver
+    return true
+end
+_M.enable_v3 = enable_v3
+
+
+function _M.to_v3(body, action)
+    if not enable_v3() then
+        body.action = action
+    end
+end
+
+
+function _M.to_v3_list(body)
+    if not enable_v3() then
+        return
+    end
+
+    if body.node.dir then
+        body.list = body.node.nodes
+        body.node = nil
+    end
+end
+
+
+local function sort(l, r)
+    return l.createdIndex < r.createdIndex
+end
+
+
+local function pagination(body, args)
+    args.page = tonumber(args.page)
+    args.page_size = tonumber(args.page_size)
+    if not args.page or not args.page_size then
+        return
+    end
+
+    if args.page_size < 10 or args.page_size > 500 then
+        return response.exit(400, "page_size must be between 10 and 500")
+    end
+
+    if not args.page or args.page < 1 then
+        -- default page is 1
+        args.page = 1
+    end
+
+    local list = body.list
+
+    -- sort nodes by there createdIndex
+    table.sort(list, sort)
+
+    local to = args.page * args.page_size
+    local from =  to - args.page_size + 1
+
+    local res = table.new(20, 0)
+
+    for i = from, to do
+        if list[i] then
+            res[i - from + 1] = list[i]
+        end
+    end
+
+    body.list = res
+end
+
+
+local function filter(body, args)
+    if not args.name and not args.label and not args.uri then
+        return
+    end
+
+    for i = #body.list, 1, -1 do
+        local name_matched = true
+        local label_matched = true
+        local uri_matched = true
+        if args.name then
+            name_matched = false
+            local matched = re_find(body.list[i].value.name, args.name, "jo")
+            if matched then
+                name_matched = true
+            end
+        end
+
+        if args.label then
+            label_matched = false
+            if body.list[i].value.labels then
+                for k, _ in pairs(body.list[i].value.labels) do
+                    if k == args.label then
+                        label_matched = true
+                        break
+                    end
+                end
+            end
+        end
+
+        if args.uri then
+            uri_matched = false
+            if body.list[i].value.uri then
+                local matched = re_find(body.list[i].value.uri, args.uri, "jo")
+                if matched then
+                    uri_matched = true
+                end
+            end
+
+            if body.list[i].value.uris then
+                for _, uri in pairs(body.list[i].value.uris) do
+                    if re_find(uri, args.uri, "jo") then
+                        uri_matched = true
+                        break
+                    end
+                end
+            end
+        end
+
+        if not name_matched or not label_matched or not uri_matched then
+            table.remove(body.list, i)
+        end
+    end
+end
+
+
+function _M.filter(body)
+    if not enable_v3() then
+        return
+    end
+
+    local args = request.get_uri_args()
+
+    pagination(body, args)
+    filter(body, args)
+end
+
+
+return _M
diff --git a/apisix/core/etcd.lua b/apisix/core/etcd.lua
index 7f0972e92..7ac08334e 100644
--- a/apisix/core/etcd.lua
+++ b/apisix/core/etcd.lua
@@ -21,6 +21,7 @@
 
 local fetch_local_conf  = require("apisix.core.config_local").local_conf
 local array_mt          = require("apisix.core.json").array_mt
+local v3_adapter        = require("apisix.admin.v3_adapter")
 local etcd              = require("resty.etcd")
 local clone_tab         = require("table.clone")
 local health_check      = require("resty.etcd.health_check")
@@ -218,7 +219,7 @@ function _M.get_format(res, real_key, is_dir, formatter)
         return not_found(res)
     end
 
-    res.body.action = "get"
+    v3_adapter.to_v3(res.body, "get")
 
     if formatter then
         return formatter(res)
@@ -246,6 +247,7 @@ function _M.get_format(res, real_key, is_dir, formatter)
     end
 
     res.body.kvs = nil
+    v3_adapter.to_v3_list(res.body)
     return res
 end
 
@@ -326,7 +328,7 @@ local function set(key, value, ttl)
     res.headers["X-Etcd-Index"] = res.body.header.revision
 
     -- etcd v3 set would not return kv info
-    res.body.action = "set"
+    v3_adapter.to_v3(res.body, "set")
     res.body.node = {}
     res.body.node.key = prefix .. key
     res.body.node.value = value
@@ -389,7 +391,7 @@ function _M.atomic_set(key, value, ttl, mod_revision)
 
     res.headers["X-Etcd-Index"] = res.body.header.revision
     -- etcd v3 set would not return kv info
-    res.body.action = "compareAndSwap"
+    v3_adapter.to_v3(res.body, "compareAndSwap")
     res.body.node = {
         key = key,
         value = value,
@@ -427,7 +429,7 @@ function _M.push(key, value, ttl)
         return nil, err
     end
 
-    res.body.action = "create"
+    v3_adapter.to_v3(res.body, "create")
     return res, nil
 end
 
@@ -451,7 +453,7 @@ function _M.delete(key)
     end
 
     -- etcd v3 set would not return kv info
-    res.body.action = "delete"
+    v3_adapter.to_v3(res.body, "delete")
     res.body.node = {}
     res.body.key = prefix .. key
 
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index c565b4869..642d4e89d 100755
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -85,6 +85,8 @@ apisix:
     admin_ssl_cert_key: ""      # Path of your self-signed server side key.
     admin_ssl_ca_cert: ""       # Path of your self-signed ca cert.The CA is 
used to sign all admin api callers' certificates.
 
+  #admin_api_version: v3        # The version of admin api, latest version is 
v3.
+
   # Default token when use API to call for Admin API.
   # *NOTE*: Highly recommended to modify this value to protect APISIX's Admin 
API.
   # Disabling this configuration item means that the Admin API does not
diff --git a/t/admin/filter.t b/t/admin/filter.t
new file mode 100644
index 000000000..ea2b004f6
--- /dev/null
+++ b/t/admin/filter.t
@@ -0,0 +1,754 @@
+#
+# 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);
+worker_connections(1024);
+no_root_location();
+no_shuffle();
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $user_yaml_config = <<_EOC_;
+apisix:
+    node_listen: 1984
+    admin_key: null
+    admin_api_version: v3
+_EOC_
+    $block->set_value("yaml_config", $user_yaml_config);
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: bad page_size(page_size must be between 10 and 500)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            ngx.sleep(0.5)
+
+            local code, body = t('/apisix/admin/routes/?page=1&page_size=2',
+                ngx.HTTP_GET
+            )
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- error_code: 400
+--- response_body
+page_size must be between 10 and 500
+
+
+
+=== TEST 2: ignore bad page and would use default value 1
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+
+            for i = 1, 11 do
+                local code, body = t('/apisix/admin/routes/' .. i,
+                    ngx.HTTP_PUT,
+                    [[{
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/hello]] .. i .. [["
+                    }]]
+                )
+            end
+
+            ngx.sleep(0.5)
+
+            local code, body, res = 
t('/apisix/admin/routes/?page=-1&page_size=10',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            assert(#res.list == 10)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 3: sort by createdIndex
+# the smaller the createdIndex, the higher the ranking
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+
+            for i = 1, 11 do
+                local code, body = t('/apisix/admin/routes/' .. i,
+                    ngx.HTTP_PUT,
+                    [[{
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/hello]] .. i .. [["
+                    }]]
+                )
+            end
+
+            ngx.sleep(0.5)
+
+            local code, body, res = 
t('/apisix/admin/routes/?page=1&page_size=10',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+
+            for i = 1, #res.list - 1 do
+                assert(res.list[i].createdIndex < res.list[i + 1].createdIndex)
+            end
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 4: routes pagination
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+
+            for i = 1, 11 do
+                local code, body = t('/apisix/admin/routes/' .. i,
+                    ngx.HTTP_PUT,
+                    [[{
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/hello]] .. i .. [["
+                    }]]
+                )
+            end
+
+            ngx.sleep(0.5)
+
+            local code, body, res = 
t('/apisix/admin/routes/?page=1&page_size=10',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            assert(#res.list == 10)
+
+            code, body, res = t('/apisix/admin/routes/?page=2&page_size=10',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            assert(#res.list == 1)
+
+            code, body, res = t('/apisix/admin/routes/?page=3&page_size=10',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            assert(#res.list == 0)
+
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: services pagination
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+
+            for i = 1, 11 do
+                local code, body = t('/apisix/admin/services/' .. i,
+                    ngx.HTTP_PUT,
+                    [[{
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        }
+                    }]]
+                )
+            end
+
+            ngx.sleep(0.5)
+
+            local code, body, res = 
t('/apisix/admin/services/?page=1&page_size=10',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            assert(#res.list == 10)
+
+            code, body, res = t('/apisix/admin/services/?page=2&page_size=10',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            assert(#res.list == 1)
+
+            code, body, res = t('/apisix/admin/services/?page=3&page_size=10',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            assert(#res.list == 0)
+
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 6: only search name or labels
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            for i = 1, 11 do
+                local code, body = t('/apisix/admin/services/' .. i,
+                    ngx.HTTP_PUT,
+                    [[{
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "name": "]] .. i .. [[",
+                        "labels": {"]] .. i .. '":"' .. i .. [["}
+                    }]]
+                )
+            end
+
+            ngx.sleep(0.5)
+
+            local matched = {1, 10, 11}
+
+            local code, body, res = t('/apisix/admin/services/?name=1',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            -- match the name are 1, 10, 11
+            assert(#res.list == 3)
+
+            for _, node in ipairs(res.list) do
+                assert(core.table.array_find(matched, 
tonumber(node.value.name)))
+            end
+
+            code, body, res = t('/apisix/admin/services/?label=1',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            -- match the label are 1, 10, 11
+            assert(#res.list == 1)
+            assert(res.list[1].value.id == "1")
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 7: services filter
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            for i = 1, 11 do
+                local code, body = t('/apisix/admin/services/' .. i,
+                    ngx.HTTP_PUT,
+                    [[{
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "name": "]] .. i .. [["
+                    }]]
+                )
+            end
+
+            ngx.sleep(0.5)
+
+            local code, body, res = t('/apisix/admin/services/?name=1',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+
+            -- match the name and label are 1, 10, 11
+            assert(#res.list == 3)
+
+            local matched = {1, 10, 11}
+            for _, node in ipairs(res.list) do
+                assert(core.table.array_find(matched, 
tonumber(node.value.name)))
+            end
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 8: routes filter
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            for i = 1, 11 do
+                local code, body = t('/apisix/admin/routes/' .. i,
+                    ngx.HTTP_PUT,
+                    [[{
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "name": "]] .. i .. [[",
+                        "uri": "]] .. i .. [["
+                    }]]
+                )
+            end
+
+            ngx.sleep(0.5)
+
+            local code, body, res = t('/apisix/admin/services/?name=1',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+
+            -- match the name and label are 1, 10, 11
+            assert(#res.list == 3)
+
+            local matched = {1, 10, 11}
+            for _, node in ipairs(res.list) do
+                assert(core.table.array_find(matched, 
tonumber(node.value.name)))
+            end
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 9: filter with pagination
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            local code, body, res = 
t('/apisix/admin/services/?name=1&page=1&page_size=10',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+
+            -- match the name and label are 1, 10
+            assert(#res.list == 2)
+
+            local matched = {1, 10}
+            for _, node in ipairs(res.list) do
+                assert(core.table.array_find(matched, 
tonumber(node.value.name)))
+            end
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 10: routes filter with uri
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            for i = 1, 11 do
+                local code, body = t('/apisix/admin/routes/' .. i,
+                    ngx.HTTP_PUT,
+                    [[{
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1980": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "name": "]] .. i .. [[",
+                        "uri": "]] .. i .. [["
+                    }]]
+                )
+            end
+
+            ngx.sleep(0.5)
+
+            local code, body, res = t('/apisix/admin/routes/?uri=1',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+
+            -- match the name and label are 1, 10, 11
+            assert(#res.list == 3)
+
+            local matched = {1, 10, 11}
+            for _, node in ipairs(res.list) do
+                assert(core.table.array_find(matched, 
tonumber(node.value.name)))
+            end
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 11: match labels
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello",
+                    "labels": {
+                        "env": "production"
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello2",
+                    "labels": {
+                        "env2": "production"
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            ngx.sleep(0.5)
+
+            -- only match labels' keys
+            local code, body, res = t('/apisix/admin/routes/?label=env',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            assert(#res.list == 1)
+            assert(res.list[1].value.id == "1")
+
+            -- don't match labels' values
+            code, body, res = t('/apisix/admin/routes/?label=production',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            assert(#res.list == 0)
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 12: match uris
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uris": ["/hello", "/world"]
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uris": ["/foo", "/bar"]
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            ngx.sleep(0.5)
+
+            local code, body, res = t('/apisix/admin/routes/?uri=world',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            assert(#res.list == 1)
+            assert(res.list[1].value.id == "1")
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 13: match uris & labels
+# uris are same in different routes, filter by labels
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uris": ["/hello", "/world"],
+                    "labels": {
+                        "env": "production"
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uris": ["/hello", "/world"],
+                    "labels": {
+                        "build": "16"
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            ngx.sleep(0.5)
+
+            -- only match route 1
+            local code, body, res = 
t('/apisix/admin/routes/?uri=world&label=env',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            assert(#res.list == 1)
+            assert(res.list[1].value.id == "1")
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 14: match uri & labels
+# uri is same in different routes, filter by labels
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local core = require("apisix.core")
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello",
+                    "labels": {
+                        "env": "production"
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello",
+                    "labels": {
+                        "env2": "production"
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            ngx.sleep(0.5)
+
+            local code, body, res = 
t('/apisix/admin/routes/?uri=hello&label=env',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            assert(#res.list == 1)
+            assert(res.list[1].value.id == "1")
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
diff --git a/t/admin/protos.t b/t/admin/protos.t
new file mode 100644
index 000000000..6265020e4
--- /dev/null
+++ b/t/admin/protos.t
@@ -0,0 +1,82 @@
+#
+# 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);
+no_long_string();
+no_root_location();
+no_shuffle();
+log_level("info");
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    if (!$block->no_error_log && !$block->error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: test /apisix/admin/protos/{id}
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/protos/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                        "content": "syntax = \"proto3\";
+                        package proto;
+                        message HelloRequest{
+                            string name = 1;
+                        }
+
+                        message HelloResponse{
+                            int32 code = 1;
+                            string msg = 2;
+                        }
+                        // The greeting service definition.
+                        service Hello {
+                            // Sends a greeting
+                            rpc SayHi (HelloRequest) returns (HelloResponse){}
+                        }"
+                }]],
+                [[
+                    {
+                        "action": "set"
+                    }
+                ]]
+                )
+
+            if code ~= 200 then
+                ngx.status = code
+                ngx.say("[put proto] code: ", code, " message: ", message)
+                return
+            end
+
+            ngx.say("[put proto] code: ", code, " message: ", message)
+        }
+    }
+--- response_body
+[put proto] code: 200 message: passed
diff --git a/t/admin/response_body_format.t b/t/admin/response_body_format.t
new file mode 100644
index 000000000..246e78154
--- /dev/null
+++ b/t/admin/response_body_format.t
@@ -0,0 +1,237 @@
+#
+# 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);
+no_long_string();
+no_root_location();
+no_shuffle();
+log_level("info");
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    my $user_yaml_config = <<_EOC_;
+apisix:
+    node_listen: 1984
+    admin_key: null
+    admin_api_version: v3
+_EOC_
+    $block->set_value("yaml_config", $user_yaml_config);
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    if (!$block->no_error_log && !$block->error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: use v3 admin api, no action in response body
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:8080": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "desc": "new route",
+                    "uri": "/index.html"
+                }]],
+                [[{
+                    "node": {
+                        "value": {
+                            "methods": [
+                                "GET"
+                            ],
+                            "uri": "/index.html",
+                            "desc": "new route",
+                            "upstream": {
+                                "nodes": {
+                                    "127.0.0.1:8080": 1
+                                },
+                                "type": "roundrobin"
+                            }
+                        },
+                        "key": "/apisix/routes/1"
+                    }
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 2: response body format only have count and list(count is 1)
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+            local code, message, res = t('/apisix/admin/routes',
+                ngx.HTTP_GET
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(message)
+                return
+            end
+
+            res = json.decode(res)
+            assert(res.count == #res.list)
+            assert(res.action == nil)
+            assert(res.node == nil)
+            assert(res.list.key == nil)
+            assert(res.list.dir == nil)
+            ngx.say(json.encode(res))
+        }
+    }
+--- response_body eval
+qr/\{"count":1,"list":\[\{.*\}\]/
+
+
+
+=== TEST 3: response body format only have count and list(count is 2)
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:8080": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "desc": "new route",
+                    "uri": "/index.html"
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+            end
+
+            local code, message, res = t('/apisix/admin/routes',
+                ngx.HTTP_GET
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(message)
+                return
+            end
+
+            res = json.decode(res)
+            assert(res.count == #res.list)
+            assert(res.action == nil)
+            assert(res.node == nil)
+            assert(res.list.key == nil)
+            assert(res.list.dir == nil)
+            ngx.say(json.encode(res))
+        }
+    }
+--- response_body eval
+qr/\{"count":2,"list":\[\{.*\},\{.*\}\]/
+
+
+
+=== TEST 4: response body format(test services)
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/services/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "desc": "new service 001"
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+
+            local code, body = t('/apisix/admin/services/2',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "desc": "new service 002"
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+
+            local code, message, res = t('/apisix/admin/services',
+                ngx.HTTP_GET
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(message)
+                return
+            end
+
+            res = json.decode(res)
+            assert(res.count == #res.list)
+            assert(res.action == nil)
+            assert(res.node == nil)
+            assert(res.list.key == nil)
+            assert(res.list.dir == nil)
+            ngx.say(json.encode(res))
+        }
+    }
+--- response_body eval
+qr/\{"count":2,"list":\[\{.*\},\{.*\}\]/
diff --git a/t/admin/ssls.t b/t/admin/ssls.t
new file mode 100644
index 000000000..33c98e11e
--- /dev/null
+++ b/t/admin/ssls.t
@@ -0,0 +1,79 @@
+#
+# 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);
+no_long_string();
+no_root_location();
+no_shuffle();
+log_level("info");
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    if (!$block->no_error_log && !$block->error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: test /apisix/admin/ssls/{id}
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local etcd = require("apisix.core.etcd")
+            local t = require("lib.test_admin")
+
+            local ssl_cert = t.read_file("t/certs/apisix.crt")
+            local ssl_key =  t.read_file("t/certs/apisix.key")
+            local data = {cert = ssl_cert, key = ssl_key, sni = "test.com"}
+
+            local code, body = t.test('/apisix/admin/ssls/1',
+                ngx.HTTP_PUT,
+                core.json.encode(data),
+                [[{
+                    "node": {
+                        "value": {
+                            "sni": "test.com"
+                        },
+                        "key": "/apisix/ssl/1"
+                    },
+                    "action": "set"
+                }]]
+                )
+
+            ngx.status = code
+            ngx.say(body)
+
+            local res = assert(etcd.get('/ssl/1'))
+            local prev_create_time = res.body.node.value.create_time
+            assert(prev_create_time ~= nil, "create_time is nil")
+            local update_time = res.body.node.value.update_time
+            assert(update_time ~= nil, "update_time is nil")
+
+        }
+    }
+--- response_body
+passed

Reply via email to