Skip to content

Commit

Permalink
config: automatically set missing names
Browse files Browse the repository at this point in the history
For now it's impossible to use config module in order to recover
from snaps, which don't have names set in them. Calling box.cfg
with names on recovery fails with MISMATCH error, which is caused
by difficult implementation of setting names on first box.cfg,
as names can be set only on rw instance.

This commit doesn't call box.cfg with names, if these names are
missing from the snapshot file. Instead it creates a fiber, which
will set names, when it's possible to do so. Only master sets the
names for the whole cluster.

Closes tarantool#8978

NO_DOC=tarantool/doc#3661
NO_CHANGELOG=following commits
  • Loading branch information
Serpentian committed Oct 12, 2023
1 parent c1649b7 commit 2d8705e
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 2 deletions.
94 changes: 92 additions & 2 deletions src/box/lua/config/applier/box_cfg.lua
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,26 @@ local function apply(config)
-- mix instances with data from different replicasets into one
-- replicaset.
local names = configdata:names()
box_cfg.instance_name = names.instance_name
box_cfg.replicaset_name = names.replicaset_name
box_cfg.instance_uuid = names.instance_uuid
box_cfg.replicaset_uuid = names.replicaset_uuid

-- Names are applied only if they're already in snap file.
-- Otherwise, master must apply names after box.cfg.
if not configdata._missing_names[names.instance_name] then
box_cfg.instance_name = names.instance_name
end
if not configdata._missing_names[names.replicaset_name] then
box_cfg.replicaset_name = names.replicaset_name
end

if is_startup then
for name, uuid in pairs(configdata._missing_names) do
local warning = string.format('box_cfg.apply: name %s for '..
'%s is missing', name, uuid)
config:_alert(name, {type = 'warn', message = warning})
end
end

-- Set bootstrap_leader option.
box_cfg.bootstrap_leader = configdata:bootstrap_leader()

Expand Down Expand Up @@ -426,7 +441,82 @@ local function apply(config)
box.cfg(box_cfg)
end

local function apply_names_schema_on_replace(old, new)
if old == nil and new[1] == 'replicaset_name' then
box.broadcast('apply_name', new[2])
end
end

local function apply_names_cluster_on_replace(old, new)
if old[3] == nil and new[3] ~= nil then
box.broadcast('apply_name', new[3])
end
end

local function apply_names_f(configdata)
local name = 'config_apply_names'
fiber.self():name(name)

box.ctl.wait_rw()

if table.equals(configdata._missing_names, {}) then
return
end

local names = configdata:names()
if configdata._missing_names[names.replicaset_name] then
box.space._schema:insert{'replicaset_name', names.replicaset_name}
configdata._missing_names[names.replicaset_name] = nil
end

for name, uuid in pairs(configdata._missing_names) do
local tuple = box.space._cluster.index.uuid:select(uuid)
assert(tuple ~= nil)
box.space._cluster:update(tuple[1][1], {{'=', 3, name}})
end
end

local function post_apply(config)
local configdata = config._configdata
if table.equals(configdata._missing_names, {}) then
return
end

local apply_names_fiber = fiber.new(apply_names_f, configdata)

-- Set triggers to notify apply_name watcher
box.space._cluster:on_replace(apply_names_cluster_on_replace)
box.space._schema:on_replace(apply_names_schema_on_replace)

local watch
local names = configdata:names()
watch = box.watch('apply_name', function(_, name)
-- Call box.cfg in order to properly initialize box.info.
if name == names.replicaset_name then
box.cfg{replicaset_name = name}
end
if name == names.instance_name then
box.cfg{instance_name = name}
end

if not configdata._missing_names[name] then
return
end
-- Delete name from _missing_names and drop the associated alert.
configdata._missing_names[name] = nil
config:_alert_drop(name)

if table.equals(configdata._missing_names, {}) then
apply_names_fiber:cancel()
box.space._schema:on_replace(nil, apply_names_schema_on_replace)
box.space._cluster:on_replace(nil, apply_names_cluster_on_replace)
watch:unregister()
end
end)
end

