1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
-- SPDX-License-Identifier: GPL-3.0-or-later
local ffi = require('ffi')
log_info(ffi.C.LOG_GRP_TESTS, 'my PID = %d', worker.pid)
trust_anchors.remove('.')
cache.size = 2*MB
net = { '{{SELF_ADDR}}' }
{% if QMIN == "false" %}
option('NO_MINIMIZE', true)
{% else %}
option('NO_MINIMIZE', false)
{% endif %}
-- Self-checks on globals
assert(help() ~= nil)
assert(worker.id ~= nil)
-- Self-checks on facilities
assert(cache.count() == 0)
assert(cache.stats() ~= nil)
assert(cache.backends() ~= nil)
assert(worker.stats() ~= nil)
assert(net.interfaces() ~= nil)
-- Self-checks on loaded stuff
assert(#modules.list() > 0)
-- Self-check timers
ev = event.recurrent(1 * sec, function (ev) return 1 end)
event.cancel(ev)
local kluautil = require('kluautil')
local tap = require('tapered')
local checks_total = 16
local n_instances = 3 -- must match deckard.yaml
worker.control_path = worker.cwd .. '/../kresd3/control/'
net.listen(worker.control_path .. worker.pid, nil, {kind = 'control'})
assert(#net.list() >= 3) -- UDP, TCP, control
-- debug, kept for future use
--log_level("debug")
log_debug(ffi.C.LOG_GRP_TESTS, '%s', worker.control_path)
log_debug(ffi.C.LOG_GRP_TESTS, '%s', table_print(net.list()))
function wait_for_sockets()
log_info(ffi.C.LOG_GRP_TESTS, 'waiting for control sockets')
local timeout = 5000 -- ms
local start_time = tonumber(ffi.C.kr_now())
local now
while true do
now = tonumber(ffi.C.kr_now())
if now > start_time + timeout then
log_info(ffi.C.LOG_GRP_TESTS, 'timeout while waiting for control sockets to appear')
os.exit(3)
end
local pids = kluautil.list_dir(worker.control_path)
if #pids == n_instances then
-- debug, kept for future use
log_debug(ffi.C.LOG_GRP_TESTS, 'got control sockets:')
log_debug(ffi.C.LOG_GRP_TESTS, table_print(pids))
break
else
worker.sleep(0.1)
end
end
log_info(ffi.C.LOG_GRP_TESTS, 'PIDs are visible now (waiting took %d ms)', now - start_time)
end
-- expression should throw Lua error:
-- wrap it in a function which runs the expression on leader and follower
-- separately so we can guarantee both cases are covered
function boom_follower_and_leader(boom_expr, desc)
local variants = {leader = '~=', follower = '=='}
for name, operator in pairs(variants) do
-- beware, newline is not allowed in expr
local full_expr = string.format(
'if (worker.pid %s %s) then return true '
.. 'else return %s end',
operator, worker.pid, boom_expr)
local full_desc = name .. ': '
if desc then
full_desc = full_desc .. desc .. ' (' .. boom_expr .. ')'
else
full_desc = full_desc .. boom_expr
end
tap.boom(map, {full_expr}, full_desc)
end
end
function tests()
-- add delay to each test to force scheduler to interleave tests and DNS queries
local test_delay = 20 / 1000 -- seconds
log_info(ffi.C.LOG_GRP_TESTS, 'starting map() tests now')
tap.boom(map, {'1 ++ 1'}, 'syntax error in command is detected')
worker.sleep(test_delay)
-- array of integers
local pids = map('worker.pid')
tap.same(pids.n, n_instances, 'all pids were obtained')
table.sort(pids)
worker.sleep(test_delay)
-- expression produces array of integers
local pids_plus_one = map('worker.pid + 1')
tap.same(pids_plus_one.n, n_instances, 'all pids were obtained')
table.sort(pids_plus_one)
for idx=1,n_instances do
tap.same(pids[idx] + 1, pids_plus_one[idx],
'increment expression worked')
end
worker.sleep(test_delay)
-- error detection
boom_follower_and_leader('error("explosion")')
worker.sleep(test_delay)
-- unsupported number of return values
boom_follower_and_leader('1, 2')
worker.sleep(test_delay)
boom_follower_and_leader('unpack({})')
worker.sleep(test_delay)
-- unsupported return type
boom_follower_and_leader(
'function() print("this cannot be serialized") end')
worker.sleep(test_delay)
tap.same({n = n_instances}, map('nil'),
'nil values are counted as returned')
worker.sleep(test_delay)
local exp = {n = n_instances}
for i=1,n_instances do
table.insert(exp, {nil, 2, nil, n=3})
end
local got = map('require("kluautil").kr_table_pack(nil, 2, nil)')
tap.same(got, exp, 'kr_table_pack handles nil values')
worker.sleep(test_delay)
end
local started = false
function tests_start()
-- just in case, duplicates should not happen
if started then
log_info(ffi.C.LOG_GRP_TESTS, 'huh? duplicate test invocation ignored, a retransmit?')
return
end
started = true
log_info(ffi.C.LOG_GRP_TESTS, 'start query triggered, scheduling tests')
-- DNS queries and map() commands must be serviced while sleep is running
worker.coroutine(function() worker.sleep(3600) end)
worker.coroutine(tests)
end
-- Deckard query will trigger tests
policy.add(policy.suffix(tests_start, {'\5start\0'}))
function tests_done()
print('final query triggered')
event.after(0, function()
tap.done(checks_total)
end)
end
-- Deckard query will execute tap.done() which will call os.exit()
-- i.e. this callback has to be called only after answer to Deckard was sent
policy.add(policy.suffix(tests_done, {'\4done\0'}), true)
-- add delay to each query to force scheduler to interleave tests and DNS queries
policy.add(policy.all(
function()
local delay = 10 -- ms
log_info(ffi.C.LOG_GRP_TESTS, 'packet delayed by %d ms', delay)
worker.sleep(delay / 1000)
end))
wait_for_sockets()
{% if DAEMON_NAME == "kresd1" %}
-- forward to Deckard test server
policy.add(policy.all(policy.FORWARD('192.0.2.1')))
{% else %}
-- forward to next kresd instance in chain
{# find out IP address of kresd instance with lower number,
i.e. kresd2 forwards to kresd1 #}
policy.add(policy.all(policy.FORWARD('{{ PROGRAMS[ "kresd" ~ (DAEMON_NAME[-1]|int() - 1)]["address"] }}')))
{% endif %}
|