From 15011d6dc92f92a3afe14b83ea765aae902f9806 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Wed, 27 Mar 2024 20:27:02 +0300 Subject: [PATCH] roles: introduce router configuration This patch introduces `roles.crud-router` role configuration through `roles_cfg`, similar to existing Cartridge clusterwide configuration support. For now, storages don't have any configuration, so they remain unchanged. After this patch, Tarantool 3 roles have all features supported in Cartridge roles. Closes #415 --- CHANGELOG.md | 1 + README.md | 15 +++++++ roles/crud-router.lua | 61 ++++++++++++++++++++++++++++- test/helper.lua | 4 ++ test/integration/cfg_test.lua | 51 ++++++++++++++++++------ test/integration/stats_test.lua | 34 +++++++++++----- test/tarantool3_helpers/cluster.lua | 27 ++++++++++++- 7 files changed, 168 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7110aea7..997478d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added * Asynchronous bootstrap support for storages (#412). * Tarantool 3 roles for setting up crud routers and storages (#415). +* Ability to configure crud through Tarantool 3 roles configuration (#415). ### Changed * Explicitly forbid datetime interval conditions (#373). diff --git a/README.md b/README.md index cf210a25..bf6ce01a 100644 --- a/README.md +++ b/README.md @@ -2063,6 +2063,21 @@ issues. 4. Start the application cluster. You can check whether asynchronous bootstrap had finished through `crud.storage_info()` calls on router. +5. Configure the statistics with roles configuration + (see `crud.cfg` options in [statistics](#statistics) section): + ```yaml + roles: + - roles.crud-router + roles_cfg: + roles.crud-router: + stats: true + stats_driver: metrics + stats_quantiles: false + stats_quantile_tolerated_error: 0.001 + stats_quantile_age_buckets_count: 5 + stats_quantile_max_age_time: 180 + ``` + Now your cluster contains storages that are configured to be used for CRUD-operations. You can simply call CRUD functions on the router to insert, select, and update diff --git a/roles/crud-router.lua b/roles/crud-router.lua index 62450964..31a0df1d 100644 --- a/roles/crud-router.lua +++ b/roles/crud-router.lua @@ -3,6 +3,7 @@ local errors = require('errors') local crud = require('crud') local common_role_utils = require('crud.common.roles') local common_utils = require('crud.common.utils') +local stats = require('crud.stats') local TarantoolRoleConfigurationError = errors.new_class('TarantoolRoleConfigurationError') @@ -12,15 +13,71 @@ TarantoolRoleConfigurationError:assert( ('Tarantool 3 role is not supported for Tarantool %s, use 3.0.2 or newer'):format(tarantool_version) ) -local function validate() + +local function validate_enabled_on_sharding_router() TarantoolRoleConfigurationError:assert( common_role_utils.is_sharding_role_enabled('router'), 'Instance must be a sharding router to enable roles.crud-router' ) end -local function apply() +local cfg_types = { + stats = 'boolean', + stats_driver = 'string', + stats_quantiles = 'boolean', + stats_quantile_tolerated_error = 'number', + stats_quantile_age_buckets_count = 'number', + stats_quantile_max_age_time = 'number', +} + +local cfg_values = { + stats_driver = function(value) + TarantoolRoleConfigurationError:assert( + stats.is_driver_supported(value), + 'Invalid "stats_driver" field value: %q is not supported', + value + ) + end, +} + +local function validate_roles_cfg(roles_cfg) + if roles_cfg == nil then + return + end + + TarantoolRoleConfigurationError:assert( + type(roles_cfg) == 'table', + 'roles_cfg must be a table' + ) + + for name, value in pairs(roles_cfg) do + TarantoolRoleConfigurationError:assert( + cfg_types[name] ~= nil, + 'Unknown field %q', name + ) + + TarantoolRoleConfigurationError:assert( + type(value) == cfg_types[name], + 'Invalid %q field type: expected %s, got %s', + name, cfg_types[name], type(value) + ) + + if cfg_values[name] ~= nil then + cfg_values[name](value) + end + end +end + +local function validate(roles_cfg) + validate_enabled_on_sharding_router() + + validate_roles_cfg(roles_cfg) +end + +local function apply(roles_cfg) crud.init_router() + + crud.cfg(roles_cfg) end local function stop() diff --git a/test/helper.lua b/test/helper.lua index 0a719d3f..34c752ce 100644 --- a/test/helper.lua +++ b/test/helper.lua @@ -1468,4 +1468,8 @@ function helpers.skip_if_tarantool3_crud_roles_unsupported() ("Tarantool %s does not support crud roles"):format(version)) end +function helpers.skip_not_config_backend(backend) + t.skip_if(backend ~= helpers.backend.CONFIG, "The test is for Tarantool 3 with config only") +end + return helpers diff --git a/test/integration/cfg_test.lua b/test/integration/cfg_test.lua index 1c279472..0ab5b906 100644 --- a/test/integration/cfg_test.lua +++ b/test/integration/cfg_test.lua @@ -180,32 +180,59 @@ end local role_cfg_error_cases = { wrong_section_type = { - args = {crud = 'enabled'}, - err = 'Configuration \\\"crud\\\" section must be a table', + args = 'enabled', + err_cartridge = 'Configuration \\\"crud\\\" section must be a table', + err_tarantool3 = 'Wrong config for role roles.crud-router: TarantoolRoleConfigurationError: '.. + 'roles_cfg must be a table', }, wrong_structure = { - args = {crud = {crud = {stats = true}}}, - err = '\\\"crud\\\" section is already presented as a name of \\\"crud.yml\\\", ' .. - 'do not use it as a top-level section name', + args = {crud = {stats = true}}, + err_cartridge = '\\\"crud\\\" section is already presented as a name of \\\"crud.yml\\\", ' .. + 'do not use it as a top-level section name', + err_tarantool3 = 'Wrong config for role roles.crud-router: TarantoolRoleConfigurationError: '.. + 'Unknown field \"crud\"', }, wrong_type = { - args = {crud = {stats = 'enabled'}}, - err = 'Invalid crud configuration field \\\"stats\\\" type: expected boolean, got string', + args = {stats = 'enabled'}, + err_cartridge = 'Invalid crud configuration field \\\"stats\\\" type: expected boolean, got string', + err_tarantool3 = 'Wrong config for role roles.crud-router: TarantoolRoleConfigurationError: '.. + 'Invalid \"stats\" field type: expected boolean, got string', }, wrong_value = { - args = {crud = {stats_driver = 'prometheus'}}, - err = 'Invalid crud configuration field \\\"stats_driver\\\" value: \\\"prometheus\\\" is not supported', + args = {stats_driver = 'prometheus'}, + err_cartridge = 'Invalid crud configuration field \\\"stats_driver\\\" value: '.. + '\\\"prometheus\\\" is not supported', + err_tarantool3 = 'Wrong config for role roles.crud-router: TarantoolRoleConfigurationError: '.. + 'Invalid \"stats_driver\" field value: \"prometheus\" is not supported', } } for name, case in pairs(role_cfg_error_cases) do - group['test_role_cfg_' .. name] = function(g) + group['test_cartridge_role_cfg_' .. name] = function(g) helpers.skip_not_cartridge_backend(g.params.backend) local success, error = pcall(function() - g.router:upload_config(case.args) + g.router:upload_config({ + crud = case.args, + }) end) t.assert_equals(success, false) - t.assert_str_contains(error.response.body, case.err) + t.assert_str_contains(error.response.body, case.err_cartridge) + end + + group['test_tarantool3_role_cfg_' .. name] = function(g) + helpers.skip_not_config_backend(g.params.backend) + local success, error = pcall(function() + local cfg = g.cluster:cfg() + + cfg.groups['routers'].roles_cfg = { + ['roles.crud-router'] = case.args, + } + + g.cluster:reload_config(cfg) + end) + + t.assert_equals(success, false) + t.assert_str_contains(tostring(error), case.err_tarantool3) end end diff --git a/test/integration/stats_test.lua b/test/integration/stats_test.lua index b9d23167..39b313be 100644 --- a/test/integration/stats_test.lua +++ b/test/integration/stats_test.lua @@ -9,15 +9,19 @@ local matrix = helpers.backend_matrix({ { way = 'call', args = { driver = 'metrics', quantiles = false }}, { way = 'call', args = { driver = 'metrics', quantiles = true }}, }) -table.insert(matrix, {backend = helpers.backend.CARTRIDGE, - way = 'role', args = { driver = 'local' }, -}) -table.insert(matrix, {backend = helpers.backend.CARTRIDGE, - way = 'role', args = { driver = 'metrics', quantiles = false }, -}) -table.insert(matrix, {backend = helpers.backend.CARTRIDGE, - way = 'role', args = { driver = 'metrics', quantiles = true }, -}) + +for _, backend in ipairs({helpers.backend.CARTRIDGE, helpers.backend.CONFIG}) do + table.insert(matrix, {backend = backend, + way = 'role', args = { driver = 'local' }, + }) + table.insert(matrix, {backend = backend, + way = 'role', args = { driver = 'metrics', quantiles = false }, + }) + table.insert(matrix, {backend = backend, + way = 'role', args = { driver = 'metrics', quantiles = true }, + }) +end + local pgroup = t.group('stats_integration', matrix) matrix = helpers.backend_matrix({ @@ -57,7 +61,17 @@ local call_cfg = function(g, way, cfg) require('crud').cfg(...) ]], { cfg }) elseif way == 'role' then - g.router:upload_config{crud = cfg} + if g.params.backend == helpers.backend.CARTRIDGE then + g.router:upload_config{crud = cfg} + elseif g.params.backend == helpers.backend.CONFIG then + local cluster_cfg = g.cluster:cfg() + + cluster_cfg.groups['routers'].roles_cfg = { + ['roles.crud-router'] = cfg, + } + + g.cluster:reload_config(cluster_cfg) + end end end diff --git a/test/tarantool3_helpers/cluster.lua b/test/tarantool3_helpers/cluster.lua index 3b56ce83..63f8dd4c 100644 --- a/test/tarantool3_helpers/cluster.lua +++ b/test/tarantool3_helpers/cluster.lua @@ -297,8 +297,33 @@ function Cluster:cfg(new_config) return table.deepcopy(self.config) end +local function strip_all_entries(t, name) + if type(t) ~= 'table' then + return t + end + + t[name] = nil + + for k, v in pairs(t) do + t[k] = strip_all_entries(v, name) + end + + return t +end + +local function check_only_roles_cfg_changed_in_groups(old_groups, new_groups) + old_groups = table.deepcopy(old_groups) + new_groups = table.deepcopy(new_groups) + + local old_groups_no_roles_cfg = strip_all_entries(old_groups, 'roles_cfg') + local new_groups_no_roles_cfg = strip_all_entries(new_groups, 'roles_cfg') + + t.assert_equals(new_groups_no_roles_cfg, old_groups_no_roles_cfg, + 'groups reload supports only roles_cfg reload') +end + function Cluster:reload_config(new_config) - t.assert_equals(new_config.groups, self.config.groups, 'groups reload is not supported yet') + check_only_roles_cfg_changed_in_groups(self.config.groups, new_config.groups) for _, server in ipairs(self.servers) do write_config(self.dirs[server], new_config)