You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by mo...@apache.org on 2023/04/06 03:35:47 UTC

[apisix] branch master updated: fix: check upstream reference in traffic-split plugin when delete upstream (#9044)

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 dff1c7671 fix: check upstream reference in traffic-split plugin when delete upstream (#9044)
dff1c7671 is described below

commit dff1c767134920cb3c54c918d6166ab6813af00d
Author: Tristan <ji...@foxmail.com>
AuthorDate: Thu Apr 6 11:35:38 2023 +0800

    fix: check upstream reference in traffic-split plugin when delete upstream (#9044)
---
 apisix/admin/upstreams.lua | 105 ++++++++--
 apisix/consumer_group.lua  |   8 +
 apisix/plugin_config.lua   |   8 +
 t/admin/upstream5.t        | 486 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 589 insertions(+), 18 deletions(-)

diff --git a/apisix/admin/upstreams.lua b/apisix/admin/upstreams.lua
index a85d24d8d..687e09cd4 100644
--- a/apisix/admin/upstreams.lua
+++ b/apisix/admin/upstreams.lua
@@ -15,13 +15,17 @@
 -- limitations under the License.
 --
 local core = require("apisix.core")
+local config_util = require("apisix.core.config_util")
+local router = require("apisix.router")
 local get_routes = require("apisix.router").http_routes
 local get_services = require("apisix.http.service").services
+local get_plugin_configs = require("apisix.plugin_config").plugin_configs
+local get_consumers = require("apisix.consumer").consumers
+local get_consumer_groups = require("apisix.consumer_group").consumer_groups
 local apisix_upstream = require("apisix.upstream")
 local resource = require("apisix.admin.resource")
 local tostring = tostring
 local ipairs = ipairs
-local type = type
 
 
 local function check_conf(id, conf, need_id)
@@ -34,31 +38,96 @@ local function check_conf(id, conf, need_id)
 end
 
 
-local function delete_checker(id)
-    local routes, routes_ver = get_routes()
-    if routes_ver and routes then
-        for _, route in ipairs(routes) do
-            if type(route) == "table" and route.value
-               and route.value.upstream_id
-               and tostring(route.value.upstream_id) == id then
-                return 400, {error_msg = "can not delete this upstream,"
-                                         .. " route [" .. route.value.id
+local function up_id_in_plugins(plugins, up_id)
+    if plugins and plugins["traffic-split"]
+        and plugins["traffic-split"].rules then
+
+        for _, rule in ipairs(plugins["traffic-split"].rules) do
+            local plugin_upstreams = rule.weighted_upstreams
+            for _, plugin_upstream in ipairs(plugin_upstreams) do
+                if plugin_upstream.upstream_id
+                    and tostring(plugin_upstream.upstream_id) == up_id then
+                     return true
+                end
+            end
+        end
+
+        return false
+    end
+end
+
+
+local function check_resources_reference(resources, up_id,
+                                         only_check_plugin, resources_name)
+    if resources then
+        for _, resource in config_util.iterate_values(resources) do
+            if resource and resource.value then
+                if up_id_in_plugins(resource.value.plugins, up_id) then
+                    return {error_msg = "can not delete this upstream,"
+                                        .. " plugin in "
+                                        .. resources_name .. " ["
+                                        .. resource.value.id
+                                        .. "] is still using it now"}
+                end
+
+                if not only_check_plugin and resource.value.upstream_id
+                    and tostring(resource.value.upstream_id) == up_id then
+                     return {error_msg = "can not delete this upstream, "
+                                         .. resources_name .. " [" .. resource.value.id
                                          .. "] is still using it now"}
+                end
             end
         end
     end
+end
+
+
+local function delete_checker(id)
+    local routes = get_routes()
+    local err_msg = check_resources_reference(routes, id, false, "route")
+    if err_msg then
+        return 400, err_msg
+    end
 
     local services, services_ver = get_services()
     core.log.info("services: ", core.json.delay_encode(services, true))
     core.log.info("services_ver: ", services_ver)
-    if services_ver and services then
-        for _, service in ipairs(services) do
-            if type(service) == "table" and service.value
-               and service.value.upstream_id
-               and tostring(service.value.upstream_id) == id then
-                return 400, {error_msg = "can not delete this upstream,"
-                                         .. " service [" .. service.value.id
-                                         .. "] is still using it now"}
+    local err_msg = check_resources_reference(services, id, false, "service")
+    if err_msg then
+        return 400, err_msg
+    end
+
+    local plugin_configs = get_plugin_configs()
+    local err_msg = check_resources_reference(plugin_configs, id, true, "plugin_config")
+    if err_msg then
+        return 400, err_msg
+    end
+
+    local consumers = get_consumers()
+    local err_msg = check_resources_reference(consumers, id, true, "consumer")
+    if err_msg then
+        return 400, err_msg
+    end
+
+    local consumer_groups = get_consumer_groups()
+    local err_msg = check_resources_reference(consumer_groups, id, true, "consumer_group")
+    if err_msg then
+        return 400, err_msg
+    end
+
+    -- TODO: Refactor router.global_rules and then refactor the following code
+    local global_rules = router.global_rules
+    if global_rules and global_rules.values
+        and #global_rules.values > 0 then
+
+        for _, global_rule in config_util.iterate_values(global_rules.values) do
+            if global_rule and global_rule.value
+                and global_rule.value.plugins
+                and up_id_in_plugins(global_rule.value.plugins, id) then
+                 return 400, {error_msg = "can not delete this upstream,"
+                                          .. " plugin in global_rule ["
+                                          .. global_rule.value.id
+                                          .. "] is still using it now"}
             end
         end
     end
diff --git a/apisix/consumer_group.lua b/apisix/consumer_group.lua
index 8c17bdd71..3be59ec92 100644
--- a/apisix/consumer_group.lua
+++ b/apisix/consumer_group.lua
@@ -39,6 +39,14 @@ function _M.init_worker()
 end
 
 
+function _M.consumer_groups()
+    if not consumer_groups then
+        return nil, nil
+    end
+    return consumer_groups.values, consumer_groups.conf_version
+end
+
+
 function _M.get(id)
     return consumer_groups:get(id)
 end
diff --git a/apisix/plugin_config.lua b/apisix/plugin_config.lua
index cc5a6ff38..828ebf1e2 100644
--- a/apisix/plugin_config.lua
+++ b/apisix/plugin_config.lua
@@ -40,6 +40,14 @@ function _M.init_worker()
 end
 
 
+function _M.plugin_configs()
+    if not plugin_configs then
+        return nil, nil
+    end
+    return plugin_configs.values, plugin_configs.conf_version
+end
+
+
 function _M.get(id)
     return plugin_configs:get(id)
 end
diff --git a/t/admin/upstream5.t b/t/admin/upstream5.t
index 9fd59bfe7..f589a415d 100644
--- a/t/admin/upstream5.t
+++ b/t/admin/upstream5.t
@@ -111,3 +111,489 @@ passed
     }
 --- response_body
 passed
+
+
+
+=== TEST 4: prepare upstream
+--- 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:1980": 1
+                },
+                "type": "roundrobin"
+            }]])
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: prepare route
+--- 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, [[{
+                "plugins": {
+                    "traffic-split": {
+                        "rules": [
+                            {
+                                "weighted_upstreams": [
+                                    {
+                                        "upstream_id": 1,
+                                        "weight": 1
+                                    },
+                                    {
+                                        "weight": 1
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                },
+                "upstream": {
+                    "nodes": {
+                        "127.0.0.1:1980": 1
+                    },
+                    "type": "roundrobin"
+                },
+                "uri": "/hello"
+            }]])
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 6: delete upstream when plugin in route still refer it
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t("/apisix/admin/upstreams/1", ngx.HTTP_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.print(body)
+        }
+    }
+--- error_code: 400
+--- response_body
+{"error_msg":"can not delete this upstream, plugin in route [1] is still using it now"}
+
+
+
+=== TEST 7: delete route
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t("/apisix/admin/routes/1", ngx.HTTP_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 8: prepare service
+--- 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, [[{
+                "plugins": {
+                    "traffic-split": {
+                        "rules": [
+                            {
+                                "weighted_upstreams": [
+                                    {
+                                        "upstream_id": 1,
+                                        "weight": 1
+                                    },
+                                    {
+                                        "weight": 1
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                }
+            }]])
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 9: delete upstream when plugin in service still refer it
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t("/apisix/admin/upstreams/1", ngx.HTTP_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.print(body)
+        }
+    }
+--- error_code: 400
+--- response_body
+{"error_msg":"can not delete this upstream, plugin in service [1] is still using it now"}
+
+
+
+=== TEST 10: delete service
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t("/apisix/admin/services/1", ngx.HTTP_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 11: prepare global_rule
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            local code, body = t("/apisix/admin/global_rules/1", ngx.HTTP_PUT, [[{
+                "plugins": {
+                    "traffic-split": {
+                        "rules": [
+                            {
+                                "weighted_upstreams": [
+                                    {
+                                        "upstream_id": 1,
+                                        "weight": 1
+                                    },
+                                    {
+                                        "weight": 1
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                }
+            }]])
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 12: delete upstream when plugin in global_rule still refer it
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t("/apisix/admin/upstreams/1", ngx.HTTP_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.print(body)
+        }
+    }
+--- error_code: 400
+--- response_body
+{"error_msg":"can not delete this upstream, plugin in global_rule [1] is still using it now"}
+
+
+
+=== TEST 13: delete global_rule
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t("/apisix/admin/global_rules/1", ngx.HTTP_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 14: prepare plugin_config
+--- 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": {
+                    "traffic-split": {
+                        "rules": [
+                            {
+                                "weighted_upstreams": [
+                                    {
+                                        "upstream_id": 1,
+                                        "weight": 1
+                                    },
+                                    {
+                                        "weight": 1
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                }
+            }]])
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 15: delete upstream when plugin in plugin_config still refer it
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t("/apisix/admin/upstreams/1", ngx.HTTP_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.print(body)
+        }
+    }
+--- error_code: 400
+--- response_body
+{"error_msg":"can not delete this upstream, plugin in plugin_config [1] is still using it now"}
+
+
+
+=== TEST 16: delete plugin_config
+--- 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_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 17: prepare consumer
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+
+            local code, body = t("/apisix/admin/consumers", ngx.HTTP_PUT, [[{
+                "username": "test",
+                "plugins": {
+                    "key-auth": {
+                        "key": "auth-one"
+                    },
+                    "traffic-split": {
+                        "rules": [
+                            {
+                                "weighted_upstreams": [
+                                    {
+                                        "upstream_id": 1,
+                                        "weight": 1
+                                    },
+                                    {
+                                        "weight": 1
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                }
+            }]])
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 18: delete upstream when plugin in consumer still refer it
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t("/apisix/admin/upstreams/1", ngx.HTTP_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.print(body)
+        }
+    }
+--- error_code: 400
+--- response_body
+{"error_msg":"can not delete this upstream, plugin in consumer [test] is still using it now"}
+
+
+
+=== TEST 19: delete consumer
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t("/apisix/admin/consumers/test", ngx.HTTP_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 20: prepare consumer_group
+--- 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": {
+                    "key-auth": {
+                        "key": "auth-one"
+                    },
+                    "traffic-split": {
+                        "rules": [
+                            {
+                                "weighted_upstreams": [
+                                    {
+                                        "upstream_id": 1,
+                                        "weight": 1
+                                    },
+                                    {
+                                        "weight": 1
+                                    }
+                                ]
+                            }
+                        ]
+                    }
+                }
+            }]])
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 21: delete upstream when plugin in consumer_group still refer it
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t("/apisix/admin/upstreams/1", ngx.HTTP_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.print(body)
+        }
+    }
+--- error_code: 400
+--- response_body
+{"error_msg":"can not delete this upstream, plugin in consumer_group [1] is still using it now"}
+
+
+
+=== TEST 22: delete consumer_group
+--- 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_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 23: delete upstream
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t("/apisix/admin/upstreams/1", ngx.HTTP_DELETE)
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed