Skip to content

Commit

Permalink
fix(vitals): control plane only create table rotater timer once (#9083)
Browse files Browse the repository at this point in the history
* fix(vitals): control plane only create table rotater timer once

After enabling vitals, whenever each data plane connects to the control plane,
it will trigger the control plane to generate a table rotater timer,
and the table rotater timer will cyclically create itself, causing timer leaks.

This fix moves the creation of the table rotater timer to the start function,
where it checks the self:get_running() status to determine if the table rotater
timer has already started, ensuring that the table rotater timer is only
created once.

Fix [FTI-5905](https://konghq.atlassian.net/browse/FTI-5905)

Signed-off-by: tzssangglass <tzssangglass@gmail.com>
  • Loading branch information
tzssangglass authored May 15, 2024
1 parent bed7ec6 commit 7bad8d4
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
message: |
**Vitals**: Fixed a bug that each data plane connecting to the control plane would trigger the control plane to create a redundant table rotater timer.
type: bugfix
scope: Core
18 changes: 9 additions & 9 deletions kong/vitals/postgres/strategy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ function _M.new(db, opts)
local table_rotater = table_rotater_module.new(
{
connector = db.connector,
rotation_interval = opts.ttl_seconds or 3600,
ttl_seconds = opts.ttl_seconds or 3600,
list_cache = ngx.shared.kong_vitals_lists,
}
)
Expand Down Expand Up @@ -266,12 +266,6 @@ function _M:init(node_id, hostname)
return nil, "node_id is required"
end

local ok, err = self.table_rotater:init()

if not ok then
return nil, "failed to init table rotator: " .. err
end

self.node_id = node_id
self.hostname = hostname

Expand All @@ -285,13 +279,19 @@ end


function _M:start()
-- delete timer
-- make sure the timer is started only once, clustering strategy will call
-- table rotater timer and delete timer
-- make sure these two timers are started only once, clustering strategy will call
-- this function multiple times
if self:get_running() then
return true
end

local ok, err = self.table_rotater:init()

if not ok then
return nil, "failed to init table rotator: " .. err
end

local when = self.delete_interval
log(INFO, _log_prefix, "starting initial postgres delete timer in ", when, " seconds")

Expand Down
2 changes: 2 additions & 0 deletions kong/vitals/postgres/table_rotater.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ local log = ngx.log
local WARN = ngx.WARN
local DEBUG = ngx.DEBUG
local ERR = ngx.ERR
local NOTICE = ngx.NOTICE
local time = ngx.time


Expand Down Expand Up @@ -93,6 +94,7 @@ end


function _M:init()
log(NOTICE, _log_prefix, "init vitals table rotater")
-- make sure we have a current vitals_stats_seconds table
local query = fmt(CREATE_VITALS_STATS_SECONDS, self:current_table_name())
local _, err = self.connector:query(query)
Expand Down
102 changes: 102 additions & 0 deletions spec-ee/02-integration/03-vitals/13-table_rotater_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
-- This software is copyright Kong Inc. and its licensors.
-- Use of the software is subject to the agreement between your organization
-- and Kong Inc. If there is no such agreement, use is governed by and
-- subject to the terms of the Kong Master Software License Agreement found
-- at https://konghq.com/enterprisesoftwarelicense/.
-- [ END OF LICENSE 0867164ffc95e54f04670b5169c09574bdbd9bba ]

local helpers = require "spec.helpers"
local clear_license_env = require("spec-ee.helpers").clear_license_env
local get_portal_and_vitals_key = require("spec-ee.helpers").get_portal_and_vitals_key
local ngx_time = ngx.time
local tonumber = tonumber
local max = math.max
local unpack = unpack


local function select_vitals_seconds_tables(db)
local query = [[
select table_name from information_schema.tables
where table_name like 'vitals_stats_seconds_%'
]]

local res = assert(db:query(query))
return res
end


for _, strategy in helpers.each_strategy() do
describe("vitals table rotater #" .. strategy, function()
local reset_license_data, db

lazy_setup(function()
reset_license_data = clear_license_env()
local _
_, db = helpers.get_db_utils(strategy, {
"clustering_data_planes",
"licenses",
})
db = db.connector

assert(helpers.start_kong({
role = "control_plane",
cluster_cert = "spec/fixtures/kong_clustering.crt",
cluster_cert_key = "spec/fixtures/kong_clustering.key",
lua_ssl_trusted_certificate = "spec/fixtures/kong_clustering.crt",
database = strategy,
db_update_frequency = 0.1,
cluster_listen = "127.0.0.1:9005",
nginx_conf = "spec/fixtures/custom_nginx.template",
log_level = "notice",
license_path = "spec-ee/fixtures/mock_license.json",
portal_and_vitals_key = get_portal_and_vitals_key(),
vitals = true,
vitals_ttl_seconds = 2,
}))
end)

lazy_teardown(function()
helpers.stop_kong("servroot")
helpers.stop_kong("servroot2")
reset_license_data()
end)

it("only init in control plane init worker phase", function()
assert.logfile("servroot/logs/error.log").has.line("[vitals-table-rotater] init vitals table rotater, context: init_worker_by_lua", true, 1)

helpers.clean_logfile("servroot/logs/error.log")

assert(helpers.start_kong({
role = "data_plane",
database = "off",
prefix = "servroot2",
cluster_cert = "spec/fixtures/kong_clustering.crt",
cluster_cert_key = "spec/fixtures/kong_clustering.key",
lua_ssl_trusted_certificate = "spec/fixtures/kong_clustering.crt",
cluster_control_plane = "127.0.0.1:9005",
proxy_listen = "0.0.0.0:19002",
nginx_conf = "spec/fixtures/custom_nginx.template",
log_level = "info",
vitals_flush_interval = 1,
license_path = "spec-ee/fixtures/mock_license.json",
portal_and_vitals_key = get_portal_and_vitals_key(),
vitals = true,
}))

-- check if vitals table rotater init trigger by dataplane connect
assert.logfile("servroot/logs/error.log").has.no.line("init vitals table rotater, context: ngx.timer", true, 5)

local res = select_vitals_seconds_tables(db)
local seconds_times = {}
local seconds_table_prefix = "vitals_stats_seconds_"
for i = 1, #res do
seconds_times[i] = tonumber(res[i].table_name:sub(#seconds_table_prefix + 1))
end

local nearly_table_time = max(unpack(seconds_times))
local now = ngx_time()
-- verify that table rotater timer work(table rotater timer should create new table every 2s during ngx.sleep(5))
assert.is_true(now - nearly_table_time < 3)
end)
end)
end

0 comments on commit 7bad8d4

Please sign in to comment.