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

monkeydluffy 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 3e3b410b8 feat: support force delete resource (#9810)
3e3b410b8 is described below

commit 3e3b410b8cd23965f6102723f74a0d7054e83821
Author: Sarasa Kisaragi <[email protected]>
AuthorDate: Fri Jul 14 15:05:32 2023 +0800

    feat: support force delete resource (#9810)
---
 apisix/admin/proto.lua                |   2 +-
 apisix/admin/resource.lua             |   4 +-
 docs/en/latest/admin-api.md           |  27 ++++++
 docs/zh/latest/admin-api.md           |  27 ++++++
 t/admin/consumer-group-force-delete.t | 163 +++++++++++++++++++++++++++++++
 t/admin/plugin-configs-force-delete.t | 163 +++++++++++++++++++++++++++++++
 t/admin/protos-force-delete.t         | 175 ++++++++++++++++++++++++++++++++++
 t/admin/services-force-delete.t       | 156 ++++++++++++++++++++++++++++++
 t/admin/upstream-force-delete.t       | 154 ++++++++++++++++++++++++++++++
 9 files changed, 868 insertions(+), 3 deletions(-)

diff --git a/apisix/admin/proto.lua b/apisix/admin/proto.lua
index de4d24e23..f8133cc80 100644
--- a/apisix/admin/proto.lua
+++ b/apisix/admin/proto.lua
@@ -50,7 +50,7 @@ local function check_proto_used(plugins, deleting, ptype, pid)
         if type(plugins) == "table" and plugins["grpc-transcode"]
            and plugins["grpc-transcode"].proto_id
            and tostring(plugins["grpc-transcode"].proto_id) == deleting then
-            return false, {error_msg = "can not delete this proto,"
+            return false, {error_msg = "can not delete this proto, "
                                      .. ptype .. " [" .. pid
                                      .. "] is still using it now"}
         end
diff --git a/apisix/admin/resource.lua b/apisix/admin/resource.lua
index bfc6789df..35fe3bba2 100644
--- a/apisix/admin/resource.lua
+++ b/apisix/admin/resource.lua
@@ -230,7 +230,7 @@ function _M:put(id, conf, sub_path, args)
 end
 
 -- Keep the unused conf to make the args list consistent with other methods
-function _M:delete(id, conf, sub_path)
+function _M:delete(id, conf, sub_path, uri_args)
     if core.table.array_find(self.unsupported_methods, "delete") then
         return 405, {error_msg = "not supported `DELETE` method for " .. 
self.kind}
     end
@@ -253,7 +253,7 @@ function _M:delete(id, conf, sub_path)
 
     key = key .. "/" .. id
 
-    if self.delete_checker then
+    if self.delete_checker and uri_args.force ~= "true" then
         local code, err = self.delete_checker(id)
         if err then
             return code, err
diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md
index af2775e7b..5096684d0 100644
--- a/docs/en/latest/admin-api.md
+++ b/docs/en/latest/admin-api.md
@@ -103,6 +103,33 @@ deployment:
 
 This will find the environment variable `ADMIN_KEY` first, and if it does not 
exist, it will use `edd1c9f034335f136f87ad84b625c8f1` as the default value.
 
+### Force Delete
+
+By default, the Admin API checks for references between resources and will 
refuse to delete resources in use.
+
+You can make a force deletion by adding the request argument `force=true` to 
the delete request, for example:
+
+```bash
+$ curl http://127.0.0.1:9180/apisix/admin/upstreams/1 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{
+    "nodes": {
+        "127.0.0.1:8080": 1
+    },
+    "type": "roundrobin"
+}'
+$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{
+    "uri": "/*",
+    "upstream_id": 1
+}'
+{"value":{"priority":0,"upstream_id":1,"uri":"/*","create_time":1689038794,"id":"1","status":1,"update_time":1689038916},"key":"/apisix/routes/1"}
+
+$ curl http://127.0.0.1:9180/apisix/admin/upstreams/1 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X DELETE
+{"error_msg":"can not delete this upstream, route [1] is still using it now"}
+$ curl "http://127.0.0.1:9180/apisix/admin/upstreams/1?force=anyvalue"; -H 
'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X DELETE
+{"error_msg":"can not delete this upstream, route [1] is still using it now"}
+$ curl "http://127.0.0.1:9180/apisix/admin/upstreams/1?force=true"; -H 
'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X DELETE
+{"deleted":"1","key":"/apisix/upstreams/1"}
+```
+
 ## V3 new feature
 
 The Admin API has made some breaking changes in V3 version, as well as 
supporting additional features.
diff --git a/docs/zh/latest/admin-api.md b/docs/zh/latest/admin-api.md
index 5e623d990..f2f4d1f81 100644
--- a/docs/zh/latest/admin-api.md
+++ b/docs/zh/latest/admin-api.md
@@ -105,6 +105,33 @@ deployment:
 
 首先查找环境变量 `ADMIN_KEY`,如果该环境变量不存在,它将使用 `edd1c9f034335f136f87ad84b625c8f1` 作为默认值。
 
+### 强制删除 {#force-delete}
+
+默认情况下,Admin API 会检查资源间的引用关系,将会拒绝删除正在使用中的资源。
+
+可以通过在删除请求中添加请求参数 `force=true` 来进行强制删除,例如:
+
+```bash
+$ curl http://127.0.0.1:9180/apisix/admin/upstreams/1 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{
+    "nodes": {
+        "127.0.0.1:8080": 1
+    },
+    "type": "roundrobin"
+}'
+$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{
+    "uri": "/*",
+    "upstream_id": 1
+}'
+{"value":{"priority":0,"upstream_id":1,"uri":"/*","create_time":1689038794,"id":"1","status":1,"update_time":1689038916},"key":"/apisix/routes/1"}
+
+$ curl http://127.0.0.1:9180/apisix/admin/upstreams/1 -H 'X-API-KEY: 
edd1c9f034335f136f87ad84b625c8f1' -X DELETE
+{"error_msg":"can not delete this upstream, route [1] is still using it now"}
+$ curl "http://127.0.0.1:9180/apisix/admin/upstreams/1?force=anyvalue"; -H 
'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X DELETE
+{"error_msg":"can not delete this upstream, route [1] is still using it now"}
+$ curl "http://127.0.0.1:9180/apisix/admin/upstreams/1?force=true"; -H 
'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X DELETE
+{"deleted":"1","key":"/apisix/upstreams/1"}
+```
+
 ## v3 版本新功能 {#v3-new-function}
 
 在 APISIX v3 版本中,Admin API 支持了一些不向下兼容的新特性,比如支持新的响应体格式、支持分页查询、支持过滤资源等。
diff --git a/t/admin/consumer-group-force-delete.t 
b/t/admin/consumer-group-force-delete.t
new file mode 100644
index 000000000..4b2fb2d09
--- /dev/null
+++ b/t/admin/consumer-group-force-delete.t
@@ -0,0 +1,163 @@
+#
+# 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: set consumer_group(id: 1)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/consumer_groups/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "plugins": {
+                        "limit-count": {
+                            "count": 200,
+                            "time_window": 60,
+                            "rejected_code": 503,
+                            "group": "$consumer_group_id"
+                        }
+                    }
+                }]]
+                )
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- error_code: 201
+--- response_body
+passed
+
+
+
+=== TEST 2: add consumer
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/consumers/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "username": "1",
+                    "plugins": {
+                        "key-auth": {
+                            "key": "auth-one"
+                        }
+                    },
+                    "group_id": "1"
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+                ngx.print(message)
+                return
+            end
+            ngx.say(message)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 3: delete consumer_group(wrong header)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = 
t('/apisix/admin/consumer_groups/1?force=anyvalue',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body
+[delete] code: 400 message: {"error_msg":"can not delete this consumer group, 
consumer [1] is still using it now"}
+
+
+
+=== TEST 4: delete consumer_group(without force delete header)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/consumer_groups/1',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body
+[delete] code: 400 message: {"error_msg":"can not delete this consumer group, 
consumer [1] is still using it now"}
+
+
+
+=== TEST 5: delete consumer_group(force delete)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = 
t('/apisix/admin/consumer_groups/1?force=true',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body chomp
+[delete] code: 200 message: passed
+
+
+
+=== TEST 6: delete consumer
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/consumers/1',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body chomp
+[delete] code: 200 message: passed
diff --git a/t/admin/plugin-configs-force-delete.t 
b/t/admin/plugin-configs-force-delete.t
new file mode 100644
index 000000000..7d4f73739
--- /dev/null
+++ b/t/admin/plugin-configs-force-delete.t
@@ -0,0 +1,163 @@
+#
+# 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: set plugin_configs(id: 1)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/plugin_configs/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "plugins": {
+                        "limit-count": {
+                            "count": 2,
+                            "time_window": 60,
+                            "rejected_code": 503
+                        }
+                    }
+                }]]
+                )
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- error_code: 201
+--- response_body
+passed
+
+
+
+=== TEST 2: add route
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugin_config_id": 1,
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        }
+                    },
+                    "uri": "/index.html"
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+                ngx.print(message)
+                return
+            end
+            ngx.say(message)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 3: delete plugin_configs(wrong header)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = 
t('/apisix/admin/plugin_configs/1?force=anyvalue',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body
+[delete] code: 400 message: {"error_msg":"can not delete this plugin config, 
route [1] is still using it now"}
+
+
+
+=== TEST 4: delete plugin_configs(without force delete header)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/plugin_configs/1',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body
+[delete] code: 400 message: {"error_msg":"can not delete this plugin config, 
route [1] is still using it now"}
+
+
+
+=== TEST 5: delete plugin_configs(force delete)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = 
t('/apisix/admin/plugin_configs/1?force=true',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body chomp
+[delete] code: 200 message: passed
+
+
+
+=== TEST 6: delete route
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/routes/1',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body chomp
+[delete] code: 200 message: passed
diff --git a/t/admin/protos-force-delete.t b/t/admin/protos-force-delete.t
new file mode 100644
index 000000000..909128924
--- /dev/null
+++ b/t/admin/protos-force-delete.t
@@ -0,0 +1,175 @@
+#
+# 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: set proto(id: 1)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/protos/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "content" : "syntax = \"proto3\";
+                    package helloworld;
+                    service Greeter {
+                        rpc SayHello (HelloRequest) returns (HelloReply) {}
+                    }
+                    message HelloRequest {
+                        string name = 1;
+                    }
+                    message HelloReply {
+                        string message = 1;
+                    }"
+                }]]
+                )
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- error_code: 201
+--- response_body
+passed
+
+
+
+=== TEST 2: add route
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "methods": ["GET"],
+                    "uri": "/grpctest",
+                    "plugins": {
+                        "grpc-transcode": {
+                         "proto_id": "1",
+                         "service": "helloworld.Greeter",
+                         "method": "SayHello"
+                        }
+                    },
+                    "upstream": {
+                        "scheme": "grpc",
+                        "type": "roundrobin",
+                        "nodes": {
+                            "127.0.0.1:50051": 1
+                        }
+                    }
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+                ngx.print(message)
+                return
+            end
+            ngx.say(message)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 3: delete proto(wrong header)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/protos/1?force=anyvalue',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body
+[delete] code: 400 message: {"error_msg":"can not delete this proto, route [1] 
is still using it now"}
+
+
+
+=== TEST 4: delete proto(without force delete header)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/protos/1',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body
+[delete] code: 400 message: {"error_msg":"can not delete this proto, route [1] 
is still using it now"}
+
+
+
+=== TEST 5: delete proto(force delete)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/protos/1?force=true',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body chomp
+[delete] code: 200 message: passed
+
+
+
+=== TEST 6: delete route
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/routes/1',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body chomp
+[delete] code: 200 message: passed
diff --git a/t/admin/services-force-delete.t b/t/admin/services-force-delete.t
new file mode 100644
index 000000000..439b44e09
--- /dev/null
+++ b/t/admin/services-force-delete.t
@@ -0,0 +1,156 @@
+#
+# 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: set service(id: 1)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/services/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:8080": 1
+                        },
+                        "type": "roundrobin"
+                    }
+                }]]
+                )
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- error_code: 201
+--- response_body
+passed
+
+
+
+=== TEST 2: add route
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "service_id": 1,
+                    "uri": "/index.html"
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+                ngx.print(message)
+                return
+            end
+            ngx.say(message)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 3: delete service(wrong header)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/services/1?force=anyvalue',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body
+[delete] code: 400 message: {"error_msg":"can not delete this service 
directly, route [1] is still using it now"}
+
+
+
+=== TEST 4: delete service(without force delete header)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/services/1',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body
+[delete] code: 400 message: {"error_msg":"can not delete this service 
directly, route [1] is still using it now"}
+
+
+
+=== TEST 5: delete service(force delete)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/services/1?force=true',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body chomp
+[delete] code: 200 message: passed
+
+
+
+=== TEST 6: delete route
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/routes/1',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body chomp
+[delete] code: 200 message: passed
diff --git a/t/admin/upstream-force-delete.t b/t/admin/upstream-force-delete.t
new file mode 100644
index 000000000..6d834b114
--- /dev/null
+++ b/t/admin/upstream-force-delete.t
@@ -0,0 +1,154 @@
+#
+# 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: set upstream(id: 1)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/upstreams/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "nodes": {
+                        "127.0.0.1:8080": 1
+                    },
+                    "type": "roundrobin"
+                }]]
+                )
+            ngx.status = code
+            ngx.say(body)
+        }
+    }
+--- error_code: 201
+--- response_body
+passed
+
+
+
+=== TEST 2: add route
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "upstream_id": 1,
+                    "uri": "/index.html"
+                }]]
+                )
+            if code >= 300 then
+                ngx.status = code
+                ngx.print(message)
+                return
+            end
+            ngx.say(message)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 3: delete upstream(wrong header)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/upstreams/1?force=anyvalue',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body
+[delete] code: 400 message: {"error_msg":"can not delete this upstream, route 
[1] is still using it now"}
+
+
+
+=== TEST 4: delete upstream(without force delete header)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/upstreams/1',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body
+[delete] code: 400 message: {"error_msg":"can not delete this upstream, route 
[1] is still using it now"}
+
+
+
+=== TEST 5: delete upstream(force delete)
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/upstreams/1?force=true',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body chomp
+[delete] code: 200 message: passed
+
+
+
+=== TEST 6: delete route
+--- config
+    location /t {
+        content_by_lua_block {
+            ngx.sleep(0.3)
+            local t = require("lib.test_admin").test
+            local code, message = t('/apisix/admin/routes/1',
+                ngx.HTTP_DELETE
+            )
+            ngx.print("[delete] code: ", code, " message: ", message)
+        }
+    }
+--- response_body chomp
+[delete] code: 200 message: passed

Reply via email to