Skip to content

Commit fe005ce

Browse files
Oleg Chaplashkinylobankov
Oleg Chaplashkin
authored andcommitted
Adapt server to use declarative config
You can use a declarative config file to configure a server instance via the following options: - config_file (optional) Declarative YAML configuration for a server instance. Used to deduce advertise URI to connect `net.box` to the instance. The special value '' means running without `--config ...` CLI option (but still passes `--name <alias>`). - remote_config (optional) If `config_file` is not passed, this config value is used to deduce the advertise URI to connect `net.box` to the instance. Close #367
1 parent 82e8777 commit fe005ce

File tree

3 files changed

+156
-8
lines changed

3 files changed

+156
-8
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- Add `--list-test-cases` and `--run-test-case` CLI options.
1515
- Introduce preloaded hooks (gh-380).
1616
- Add `treegen` helper as a tree generator (gh-364).
17+
- Add support for declarative configuration to `server.lua` (gh-367).
1718

1819
## 1.0.1
1920

Diff for: luatest/server.lua

+133-8
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ local http_client = require('http.client')
1212
local json = require('json')
1313
local net_box = require('net.box')
1414
local tarantool = require('tarantool')
15-
local uri = require('uri')
15+
local urilib = require('uri')
16+
local yaml = require('yaml')
1617
local _, luacov_runner = pcall(require, 'luacov.runner') -- luacov may not be installed
1718

1819
local assertions = require('luatest.assertions')
@@ -39,6 +40,9 @@ local Server = {
3940
args = '?table',
4041
box_cfg = '?table',
4142

43+
config_file = '?string',
44+
remote_config = '?table',
45+
4246
http_port = '?number',
4347
net_box_port = '?number',
4448
net_box_uri = '?string|table',
@@ -93,6 +97,12 @@ end
9397
-- `net.box` connection to the new server.
9498
-- @tab[opt] object.box_cfg Extra options for `box.cfg()` and the value of the
9599
-- `TARANTOOL_BOX_CFG` env variable which is passed into the server process.
100+
-- @string[opt] object.config_file Declarative YAML configuration for a server
101+
-- instance. Used to deduce advertise URI to connect net.box to the instance.
102+
-- The special value '' means running without `--config <...>` CLI option
103+
-- (but still passes `--name <alias>`).
104+
-- @tab[opt] object.remote_config If `config_file` is not passed, this config
105+
-- value is used to deduce advertise URI to connect net.box to the instance.
96106
-- @tab[opt] extra Table with extra properties for the server object.
97107
-- @return table
98108
function Server:new(object, extra)
@@ -127,8 +137,111 @@ function Server:new(object, extra)
127137
return object
128138
end
129139

140+
-- Determine advertise URI for given instance from a cluster
141+
-- configuration.
142+
local function find_advertise_uri(config, instance_name, dir)
143+
if config == nil or next(config) == nil then
144+
return nil
145+
end
146+
147+
-- Determine listen and advertise options that are in effect
148+
-- for the given instance.
149+
local advertise
150+
local listen
151+
152+
for _, group in pairs(config.groups or {}) do
153+
for _, replicaset in pairs(group.replicasets or {}) do
154+
local instance = (replicaset.instances or {})[instance_name]
155+
if instance == nil then
156+
break
157+
end
158+
if instance.iproto ~= nil then
159+
if instance.iproto.advertise ~= nil then
160+
advertise = advertise or instance.iproto.advertise.client
161+
end
162+
listen = listen or instance.iproto.listen
163+
end
164+
if replicaset.iproto ~= nil then
165+
if replicaset.iproto.advertise ~= nil then
166+
advertise = advertise or replicaset.iproto.advertise.client
167+
end
168+
listen = listen or replicaset.iproto.listen
169+
end
170+
if group.iproto ~= nil then
171+
if group.iproto.advertise ~= nil then
172+
advertise = advertise or group.iproto.advertise.client
173+
end
174+
listen = listen or group.iproto.listen
175+
end
176+
end
177+
end
178+
179+
if config.iproto ~= nil then
180+
if config.iproto.advertise ~= nil then
181+
advertise = advertise or config.iproto.advertise.client
182+
end
183+
listen = listen or config.iproto.listen
184+
end
185+
186+
local uris
187+
if advertise ~= nil then
188+
uris = {{uri = advertise}}
189+
else
190+
uris = listen
191+
end
192+
-- luacheck: push ignore 431
193+
for _, uri in ipairs(uris or {}) do
194+
uri = table.copy(uri)
195+
uri.uri = uri.uri:gsub('{{ *instance_name *}}', instance_name)
196+
uri.uri = uri.uri:gsub('unix/:%./', ('unix/:%s/'):format(dir))
197+
local u = urilib.parse(uri)
198+
if u.ipv4 ~= '0.0.0.0' and u.ipv6 ~= '::' and u.service ~= '0' then
199+
return uri
200+
end
201+
end
202+
-- luacheck: pop
203+
error('No suitable URI to connect is found')
204+
end
205+
130206
-- Initialize the server object.
131207
function Server:initialize()
208+
if self.config_file ~= nil then
209+
self.command = arg[-1]
210+
211+
self.args = fun.chain(self.args or {}, {'--name', self.alias}):totable()
212+
213+
if self.config_file ~= '' then
214+
table.insert(self.args, '--config')
215+
table.insert(self.args, self.config_file)
216+
217+
-- Take into account self.chdir to calculate a config
218+
-- file path.
219+
local config_file_path = utils.pathjoin(self.chdir, self.config_file)
220+
221+
-- Read the provided config file.
222+
local fh, err = fio.open(config_file_path, {'O_RDONLY'})
223+
if fh == nil then
224+
error(('Unable to open file %q: %s'):format(config_file_path, err))
225+
end
226+
self.config = yaml.decode(fh:read())
227+
fh:close()
228+
end
229+
230+
if self.net_box_uri == nil then
231+
local config = self.config or self.remote_config
232+
233+
-- NB: listen and advertise URIs are relative to
234+
-- process.work_dir, which, in turn, is relative to
235+
-- self.chdir.
236+
local work_dir
237+
if config.process ~= nil and config.process.work_dir ~= nil then
238+
work_dir = config.process.work_dir
239+
end
240+
local dir = utils.pathjoin(self.chdir, work_dir)
241+
self.net_box_uri = find_advertise_uri(config, self.alias, dir)
242+
end
243+
end
244+
132245
if self.alias == nil then
133246
self.alias = DEFAULT_ALIAS
134247
end
@@ -157,7 +270,7 @@ function Server:initialize()
157270
self.net_box_uri = 'localhost:' .. self.net_box_port
158271
end
159272
end
160-
local parsed_net_box_uri = uri.parse(self.net_box_uri)
273+
local parsed_net_box_uri = urilib.parse(self.net_box_uri)
161274
if parsed_net_box_uri.host == 'unix/' then
162275
-- Linux uses max 108 bytes for Unix domain socket paths, which means a 107 characters
163276
-- string ended by a null terminator. Other systems use 104 bytes and 103 characters strings.
@@ -170,7 +283,7 @@ function Server:initialize()
170283
end
171284
end
172285
if type(self.net_box_uri) == 'table' then
173-
self.net_box_uri = uri.format(parsed_net_box_uri, true)
286+
self.net_box_uri = urilib.format(parsed_net_box_uri, true)
174287
end
175288

176289
self.env = utils.merge(self.env or {}, self:build_env())
@@ -281,7 +394,7 @@ end
281394
--- Make directory for the server's Unix socket.
282395
-- Invoked on the server's start.
283396
function Server:make_socketdir()
284-
local parsed_net_box_uri = uri.parse(self.net_box_uri)
397+
local parsed_net_box_uri = urilib.parse(self.net_box_uri)
285398
if parsed_net_box_uri.host == 'unix/' then
286399
fio.mktree(fio.dirname(parsed_net_box_uri.service))
287400
end
@@ -333,10 +446,14 @@ function Server:start(opts)
333446
})
334447

