Skip to content

Commit 460bca6

Browse files
Add range checks for issues.set_limits (#1543)
Nowadays, only types are checked when calling `issues.set_limits`. However, there are some restrictions for these values (e.g. `clock_delta_threshold_warning` cannot be negative). This patch makes limits validation stricter.
1 parent 87d0765 commit 460bca6

File tree

5 files changed

+203
-18
lines changed

5 files changed

+203
-18
lines changed

cartridge.lua

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,11 @@ local function cfg(opts, box_opts)
706706
return nil, err
707707
end
708708

709+
local ok, err = issues.validate_limits(issue_limits)
710+
if not ok then
711+
return nil, err
712+
end
713+
709714
issues.set_limits(issue_limits)
710715

711716
if opts.upload_prefix ~= nil then

cartridge/issues.lua

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ local fio = require('fio')
5858
local log = require('log')
5959
local fun = require('fun')
6060
local fiber = require('fiber')
61-
local checks = require('checks')
61+
local errors = require('errors')
6262
local membership = require('membership')
6363

6464
local pool = require('cartridge.pool')
@@ -67,6 +67,8 @@ local failover = require('cartridge.failover')
6767
local confapplier = require('cartridge.confapplier')
6868
local lua_api_proxy = require('cartridge.lua-api.proxy')
6969

70+
local ValidateConfigError = errors.new_class('ValidateConfigError')
71+
7072
local vars = require('cartridge.vars').new(mod_name)
7173

7274
--- Thresholds for issuing warnings.
@@ -80,6 +82,15 @@ local default_limits = {
8082
fragmentation_threshold_warning = 0.6, -- number: *default*: 0.6.
8183
clock_delta_threshold_warning = 5, -- number: *default*: 5.
8284
}
85+
86+
-- Min and max values for issues_limits theshold
87+
-- range[1] <= value <= range[2]
88+
local limits_ranges = {
89+
fragmentation_threshold_warning = {0, 1},
90+
fragmentation_threshold_critical = {0, 1},
91+
clock_delta_threshold_warning = {0, math.huge},
92+
}
93+
8394
vars:new('limits', default_limits)
8495

8596
local function describe(uri)
@@ -476,12 +487,63 @@ local function list_on_cluster()
476487
return ret, err
477488
end
478489

490+
--- Validate limits configuration.
491+
--
492+
-- @function validate_limits
493+
-- @local
494+
-- @tparam table limits
495+
-- @treturn[1] boolean true
496+
-- @treturn[2] nil
497+
-- @treturn[2] table Error description
498+
local function validate_limits(limits)
499+
if type(limits) ~= 'table' then
500+
return nil, ValidateConfigError:new(
501+
'limits must be a table, got %s', type(limits)
502+
)
503+
end
504+
505+
for name, value in pairs(limits) do
506+
if type(name) ~= 'string' then
507+
return nil, ValidateConfigError:new(
508+
'limits table keys must be string, got %s', type(name)
509+
)
510+
end
511+
512+
local range = limits_ranges[name]
513+
if range == nil then
514+
return nil, ValidateConfigError:new(
515+
'unknown limits key %q', name
516+
)
517+
elseif type(value) ~= 'number' then
518+
return nil, ValidateConfigError:new(
519+
'limits.%s must be a number, got %s',
520+
name, type(value)
521+
)
522+
elseif not (value >= range[1] and value <= range[2]) then
523+
return nil, ValidateConfigError:new(
524+
'limits.%s must be in range [%g, %g]',
525+
name, range[1], range[2]
526+
)
527+
end
528+
end
529+
530+
return true
531+
end
532+
533+
--- Update limits configuration.
534+
--
535+
-- @function set_limits
536+
-- @local
537+
-- @tparam table limits
538+
-- @treturn[1] boolean true
539+
-- @treturn[2] nil
540+
-- @treturn[2] table Error description
479541
local function set_limits(limits)
480-
checks({
481-
fragmentation_threshold_critical = '?number',
482-
fragmentation_threshold_warning = '?number',
483-
clock_delta_threshold_warning = '?number',
484-
})
542+
local ok, err = validate_limits(limits)
543+
if not ok then
544+
return nil, err
545+
end
546+
485547
vars.limits = fun.chain(vars.limits, limits):tomap()
486548
return true
487549
end
@@ -490,5 +552,7 @@ _G.__cartridge_issues_list_on_instance = list_on_instance
490552

491553
return {
492554
list_on_cluster = list_on_cluster,
555+
default_limits = default_limits,
556+
validate_limits = validate_limits,
493557
set_limits = set_limits,
494558
}

test/integration/issues_test.lua

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ g.before_all(function()
2525
}},
2626
}},
2727
env = {
28-
TARANTOOL_CLOCK_DELTA_THRESHOLD_WARNING = 100000000,
29-
TARANTOOL_FRAGMENTATION_THRESHOLD_WARNING = 100000000,
30-
TARANTOOL_FRAGMENTATION_THRESHOLD_CRITICAL = 100000000,
28+
TARANTOOL_CLOCK_DELTA_THRESHOLD_WARNING = math.huge,
29+
TARANTOOL_FRAGMENTATION_THRESHOLD_WARNING = 1,
30+
TARANTOOL_FRAGMENTATION_THRESHOLD_CRITICAL = 1,
3131
}
3232
})
3333
g.cluster:start()
@@ -63,21 +63,20 @@ end)
6363