return {
name = 'box_cfg',
apply = apply,
post_apply = post_apply,
}
131 changes: 131 additions & 0 deletions test/config-luatest/set_names_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
local t = require('luatest')
local treegen = require('test.treegen')
local server = require('test.luatest_helpers.server')
local replica_set = require('luatest.replica_set')
local yaml = require('yaml')
local uuid = require('uuid')
local fun = require('fun')

local g = t.group('set-names-automatically')

local function initialize_xlogs(g, uuids)
g.replica_set = replica_set:new({})
local box_cfg = {
replicaset_uuid = uuids['replicaset-001'],
replication = {
server.build_listen_uri('instance-001', g.replica_set.id),
server.build_listen_uri('instance-002', g.replica_set.id),
},
}

box_cfg.instance_uuid = uuids['instance-001']
local instance_1 = g.replica_set:build_and_add_server(
{alias = 'instance-001', box_cfg = box_cfg})

box_cfg.instance_uuid = uuids['instance-002']
local instance_2 = g.replica_set:build_and_add_server(
{alias = 'instance-002', box_cfg = box_cfg})

g.replica_set:start()
g.replica_set:wait_for_fullmesh()
for _, replica in ipairs(g.replica_set.servers) do
replica:exec(function()
box.snapshot()
end)
end

g.replica_set:stop()
return instance_1.workdir, instance_2.workdir
end

g.before_all(function(g)
treegen.init(g)
local uuids = {
['replicaset-001'] = uuid.str(),
['instance-001'] = uuid.str(),
['instance-002'] = uuid.str(),
}

local workdir_1, workdir_2 = initialize_xlogs(g, uuids)
local config = {
credentials = {
users = {
guest = {
roles = {'super'},
},
},
},

iproto = {
listen = 'unix/:./{{ instance_name }}.iproto',
},

groups = {
['group-001'] = {
replicasets = {
['replicaset-001'] = {
database = {
replicaset_uuid = uuids['replicaset-001']
},
instances = {
['instance-001'] = {
snapshot = { dir = workdir_1, },
wal = { dir = workdir_1, },
database = {
instance_uuid = uuids['instance-001'],
mode = 'rw',
},
},
['instance-002'] = {
snapshot = { dir = workdir_2, },
wal = { dir = workdir_2, },
database = {
instance_uuid = uuids['instance-002']
},
},
},
},
},
},
},
}

local cfg = yaml.encode(config)
local dir = treegen.prepare_directory(g, {}, {})
local config_file = treegen.write_script(dir, 'cfg.yaml', cfg)
local opts = {config_file = config_file, chdir = dir}
g.instance_1 = server:new(fun.chain(opts, {alias = 'instance-001'}):tomap())
g.instance_2 = server:new(fun.chain(opts, {alias = 'instance-002'}):tomap())

g.instance_1:start({wait_until_ready = false})
g.instance_2:start({wait_until_ready = false})
t.helpers.retrying({}, function()
g.instance_1:connect_net_box()
g.instance_2:connect_net_box()
end)
end)

g.after_all(function(g)
g.replica_set:drop()
g.instance_1:drop()
g.instance_2:drop()
treegen.clean(g)
end)

g.test_names_are_set = function(g)
g.instance_1:exec(function()
local info = box.info
t.helpers.retrying({}, function()
t.assert_equals(info.replicaset.name, 'replicaset-001')
t.assert_equals(info.name, 'instance-001')
end)
end)

g.instance_2:exec(function()
local info = box.info
t.helpers.retrying({}, function()
t.assert_equals(info.replicaset.name, 'replicaset-001')
t.assert_equals(info.name, 'instance-002')
end)
end)
end

0 comments on commit 2d8705e

Please sign in to comment.