From 5ce96a4bcdc284a18426d3267b889a90aa12eda2 Mon Sep 17 00:00:00 2001 From: Aapo Talvensaari Date: Thu, 5 Oct 2023 23:43:18 +0300 Subject: [PATCH] feat(runloop): plugin:configure(configs) handler ### Summary Adds a new handler that plugins can implement: ``` -- configs will be `nil` if there is no active configs for the plugin function Plugin:configure(configs) end ``` See the change in `acme` plugin. Signed-off-by: Aapo Talvensaari --- .../kong/plugin-configure-phase.yml | 6 + kong/init.lua | 8 +- kong/plugins/acme/client.lua | 18 +- kong/plugins/acme/handler.lua | 35 +- kong/plugins/prometheus/exporter.lua | 69 +--- kong/plugins/prometheus/handler.lua | 10 +- kong/runloop/handler.lua | 14 + kong/runloop/plugins_iterator.lua | 364 +++++++++++------- .../21-grpc_plugins_triggering_spec.lua | 4 + .../28-stream_plugins_triggering_spec.lua | 4 + spec/02-integration/07-sdk/02-log_spec.lua | 1 + .../kong/plugins/logger-last/handler.lua | 1 + .../kong/plugins/logger/handler.lua | 7 +- 13 files changed, 310 insertions(+), 231 deletions(-) create mode 100644 changelog/unreleased/kong/plugin-configure-phase.yml diff --git a/changelog/unreleased/kong/plugin-configure-phase.yml b/changelog/unreleased/kong/plugin-configure-phase.yml new file mode 100644 index 000000000000..35f9ea733a2d --- /dev/null +++ b/changelog/unreleased/kong/plugin-configure-phase.yml @@ -0,0 +1,6 @@ +message: > + Plugins can now implement `Plugin:configure(configs)` function that is called whenever + there is a change in plugin entities. An array of current plugin configurations is + passed to the function, or `nil` in case there is no active configurations for the plugin. +type: feature +scope: Core diff --git a/kong/init.lua b/kong/init.lua index 811e09498276..426bcec551aa 100644 --- a/kong/init.lua +++ b/kong/init.lua @@ -470,7 +470,7 @@ local function execute_collected_plugins_iterator(plugins_iterator, phase, ctx) span:finish() end end - + if is_timing_enabled then req_dyn_hook_run_hooks(ctx, "timing", "after:plugin_iterator") end @@ -946,8 +946,8 @@ function Kong.init_worker() local errors = execute_init_worker_plugins_iterator(plugins_iterator, ctx) if errors then for _, e in ipairs(errors) do - local err = "failed to execute the \"init_worker\" " .. - "handler for plugin \"" .. e.plugin .."\": " .. e.err + local err = 'failed to execute the "init_worker" ' .. + 'handler for plugin "' .. e.plugin ..'": ' .. e.err stash_init_worker_error(err) end end @@ -966,6 +966,8 @@ function Kong.init_worker() stash_init_worker_error(err) return end + + plugins_iterator:configure(ctx) end diff --git a/kong/plugins/acme/client.lua b/kong/plugins/acme/client.lua index eed2dabf8429..cb3cb3d8749e 100644 --- a/kong/plugins/acme/client.lua +++ b/kong/plugins/acme/client.lua @@ -508,21 +508,9 @@ local function renew_certificate_storage(conf) end -local function renew_certificate(premature) - if premature then - return - end - - for plugin, err in kong.db.plugins:each(1000) do - if err then - kong.log.warn("error fetching plugin: ", err) - end - - if plugin.name == "acme" then - kong.log.info("renew storage configured in acme plugin: ", plugin.id) - renew_certificate_storage(plugin.config) - end - end +local function renew_certificate(config) + kong.log.info("renew storage configured in acme plugin: ", config.__plugin_id) + renew_certificate_storage(config) end local function load_renew_hosts(conf) diff --git a/kong/plugins/acme/handler.lua b/kong/plugins/acme/handler.lua index 043fc6c388b9..58cf7fa6000a 100644 --- a/kong/plugins/acme/handler.lua +++ b/kong/plugins/acme/handler.lua @@ -70,28 +70,33 @@ local domains_matcher -- expose it for use in api.lua ACMEHandler.build_domain_matcher = build_domain_matcher -function ACMEHandler:init_worker() - local worker_id = ngx.worker.id() - kong.log.info("acme renew timer started on worker ", worker_id) - ngx.timer.every(86400, client.renew_certificate) - -- handle cache updating of domains_matcher - kong.worker_events.register(function(data) - if data.entity.name ~= "acme" then - return - end +local CONFIG - local operation = data.operation - if operation == "create" or operation == "update" then - local conf = data.entity.config - domains_matcher = build_domain_matcher(conf.domains) - end +local function renew(premature) + if premature or not CONFIG then + return + end + client.renew_certificate(CONFIG) +end - end, "crud", "plugins") +function ACMEHandler:init_worker() + local worker_id = ngx.worker.id() + kong.log.info("acme renew timer started on worker ", worker_id) + ngx.timer.every(86400, renew) +end + + +function ACMEHandler:configure(configs) + CONFIG = configs and configs[1] or nil + if CONFIG then + domains_matcher = build_domain_matcher(CONFIG.domains) + end end + local function check_domains(conf, host) if not conf.enable_ipv4_common_name and string_find(host, "^(%d+)%.(%d+)%.(%d+)%.(%d+)$") then return false diff --git a/kong/plugins/prometheus/exporter.lua b/kong/plugins/prometheus/exporter.lua index c6e1635b9f9f..f1518164794c 100644 --- a/kong/plugins/prometheus/exporter.lua +++ b/kong/plugins/prometheus/exporter.lua @@ -19,6 +19,8 @@ local role = kong.configuration.role local KONG_LATENCY_BUCKETS = { 1, 2, 5, 7, 10, 15, 20, 30, 50, 75, 100, 200, 500, 750, 1000} local UPSTREAM_LATENCY_BUCKETS = {25, 50, 80, 100, 250, 400, 700, 1000, 2000, 5000, 10000, 30000, 60000 } +local IS_PROMETHEUS_ENABLED + local metrics = {} -- prometheus.lua instance @@ -33,60 +35,6 @@ local kong_subsystem = ngx.config.subsystem local http_subsystem = kong_subsystem == "http" --- should we introduce a way to know if a plugin is configured or not? -local is_prometheus_enabled, register_events_handler do - local PLUGIN_NAME = "prometheus" - local CACHE_KEY = "prometheus:enabled" - - local function is_prometheus_enabled_fetch() - for plugin, err in kong.db.plugins:each() do - if err then - kong.log.crit("could not obtain list of plugins: ", err) - return nil, err - end - - if plugin.name == PLUGIN_NAME and plugin.enabled then - return true - end - end - - return false - end - - - -- Returns `true` if Prometheus is enabled anywhere inside Kong. - -- The results are then cached and purged as necessary. - function is_prometheus_enabled() - local enabled, err = kong.cache:get(CACHE_KEY, nil, is_prometheus_enabled_fetch) - - if err then - error("error when checking if prometheus enabled: " .. err) - end - - return enabled - end - - - -- invalidate cache when a plugin is added/removed/updated - function register_events_handler() - local worker_events = kong.worker_events - - if kong.configuration.database == "off" then - worker_events.register(function() - kong.cache:invalidate(CACHE_KEY) - end, "declarative", "reconfigure") - - else - worker_events.register(function(data) - if data.entity.name == PLUGIN_NAME then - kong.cache:invalidate(CACHE_KEY) - end - end, "crud", "plugins") - end - end -end - - local function init() local shm = "prometheus_metrics" if not ngx.shared[shm] then @@ -230,11 +178,17 @@ local function init() end end + local function init_worker() prometheus:init_worker() - register_events_handler() end + +local function configure(configs) + IS_PROMETHEUS_ENABLED = configs ~= nil +end + + -- Convert the MD5 hex string to its numeric representation -- Note the following will be represented as a float instead of int64 since luajit -- don't like int64. Good news is prometheus uses float instead of int64 as well @@ -412,7 +366,7 @@ local function metric_data(write_fn) -- upstream targets accessible? local upstreams_dict = get_all_upstreams() for key, upstream_id in pairs(upstreams_dict) do - -- long loop maybe spike proxy request latency, so we + -- long loop maybe spike proxy request latency, so we -- need yield to avoid blocking other requests -- kong.tools.utils.yield(true) yield(true, phase) @@ -489,7 +443,7 @@ local function metric_data(write_fn) -- notify the function if prometheus plugin is enabled, -- so that it can avoid exporting unnecessary metrics if not - prometheus:metric_data(write_fn, not is_prometheus_enabled()) + prometheus:metric_data(write_fn, not IS_PROMETHEUS_ENABLED) end local function collect() @@ -525,6 +479,7 @@ end return { init = init, init_worker = init_worker, + configure = configure, log = log, metric_data = metric_data, collect = collect, diff --git a/kong/plugins/prometheus/handler.lua b/kong/plugins/prometheus/handler.lua index efca5e187371..d7bce154eb74 100644 --- a/kong/plugins/prometheus/handler.lua +++ b/kong/plugins/prometheus/handler.lua @@ -11,14 +11,20 @@ local PrometheusHandler = { VERSION = kong_meta.version, } -function PrometheusHandler.init_worker() +function PrometheusHandler:init_worker() exporter.init_worker() end + +function PrometheusHandler:configure(configs) + exporter.configure(configs) +end + + local http_subsystem = ngx.config.subsystem == "http" -function PrometheusHandler.log(self, conf) +function PrometheusHandler:log(conf) local message = kong.log.serialize() local serialized = {} diff --git a/kong/runloop/handler.lua b/kong/runloop/handler.lua index e9b8873d9adc..98947fce8964 100644 --- a/kong/runloop/handler.lua +++ b/kong/runloop/handler.lua @@ -46,6 +46,7 @@ local exec = ngx.exec local header = ngx.header local set_header = ngx.req.set_header local timer_at = ngx.timer.at +local get_phase = ngx.get_phase local subsystem = ngx.config.subsystem local clear_header = ngx.req.clear_header local http_version = ngx.req.http_version @@ -519,6 +520,14 @@ local function build_plugins_iterator(version) if not plugins_iterator then return nil, err end + + local phase = get_phase() + -- skip calling plugins_iterator:configure on init/init_worker + -- as it is explicitly called on init_worker + if phase ~= "init" and phase ~= "init_worker" then + plugins_iterator:configure() + end + PLUGINS_ITERATOR = plugins_iterator return true end @@ -719,6 +728,11 @@ do end if plugins_iterator then + -- Before we replace plugin iterator we need to call configure handler + -- of each plugin. There is a slight chance that plugin configure handler + -- would yield, and that should be considered a bad practice. + plugins_iterator:configure() + PLUGINS_ITERATOR = plugins_iterator CURRENT_PLUGINS_HASH = plugins_hash or 0 end diff --git a/kong/runloop/plugins_iterator.lua b/kong/runloop/plugins_iterator.lua index e7671abd684b..a2caffa4f0f4 100644 --- a/kong/runloop/plugins_iterator.lua +++ b/kong/runloop/plugins_iterator.lua @@ -1,22 +1,28 @@ -local workspaces = require "kong.workspaces" -local constants = require "kong.constants" -local utils = require "kong.tools.utils" -local tablepool = require "tablepool" - - -local var = ngx.var -local null = ngx.null -local subsystem = ngx.config.subsystem -local format = string.format -local fetch_table = tablepool.fetch +local workspaces = require "kong.workspaces" +local constants = require "kong.constants" +local utils = require "kong.tools.utils" +local tablepool = require "tablepool" + + +local kong = kong +local error = error +local assert = assert +local var = ngx.var +local null = ngx.null +local pcall = pcall +local subsystem = ngx.config.subsystem +local pairs = pairs +local ipairs = ipairs +local format = string.format +local fetch_table = tablepool.fetch local release_table = tablepool.release -local TTL_ZERO = { ttl = 0 } +local TTL_ZERO = { ttl = 0 } local GLOBAL_QUERY_OPTS = { workspace = null, show_ws_id = true } -local NON_COLLECTING_PHASES, DOWNSTREAM_PHASES, DOWNSTREAM_PHASES_COUNT, COLLECTING_PHASE +local NON_COLLECTING_PHASES, DOWNSTREAM_PHASES, DOWNSTREAM_PHASES_COUNT, COLLECTING_PHASE, CONFIGURE_PHASE do if subsystem == "stream" then NON_COLLECTING_PHASES = { @@ -51,12 +57,21 @@ do end DOWNSTREAM_PHASES_COUNT = #DOWNSTREAM_PHASES + CONFIGURE_PHASE = "configure" end + +local NEXT_SEQ = 0 local PLUGINS_NS = "plugins." .. subsystem +local ENABLED_PLUGINS +local LOADED_PLUGINS +local CONFIGURABLE_PLUGINS + local PluginsIterator = {} + +--- -- Build a compound key by string formatting route_id, service_id, and consumer_id with colons as separators. -- -- @function build_compound_key @@ -64,11 +79,14 @@ local PluginsIterator = {} -- @tparam string|nil service_id The service identifier. If `nil`, an empty string is used. -- @tparam string|nil consumer_id The consumer identifier. If `nil`, an empty string is used. -- @treturn string The compound key, in the format `route_id:service_id:consumer_id`. ---- local function build_compound_key(route_id, service_id, consumer_id) return format("%s:%s:%s", route_id or "", service_id or "", consumer_id or "") end + +local PLUGIN_GLOBAL_KEY = build_compound_key() -- all nil + + local function get_table_for_ctx(ws) local tbl = fetch_table(PLUGINS_NS, 0, DOWNSTREAM_PHASES_COUNT + 2) if not tbl.initialized then @@ -87,6 +105,7 @@ local function get_table_for_ctx(ws) return tbl end + local function release(ctx) local plugins = ctx.plugins if plugins then @@ -96,15 +115,26 @@ local function release(ctx) end -local enabled_plugins -local loaded_plugins - - local function get_loaded_plugins() return assert(kong.db.plugins:get_handlers()) end +local function get_configurable_plugins() + local i = 0 + local plugins_with_configure_phase = {} + for _, plugin in ipairs(LOADED_PLUGINS) do + if plugin.handler[CONFIGURE_PHASE] then + i = i + 1 + local name = plugin.name + plugins_with_configure_phase[name] = true + plugins_with_configure_phase[i] = plugin + end + end + return plugins_with_configure_phase +end + + local function should_process_plugin(plugin) if plugin.enabled then local c = constants.PROTOCOLS_WITH_SUBSYSTEM @@ -117,8 +147,6 @@ local function should_process_plugin(plugin) end -local next_seq = 0 - local function get_plugin_config(plugin, name, ws_id) if not plugin or not plugin.enabled then return @@ -126,8 +154,8 @@ local function get_plugin_config(plugin, name, ws_id) local cfg = plugin.config or {} - cfg.route_id = plugin.route and plugin.route.id - cfg.service_id = plugin.service and plugin.service.id + cfg.route_id = plugin.route and plugin.route.id + cfg.service_id = plugin.service and plugin.service.id cfg.consumer_id = plugin.consumer and plugin.consumer.id cfg.plugin_instance_name = plugin.instance_name cfg.__plugin_id = plugin.id @@ -142,14 +170,13 @@ local function get_plugin_config(plugin, name, ws_id) -- TODO: deprecate usage of __key__ as id of plugin if not cfg.__key__ then cfg.__key__ = key - cfg.__seq__ = next_seq - next_seq = next_seq + 1 + cfg.__seq__ = NEXT_SEQ + NEXT_SEQ = NEXT_SEQ + 1 end return cfg end -local PLUGIN_GLOBAL_KEY = build_compound_key() -- all nil --- -- Lookup a configuration for a given combination of route_id, service_id, consumer_id @@ -157,14 +184,14 @@ local PLUGIN_GLOBAL_KEY = build_compound_key() -- all nil -- The function checks various combinations of route_id, service_id and consumer_id to find -- the best matching configuration in the given 'combos' table. The priority order is as follows: -- --- Route, Service, Consumer --- Route, Consumer --- Service, Consumer --- Route, Service --- Consumer --- Route --- Service --- Global +-- 1. Route, Service, Consumer +-- 2. Route, Consumer +-- 3. Service, Consumer +-- 4. Route, Service +-- 5. Consumer +-- 6. Route +-- 7. Service +-- 8. Global -- -- @function lookup_cfg -- @tparam table combos A table containing configuration data indexed by compound keys. @@ -172,55 +199,58 @@ local PLUGIN_GLOBAL_KEY = build_compound_key() -- all nil -- @tparam string|nil service_id The service identifier. -- @tparam string|nil consumer_id The consumer identifier. -- @return any|nil The configuration corresponding to the best matching combination, or 'nil' if no configuration is found. ---- local function lookup_cfg(combos, route_id, service_id, consumer_id) -- Use the build_compound_key function to create an index for the 'combos' table - - local key - if route_id and service_id and consumer_id then - key = build_compound_key(route_id, service_id, consumer_id) - if combos[key] then - return combos[key] - end + if route_id and service_id and consumer_id then + local key = build_compound_key(route_id, service_id, consumer_id) + if combos[key] then + return combos[key] end - if route_id and consumer_id then - key = build_compound_key(route_id, nil, consumer_id) - if combos[key] then - return combos[key] - end + end + + if route_id and consumer_id then + local key = build_compound_key(route_id, nil, consumer_id) + if combos[key] then + return combos[key] end - if service_id and consumer_id then - key = build_compound_key(nil, service_id, consumer_id) - if combos[key] then - return combos[key] - end + end + + if service_id and consumer_id then + local key = build_compound_key(nil, service_id, consumer_id) + if combos[key] then + return combos[key] end - if route_id and service_id then - key = build_compound_key(route_id, service_id, nil) - if combos[key] then - return combos[key] - end + end + + if route_id and service_id then + local key = build_compound_key(route_id, service_id, nil) + if combos[key] then + return combos[key] end - if consumer_id then - key = build_compound_key(nil, nil, consumer_id) - if combos[key] then - return combos[key] - end + end + + if consumer_id then + local key = build_compound_key(nil, nil, consumer_id) + if combos[key] then + return combos[key] end - if route_id then - key = build_compound_key(route_id, nil, nil) - if combos[key] then - return combos[key] - end + end + + if route_id then + local key = build_compound_key(route_id, nil, nil) + if combos[key] then + return combos[key] end - if service_id then - key = build_compound_key(nil, service_id, nil) - if combos[key] then - return combos[key] - end + end + + if service_id then + local key = build_compound_key(nil, service_id, nil) + if combos[key] then + return combos[key] end - return combos[PLUGIN_GLOBAL_KEY] + end + return combos[PLUGIN_GLOBAL_KEY] end @@ -236,19 +266,18 @@ end -- @tparam table combos A table containing configuration data indexed by compound keys. -- @tparam table plugin A table containing plugin information, including the handler with no_route, no_service, and no_consumer rules. -- @treturn any|nil The configuration corresponding to the best matching combination, or 'nil' if no configuration is found. ---- local function load_configuration_through_combos(ctx, combos, plugin) - - -- Filter out route, service, and consumer based on the plugin handler rules and get their ids - local route_id = (ctx.route and not plugin.handler.no_route) and ctx.route.id or nil - local service_id = (ctx.service and not plugin.handler.no_service) and ctx.service.id or nil - -- TODO: kong.client.get_consumer() for more consistency. This might be an exception though as we're aiming for raw speed instead of compliance. - local consumer_id = (ctx.authenticated_consumer and not plugin.handler.no_consumer) and ctx.authenticated_consumer.id or nil + -- Filter out route, service, and consumer based on the plugin handler rules and get their ids + local handler = plugin.handler + local route_id = (not handler.no_route and ctx.route) and ctx.route.id or nil + local service_id = (not handler.no_service and ctx.service) and ctx.service.id or nil + local consumer_id = (not handler.no_consumer and ctx.authenticated_consumer) and ctx.authenticated_consumer.id or nil -- Call the lookup_cfg function to get the best matching plugin configuration return lookup_cfg(combos, route_id, service_id, consumer_id) end + local function get_workspace(self, ctx) if not ctx then return self.ws[kong.default_workspace] @@ -257,6 +286,7 @@ local function get_workspace(self, ctx) return self.ws[workspaces.get_workspace_id(ctx) or kong.default_workspace] end + local function get_next_init_worker(plugins, i) local i = i + 1 local plugin = plugins[i] @@ -293,7 +323,7 @@ end local function get_global_iterator(self, phase) local plugins = self.globals[phase] - local count = plugins[0] + local count = plugins and plugins[0] or 0 if count == 0 then return nil end @@ -315,7 +345,7 @@ local function get_collected_iterator(self, phase, ctx) local plugins = ctx.plugins if plugins then plugins = plugins[phase] - if plugins[0] == 0 then + if not plugins or plugins[0] == 0 then return nil end @@ -337,6 +367,7 @@ local function get_next_and_collect(ctx, i) local plugin = plugins[i] local name = plugin.name local cfg + -- Only pass combos for the plugin we're operating on local combos = ws.combos[name] if combos then cfg = load_configuration_through_combos(ctx, combos, plugin) @@ -350,7 +381,7 @@ local function get_next_and_collect(ctx, i) local n = collected[phase][0] + 2 collected[phase][0] = n collected[phase][n] = cfg - collected[phase][n-1] = plugin + collected[phase][n - 1] = plugin if phase == "response" and not ctx.buffered_proxying then ctx.buffered_proxying = true end @@ -366,8 +397,10 @@ local function get_next_and_collect(ctx, i) return get_next_and_collect(ctx, i) end + local function get_collecting_iterator(self, ctx) local ws = get_workspace(self, ctx) + ctx.plugins = get_table_for_ctx(ws) if not ws then return nil end @@ -377,11 +410,10 @@ local function get_collecting_iterator(self, ctx) return nil end - ctx.plugins = get_table_for_ctx(ws) - return get_next_and_collect, ctx end + local function new_ws_data() return { plugins = { [0] = 0 }, @@ -390,15 +422,64 @@ local function new_ws_data() end +local function configure(configurable, ctx) + ctx = ctx or ngx.ctx + local kong_global = require "kong.global" + for _, plugin in ipairs(CONFIGURABLE_PLUGINS) do + local name = plugin.name + + kong_global.set_namespaced_log(kong, plugin.name, ctx) + local start = utils.get_updated_monotonic_ms() + local ok, err = pcall(plugin.handler[CONFIGURE_PHASE], plugin.handler, configurable[name]) + local elapsed = utils.get_updated_monotonic_ms() - start + kong_global.reset_log(kong, ctx) + + if not ok then + kong.log.err("failed to execute plugin '", name, ":", CONFIGURE_PHASE, " (", err, ")") + else + if elapsed > 50 then + kong.log.notice("executing plugin '", name, ":", CONFIGURE_PHASE, " took excessively long: ", elapsed, " ms") + end + end + end +end + + +local function create_configure(configurable) + -- we only want the plugin_iterator:configure to be only available on proxying + -- nodes (or data planes), thus we disable it if this code gets executed on control + -- plane or on a node that does not listen any proxy ports. + -- + -- TODO: move to PDK, e.g. kong.node.is_proxying() + if kong.configuration.role == "control_plane" + or ((subsystem == "http" and #kong.configuration.proxy_listeners == 0) or + (subsystem == "stream" and #kong.configuration.stream_listeners == 0)) + then + return function() end + end + + return function(self, ctx) + configure(configurable, ctx) + -- self destruct the function so that it cannot be called twice + -- if it ever happens to be called twice, it should be very visible + -- because of this. + self.configure = nil + configurable = nil + end +end + + function PluginsIterator.new(version) - if kong.db.strategy ~= "off" then + local is_not_dbless = kong.db.strategy ~= "off" + if is_not_dbless then if not version then error("version must be given", 2) end end - loaded_plugins = loaded_plugins or get_loaded_plugins() - enabled_plugins = enabled_plugins or kong.configuration.loaded_plugins + LOADED_PLUGINS = LOADED_PLUGINS or get_loaded_plugins() + CONFIGURABLE_PLUGINS = CONFIGURABLE_PLUGINS or get_configurable_plugins() + ENABLED_PLUGINS = ENABLED_PLUGINS or kong.configuration.loaded_plugins local ws_id = workspaces.get_workspace_id() or kong.default_workspace local ws = { @@ -406,7 +487,6 @@ function PluginsIterator.new(version) } local counter = 0 - local page_size = kong.db.plugins.pagination.max_page_size local globals do globals = {} @@ -415,31 +495,21 @@ function PluginsIterator.new(version) end end + local configurable = {} local has_plugins = false + local page_size = kong.db.plugins.pagination.max_page_size for plugin, err in kong.db.plugins:each(page_size, GLOBAL_QUERY_OPTS) do if err then return nil, err end local name = plugin.name - if not enabled_plugins[name] then + if not ENABLED_PLUGINS[name] then return nil, name .. " plugin is in use but not enabled" end - local data = ws[plugin.ws_id] - if not data then - data = new_ws_data() - ws[plugin.ws_id] = data - end - local plugins = data.plugins - local combos = data.combos - - if kong.core_cache - and counter > 0 - and counter % page_size == 0 - and kong.db.strategy ~= "off" then - + if is_not_dbless and counter > 0 and counter % page_size == 0 and kong.core_cache then local new_version, err = kong.core_cache:get("plugins_iterator:version", TTL_ZERO, utils.uuid) if err then return nil, "failed to retrieve plugins iterator version: " .. err @@ -454,27 +524,37 @@ function PluginsIterator.new(version) end if should_process_plugin(plugin) then - if not has_plugins then + -- Get the plugin configuration for the specified workspace (ws_id) + local cfg = get_plugin_config(plugin, name, plugin.ws_id) + if cfg then has_plugins = true - end - plugins[name] = true + if CONFIGURABLE_PLUGINS[name] then + configurable[name] = configurable[name] or {} + configurable[name][#configurable[name] + 1] = cfg + end + + local data = ws[plugin.ws_id] + if not data then + data = new_ws_data() + ws[plugin.ws_id] = data + end + local plugins = data.plugins + local combos = data.combos - -- Retrieve route_id, service_id, and consumer_id from the plugin object, if they exist - local route_id = plugin.route and plugin.route.id - local service_id = plugin.service and plugin.service.id - local consumer_id = plugin.consumer and plugin.consumer.id + plugins[name] = true - -- Get the plugin configuration for the specified workspace (ws_id) - local cfg = get_plugin_config(plugin, name, plugin.ws_id) - -- Determine if the plugin configuration is global (i.e., not tied to any route, service, consumer or group) - local is_global = not route_id and not service_id and not consumer_id and plugin.ws_id == kong.default_workspace - if is_global then - -- Store the global configuration for the plugin in the 'globals' table - globals[name] = cfg - end + -- Retrieve route_id, service_id, and consumer_id from the plugin object, if they exist + local route_id = plugin.route and plugin.route.id + local service_id = plugin.service and plugin.service.id + local consumer_id = plugin.consumer and plugin.consumer.id + + -- Determine if the plugin configuration is global (i.e., not tied to any route, service, consumer or group) + if not (route_id or service_id or consumer_id) and plugin.ws_id == kong.default_workspace then + -- Store the global configuration for the plugin in the 'globals' table + globals[name] = cfg + end - if cfg then -- Initialize an empty table for the plugin in the 'combos' table if it doesn't already exist combos[name] = combos[name] or {} @@ -485,30 +565,35 @@ function PluginsIterator.new(version) combos[name][compound_key] = cfg end end + counter = counter + 1 end - for _, plugin in ipairs(loaded_plugins) do - local name = plugin.name - for _, data in pairs(ws) do - local plugins = data.plugins - if plugins[name] then - local n = plugins[0] + 1 - plugins[n] = plugin - plugins[0] = n - plugins[name] = nil + if has_plugins then + -- loaded_plugins contains all the plugins that we _may_ execute + for _, plugin in ipairs(LOADED_PLUGINS) do + local name = plugin.name + -- ws contains all the plugins that are associated to the request via route/service/global mappings + for _, data in pairs(ws) do + local plugins = data.plugins + if plugins[name] then -- is the plugin associated to the request(workspace/route/service)? + local n = plugins[0] + 1 + plugins[n] = plugin -- next item goes into next slot + plugins[0] = n -- index 0 holds table size + plugins[name] = nil -- remove the placeholder value + end end - end - local cfg = globals[name] - if cfg then - for _, phase in ipairs(NON_COLLECTING_PHASES) do - if plugin.handler[phase] then - local plugins = globals[phase] - local n = plugins[0] + 2 - plugins[0] = n - plugins[n] = cfg - plugins[n - 1] = plugin + local cfg = globals[name] + if cfg then + for _, phase in ipairs(NON_COLLECTING_PHASES) do + if plugin.handler[phase] then + local plugins = globals[phase] + local n = plugins[0] + 2 + plugins[0] = n + plugins[n] = cfg + plugins[n - 1] = plugin + end end end end @@ -517,7 +602,8 @@ function PluginsIterator.new(version) return { version = version, ws = ws, - loaded = loaded_plugins, + loaded = LOADED_PLUGINS, + configure = create_configure(configurable), globals = globals, get_init_worker_iterator = get_init_worker_iterator, get_global_iterator = get_global_iterator, @@ -528,8 +614,10 @@ function PluginsIterator.new(version) } end + -- for testing PluginsIterator.lookup_cfg = lookup_cfg PluginsIterator.build_compound_key = build_compound_key + return PluginsIterator diff --git a/spec/02-integration/05-proxy/21-grpc_plugins_triggering_spec.lua b/spec/02-integration/05-proxy/21-grpc_plugins_triggering_spec.lua index c95d5f612a08..1dc731df5604 100644 --- a/spec/02-integration/05-proxy/21-grpc_plugins_triggering_spec.lua +++ b/spec/02-integration/05-proxy/21-grpc_plugins_triggering_spec.lua @@ -77,6 +77,7 @@ end local phrases = { ["%[logger%] init_worker phase"] = 1, + ["%[logger%] configure phase"] = 1, ["%[logger%] rewrite phase"] = 1, ["%[logger%] access phase"] = 1, ["%[logger%] header_filter phase"] = 1, @@ -86,6 +87,7 @@ local phrases = { local phrases_ssl = { ["%[logger%] init_worker phase"] = 1, + ["%[logger%] configure phase"] = 1, ["%[logger%] certificate phase"] = 1, ["%[logger%] rewrite phase"] = 1, ["%[logger%] access phase"] = 1, @@ -109,6 +111,7 @@ local phrases_ssl = { local phrases_reflection = { ["%[logger%] init_worker phase"] = 1, + ["%[logger%] configure phase"] = 1, ["%[logger%] rewrite phase"] = 2, ["%[logger%] access phase"] = 2, ["%[logger%] header_filter phase"] = 2, @@ -118,6 +121,7 @@ local phrases_reflection = { local phrases_ssl_reflection = { ["%[logger%] init_worker phase"] = 1, + ["%[logger%] configure phase"] = 1, ["%[logger%] certificate phase"] = 1, ["%[logger%] rewrite phase"] = 2, ["%[logger%] access phase"] = 2, diff --git a/spec/02-integration/05-proxy/28-stream_plugins_triggering_spec.lua b/spec/02-integration/05-proxy/28-stream_plugins_triggering_spec.lua index c0546d2e8fc1..927497446c8c 100644 --- a/spec/02-integration/05-proxy/28-stream_plugins_triggering_spec.lua +++ b/spec/02-integration/05-proxy/28-stream_plugins_triggering_spec.lua @@ -40,18 +40,21 @@ end local phases = { ["%[logger%] init_worker phase"] = 1, + ["%[logger%] configure phase"] = 1, ["%[logger%] preread phase"] = 1, ["%[logger%] log phase"] = 1, } local phases_2 = { ["%[logger%] init_worker phase"] = 1, + ["%[logger%] configure phase"] = 1, ["%[logger%] preread phase"] = 0, ["%[logger%] log phase"] = 1, } local phases_tls = { ["%[logger%] init_worker phase"] = 1, + ["%[logger%] configure phase"] = 1, ["%[logger%] certificate phase"] = 1, ["%[logger%] preread phase"] = 1, ["%[logger%] log phase"] = 1, @@ -59,6 +62,7 @@ local phases_tls = { local phases_tls_2 = { ["%[logger%] init_worker phase"] = 1, + ["%[logger%] configure phase"] = 1, ["%[logger%] certificate phase"] = 1, ["%[logger%] preread phase"] = 0, ["%[logger%] log phase"] = 1, diff --git a/spec/02-integration/07-sdk/02-log_spec.lua b/spec/02-integration/07-sdk/02-log_spec.lua index 7f2b7e607b4a..a60a01d72284 100644 --- a/spec/02-integration/07-sdk/02-log_spec.lua +++ b/spec/02-integration/07-sdk/02-log_spec.lua @@ -95,6 +95,7 @@ describe("PDK: kong.log", function() local phrases = { "%[logger%] init_worker phase", "%[logger%-last%] init_worker phase", + "%[logger%] configure phase", "%[logger%-last%] configure phase", "%[logger%] certificate phase", "%[logger%-last%] certificate phase", diff --git a/spec/fixtures/custom_plugins/kong/plugins/logger-last/handler.lua b/spec/fixtures/custom_plugins/kong/plugins/logger-last/handler.lua index 08a338af703e..4bcc73e2f33b 100644 --- a/spec/fixtures/custom_plugins/kong/plugins/logger-last/handler.lua +++ b/spec/fixtures/custom_plugins/kong/plugins/logger-last/handler.lua @@ -7,6 +7,7 @@ local LoggerLastHandler = { LoggerLastHandler.init_worker = LoggerHandler.init_worker +LoggerLastHandler.configure = LoggerHandler.configure LoggerLastHandler.certificate = LoggerHandler.certificate LoggerLastHandler.preread = LoggerHandler.preread LoggerLastHandler.rewrite = LoggerHandler.rewrite diff --git a/spec/fixtures/custom_plugins/kong/plugins/logger/handler.lua b/spec/fixtures/custom_plugins/kong/plugins/logger/handler.lua index 760137b5063f..691945f9d9ef 100644 --- a/spec/fixtures/custom_plugins/kong/plugins/logger/handler.lua +++ b/spec/fixtures/custom_plugins/kong/plugins/logger/handler.lua @@ -4,11 +4,16 @@ local LoggerHandler = { } -function LoggerHandler:init_worker(conf) +function LoggerHandler:init_worker() kong.log("init_worker phase") end +function LoggerHandler:configure(configs) + kong.log("configure phase") +end + + function LoggerHandler:certificate(conf) kong.log("certificate phase") end