Skip to content

Commit

Permalink
config: set names automatically on schema upgrade
Browse files Browse the repository at this point in the history
It's impossible to set names on Tarantool below 3.0.0, as all DDL
is forbidden before schema upgrade.

Let's make names NoOp on schema below Tarantool 3.0.0 and set names
automatically only when schema upgrade is done.

Follow-up tarantool#8978

NO_DOC=tarantool/doc#3661
  • Loading branch information
Serpentian committed Oct 26, 2023
1 parent e4a31ef commit 216c037
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## feature/config

* If Tarantool is configured via a YAML file or etcd, then instance and replica
set names are automatically set when possible (gh-8978).
47 changes: 42 additions & 5 deletions src/box/lua/config/applier/box_cfg.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ local log = require('internal.config.utils.log')
local instance_config = require('internal.config.instance_config')
local snapshot = require('internal.config.utils.snapshot')
local schedule_task = fiber._internal.schedule_task
local mkversion = require('internal.mkversion')

local function peer_uri(configdata, peer_name)
local iconfig = configdata._peers[peer_name].iconfig_def
Expand Down Expand Up @@ -183,6 +184,25 @@ local function names_rw_watcher()
end)
end

local function names_schema_upgrade_on_replace(old, new)
if old == nil or new == nil then
return
end

local version_3 = mkversion(3, 0, 0)
local old_version = mkversion.from_tuple(old)
local new_version = mkversion.from_tuple(new)
if old_version < version_3 and new_version >= version_3 then
-- We cannot do it inside on_replace trigger, as the version
-- is not considered set yet and we may try to set names, when
-- schema is not yet updated, which will fail as DDL is
-- prohibited before schema upgrade.
box.on_commit(function()
names_state.rw_watcher = names_rw_watcher()
end)
end
end

local function names_cluster_on_replace(old, new)
-- Ignore insert of a new replica. Every new replica will have
-- name set, as it may join to a replicaset only by bootstrap, which
Expand Down Expand Up @@ -238,7 +258,7 @@ names_check_and_clean = function()
end
end

local function names_apply(config, missing_names)
local function names_apply(config, missing_names, schema_version)
-- names_state.config should be updated, as all triggers rely on configdata,
-- saved in it. configdata may be different from the one, we already have.
names_state.config = config
Expand All @@ -254,11 +274,26 @@ local function names_apply(config, missing_names)
box.space._schema:on_replace(names_schema_on_replace)
box.space._cluster:on_replace(names_cluster_on_replace)

-- Wait for rw state.
names_state.rw_watcher = names_rw_watcher()
-- Wait for rw state. If schema version is nil, bootstrap is
-- going to be done.
if schema_version and schema_version < mkversion(3, 0, 0) then
box.space._schema:on_replace(names_schema_upgrade_on_replace)
else
names_state.rw_watcher = names_rw_watcher()
end

names_state.is_configured = true
end

local function get_schema_version_before_cfg(config)
local snap_path = snapshot.get_path(config._configdata._iconfig_def)
if snap_path ~= nil then
return mkversion.from_tuple(snapshot.get_schema_version(snap_path))
end
-- Bootstrap, config not found
return nil
end

-- Returns nothing or {needs_retry = true}.
local function apply(config)
local configdata = config._configdata
Expand Down Expand Up @@ -490,17 +525,19 @@ local function apply(config)
if not missing_names_is_empty(missing_names, names.replicaset_name) then
if is_startup then
local on_schema_init
-- schema version may be nil, if bootstrap is done.
local version = get_schema_version_before_cfg(config)
-- on_schema init trigger isn't deleted, as it's executed only once.
on_schema_init = box.ctl.on_schema_init(function()
-- missing_names cannot be gathered inside on_schema_init
-- trigger, as box.cfg is already considered configured but
-- box.info is still not properly initialized.
names_apply(config, missing_names)
names_apply(config, missing_names, version)
box.ctl.on_schema_init(nil, on_schema_init)
end)
else
-- Note, that we try to find new missing names on every reload.
names_apply(config, missing_names)
names_apply(config, missing_names, mkversion.get())
end
end

Expand Down
23 changes: 23 additions & 0 deletions src/box/lua/config/utils/snapshot.lua
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,30 @@ local function get_snapshot_names(snap_path)
}
end

