You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2022/06/23 07:14:07 UTC

[apisix] branch master updated: feat: allows users to specify plugin execution priority (#7273)

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 cbc29a717 feat: allows users to specify plugin execution priority (#7273)
cbc29a717 is described below

commit cbc29a7170aae034bfc2cbbd09f5529bd1968b8d
Author: tzssangglass <tz...@gmail.com>
AuthorDate: Thu Jun 23 15:14:01 2022 +0800

    feat: allows users to specify plugin execution priority (#7273)
---
 apisix/init.lua                      |   5 +-
 apisix/plugin.lua                    |  62 +++-
 apisix/schema_def.lua                |   4 +
 docs/en/latest/terminology/plugin.md |  37 ++
 docs/zh/latest/terminology/plugin.md |  37 ++
 t/admin/plugins.t                    |   4 +-
 t/plugin/custom_sort_plugins.t       | 633 +++++++++++++++++++++++++++++++++++
 7 files changed, 777 insertions(+), 5 deletions(-)

diff --git a/apisix/init.lua b/apisix/init.lua
index 08899f4ab..9cbe6d204 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -445,9 +445,10 @@ function _M.http_access_phase()
             if changed then
                 api_ctx.matched_route = route
                 core.table.clear(api_ctx.plugins)
-                api_ctx.plugins = plugin.filter(api_ctx, route, api_ctx.plugins)
+                local phase = "rewrite_in_consumer"
+                api_ctx.plugins = plugin.filter(api_ctx, route, api_ctx.plugins, nil, phase)
                 -- rerun rewrite phase for newly added plugins in consumer
-                plugin.run_plugin("rewrite_in_consumer", api_ctx.plugins, api_ctx)
+                plugin.run_plugin(phase, api_ctx.plugins, api_ctx)
             end
         end
         plugin.run_plugin("access", plugins, api_ctx)
diff --git a/apisix/plugin.lua b/apisix/plugin.lua
index 2276a5c33..b0344eb09 100644
--- a/apisix/plugin.lua
+++ b/apisix/plugin.lua
@@ -68,6 +68,10 @@ local function sort_plugin(l, r)
     return l.priority > r.priority
 end
 
+local function custom_sort_plugin(l, r)
+    return l._meta.priority > r._meta.priority
+end
+
 
 local PLUGIN_TYPE_HTTP = 1
 local PLUGIN_TYPE_STREAM = 2
@@ -368,7 +372,7 @@ local function trace_plugins_info_for_debug(ctx, plugins)
 end
 
 
-function _M.filter(ctx, conf, plugins, route_conf)
+function _M.filter(ctx, conf, plugins, route_conf, phase)
     local user_plugin_conf = conf.value.plugins
     if user_plugin_conf == nil or
        core.table.nkeys(user_plugin_conf) == 0 then
@@ -378,6 +382,7 @@ function _M.filter(ctx, conf, plugins, route_conf)
         return plugins or core.tablepool.fetch("plugins", 0, 0)
     end
 
+    local custom_sort = false
     local route_plugin_conf = route_conf and route_conf.value.plugins
     plugins = plugins or core.tablepool.fetch("plugins", 32, 0)
     for _, plugin_obj in ipairs(local_plugins) do
@@ -392,6 +397,9 @@ function _M.filter(ctx, conf, plugins, route_conf)
                 end
             end
 
+            if plugin_conf._meta and plugin_conf._meta.priority then
+                custom_sort = true
+            end
             core.table.insert(plugins, plugin_obj)
             core.table.insert(plugins, plugin_conf)
 
@@ -401,6 +409,51 @@ function _M.filter(ctx, conf, plugins, route_conf)
 
     trace_plugins_info_for_debug(ctx, plugins)
 
+    if custom_sort then
+        local tmp_plugin_objs = core.tablepool.fetch("tmp_plugin_objs", 0, #plugins / 2)
+        local tmp_plugin_confs = core.tablepool.fetch("tmp_plugin_confs", #plugins / 2, 0)
+
+        for i = 1, #plugins, 2 do
+            local plugin_obj = plugins[i]
+            local plugin_conf = plugins[i + 1]
+
+            -- in the rewrite phase, the plugin executes in the following order:
+            -- 1. execute the rewrite phase of the plugins on route(including the auth plugins)
+            -- 2. merge plugins from consumer and route
+            -- 3. execute the rewrite phase of the plugins on consumer(phase: rewrite_in_consumer)
+            -- in this case, we need to skip the plugins that was already executed(step 1)
+            if phase == "rewrite_in_consumer" and not plugin_conf._from_consumer then
+                plugin_conf._skip_rewrite_in_consumer = true
+            end
+
+            tmp_plugin_objs[plugin_conf] = plugin_obj
+            core.table.insert(tmp_plugin_confs, plugin_conf)
+
+            if not plugin_conf._meta then
+                plugin_conf._meta = core.table.new(0, 1)
+                plugin_conf._meta.priority = plugin_obj.priority
+            else
+                if not plugin_conf._meta.priority then
+                    plugin_conf._meta.priority = plugin_obj.priority
+                end
+            end
+        end
+
+        sort_tab(tmp_plugin_confs, custom_sort_plugin)
+
+        local index
+        for i = 1, #tmp_plugin_confs do
+            index = i * 2 - 1
+            local plugin_conf = tmp_plugin_confs[i]
+            local plugin_obj = tmp_plugin_objs[plugin_conf]
+            plugins[index] = plugin_obj
+            plugins[index + 1] = plugin_conf
+        end
+
+        core.tablepool.release("tmp_plugin_objs", tmp_plugin_objs)
+        core.tablepool.release("tmp_plugin_confs", tmp_plugin_confs)
+    end
+
     return plugins
 end
 
@@ -757,6 +810,11 @@ function _M.run_plugin(phase, plugins, api_ctx)
                 phase = "rewrite"
             end
             local phase_func = plugins[i][phase]
+
+            if phase == "rewrite" and plugins[i + 1]._skip_rewrite_in_consumer then
+                goto CONTINUE
+            end
+
             if phase_func then
                 plugin_run = true
                 local conf = plugins[i + 1]
@@ -784,6 +842,8 @@ function _M.run_plugin(phase, plugins, api_ctx)
                     end
                 end
             end
+
+            ::CONTINUE::
         end
         return api_ctx, plugin_run
     end
diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua
index 7d39b62aa..16dccc6d8 100644
--- a/apisix/schema_def.lua
+++ b/apisix/schema_def.lua
@@ -952,6 +952,10 @@ _M.plugin_injected_schema = {
                     { type = "object" },
                 }
             },
+            priority = {
+                description = "priority of plugins by customized order",
+                type = "integer",
+            },
         }
     }
 }
diff --git a/docs/en/latest/terminology/plugin.md b/docs/en/latest/terminology/plugin.md
index 4bb12a4e3..6ab969769 100644
--- a/docs/en/latest/terminology/plugin.md
+++ b/docs/en/latest/terminology/plugin.md
@@ -95,6 +95,43 @@ the configuration above means customizing the error response from the jwt-auth p
 | Name         | Type | Description |
 |--------------|------|-------------|
 | error_response | string/object  | Custom error response |
+| priority       | integer        | Custom plugin priority |
+
+### Custom Plugin Priority
+
+All plugins have a default priority, but it is possible to customize the plugin priority to change the plugin's execution order.
+
+```json
+ {
+    "serverless-post-function": {
+        "_meta": {
+            "priority": 10000
+        },
+        "phase": "rewrite",
+        "functions" : ["return function(conf, ctx)
+                    ngx.say(\"serverless-post-function\");
+                    end"]
+    },
+    "serverless-pre-function": {
+        "_meta": {
+            "priority": -2000
+        },
+        "phase": "rewrite",
+        "functions": ["return function(conf, ctx)
+                    ngx.say(\"serverless-pre-function\");
+                    end"]
+    }
+}
+```
+
+The default priority of serverless-pre-function is 10000, and the default priority of serverless-post-function is -2000. By default, the serverless-pre-function plugin will be executed first, and serverless-post-function plugin will be executed next.
+
+The above configuration means setting the priority of the serverless-pre-function plugin to -2000 and the priority of the serverless-post-function plugin to 10000. The serverless-post-function plugin will be executed first, and serverless-pre-function plugin will be executed next.
+
+Note:
+
+- Custom plugin priority only affects the current object(route, service ...) of the plugin instance binding, not all instances of that plugin. For example, if the above plugin configuration belongs to Route A, the order of execution of the plugins serverless-post-function and serverless-post-function on Route B will not be affected and the default priority will be used.
+- Custom plugin priority does not apply to the rewrite phase of some plugins configured on the consumer. The rewrite phase of plugins configured on the route will be executed first, and then the rewrite phase of plugins (exclude auth plugins) from the consumer will be executed.
 
 ## Hot Reload
 
diff --git a/docs/zh/latest/terminology/plugin.md b/docs/zh/latest/terminology/plugin.md
index 8883ef374..86bed6442 100644
--- a/docs/zh/latest/terminology/plugin.md
+++ b/docs/zh/latest/terminology/plugin.md
@@ -89,6 +89,43 @@ local _M = {
 | 名称         | 类型 | 描述           |
 |--------------|------|----------------|
 | error_response | string/object  | 自定义错误响应 |
+| priority       | integer        | 自定义插件优先级 |
+
+### 自定义插件优先级
+
+所有插件都有默认优先级,但是可以自定义插件优先级来改变插件执行顺序。
+
+```json
+ {
+    "serverless-post-function": {
+        "_meta": {
+            "priority": 10000
+        },
+        "phase": "rewrite",
+        "functions" : ["return function(conf, ctx)
+                    ngx.say(\"serverless-post-function\");
+                    end"]
+    },
+    "serverless-pre-function": {
+        "_meta": {
+            "priority": -2000
+        },
+        "phase": "rewrite",
+        "functions": ["return function(conf, ctx)
+                    ngx.say(\"serverless-pre-function\");
+                    end"]
+    }
+}
+```
+
+serverless-pre-function 的默认优先级是 10000,serverless-post-function 的默认优先级是 -2000。默认情况下会先执行 serverless-pre-function 插件,再执行 serverless-post-function 插件。
+
+上面的配置意味着将 serverless-pre-function 插件的优先级设置为 -2000,serverless-post-function 插件的优先级设置为 10000。serverless-post-function 插件会先执行,再执行 serverless-pre-function 插件。
+
+注意:
+
+- 自定义插件优先级只会影响插件实例绑定的主体,不会影响该插件的所有实例。比如上面的插件配置属于路由 A ,路由 B 上的插件 serverless-post-function 和 serverless-post-function 插件执行顺序不会受到影响,会使用默认优先级。
+- 自定义插件优先级不适用于 consumer 上配置的插件的 rewrite 阶段。路由上配置的插件的 rewrite 阶段将会优先运行,然后才会运行 consumer 上除 auth 插件之外的其他插件的 rewrite 阶段。
 
 ## 热加载
 
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index c370c3c51..d7881249d 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -265,7 +265,7 @@ plugins:
         }
     }
 --- response_body eval
