Skip to content

Commit de42344

Browse files
mandeserolocker
authored andcommitted
cbuilder: fix duplicate instance creation
Fix a bug in cbuilder when `set_instance_option()` is called without `use_group()`/`use_replicaset()`: previously it always assumed `group-001`/`replicaset-001` and could silently create a duplicate instance entry with the same name in another replicaset, leading to duplicated instances in the generated config. Now cbuilder searches for the instance across all groups/replicasets when no explicit group or replicaset is set, and raises an error if the instance is not found instead of creating it implicitly. Closes #404
1 parent 9f574e9 commit de42344

File tree

3 files changed

+103
-2
lines changed

3 files changed

+103
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
allowing tests to keep clusters alive between test runs (gh-414).
1111
- Fixed a bug where URI search would terminate prematurely when multiple
1212
replicasets existed (gh-427).
13+
- Fixed `cbuilder:set_instance_option()` to reuse existing instances found in a
14+
provided configuration and error when a target instance is missing. Also,
15+
`cbuilder:add_instance()` now rejects duplicate instance names across groups
16+
and replicasets (gh-404).
1317

1418
## 1.2.1
1519

luatest/cbuilder.lua

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,16 +194,47 @@ function Builder:set_replicaset_option(path, value)
194194
return self
195195
end
196196

197+
local function find_instance(config, instance_name)
198+
local groups = config.groups
199+
if type(groups) ~= 'table' then
200+
return nil
201+
end
202+
203+
for group_name, group in pairs(groups) do
204+
local replicasets = group.replicasets
205+
if type(replicasets) == 'table' then
206+
for replicaset_name, replicaset in pairs(replicasets) do
207+
local instances = replicaset.instances
208+
if type(instances) == 'table' and
209+
instances[instance_name] ~= nil then
210+
return group_name, replicaset_name
211+
end
212+
end
213+
end
214+
end
215+
216+
return nil
217+
end
218+
197219
-- Set an option of a particular instance in the selected replicaset.
198220
--
199221
-- @string instance_name Instance where the option will be saved.
200222
-- @string path Option path.
201223
-- @param value Option value (int, string, table).
202224
function Builder:set_instance_option(instance_name, path, value)
203225
checks('table', 'string', 'string', '?')
226+
227+
local group_name, replicaset_name = find_instance(self._config,
228+
instance_name)
229+
if group_name == nil then
230+
error(('Instance %q is not found in the configuration. ' ..
231+
'Use :add_instance() to create it first.')
232+
:format(instance_name))
233+
end
234+
204235
path = fun.chain({
205-
'groups', self._group,
206-
'replicasets', self._replicaset,
236+
'groups', group_name,
237+
'replicasets', replicaset_name,
207238
'instances', instance_name,
208239
}, path:split('.')):totable()
209240

@@ -217,6 +248,16 @@ end
217248
-- @tab iconfig Declarative config for the instance.
218249
function Builder:add_instance(instance_name, iconfig)
219250
checks('table', 'string', '?')
251+
252+
local group_name, replicaset_name = find_instance(self._config,
253+
instance_name)
254+
if group_name ~= nil then
255+
error(
256+
('Found instance with the same name %q in ' ..
257+
'the replicaset %q in the group %q')
258+
:format(instance_name, replicaset_name, group_name))
259+
end
260+
220261
local path = {
221262
'groups', self._group,
222263
'replicasets', self._replicaset,

test/cbuilder_test.lua

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,62 @@ g.test_set_instance_option = function()
147147
builder.set_instance_option, builder, 'foo', 'replication.anon', 'bar')
148148
end
149149

150+
g.test_set_instance_option_uses_existing_instance = function()
151+
t.run_only_if(utils.version_current_ge_than(3, 0, 0),
152+
[[Declarative configuration works on Tarantool 3.0.0+.
153+
See tarantool/tarantool@13149d65bc9d for details]])
154+
155+
local config_1 = config_builder:new()
156+
:use_replicaset('r-001')
157+
:add_instance('i-001', {})
158+
:set_instance_option('i-001', 'replication.timeout', 0.1)
159+
:config()
160+
161+
local config_2 = config_builder:new(config_1)
162+
:set_instance_option('i-001', 'replication.timeout', 1000)
163+
:config()
164+
165+
t.assert_equals(config_2, merge_config(config_1, {
166+
groups = {
167+
['group-001'] = {
168+
replicasets = {
169+
['r-001'] = {
170+
instances = {
171+
['i-001'] = {replication = {timeout = 1000}},
172+
},
173+
},
174+
},
175+
},
176+
},
177+
}))
178+
179+
local builder = config_builder:new()
180+
t.assert_error_msg_contains(
181+
'Instance "missing" is not found in the configuration',
182+
builder.set_instance_option, builder, 'missing',
183+
'replication.timeout', 0.1)
184+
end
185+
186+
g.test_instance_names_are_unique = function()
187+
t.run_only_if(utils.version_current_ge_than(3, 0, 0),
188+
[[Declarative configuration works on Tarantool 3.0.0+.
189+
See tarantool/tarantool@13149d65bc9d for details]])
190+
191+
local builder = config_builder:new()
192+
:use_group('g-001')
193+
:use_replicaset('r-001')
194+
:add_instance('duplicate', {})
195+
:use_group('g-002')
196+
:use_replicaset('r-002')
197+
198+
199+
t.assert_error_msg_contains(
200+
'Found instance with the same name "duplicate" in the ' ..
201+
'replicaset "r-001" in the group "g-001"',
202+
builder.add_instance, builder, 'duplicate', {}
203+
)
204+
end
205+
150206
g.test_set_replicaset_option = function()
151207
t.run_only_if(utils.version_current_ge_than(3, 0, 0),
152208
[[Declarative configuration works on Tarantool 3.0.0+.

0 commit comments

Comments
 (0)