local function get_snapshot_schema_version(snap_path)
for _, row in xlog.pairs(snap_path) do
local body = row.BODY
if not body.space_id then
goto continue
end

if body.space_id > box.schema.SCHEMA_ID then
break
end

if body.space_id == box.schema.SCHEMA_ID then
if body.tuple[1] == 'version' then
return body.tuple
end
end
::continue::
end

assert(false)
end

return {
get_path = get_snapshot_path,
get_names = get_snapshot_names,
get_schema_version = get_snapshot_schema_version,
}
Binary file not shown.
16 changes: 16 additions & 0 deletions test/box-luatest/upgrade/2.11.0/replicaset/instance-001/gen.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
box.cfg{
replicaset_uuid = 'cbf06940-0790-498b-948d-042b62cf3d29',
instance_uuid = '8a274925-a26d-47fc-9e1b-af88ce939412',
replication = {3301, 3302},
listen = 3301,
}

box.schema.user.grant('guest', 'super')
box.schema.create_space('test_space')
while not box.info.replication[2] or
not box.info.replication[2].downstream or
box.info.replication[2].downstream.status ~= 'follow' do
require('fiber').yield(0.1)
end
box.snapshot()
os.exit(0)
Binary file not shown.
10 changes: 10 additions & 0 deletions test/box-luatest/upgrade/2.11.0/replicaset/instance-002/gen.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
box.cfg{
replicaset_uuid = 'cbf06940-0790-498b-948d-042b62cf3d29',
instance_uuid = '3de2e3e1-9ebe-4d0d-abb1-26d301b84633',
replication = {3301, 3302},
read_only = true,
listen = 3302,
}

box.snapshot()
os.exit(0)
128 changes: 128 additions & 0 deletions test/config-luatest/names_upgrade_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
local t = require('luatest')
local treegen = require('test.treegen')
local server = require('test.luatest_helpers.server')
local yaml = require('yaml')
local fun = require('fun')
local fio = require('fio')

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

g.before_all(function(g)
treegen.init(g)

g.uuids = {
['replicaset-001'] = 'cbf06940-0790-498b-948d-042b62cf3d29',
['instance-001'] = '8a274925-a26d-47fc-9e1b-af88ce939412',
['instance-002'] = '3de2e3e1-9ebe-4d0d-abb1-26d301b84633'
}

local datadir_prefix = 'test/box-luatest/upgrade/2.11.0/replicaset/'
local datadir_1 = fio.abspath(fio.pathjoin(datadir_prefix, 'instance-001'))
local datadir_2 = fio.abspath(fio.pathjoin(datadir_prefix, 'instance-002'))

local dir = treegen.prepare_directory(g, {}, {})
local workdir_1 = fio.pathjoin(dir, 'instance-001')
local workdir_2 = fio.pathjoin(dir, 'instance-002')

fio.mktree(workdir_1)
fio.mktree(workdir_2)

fio.copytree(datadir_1, workdir_1)
fio.copytree(datadir_2, workdir_2)

local config = {
credentials = {
users = {
guest = {
roles = {'super'},
},
},
},

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

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

local cfg = yaml.encode(config)
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})
g.instance_1:wait_until_ready()
g.instance_2:wait_until_ready()
end)

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

local function assert_before_upgrade()
t.assert_equals(box.space._schema:get{'version'}, {'version', 2, 11, 0})
local info = box.info
t.assert_equals(info.name, nil)
t.assert_equals(info.replicaset.name, nil)
end

local function assert_after_upgrade(instance_name, replicaset_name, names)
t.helpers.retrying({}, function()
local info = box.info
t.assert_equals(info.name, instance_name)
t.assert_equals(info.replicaset.name, replicaset_name)

local alerts = require('config')._alerts
for name, _ in pairs(names) do
t.assert_equals(alerts[name], nil)
end
end)
end

g.test_upgrade = function()
g.instance_1:exec(assert_before_upgrade)
g.instance_2:exec(assert_before_upgrade)

g.instance_1:exec(function()
box.ctl.wait_rw()
box.schema.upgrade()
end)

g.instance_2:wait_for_vclock_of(g.instance_1)

local rs = 'replicaset-001'
g.instance_1:exec(assert_after_upgrade, {g.instance_1.alias, rs, g.uuids})
g.instance_2:exec(assert_after_upgrade, {g.instance_2.alias, rs, g.uuids})
end

0 comments on commit 216c037

Please sign in to comment.