Skip to content

Commit 9f574e9

Browse files
mandeserolocker
authored andcommitted
cluster: add helpers to modify cluster config
This patch adds ability to keep and adjust cluster declarative configuration with `cluster:modify_config()` and apply it later via `сluster:apply_config_changes()` without passing an explicit config. Closes #426
1 parent af30aff commit 9f574e9

File tree

3 files changed

+128
-1
lines changed

3 files changed

+128
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
- Added the ability to keep and adjust cluster declarative configuration
6+
with `cluster:modify_config()` and apply it later via
7+
`cluster:apply_config_changes()` without passing an explicit
8+
configuration (gh-426).
59
- Added an option to create `Cluster` objects without global hook management,
610
allowing tests to keep clusters alive between test runs (gh-414).
711
- Fixed a bug where URI search would terminate prematurely when multiple

luatest/cluster.lua

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
-- * :sync() Sync the configuration and collect a new set of
2626
-- instances
2727
-- * :reload() Reload the configuration.
28+
-- * :config() Return the last applied configuration.
29+
-- * :modify_config() Initialize a configuration builder based on
30+
-- the current config and store it inside the cluster object.
31+
-- * :apply_config_changes() Apply the configuration built via
32+
-- :modify_config() by passing it to :sync().
2833
--
2934
-- The module can also be used for testing failure startup
3035
-- cases:
@@ -36,6 +41,7 @@
3641
local fun = require('fun')
3742
local yaml = require('yaml')
3843
local assertions = require('luatest.assertions')
44+
local cbuilder = require('luatest.cbuilder')
3945
local helpers = require('luatest.helpers')
4046
local hooks = require('luatest.hooks')
4147
local treegen = require('luatest.treegen')
@@ -148,6 +154,13 @@ local function instance_names_from_config(config)
148154
return instance_names
149155
end
150156

157+
158+
local function assert_no_pending_config_builder(self, method_name)
159+
assert(self._config_builder == nil,
160+
(':modify_config() was called; apply configuration changes with ' ..
161+
':apply_config_changes() before calling :%s'):format(method_name))
162+
end
163+
151164
-- }}} Helpers
152165

153166
-- {{{ Cluster management
@@ -226,12 +239,14 @@ end
226239
-- @bool[opt] opts.wait_until_running Wait until servers are running
227240
-- (default: wait_until_ready; used only if start_stop is set).
228241
function Cluster:sync(config, opts)
242+
assert_no_pending_config_builder(self, 'sync()')
229243
assert(type(config) == 'table')
230244

231245
local instance_names = instance_names_from_config(config)
232246

233247
treegen.write_file(self._dir, self._config_file_rel, yaml.encode(config))
234248

249+
self._config = config
235250
local server_map = self._server_map
236251
self._server_map = {}
237252
self._servers = {}
@@ -277,10 +292,34 @@ function Cluster:sync(config, opts)
277292
end
278293
end
279294

295+
--- Apply configuration changes built via :modify_config().
296+
--
297+
-- Uses the internal configuration builder created by :modify_config(),
298+
-- converts it to a config table and calls :sync() with it.
299+
-- After the call the stored builder is cleared.
300+
--
301+
-- @tab[opt] opts Options.
302+
-- @bool[opt] opts.start_stop Start/stop added/removed servers
303+
-- (default: false).
304+
-- @bool[opt] opts.wait_until_ready Wait until servers are ready
305+
-- (default: true; used only if start_stop is set).
306+
-- @bool[opt] opts.wait_until_running Wait until servers are running
307+
-- (default: wait_until_ready; used only if start_stop is set).
308+
function Cluster:apply_config_changes(opts)
309+
assert(self._config_builder ~= nil,
310+
':modify_config() must be called before :apply_config_changes()')
311+
312+
local config = self._config_builder:config()
313+
self._config_builder = nil
314+
315+
return self:sync(config, opts)
316+
end
317+
280318
--- Reload configuration on all the instances.
281319
--
282320
-- @tab[opt] config New config.
283321
function Cluster:reload(config)
322+
assert_no_pending_config_builder(self, 'reload()')
284323
assert(config == nil or type(config) == 'table')
285324

286325
-- Rewrite the configuration file if a new config is provided.
@@ -333,12 +372,15 @@ function Cluster:new(config, server_opts, opts)
333372
assert(g._cluster == nil)
334373
end
335374

375+
self._config = table.deepcopy(config)
376+
self._config_builder = nil
377+
336378
-- Prepare a temporary directory and write a configuration
337379
-- file.
338380
local dir = opts.dir or treegen.prepare_directory({}, {})
339381
local config_file_rel = 'config.yaml'
340382
local config_file = treegen.write_file(dir, config_file_rel,
341-
yaml.encode(config))
383+
yaml.encode(self._config))
342384

343385
-- Collect names of all the instances defined in the config
344386
-- in the alphabetical order.
@@ -383,6 +425,26 @@ function Cluster:new(config, server_opts, opts)
383425
return object
384426
end
385427

428+
--- Return the last applied configuration.
429+
function Cluster:config()
430+
assert_no_pending_config_builder(self, 'config()')
431+
432+
return table.deepcopy(self._config)
433+
end
434+
435+
--- Initialize a configuration builder based on the current config.
436+
--
437+
-- The returned builder is stored inside the cluster object and later
438+
-- consumed by :apply_config_changes(), which turns it into a config
439+
-- table and passes it to :sync().
440+
function Cluster:modify_config()
441+
assert(self._config_builder == nil,
442+
':modify_config() already called and changes were not applied')
443+
444+
self._config_builder = cbuilder:new(self:config())
445+
return self._config_builder
446+
end
447+
386448
-- }}} Replicaset management
387449