6464
function g.test_issues_limits()
6565
local server = g.cluster:server('master')
66+
6667
t.assert_equals(
6768
server:eval("return require('cartridge.vars').new('cartridge.issues').limits"),
6869
{
69-
clock_delta_threshold_warning = 100000000,
70-
fragmentation_threshold_warning = 100000000,
71-
fragmentation_threshold_critical = 100000000
70+
clock_delta_threshold_warning = math.huge,
71+
fragmentation_threshold_warning = 1,
72+
fragmentation_threshold_critical = 1,
7273
}
7374
)
7475

75-
-- restore to defaults by calling set_limits
76-
server:eval([[require("cartridge.issues").set_limits({
77-
clock_delta_threshold_warning = 5,
78-
fragmentation_threshold_warning = 0.6,
79-
fragmentation_threshold_critical = 0.9
80-
})]])
76+
server:eval([[
77+
local cartridge_issues = require("cartridge.issues")
78+
cartridge_issues.set_limits(cartridge_issues.default_limits)
79+
]])
8180
t.assert_equals(
8281
server:eval("return require('cartridge.vars').new('cartridge.issues').limits"),
8382
{

test/unit/cfg_errors_test.lua

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,35 @@ g.test_http = function()
197197
end)
198198
end
199199

200+
-- issues_limits --------------------------------------------------------------
201+
-------------------------------------------------------------------------------
202+
203+
g.test_issues_limits = function()
204+
helpers.run_remotely(g.server, function()
205+
local t = require('luatest')
206+
os.setenv('TARANTOOL_CLOCK_DELTA_THRESHOLD_WARNING', '-1')
207+
208+
local ok, err = require('cartridge').cfg({
209+
swim_broadcast = false,
210+
advertise_uri = 'localhost:13301',
211+
http_enabled = false,
212+
workdir = os.getenv('TARANTOOL_WORKDIR'),
213+
roles = {},
214+
})
215+
216+
t.assert_equals(ok, nil)
217+
t.assert_covers(err, {
218+
class_name = 'ValidateConfigError',
219+
err = 'limits.clock_delta_threshold_warning must be in range [0, inf]',
220+
})
221+
end)
222+
end
223+
g.after_test('test_issues_limits', function()
224+
helpers.run_remotely(g.server, function()
225+
os.setenv('TARANTOOL_CLOCK_DELTA_THRESHOLD_WARNING', nil)
226+
end)
227+
end)
228+
200229
-- roles ----------------------------------------------------------------------
201230
-------------------------------------------------------------------------------
202231
g.test_roles = function()

test/unit/issues_limits_test.lua

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/usr/bin/env tarantool
2+
3+
local t = require('luatest')
4+
local g = t.group()
5+
6+
local issues = require("cartridge.issues")
7+
8+
function g.test_positive()
9+
local function check_ok(limits)
10+
return t.assert_equals(
11+
{issues.validate_limits(limits)},
12+
{true, nil}
13+
)
14+
end
15+
16+
check_ok({})
17+
check_ok(issues.default_limits)
18+
19+
check_ok({clock_delta_threshold_warning = 0})
20+
check_ok({clock_delta_threshold_warning = math.huge})
21+
22+
check_ok({fragmentation_threshold_warning = 0})
23+
check_ok({fragmentation_threshold_warning = 1})
24+
25+
check_ok({fragmentation_threshold_critical = 0})
26+
check_ok({fragmentation_threshold_critical = 1})
27+
end
28+
29+
function g.test_negative()
30+
local function check_error(limits, err_expected)
31+
local ok, err = issues.validate_limits(limits)
32+
t.assert_equals(ok, nil)
33+
t.assert_covers(err, {
34+
class_name = "ValidateConfigError",
35+
err = err_expected,
36+
})
37+
end
38+
39+
local nan = 0/0
40+
assert(nan ~= nan)
41+
42+
check_error(0, 'limits must be a table, got number')
43+
check_error(nil, 'limits must be a table, got nil')
44+
check_error(box.NULL, 'limits must be a table, got cdata')
45+
46+
check_error({{}}, 'limits table keys must be string, got number')
47+
check_error({[{}] = 1}, 'limits table keys must be string, got table')
48+
49+
check_error({unknown_limit = 0}, 'unknown limits key "unknown_limit"')
50+
51+
-- clock_delta_threshold_warning
52+
check_error(
53+
{clock_delta_threshold_warning = 'yes'},
54+
'limits.clock_delta_threshold_warning must be a number, got string'
55+
)
56+
check_error(
57+
{clock_delta_threshold_warning = 12ULL},
58+
'limits.clock_delta_threshold_warning must be a number, got cdata'
59+
)
60+
check_error(
61+
{clock_delta_threshold_warning = -1e-7},
62+
'limits.clock_delta_threshold_warning must be in range [0, inf]'
63+
)
64+
check_error(
65+
{clock_delta_threshold_warning = nan},
66+
'limits.clock_delta_threshold_warning must be in range [0, inf]'
67+
)
68+
69+
-- fragmentation_threshold_warning
70+
check_error(
71+
{fragmentation_threshold_warning = 0 - 1e-7},
72+
'limits.fragmentation_threshold_warning must be in range [0, 1]'
73+
)
74+
check_error(
75+
{fragmentation_threshold_warning = 1 + 1e-7},
76+
'limits.fragmentation_threshold_warning must be in range [0, 1]'
77+
)
78+
79+
-- fragmentation_threshold_critical
80+
check_error(
81+
{fragmentation_threshold_critical = 0 - 1e-7},
82+
'limits.fragmentation_threshold_critical must be in range [0, 1]'
83+
)
84+
check_error(
85+
{fragmentation_threshold_critical = 1 + 1e-7},
86+
'limits.fragmentation_threshold_critical must be in range [0, 1]'
87+
)
88+
end

0 commit comments

Comments
 (0)