335448
local wait_until_ready
336-
if self.coverage_report then
337-
wait_until_ready = self.original_command == DEFAULT_INSTANCE
449+
if self.config_file then
450+
wait_until_ready = self.net_box_uri ~= nil
338451
else
339-
wait_until_ready = self.command == DEFAULT_INSTANCE
452+
if self.coverage_report then
453+
wait_until_ready = self.original_command == DEFAULT_INSTANCE
454+
else
455+
wait_until_ready = self.command == DEFAULT_INSTANCE
456+
end
340457
end
341458
if opts ~= nil and opts.wait_until_ready ~= nil then
342459
wait_until_ready = opts.wait_until_ready
@@ -499,10 +616,18 @@ end
499616
--- Wait until the server is ready after the start.
500617
-- A server is considered ready when its `_G.ready` variable becomes `true`.
501618
function Server:wait_until_ready()
619+
local expr
620+
if self.config_file ~= nil then
621+
expr = "return require('config'):info().status == 'ready' or " ..
622+
"require('config'):info().status == 'check_warnings'"
623+
else
624+
expr = 'return _G.ready'
625+
end
626+
502627
wait_for_condition('server is ready', self, function()
503628
local ok, is_ready = pcall(function()
504629
self:connect_net_box()
505-
return self.net_box:eval('return _G.ready') == true
630+
return self.net_box:eval(expr) == true
506631
end)
507632
return ok and is_ready
508633
end)

Diff for: luatest/utils.lua

+22
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
local digest = require('digest')
2+
local fio = require('fio')
23
local fun = require('fun')
34
local yaml = require('yaml')
45

@@ -196,4 +197,25 @@ function utils.table_pack(...)
196197
return {n = select('#', ...), ...}
197198
end
198199

200+
-- Join paths in an intuitive way.
201+
-- If a component is nil, it is skipped.
202+
-- If a component is an absolute path, it skips all the previous
203+
-- components.
204+
-- The wrapper is written for two components for simplicity.
205+
function utils.pathjoin(a, b)
206+
-- No first path -- skip it.
207+
if a == nil then
208+
return b
209+
end
210+
-- No second path -- skip it.
211+
if b == nil then
212+
return a
213+
end
214+
-- The absolute path is checked explicitly due to gh-8816.
215+
if b:startswith('/') then
216+
return b
217+
end
218+
return fio.pathjoin(a, b)
219+
end
220+
199221
return utils

0 commit comments

Comments
 (0)