388450
-- {{{ Replicaset that can't start

test/cluster_test.lua

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,67 @@ g.test_reload = function()
306306
assert_instance_failover_mode(c, 'i-003', 'off')
307307
end
308308

309+
g.test_modify_config = function()
310+
t.run_only_if(utils.version_current_ge_than(3, 0, 0),
311+
[[Declarative configuration works on Tarantool 3.0.0+.
312+
See tarantool/tarantool@13149d65bc9d for details]])
313+
314+
local config = cbuilder:new()
315+
:use_group('group-001')
316+
:use_replicaset('replicaset-001')
317+
:set_replicaset_option('replication.failover', 'manual')
318+
:set_replicaset_option('leader', 'i-001')
319+
:add_instance('i-001', {})
320+
:config()
321+
322+
local c = cluster:new(config, server_opts)
323+
324+
c:start()
325+
326+
c:modify_config()
327+
:use_group('group-001')
328+
:use_replicaset('replicaset-001')
329+
:add_instance('i-002', {})
330+
c:apply_config_changes({start_stop = true})
331+
332+
assert_instance_running(c, 'i-001', 'replicaset-001')
333+
assert_instance_running(c, 'i-002', 'replicaset-001')
334+
335+
c:modify_config()
336+
:use_group('group-001')
337+
:use_replicaset('replicaset-001')
338+
:set_replicaset_option('leader', 'i-002')
339+
340+
local expected_msg =
341+
':modify_config() was called; apply configuration changes with ' ..
342+
':apply_config_changes() before calling :reload()'
343+
t.assert_error_msg_contains(expected_msg, c.reload, c)
344+
345+
expected_msg =
346+
':modify_config() was called; apply configuration changes with ' ..
347+
':apply_config_changes() before calling :sync()'
348+
t.assert_error_msg_contains(expected_msg, c.sync, c, config)
349+
350+
expected_msg =
351+
':modify_config() was called; apply configuration changes with ' ..
352+
':apply_config_changes() before calling :config()'
353+
t.assert_error_msg_contains(expected_msg, c.config, c)
354+
355+
c:apply_config_changes()
356+
357+
local updated_config = c:config()
358+
local replicaset =
359+
updated_config.groups['group-001'].replicasets['replicaset-001']
360+
t.assert_equals(replicaset.leader, 'i-002')
361+
t.assert_not_equals(replicaset.instances['i-002'], nil)
362+
363+
t.assert_equals(c:size(), 2)
364+
365+
c:stop()
366+
assert_instance_stopped(c, 'i-001')
367+
assert_instance_stopped(c, 'i-002')
368+
end
369+
309370
g.test_each = function()
310371
t.run_only_if(utils.version_current_ge_than(3, 0, 0),
311372
[[Declarative configuration works on Tarantool 3.0.0+.

0 commit comments

Comments
 (0)