-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat: add toolset plugin #13444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
+3,019
−1
Closed
feat: add toolset plugin #13444
Changes from 4 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
8cc56dc
feat: add toolset plugin
AlinsRan 2d39d74
fix: handle timer creation error in toolset plugin init()
AlinsRan be57db2
test: add toolset plugin tests and zh docs
AlinsRan 13b8d31
fix: add license headers, Makefile install entries, and sync race con…
AlinsRan 8d82723
fix(toolset): add sleep after plugin reload in TEST 3 to avoid race c…
AlinsRan 5537e2f
fix(toolset): address code review issues
AlinsRan 4401f25
fix(toolset): correct configuration mechanism in docs and remove inva…
AlinsRan 648af75
fix(toolset): fix syntax error in match_route wrapper causing trace.l…
AlinsRan ca6d817
fix(toolset): avoid table.pack/unpack to fix luacheck warnings
AlinsRan 7e09b84
fix(toolset/trace): restore unique_random pool and add trace_active g…
AlinsRan 984d09b
fix(toolset): bail early in sync() if stop_timer is set
AlinsRan a467764
test(toolset/trace): use internal subrequest in reload test to avoid …
AlinsRan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| -- | ||
| -- 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. | ||
| -- | ||
| return { | ||
| trace = { | ||
| rate = 1, -- allow only 1 request per 100 requests | ||
| hosts = {}, -- only the requests carrying these host headers will be traced | ||
| paths = {}, -- only these request_uris will be traced | ||
| gen_uid = false, -- adds a UID to the trace if none of the traceable headers are found | ||
| vars = {}, -- add these nginx or inbuilt variables to trace table | ||
| timespan_threshold = 0 -- requests taking longer than this value (in seconds) will be traced | ||
| }, | ||
| table_count = { | ||
| lua_modules = {}, -- change it | ||
| interval = 5, | ||
| depth = 10, -- when it is not passed, default depth will be 1 | ||
| -- optional, default is all APISIX processes | ||
| scopes = {"worker", "privileged agent"} | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| -- | ||
| -- Licensed to the Apache Software Foundation (ASF) under one or more | ||
| -- contributor license agreements. See the NOTICE file distributed with | ||
| -- this work for additional information regarding copyright ownership. | ||
| -- The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| -- (the "License"); you may not use this file except in compliance with | ||
| -- the License. You may obtain a copy of the License at | ||
| -- | ||
| -- http://www.apache.org/licenses/LICENSE-2.0 | ||
| -- | ||
| -- Unless required by applicable law or agreed to in writing, software | ||
| -- distributed under the License is distributed on an "AS IS" BASIS, | ||
| -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| -- See the License for the specific language governing permissions and | ||
| -- limitations under the License. | ||
| -- | ||
| local pairs = pairs | ||
| local core = require("apisix.core") | ||
| local ngx = ngx | ||
| local cache = core.table.new(0, 32) | ||
| local stop_timer = false | ||
| local load, unload = "load", "unload" | ||
| local package = package | ||
| local pcall = pcall | ||
| local require = require | ||
| local string = string | ||
|
|
||
| local _M = { | ||
| version = 0.1, | ||
| priority = 22901, | ||
| name = "toolset", | ||
| schema = {}, | ||
| scope = "global", | ||
| } | ||
|
|
||
|
|
||
| local function get_plugin_config() | ||
| -- clear cache to reload | ||
| package.loaded["apisix.plugins.toolset.config"] = nil | ||
| local loaded, plugins_config = pcall(require, "apisix.plugins.toolset.config") | ||
| if loaded and plugins_config == true then | ||
| core.log.warn("empty plugin config file") | ||
| return nil | ||
| end | ||
| if not loaded then | ||
| core.log.error("failed to load plugin config: ", plugins_config) | ||
| return nil | ||
| end | ||
| return plugins_config | ||
| end | ||
|
|
||
|
|
||
| local function is_config_changed(plugin_name, plugin_config) | ||
| if core.table.deep_eq(cache[plugin_name], plugin_config) then | ||
| return false | ||
| end | ||
| return true | ||
| end | ||
|
|
||
|
|
||
| local function is_config_empty(plugin_config) | ||
| return plugin_config == nil or core.table.deep_eq(plugin_config, {}) | ||
| end | ||
|
|
||
|
|
||
| local function perform_operation_for_plugin(plugin_name, plugin_config, operation) | ||
| if operation == load then | ||
| local loaded, plugin = pcall(require, "apisix.plugins.toolset.src." | ||
| .. string.gsub(plugin_name, "_", "-")) | ||
| if not loaded then | ||
| core.log.warn("could not load plugin because it was not found: ", plugin_name) | ||
| return | ||
| end | ||
| core.log.warn("initializing sub plugin for toolset plugin: ", plugin_name) | ||
| plugin.init() | ||
| cache[plugin_name] = plugin_config | ||
| elseif operation == unload then | ||
| local loaded, plugin = pcall(require, "apisix.plugins.toolset.src." .. | ||
| string.gsub(plugin_name, "_", "-")) | ||
| if not loaded then | ||
| core.log.warn("could not unload plugin because it was not found: ", plugin_name) | ||
| return | ||
| end | ||
| core.log.warn("destroying sub plugin for toolset plugin: ", plugin_name) | ||
| plugin.destroy() | ||
| cache[plugin_name] = nil | ||
| end | ||
| end | ||
|
|
||
|
|
||
| local function sync() | ||
| if stop_timer then | ||
| return | ||
| end | ||
| core.log.info("syncing toolset plugin") | ||
| local plugin_configs = get_plugin_config() | ||
| local processed_plugins = {} | ||
| if plugin_configs then | ||
| for plugin_name, plugin_config in pairs(plugin_configs) do | ||
|
AlinsRan marked this conversation as resolved.
|
||
| processed_plugins[plugin_name] = true | ||
| -- checks if the config is different from cache | ||
| if is_config_changed(plugin_name, plugin_config) then | ||
| if is_config_empty(plugin_config) then | ||
| -- allow executing even with empty config. | ||
| -- Assuming the plugin will run with default values | ||
| core.log.warn("empty config found for ", plugin_name,".Running with default values") | ||
| end | ||
| core.log.warn("config changed. reloading plugin: ", plugin_name) | ||
| local ok, err = pcall(perform_operation_for_plugin, plugin_name, plugin_config, load) | ||
| if not ok then | ||
| core.log.error("toolset plugin load raised: ", err) | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
| for plugin_name, plugin_config in pairs(cache) do | ||
| if not processed_plugins[plugin_name] then | ||
| core.log.warn("plugin config unloaded: ", plugin_name) | ||
| local ok, err = pcall(perform_operation_for_plugin, plugin_name, plugin_config, unload) | ||
| if not ok then | ||
| core.log.error("toolset plugin unload raised: ", err) | ||
| end | ||
| end | ||
| end | ||
| if not stop_timer then | ||
| local ok, err = ngx.timer.at(1, sync) | ||
| if not ok then | ||
| core.log.error("failed to create timer for running toolset ", err) | ||
| end | ||
| end | ||
| end | ||
|
|
||
|
|
||
| function _M.init() | ||
| core.log.info("initializing toolset plugin") | ||
| local plugins_config = get_plugin_config() | ||
| if plugins_config then | ||
| for plugin_name, plugin_config in pairs(plugins_config) do | ||
| if is_config_empty(plugin_config) then | ||
| -- allow executing even with empty config. | ||
| -- Assuming the plugin will run with default values | ||
| core.log.warn("empty config found for ", plugin_name,".Running with default values") | ||
| end | ||
| perform_operation_for_plugin(plugin_name, plugin_config, load) | ||
| end | ||
| end | ||
| local ok, err = ngx.timer.at(1, sync) | ||
| if not ok then | ||
| core.log.error("failed to create timer for running toolset ", err) | ||
| end | ||
| end | ||
|
|
||
|
|
||
| function _M.destroy() | ||
| local plugin_configs = get_plugin_config() | ||
| if plugin_configs then | ||
| for plugin_name, plugin_config in pairs(plugin_configs) do | ||
| perform_operation_for_plugin(plugin_name, plugin_config, unload) | ||
| end | ||
|
|
||
| end | ||
| for plugin_name, plugin_config in pairs(cache) do | ||
| perform_operation_for_plugin(plugin_name, plugin_config, unload) | ||
| end | ||
|
|
||
| stop_timer = true | ||
| end | ||
|
|
||
| return _M | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| -- | ||
| -- Licensed to the Apache Software Foundation (ASF) under one or more | ||
| -- contributor license agreements. See the NOTICE file distributed with | ||
| -- this work for additional information regarding copyright ownership. | ||
| -- The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| -- (the "License"); you may not use this file except in compliance with | ||
| -- the License. You may obtain a copy of the License at | ||
| -- | ||
| -- http://www.apache.org/licenses/LICENSE-2.0 | ||
| -- | ||
| -- Unless required by applicable law or agreed to in writing, software | ||
| -- distributed under the License is distributed on an "AS IS" BASIS, | ||
| -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| -- See the License for the specific language governing permissions and | ||
| -- limitations under the License. | ||
| -- | ||
| local core = require("apisix.core") | ||
| local ngx = require("ngx") | ||
| local process = require("ngx.process") | ||
|
|
||
| local pairs = pairs | ||
| local ipairs = ipairs | ||
| local type = type | ||
| local timer = ngx.timer | ||
| local require = require | ||
| local package = package | ||
|
|
||
| local plugin_name = "table-count" | ||
|
|
||
| local schema = {} | ||
| local stop = false | ||
| -- only one run of init() function should be running at a time. | ||
| -- when init() is reloaded the run number is incremented. It also helps in debugging. | ||
| local current_run = 0 | ||
|
|
||
| local _M = { | ||
| version = 0.1, | ||
| priority = 22902, | ||
| name = plugin_name, | ||
| schema = schema, | ||
| scope = "global", | ||
| } | ||
|
|
||
| local function tab_item_count(tab, cache,depth) | ||
| if depth == 0 then | ||
| core.log.warn("out of depth..skipping count") | ||
| return | ||
| end | ||
| depth = depth - 1 | ||
| cache = cache or {} | ||
| local count = 0 | ||
| for _, value in pairs(tab) do | ||
| if cache[value] then | ||
| core.log.warn("circular reference detected..skipping count") | ||
| goto continue | ||
| end | ||
| if type(value) == "table" and not cache[value] then | ||
| cache[value] = true | ||
| local tab_count = tab_item_count(value, cache,depth) | ||
| if tab_count then | ||
| count = count + tab_count + 1 | ||
| end | ||
| else | ||
| count = count + 1 | ||
| end | ||
| ::continue:: | ||
| end | ||
| return count | ||
| end | ||
|
|
||
| function _M.init() | ||
| package.loaded["apisix.plugins.toolset.config"] = nil | ||
| local config = require("apisix.plugins.toolset.config").table_count | ||
| if config.lua_modules == nil or #config.lua_modules == 0 then | ||
| core.log.warn("no lua_modules provided for table count") | ||
| return | ||
| end | ||
| if not config.scopes then | ||
| core.log.warn("no scope provided. Running for all scopes") | ||
| goto continue | ||
| end | ||
| if #config.scopes ~= 0 then | ||
| for _,scope in ipairs(config.scopes) do | ||
| if process.type() == scope then | ||
| goto continue | ||
| end | ||
| end | ||
| return | ||
| end | ||
| ::continue:: | ||
| -- Extract configuration values | ||
| current_run = current_run + 1 | ||
| local interval = config.interval or 5 | ||
| local run_count | ||
| run_count = function(run_no) | ||
| local depth = config.depth or 1 | ||
| for _, package_name in ipairs(config.lua_modules) do | ||
| local package = require(package_name) | ||
| local count = tab_item_count(package, {},depth) | ||
| core.log.warn("package ", package_name, " table count is: ", count," for loaded: ",run_no) | ||
|
AlinsRan marked this conversation as resolved.
|
||
| end | ||
| if stop or run_no ~= current_run then | ||
| return | ||
| end | ||
| local ok, err = timer.at(interval, run_count,current_run) | ||
| if not ok then | ||
| core.log.error("failed to create timer for running table count ", err) | ||
| end | ||
| end | ||
|
|
||
| local ok, err = timer.at(0, run_count,current_run) | ||
| if not ok then | ||
| core.log.error("failed to create timer for running table count ", err) | ||
| end | ||
| end | ||
|
|
||
| function _M.destroy() | ||
| stop = true | ||
| end | ||
|
AlinsRan marked this conversation as resolved.
|
||
|
|
||
| return _M | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.