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