diff --git a/CHANGELOG.md b/CHANGELOG.md index 76dc8b8..e3bd484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Export targets not being stopped after removal from the role configuration (#47). +- Graphite exporters stop after a configuration reload that keeps the + `graphite` section unchanged. +- Requirement of metrics 1.7.0+ for an empty `graphite` section. ## 0.4.1 - 2026-05-07 diff --git a/roles/metrics-export.lua b/roles/metrics-export.lua index 2e6fb71..155ad7a 100644 --- a/roles/metrics-export.lua +++ b/roles/metrics-export.lua @@ -174,13 +174,12 @@ local function validate_graphite_node(endpoint) validate_graphite_send_interval(endpoint) end -local function validate_graphite(conf) - local ok = pcall(require('metrics.plugins.graphite').stop) - if not ok then - error('ensure you have metrics 1.7.0+ (provided with Tarantool 3.7.0+ or can be ' .. - 'installed as an external dependency)', 2) - end +local function is_graphite_supported() + local ok, graphite = pcall(require, 'metrics.plugins.graphite') + return ok and type(graphite) == "table" and type(graphite.stop) == "function" +end +local function validate_graphite(conf) if conf ~= nil and type(conf) ~= "table" then error("graphite configuration must be a table, got " .. type(conf), 2) end @@ -190,6 +189,11 @@ local function validate_graphite(conf) error("graphite configuration must be an array, not a map", 2) end + if next(conf) ~= nil and not is_graphite_supported() then + error('ensure you have metrics 1.7.0+ (provided with Tarantool 3.7.0+ or can be ' .. + 'installed as an external dependency)', 2) + end + for _, graphite_node in ipairs(conf) do validate_graphite_node(graphite_node) end diff --git a/test/integration/reload_config_test.lua b/test/integration/reload_config_test.lua index 5bc257f..85c3983 100644 --- a/test/integration/reload_config_test.lua +++ b/test/integration/reload_config_test.lua @@ -189,6 +189,45 @@ g.test_reload_config_remove_http_target = function(cg) t.assert_not(is_tcp_connect('127.0.0.1', 8082)) end +g.before_test('test_reload_config_remove_graphite_target', function(cg) + helpers.skip_if_graphite_unsupported() + fio.copyfile(fio.pathjoin('test', 'entrypoint', 'graphite_config.yaml'), cg.workdir) +end) + +g.test_reload_config_remove_graphite_target = function(cg) + cg.server = server:new({ + config_file = fio.pathjoin(cg.workdir, 'graphite_config.yaml'), + chdir = cg.workdir, + alias = 'master', + workdir = cg.workdir, + }) + cg.server:start({wait_until_ready = true}) + t.assert_ge(graphite_helpers.count_graphite_frames("master", "127.0.0.1", 44444, 1), 1) + t.assert_ge(graphite_helpers.count_graphite_frames("tarantool", "127.0.0.1", 2223, 2), 1) + + local config_path = fio.pathjoin(cg.workdir, 'graphite_config.yaml') + local file = fio.open(config_path, {'O_RDONLY'}) + t.assert(file ~= nil) + local cfg = yaml.decode(file:read()) + file:close() + + local role_cfg = cfg.groups['group-001'].replicasets['replicaset-001']. + instances.master.roles_cfg['roles.metrics-export'] + role_cfg.graphite = nil + + file = fio.open(config_path, { + 'O_CREAT', 'O_WRONLY', 'O_TRUNC', + }, tonumber('644', 8)) + t.assert(file ~= nil) + file:write(yaml.encode(cfg)) + file:close() + + cg.server:eval("require('config'):reload()") + + t.assert_equals(graphite_helpers.count_graphite_frames("master", "127.0.0.1", 44444, 1), 0) + t.assert_equals(graphite_helpers.count_graphite_frames("tarantool", "127.0.0.1", 2223, 2), 0) +end + local function change_graphite_port_in_config(cg, new_port) if new_port == nil then new_port = '2222' @@ -241,3 +280,27 @@ g.test_reload_config_graphite = function(cg) t.assert_ge(graphite_helpers.count_graphite_frames("tarantool", "127.0.0.1", 3333, 2), 1) t.assert_equals(graphite_helpers.count_graphite_frames("tarantool", "127.0.0.1", 2223, 2), 0) end + +g.before_test('test_reload_config_graphite_not_changed', function(cg) + helpers.skip_if_graphite_unsupported() + fio.copyfile(fio.pathjoin('test', 'entrypoint', 'graphite_config.yaml'), cg.workdir) +end) + +g.test_reload_config_graphite_not_changed = function(cg) + cg.server = server:new({ + config_file = fio.pathjoin(cg.workdir, 'graphite_config.yaml'), + chdir = cg.workdir, + alias = 'master', + workdir = cg.workdir, + }) + + cg.server:start({wait_until_ready = true}) + + t.assert_ge(graphite_helpers.count_graphite_frames("master", "127.0.0.1", 44444, 1), 1) + + -- Reload the same configuration: the running exporters must survive it. + cg.server:eval("require('config'):reload()") + + t.assert_ge(graphite_helpers.count_graphite_frames("master", "127.0.0.1", 44444, 1), 1) + t.assert_ge(graphite_helpers.count_graphite_frames("tarantool", "127.0.0.1", 2223, 2), 1) +end diff --git a/test/unit/validate_test.lua b/test/unit/validate_test.lua index d2746b3..ad9c76b 100644 --- a/test/unit/validate_test.lua +++ b/test/unit/validate_test.lua @@ -1044,6 +1044,11 @@ local ok_cases = { }, }, }, + ["graphite_empty"] = { + cfg = { + graphite = {}, + }, + }, ["graphite_full"] = { cfg = { graphite = {{ @@ -1084,7 +1089,9 @@ local ok_cases = { for name, case in pairs(ok_cases) do g["test_validate_ok_" .. name] = function(gc) - if (case.cfg or {}).graphite ~= nil then + if next((case.cfg or {}).graphite or {}) ~= nil then + -- Skip if at least one endpoint exists. Empty table could + -- be possible. helpers.skip_if_graphite_unsupported() end