Skip to content

Commit 50d818b

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 d6d4bb3 commit 50d818b

File tree

6 files changed

+170
-3
lines changed

6 files changed

+170
-3
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: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,17 @@ return {
15451545

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

1548+
5. Configure the statistics with clusterwide configuration:
1549+
```yaml
1550+
crud:
1551+
stats: true
1552+
stats_driver: metrics
1553+
stats_quantiles: false
1554+
stats_quantile_tolerated_error: 0.001
1555+
stats_quantile_age_buckets_count: 5
1556+
stats_quantile_max_age_time: 180
1557+
```
1558+
15481559
Now your cluster contains storages that are configured to be used for
15491560
CRUD-operations.
15501561
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: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,75 @@ 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+
group.test_role_cfg_wrong_section_type = function(g)
180+
local success, error = pcall(function()
181+
g.cluster.main_server:upload_config({crud = 'enabled'})
182+
end)
183+
184+
t.assert_equals(success, false)
185+
t.assert_str_contains(error.response.body,
186+
'Configuration \\\"crud\\\" section must be a table')
187+
end
188+
189+
group.test_role_cfg_wrong_structure = function(g)
190+
local success, error = pcall(function()
191+
g.cluster.main_server:upload_config(
192+
{crud = {crud = {stats = true}}}
193+
)
194+
end)
195+
196+
t.assert_equals(success, false)
197+
t.assert_str_contains(error.response.body,
198+
'\\\"crud\\\" section is already presented as a name of \\\"crud.yml\\\", ' ..
199+
'do not use it as a top-level section name')
200+
end
201+
202+
group.test_role_cfg_wrong_type = function(g)
203+
local success, error = pcall(function()
204+
g.cluster.main_server:upload_config({crud = {stats = 'enabled'}})
205+
end)
206+
207+
t.assert_equals(success, false)
208+
t.assert_str_contains(error.response.body,
209+
'Invalid crud configuration field \\\"stats\\\" type: expected boolean, got string')
210+
end
211+
212+
group.test_role_cfg_wrong_value = function(g)
213+
local success, error = pcall(function()
214+
g.cluster.main_server:upload_config({crud = {stats_driver = 'prometheus'}})
215+
end)
216+
217+
t.assert_equals(success, false)
218+
t.assert_str_contains(error.response.body,
219+
'Invalid crud configuration field \\\"stats_driver\\\" value: \\\"prometheus\\\" is not supported')
220+
end

0 commit comments

Comments
 (0)