Skip to content

Commit ce5292d

Browse files
cartridge: clusterwide configuration for crud.cfg
After this patch, user may set crud.cfg with Cartridge clusterwide configuration of `cartridge.roles.crud-router` role [1]. The behavior is the same as in Lua crud.cfg call. 1. https://www.tarantool.io/en/doc/latest/book/cartridge/cartridge_dev/ Closes #332
1 parent 54c0ce2 commit ce5292d

File tree

7 files changed

+203
-30
lines changed

7 files changed

+203
-30
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1010
### Added
1111
* Added timeout condition for the validation of master presence in
1212
replicaset and for the master connection (#95).
13+
* Support Cartridge clusterwide configuration for `crud.cfg` (#332).
1314

1415
### Changed
1516
* **Breaking**: forbid using space id in `crud.len` (#255).

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,18 @@ return {
15451545

15461546
4. Don't forget to bootstrap vshard.
15471547

1548+
5. Configure the statistics with clusterwide configuration
1549+
(see `crud.cfg` options in [statistics](#statistics) section):
1550+
```yaml
1551+
crud:
1552+
stats: true
1553+
stats_driver: metrics
1554+
stats_quantiles: false
1555+
stats_quantile_tolerated_error: 0.001
1556+
stats_quantile_age_buckets_count: 5
1557+
stats_quantile_max_age_time: 180
1558+
```
1559+
15481560
Now your cluster contains storages that are configured to be used for
15491561
CRUD-operations.
15501562
You can simply call CRUD functions on the router to insert, select, and update

cartridge/roles/crud-router.lua

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
local errors = require('errors')
2+
13
local crud = require('crud')
24
local stash = require('crud.common.stash')
5+
local stats = require('crud.stats')
6+
7+
local RoleConfigurationError = errors.new_class('RoleConfigurationError', {capture_stack = false})
38

49
local function init()
510
crud.init_router()
@@ -10,10 +15,73 @@ local function stop()
1015
crud.stop_router()
1116
end
1217

18+
local cfg_types = {
19+
stats = 'boolean',
20+
stats_driver = 'string',
21+
stats_quantiles = 'boolean',
22+
stats_quantile_tolerated_error = 'number',
23+
stats_quantile_age_buckets_count = 'number',
24+
stats_quantile_max_age_time = 'number',
25+
}
26+
27+
local cfg_values = {
28+
stats_driver = function(value)
29+
RoleConfigurationError:assert(
30+
stats.is_driver_supported(value),
31+
'Invalid crud configuration field "stats_driver" value: %q is not supported',
32+
value
33+
)
34+
end,
35+
}
36+
37+
local function validate_config(conf_new, _)
38+
local crud_cfg = conf_new['crud']
39+
40+
if crud_cfg == nil then
41+
return true
42+
end
43+
44+
RoleConfigurationError:assert(
45+
type(crud_cfg) == 'table',
46+
'Configuration "crud" section must be a table'
47+
)
48+
49+
RoleConfigurationError:assert(
50+
crud_cfg.crud == nil,
51+
'"crud" section is already presented as a name of "crud.yml", ' ..
52+
'do not use it as a top-level section name'
53+
)
54+
55+
for name, value in pairs(crud_cfg) do
56+
RoleConfigurationError:assert(
57+
cfg_types[name] ~= nil,
58+
'Unknown crud configuration field %q', name
59+
)
60+
61+
RoleConfigurationError:assert(
62+
type(value) == cfg_types[name],
63+
'Invalid crud configuration field %q type: expected %s, got %s',
64+
name, cfg_types[name], type(value)
65+
)
66+
67+
if cfg_values[name] ~= nil then
68+
cfg_values[name](value)
69+
end
70+
end
71+
72+
return true
73+
end
74+
75+
local function apply_config(conf)
76+
crud.cfg(conf['crud'])
77+
end
78+
1379
return {
1480
role_name = 'crud-router',
1581
init = init,
1682
stop = stop,
83+
validate_config = validate_config,
84+
apply_config = apply_config,
1785
implies_router = true,
1886
dependencies = {'cartridge.roles.vshard-router'},
1987
}

crud/cfg.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,12 @@ local function __call(self, opts)
155155
stats_quantile_age_buckets_count = '?number',
156156
stats_quantile_max_age_time = '?number',
157157
})
158+
-- Value validation would be performed in stats checks, if required.
158159

159160
opts = table.deepcopy(opts) or {}
161+
-- opts from Cartridge clusterwide configuration is read-only,
162+
-- but we want to work with copy anyway.
163+
setmetatable(opts, {})
160164

161165
configure_stats(cfg, opts)
162166

crud/stats/init.lua

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ function stats.get_default_driver()
5858
end
5959
end
6060

61+
--- Check if provided driver is supported.
62+
--
63+
-- @function is_driver_supported
64+
--
65+
-- @string opts.driver
66+
--
67+
-- @treturn boolean Returns `true` or `false`.
68+
--
69+
function stats.is_driver_supported(driver)
70+
return drivers[driver] ~= nil
71+
end
72+
6173
--- Initializes statistics registry, enables callbacks and wrappers.
6274
--
6375
-- If already enabled, do nothing.
@@ -124,9 +136,8 @@ function stats.enable(opts)
124136
end
125137

126138
StatsError:assert(
127-
drivers[opts.driver] ~= nil,
128-
'Unsupported driver: %s', opts.driver
129-
)
139+
stats.is_driver_supported(opts.driver),
140+
'Unsupported driver: %s', opts.driver)
130141

131142
if opts.quantiles == nil then
132143
opts.quantiles = false

test/integration/cfg_test.lua

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,63 @@ group.test_gh_284_preset_stats_quantile_max_age_time_is_preserved = function(g)
146146
t.assert_equals(cfg.stats_quantile_max_age_time, 30,
147147
'Preset stats_quantile_max_age_time presents')
148148
end
149+
150+
group.test_role_cfg = function(g)
151+
local cfg = {
152+
stats = true,
153+
stats_driver = 'local',
154+
stats_quantiles = false,
155+
stats_quantile_tolerated_error = 1e-2,
156+
stats_quantile_age_buckets_count = 5,
157+
stats_quantile_max_age_time = 180,
158+
}
159+
160+
g.cluster.main_server:upload_config({crud = cfg})
161+
162+
local actual_cfg = g.cluster:server('router'):eval("return require('crud').cfg")
163+
t.assert_equals(cfg, actual_cfg)
164+
end
165+
166+
group.test_role_partial_cfg = function(g)
167+
local router = g.cluster:server('router')
168+
local cfg_before = router:eval("return require('crud').cfg()")
169+
170+
local cfg_after = table.deepcopy(cfg_before)
171+
cfg_after.stats = not cfg_before.stats
172+
173+
g.cluster.main_server:upload_config({crud = {stats = cfg_after.stats}})
174+
175+
local actual_cfg = g.cluster:server('router'):eval("return require('crud').cfg")
176+
t.assert_equals(cfg_after, actual_cfg, "Only requested field were updated")
177+
end
178+
179+
local role_cfg_error_cases = {
180+
wrong_section_type = {
181+
args = {crud = 'enabled'},
182+
err = 'Configuration \\\"crud\\\" section must be a table',
183+
},
184+
wrong_structure = {
185+
args = {crud = {crud = {stats = true}}},
186+
err = '\\\"crud\\\" section is already presented as a name of \\\"crud.yml\\\", ' ..
187+
'do not use it as a top-level section name',
188+
},
189+
wrong_type = {
190+
args = {crud = {stats = 'enabled'}},
191+
err = 'Invalid crud configuration field \\\"stats\\\" type: expected boolean, got string',
192+
},
193+
wrong_value = {
194+
args = {crud = {stats_driver = 'prometheus'}},
195+
err = 'Invalid crud configuration field \\\"stats_driver\\\" value: \\\"prometheus\\\" is not supported',
196+
}
197+
}
198+
199+
for name, case in pairs(role_cfg_error_cases) do
200+
group['test_rolce_cfg_' .. name] = function(g)
201+
local success, error = pcall(function()
202+
g.cluster.main_server:upload_config(case.args)
203+
end)
204+
205+
t.assert_equals(success, false)
206+
t.assert_str_contains(error.response.body, case.err)
207+
end
208+
end

test/integration/stats_test.lua

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ local t = require('luatest')
55
local stats_registry_utils = require('crud.stats.registry_utils')
66

77
local pgroup = t.group('stats_integration', {
8-
{ driver = 'local' },
9-
{ driver = 'metrics', quantiles = false },
10-
{ driver = 'metrics', quantiles = true },
8+
{ way = 'call', args = { driver = 'local' }},
9+
{ way = 'call', args = { driver = 'metrics', quantiles = false }},
10+
{ way = 'call', args = { driver = 'metrics', quantiles = true }},
11+
{ way = 'role', args = { driver = 'local' }},
12+
{ way = 'role', args = { driver = 'metrics', quantiles = false }},
13+
{ way = 'role', args = { driver = 'metrics', quantiles = true }},
1114
})
1215
local group_metrics = t.group('stats_metrics_integration', {
13-
{ driver = 'metrics', quantiles = false },
14-
{ driver = 'metrics', quantiles = true },
16+
{ way = 'call', args = { driver = 'metrics', quantiles = false }},
17+
{ way = 'role', args = { driver = 'metrics', quantiles = true }},
1518
})
1619

1720
local helpers = require('test.helper')
@@ -30,7 +33,7 @@ local function before_all(g)
3033
g.cluster:start()
3134
g.router = g.cluster:server('router').net_box
3235

33-
if g.params.driver == 'metrics' then
36+
if g.params.args.driver == 'metrics' then
3437
local is_metrics_supported = g.router:eval([[
3538
return require('crud.stats.metrics_registry').is_supported()
3639
]])
@@ -46,23 +49,37 @@ local function get_stats(g, space_name)
4649
return g.router:eval("return require('crud').stats(...)", { space_name })
4750
end
4851

49-
local function enable_stats(g, params)
50-
params = params or g.params
51-
g.router:eval([[
52-
local params = ...
53-
require('crud').cfg{
54-
stats = true,
55-
stats_driver = params.driver,
56-
stats_quantiles = params.quantiles,
57-
stats_quantile_tolerated_error = 1e-3,
58-
stats_quantile_age_buckets_count = 3,
59-
stats_quantile_max_age_time = 60,
60-
}
61-
]], { params })
52+
local call_cfg = function(g, way, cfg)
53+
if way == 'call' then
54+
g.router:eval([[
55+
require('crud').cfg(...)
56+
]], { cfg })
57+
elseif way == 'role' then
58+
g.cluster.main_server:upload_config{crud = cfg}
59+
end
60+
end
61+
62+
local function enable_stats(g, args)
63+
args = args or g.params.args
64+
65+
local cfg = {
66+
stats = true,
67+
stats_driver = args.driver,
68+
stats_quantiles = args.quantiles,
69+
stats_quantile_tolerated_error = 1e-3,
70+
stats_quantile_age_buckets_count = 3,
71+
stats_quantile_max_age_time = 60,
72+
}
73+
74+
call_cfg(g, g.params.way, cfg)
6275
end
6376

6477
local function disable_stats(g)
65-
g.router:eval("require('crud').cfg{ stats = false }")
78+
local cfg = {
79+
stats = false,
80+
}
81+
82+
call_cfg(g, g.params.way, cfg)
6683
end
6784

6885
local function before_each(g)
@@ -643,7 +660,7 @@ for name, case in pairs(simple_operation_cases) do
643660
t.assert_le(changed_after.latency_average, ok_average_max,
644661
'Changed average has appropriate value')
645662

646-
if g.params.quantiles == true then
663+
if g.params.args.quantiles == true then
647664
local ok_quantile_max = math.max(
648665
changed_before.latency_quantile_recent or 0,
649666
after_finish - before_start)
@@ -755,7 +772,7 @@ pgroup.before_test(
755772
generate_stats)
756773

757774
pgroup.test_role_reload_do_not_reset_observations = function(g)
758-
t.xfail_if(g.params.driver == 'metrics',
775+
t.xfail_if(g.params.args.driver == 'metrics',
759776
'See https://github.com/tarantool/metrics/issues/334')
760777

761778
local stats_before = get_stats(g)
@@ -870,7 +887,7 @@ end
870887

871888
local function validate_metrics(g, metrics)
872889
local quantile_stats
873-
if g.params.quantiles == true then
890+
if g.params.args.quantiles == true then
874891
quantile_stats = find_metric('tnt_crud_stats', metrics)
875892
t.assert_type(quantile_stats, 'table', '`tnt_crud_stats` summary metrics found')
876893
end
@@ -885,7 +902,7 @@ local function validate_metrics(g, metrics)
885902
local expected_operations = { 'insert', 'insert_many', 'get', 'replace', 'replace_many', 'update',
886903
'upsert', 'upsert_many', 'delete', 'select', 'truncate', 'len', 'count', 'borders' }
887904

888-
if g.params.quantiles == true then
905+
if g.params.args.quantiles == true then
889906
t.assert_items_equals(get_unique_label_values(quantile_stats, 'operation'), expected_operations,
890907
'Metrics are labelled with operation')
891908
end
@@ -899,7 +916,7 @@ local function validate_metrics(g, metrics)
899916

900917
local expected_statuses = { 'ok', 'error' }
901918

902-
if g.params.quantiles == true then
919+
if g.params.args.quantiles == true then
903920
t.assert_items_equals(
904921
get_unique_label_values(quantile_stats, 'status'),
905922
expected_statuses,
@@ -915,7 +932,7 @@ local function validate_metrics(g, metrics)
915932

916933
local expected_names = { space_name }
917934

918-
if g.params.quantiles == true then
935+
if g.params.args.quantiles == true then
919936
t.assert_items_equals(
920937
get_unique_label_values(quantile_stats, 'name'),
921938
expected_names,
@@ -931,7 +948,7 @@ local function validate_metrics(g, metrics)
931948
expected_names,
932949
'Metrics are labelled with space name')
933950

934-
if g.params.quantiles == true then
951+
if g.params.args.quantiles == true then
935952
local expected_quantiles = { 0.99 }
936953
t.assert_items_equals(get_unique_label_values(quantile_stats, 'quantile'), expected_quantiles,
937954
'Quantile metrics presents')

0 commit comments

Comments
 (0)