Skip to content

Commit

Permalink
config: introduce roles
Browse files Browse the repository at this point in the history
This patch introduces initial support for roles. Dependencies are not
currently supported for roles. In this regard, there is no strict order
in which roles are performed.

Part of tarantool#9078

@TarantoolBot document
Title: Roles

Roles are programs that run when a configuration is loaded or reloaded.
Roles can be defined for each instance. On first run, the role is
initialized with the init() method. On each run, the role calls
validate_config() and then apply_config() if validate_config() returns
true. If the roles were removed from the instance on reboot, the role is
stopped using the stop() method.

If a role was added to an instance, then removed from the instance, and
then added again, init() will be called.

If a role is defined more than once on an instance, it will still only
run once.

Roles must have four methods: init(), validate_config(), apply_config()
and stop().
  • Loading branch information
ImeevMA committed Sep 5, 2023
1 parent dc8973c commit 36099ba
Show file tree
Hide file tree
Showing 8 changed files with 557 additions and 2 deletions.
4 changes: 4 additions & 0 deletions changelogs/unreleased/gh-9078-roles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## feature/config

* Introduced the initial support for roles - programs that run when
a configuration is loaded or reloaded (gh-9078).
1 change: 1 addition & 0 deletions src/box/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ lua_source(lua_sources lua/config/applier/console.lua config_applier_console
lua_source(lua_sources lua/config/applier/credentials.lua config_applier_credentials_lua)
lua_source(lua_sources lua/config/applier/fiber.lua config_applier_fiber_lua)
lua_source(lua_sources lua/config/applier/mkdir.lua config_applier_mkdir_lua)
lua_source(lua_sources lua/config/applier/roles.lua config_applier_roles_lua)
lua_source(lua_sources lua/config/applier/sharding.lua config_applier_sharding_lua)
lua_source(lua_sources lua/config/cluster_config.lua config_cluster_config_lua)
lua_source(lua_sources lua/config/configdata.lua config_configdata_lua)
Expand Down
62 changes: 62 additions & 0 deletions src/box/lua/config/applier/roles.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
local log = require('internal.config.utils.log')

local last_loaded = {}

local function stop_roles(roles)
for role_name, role in pairs(last_loaded) do
if roles == nil or not roles[role_name] then
log.verbose('roles.post_apply: stop role ' .. role_name)
role.stop()
end
end
end

local function apply(config)
local configdata = config._configdata
local role_names = configdata:get('roles', {use_default = true})
if role_names == nil or next(role_names) == nil then
stop_roles()
return
end

-- Remove duplicates.
local roles = {}
for _, role_name in pairs(role_names) do
roles[role_name] = true
end

-- Run roles.
local roles_cfg = configdata:get('roles_cfg', {use_default = true}) or {}
local loaded = {}
for role_name in pairs(roles) do
local role = last_loaded[role_name]
if not role then
role = require(role_name)
local methods = {'init', 'validate_config', 'apply_config', 'stop'}
for _, method_name in pairs(methods) do
if type(role[method_name]) ~= 'function' then
local err = 'Role %s does not contain method %s'
error(err:format(role_name, method_name), 0)
end
end
log.verbose('roles.post_apply: initialize role ' .. role_name)
role.init()
end
loaded[role_name] = role
local role_cfg = roles_cfg[role_name]
if not role.validate_config(role_cfg) then
error('Wrong config for role ' .. role_name, 0)
end
log.verbose('roles.post_apply: apply config for role ' .. role_name)
role.apply_config(role_cfg)
end

-- Stop removed roles.
stop_roles(roles)
last_loaded = loaded
end

return {
name = 'roles',
apply = apply,
}
1 change: 1 addition & 0 deletions src/box/lua/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ function methods._initialize(self)
self:_register_applier(require('internal.config.applier.console'))
self:_register_applier(require('internal.config.applier.fiber'))
self:_register_applier(require('internal.config.applier.sharding'))
self:_register_applier(require('internal.config.applier.roles'))
self:_register_applier(require('internal.config.applier.app'))

if extras ~= nil then
Expand Down
7 changes: 7 additions & 0 deletions src/box/lua/config/instance_config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1802,6 +1802,13 @@ return schema.new('instance_config', schema.record({
"compatibility",
}),
})),
roles_cfg = schema.map({
key = schema.scalar({type = 'string'}),
value = schema.scalar({type = 'any'}),
}),
roles = schema.array({
items = schema.scalar({type = 'string'})
}),
}, {
-- This kind of validation cannot be implemented as the
-- 'validate' annotation of a particular schema node. There
Expand Down
5 changes: 5 additions & 0 deletions src/box/lua/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ extern char session_lua[],
config_applier_credentials_lua[],
config_applier_fiber_lua[],
config_applier_mkdir_lua[],
config_applier_roles_lua[],
config_applier_sharding_lua[],
config_cluster_config_lua[],
config_configdata_lua[],
Expand Down Expand Up @@ -387,6 +388,10 @@ static const char *lua_sources[] = {
"internal.config.applier.sharding",
config_applier_sharding_lua,

"config/applier/roles",
"internal.config.applier.roles",
config_applier_roles_lua,

"config/init",
"config",
config_init_lua,
Expand Down
36 changes: 34 additions & 2 deletions test/config-luatest/helpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,20 @@ local simple_config = {

local function prepare_case(g, opts)
local dir = opts.dir
local roles = opts.roles
local script = opts.script
local options = opts.options

if dir == nil then
dir = treegen.prepare_directory(g, {}, {})
end

if roles ~= nil and next(roles) ~= nil then
for name, body in pairs(roles) do
treegen.write_script(dir, name .. '.lua', body)
end
end

if script ~= nil then
treegen.write_script(dir, 'main.lua', script)
end
Expand Down Expand Up @@ -135,6 +142,10 @@ end
-- Start a server with the given script and the given
-- configuration, run a verification function on it.
--
-- * opts.roles
--
-- Role codes for writing into corresponding files.
--
-- * opts.script
--
-- Code write into the main.lua file.
Expand Down Expand Up @@ -164,6 +175,7 @@ end
-- Start tarantool process with the given script/config and check
-- the error.
--
-- * opts.roles
-- * opts.script
-- * opts.options
--
Expand All @@ -185,12 +197,17 @@ end
-- Start a server, write a new script/config, reload, run a
-- verification function.
--
-- * opts.roles
-- * opts.script
-- * opts.options
-- * opts.verify
--
-- Same as in success_case().
--
-- * opts.roles_2
--
-- A new list of roles to prepare before config:reload().
--
-- * opts.script_2
--
-- A new script to write into the main.lua file before
Expand All @@ -205,6 +222,7 @@ end
--
-- Verify test invariants after config:reload().
local function reload_success_case(g, opts)
local roles_2 = opts.roles_2
local script_2 = opts.script_2
local options = assert(opts.options)
local verify_2 = assert(opts.verify_2)
Expand All @@ -214,6 +232,7 @@ local function reload_success_case(g, opts)

prepare_case(g, {
dir = prepared.dir,
roles = roles_2,
script = script_2,
options = options_2,
})
Expand All @@ -227,31 +246,44 @@ end
-- Start a server, write a new script/config, reload, run a
-- verification function.
--
-- * opts.roles
-- * opts.script
-- * opts.options
-- * opts.verify
--
-- Same as in success_case().
--
-- * opts.roles_2
--
-- A new list of roles to prepare before config:reload().
--
-- * opts.script_2
--
-- A new script to write into the main.lua file before
-- config:reload().
--
-- * opts.options_2
--
-- A new config to use for the config:reload(). It is optional,
-- if not provided opts.options is used instead.
--
-- * opts.exp_err
--
-- An error that config:reload() must raise.
local function reload_failure_case(g, opts)
local script_2 = assert(opts.script_2)
local script_2 = opts.script_2
local roles_2 = opts.roles_2
local options = assert(opts.options)
local options_2 = opts.options_2 or options
local exp_err = assert(opts.exp_err)

local prepared = success_case(g, opts)

prepare_case(g, {
dir = prepared.dir,
roles = roles_2,
script = script_2,
options = options,
options = options_2,
})
t.assert_error_msg_equals(exp_err, g.server.exec, g.server, function()
local config = require('config')
Expand Down

0 comments on commit 36099ba

Please sign in to comment.