-qr/\{"metadata_schema":\{"properties":\{"ikey":\{"minimum":0,"type":"number"\},"skey":\{"type":"string"\}\},"required":\["ikey","skey"\],"type":"object"\},"priority":0,"schema":\{"\$comment":"this is a mark for our injected plugin schema","properties":\{"_meta":\{"properties":\{"error_response":\{"oneOf":\[\{"type":"string"\},\{"type":"object"\}\]\}\},"type":"object"\},"disable":\{"type":"boolean"\},"i":\{"minimum":0,"type":"number"\},"ip":\{"type":"string"\},"port":\{"type":"integer"\}, [...]
+qr/\{"metadata_schema":\{"properties":\{"ikey":\{"minimum":0,"type":"number"\},"skey":\{"type":"string"\}\},"required":\["ikey","skey"\],"type":"object"\},"priority":0,"schema":\{"\$comment":"this is a mark for our injected plugin schema","properties":\{"_meta":\{"properties":\{"error_response":\{"oneOf":\[\{"type":"string"\},\{"type":"object"\}\]\},"priority":\{"description":"priority of plugins by customized order","type":"integer"\}\},"type":"object"\},"disable":\{"type":"boolean"\}," [...]
 
 
 
@@ -366,7 +366,7 @@ qr/\{"properties":\{"password":\{"type":"string"\},"username":\{"type":"string"\
         }
     }
 --- response_body
-{"priority":1003,"schema":{"$comment":"this is a mark for our injected plugin schema","properties":{"_meta":{"properties":{"error_response":{"oneOf":[{"type":"string"},{"type":"object"}]}},"type":"object"},"burst":{"minimum":0,"type":"integer"},"conn":{"exclusiveMinimum":0,"type":"integer"},"default_conn_delay":{"exclusiveMinimum":0,"type":"number"},"disable":{"type":"boolean"},"key":{"type":"string"},"key_type":{"default":"var","enum":["var","var_combination"],"type":"string"},"only_use [...]
+{"priority":1003,"schema":{"$comment":"this is a mark for our injected plugin schema","properties":{"_meta":{"properties":{"error_response":{"oneOf":[{"type":"string"},{"type":"object"}]},"priority":{"description":"priority of plugins by customized order","type":"integer"}},"type":"object"},"burst":{"minimum":0,"type":"integer"},"conn":{"exclusiveMinimum":0,"type":"integer"},"default_conn_delay":{"exclusiveMinimum":0,"type":"number"},"disable":{"type":"boolean"},"key":{"type":"string"}," [...]
 
 
 
diff --git a/t/plugin/custom_sort_plugins.t b/t/plugin/custom_sort_plugins.t
new file mode 100644
index 000000000..41a23b9ad
--- /dev/null
+++ b/t/plugin/custom_sort_plugins.t
@@ -0,0 +1,633 @@
+#
+# 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';
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    if (!$block->error_log && !$block->no_error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
+no_long_string();
+no_root_location();
+log_level("info");
+run_tests;
+
+__DATA__
+
+=== TEST 1: custom priority and default priority on different routes
+--- 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": {
+                        "serverless-post-function": {
+                            "_meta": {
+                                "priority": 10000
+                            },
+                            "phase": "rewrite",
+                            "functions" : ["return function(conf, ctx)
+                                        ngx.say(\"serverless-post-function\");
+                                        end"]
+                        },
+                        "serverless-pre-function": {
+                            "_meta": {
+                                "priority": -2000
+                            },
+                            "phase": "rewrite",
+                            "functions": ["return function(conf, ctx)
+                                        ngx.say(\"serverless-pre-function\");
+                                        end"]
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+            end
+
+            local code, body = t('/apisix/admin/routes/2',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "serverless-post-function": {
+                            "phase": "rewrite",
+                            "functions" : ["return function(conf, ctx)
+                                        ngx.say(\"serverless-post-function\");
+                                        end"]
+                        },
+                        "serverless-pre-function": {
+                            "phase": "rewrite",
+                            "functions": ["return function(conf, ctx)
+                                        ngx.say(\"serverless-pre-function\");
+                                        end"]
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello1"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 2: verify order
+--- request
+GET /hello
+--- response_body
+serverless-post-function
+serverless-pre-function
+
+
+
+=== TEST 3: routing without custom plugin order is not affected
+--- request
+GET /hello1
+--- response_body
+serverless-pre-function
+serverless-post-function
+
+
+
+=== TEST 4: custom priority and default priority on same route
+# the priority of serverless-post-function is -2000, execute serverless-post-function first
+--- 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": {
+                        "serverless-post-function": {
+                            "phase": "rewrite",
+                            "functions" : ["return function(conf, ctx)
+                                        ngx.say(\"serverless-post-function\");
+                                        end"]
+                        },
+                        "serverless-pre-function": {
+                            "_meta": {
+                                "priority": -2001
+                            },
+                            "phase": "rewrite",
+                            "functions": ["return function(conf, ctx)
+                                        ngx.say(\"serverless-pre-function\");
+                                        end"]
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 5: verify order
+--- request
+GET /hello
+--- response_body
+serverless-post-function
+serverless-pre-function
+
+
+
+=== TEST 6: merge plugins from consumer and route, execute the rewrite phase
+# in the rewrite phase, the plugins on the route must be executed first,
+# and then executed the rewrite phase of the plugins on the consumer,
+# and the custom plugin order fails for this case.
+--- 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": "jack",
+                    "plugins": {
+                        "key-auth": {
+                            "key": "auth-one"
+                        },
+                        "serverless-post-function": {
+                            "_meta": {
+                                "priority": 10000
+                            },
+                            "phase": "rewrite",
+                            "functions" : ["return function(conf, ctx)
+                                        ngx.say(\"serverless-post-function\");
+                                        end"]
+                        }
+                    }
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+            end
+
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "key-auth": {},
+                        "serverless-pre-function": {
+                            "_meta": {
+                                "priority": -2000
+                            },
+                            "phase": "rewrite",
+                            "functions": ["return function(conf, ctx)
+                                        ngx.say(\"serverless-pre-function\");
+                                        end"]
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 7: verify order(more requests)
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require "resty.http"
+            local uri = "http://127.0.0.1:" .. ngx.var.server_port
+                        .. "/hello"
+            local httpc = http.new()
+            local headers = {}
+            headers["apikey"] = "auth-one"
+            local res, err = httpc:request_uri(uri, {method = "GET", headers = headers})
+            if not res then
+                ngx.say(err)
+                return
+            end
+            ngx.print(res.body)
+
+            local res, err = httpc:request_uri(uri, {method = "GET", headers = headers})
+            if not res then
+                ngx.say(err)
+                return
+            end
+            ngx.print(res.body)
+        }
+    }
+--- response_body
+serverless-pre-function
+serverless-post-function
+serverless-pre-function
+serverless-post-function
+
+
+
+=== TEST 8: merge plugins form custom and route, execute the access phase
+--- 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": "jack",
+                    "plugins": {
+                        "key-auth": {
+                            "key": "auth-one"
+                        },
+                        "serverless-post-function": {
+                            "_meta": {
+                                "priority": 10000
+                            },
+                            "phase": "access",
+                            "functions" : ["return function(conf, ctx)
+                                        ngx.say(\"serverless-post-function\");
+                                        end"]
+                        }
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+            end
+
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "key-auth": {},
+                        "serverless-pre-function": {
+                            "_meta": {
+                                "priority": -2000
+                            },
+                            "phase": "access",
+                            "functions": ["return function(conf, ctx)
+                                        ngx.say(\"serverless-pre-function\");
+                                        end"]
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 9: verify order
+--- request
+GET /hello
+--- more_headers
+apikey: auth-one
+--- response_body
+serverless-post-function
+serverless-pre-function
+
+
+
+=== TEST 10: merge plugins form service and route
+--- 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": {
+                        "serverless-post-function": {
+                            "_meta": {
+                                "priority": 10000
+                            },
+                            "phase": "rewrite",
+                            "functions" : ["return function(conf, ctx)
+                                        ngx.say(\"serverless-post-function\");
+                                        end"]
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    }
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+            end
+
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "serverless-pre-function": {
+                            "_meta": {
+                                "priority": -2000
+                            },
+                            "phase": "rewrite",
+                            "functions": ["return function(conf, ctx)
+                                        ngx.say(\"serverless-pre-function\");
+                                        end"]
+                        }
+                    },
+                    "service_id": "1",
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 11: verify order
+--- request
+GET /hello
+--- response_body
+serverless-post-function
+serverless-pre-function
+
+
+
+=== TEST 12: custom plugins sort is not affected by plugins reload
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require "resty.http"
+            local uri = "http://127.0.0.1:" .. ngx.var.server_port
+                        .. "/hello"
+            local httpc = http.new()
+            local res, err = httpc:request_uri(uri)
+            if not res then
+                ngx.say(err)
+                return
+            end
+            ngx.print(res.body)
+
+            local t = require("lib.test_admin").test
+            local code, _, org_body = t('/apisix/admin/plugins/reload',
+                                        ngx.HTTP_PUT)
+
+            ngx.say(org_body)
+
+            ngx.sleep(0.2)
+
+            local res, err = httpc:request_uri(uri)
+            if not res then
+                ngx.say(err)
+                return
+            end
+            ngx.print(res.body)
+        }
+    }
+--- response_body
+serverless-post-function
+serverless-pre-function
+done
+serverless-post-function
+serverless-pre-function
+
+
+
+=== TEST 13: merge plugins form plugin_configs and route
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, err = t('/apisix/admin/plugin_configs/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "serverless-post-function": {
+                            "_meta": {
+                                "priority": 10000
+                            },
+                            "phase": "rewrite",
+                            "functions" : ["return function(conf, ctx)
+                                        ngx.say(\"serverless-post-function\");
+                                        end"]
+                        }
+                    }
+                }]]
+            )
+            if code > 300 then
+                ngx.status = code
+                ngx.say(body)
+            end
+
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "serverless-pre-function": {
+                            "_meta": {
+                                "priority": -2000
+                            },
+                            "phase": "rewrite",
+                            "functions": ["return function(conf, ctx)
+                                        ngx.say(\"serverless-pre-function\");
+                                        end"]
+                        }
+                    },
+                    "plugin_config_id": 1,
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 14: verify order
+--- request
+GET /hello
+--- response_body
+serverless-post-function
+serverless-pre-function
+
+
+
+=== TEST 15: custom plugins sort on 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": {
+                        "serverless-post-function": {
+                            "_meta": {
+                                "priority": 10000
+                            },
+                            "phase": "rewrite",
+                            "functions" : ["return function(conf, ctx)
+                                        ngx.say(\"serverless-post-function on global rule\");
+                                        end"]
+                        },
+                        "serverless-pre-function": {
+                            "_meta": {
+                                "priority": -2000
+                            },
+                            "phase": "rewrite",
+                            "functions": ["return function(conf, ctx)
+                                        ngx.say(\"serverless-pre-function on global rule\");
+                                        end"]
+                        }
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+            end
+
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 16: verify order
+--- request
+GET /hello
+--- response_body
+serverless-post-function on global rule
+serverless-pre-function on global rule
+serverless-post-function
+serverless-pre-function
+
+
+
+=== TEST 17: 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
+                ngx.say(body)
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed