Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt the server to use the declarative config #384

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Add `--list-test-cases` and `--run-test-case` CLI options.
- Introduce preloaded hooks (gh-380).
- Add `treegen` helper as a tree generator (gh-364).
- Add support for declarative configuration to `server.lua` (gh-367).

## 1.0.1

Expand Down
141 changes: 133 additions & 8 deletions luatest/server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ local http_client = require('http.client')
local json = require('json')
local net_box = require('net.box')
local tarantool = require('tarantool')
local uri = require('uri')
local urilib = require('uri')
local yaml = require('yaml')
local _, luacov_runner = pcall(require, 'luacov.runner') -- luacov may not be installed

local assertions = require('luatest.assertions')
Expand All @@ -39,6 +40,9 @@ local Server = {
args = '?table',
box_cfg = '?table',

config_file = '?string',
remote_config = '?table',

http_port = '?number',
net_box_port = '?number',
net_box_uri = '?string|table',
Expand Down Expand Up @@ -93,6 +97,12 @@ end
-- `net.box` connection to the new server.
-- @tab[opt] object.box_cfg Extra options for `box.cfg()` and the value of the
-- `TARANTOOL_BOX_CFG` env variable which is passed into the server process.
-- @string[opt] object.config_file 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>`).
-- @tab[opt] object.remote_config If `config_file` is not passed, this config
-- value is used to deduce advertise URI to connect net.box to the instance.
-- @tab[opt] extra Table with extra properties for the server object.
-- @return table
function Server:new(object, extra)
Expand Down Expand Up @@ -127,8 +137,111 @@ function Server:new(object, extra)
return object
end

-- Determine advertise URI for given instance from a cluster
-- configuration.
local function find_advertise_uri(config, instance_name, dir)
if config == nil or next(config) == nil then
return nil
end

-- Determine listen and advertise options that are in effect
-- for the given instance.
local advertise
local listen

for _, group in pairs(config.groups or {}) do
for _, replicaset in pairs(group.replicasets or {}) do
local instance = (replicaset.instances or {})[instance_name]
if instance == nil then
break
end
if instance.iproto ~= nil then
if instance.iproto.advertise ~= nil then
advertise = advertise or instance.iproto.advertise.client
end
listen = listen or instance.iproto.listen
end
if replicaset.iproto ~= nil then
if replicaset.iproto.advertise ~= nil then
advertise = advertise or replicaset.iproto.advertise.client
end
listen = listen or replicaset.iproto.listen
end
if group.iproto ~= nil then
if group.iproto.advertise ~= nil then
advertise = advertise or group.iproto.advertise.client
end
listen = listen or group.iproto.listen
end
end
end

if config.iproto ~= nil then
if config.iproto.advertise ~= nil then
advertise = advertise or config.iproto.advertise.client
end
listen = listen or config.iproto.listen
end

local uris
if advertise ~= nil then
uris = {{uri = advertise}}
else
uris = listen
end

for _, uri in ipairs(uris or {}) do
uri = table.copy(uri)
uri.uri = uri.uri:gsub('{{ *instance_name *}}', instance_name)
uri.uri = uri.uri:gsub('unix/:%./', ('unix/:%s/'):format(dir))
local u = urilib.parse(uri)
if u.ipv4 ~= '0.0.0.0' and u.ipv6 ~= '::' and u.service ~= '0' then
return uri
end
end

error('No suitable URI to connect is found')
end

-- Initialize the server object.
function Server:initialize()
if self.config_file ~= nil then
self.command = arg[-1]

self.args = fun.chain(self.args or {}, {'--name', self.alias}):totable()

if self.config_file ~= '' then
table.insert(self.args, '--config')
table.insert(self.args, self.config_file)

-- Take into account self.chdir to calculate a config
-- file path.
local config_file_path = utils.pathjoin(self.chdir, self.config_file)

-- Read the provided config file.
local fh, err = fio.open(config_file_path, {'O_RDONLY'})
if fh == nil then
error(('Unable to open file %q: %s'):format(config_file_path, err))
end
self.config = yaml.decode(fh:read())
fh:close()
end

if self.net_box_uri == nil then
local config = self.config or self.remote_config

-- NB: listen and advertise URIs are relative to
-- process.work_dir, which, in turn, is relative to
-- self.chdir.
local work_dir
if config.process ~= nil and config.process.work_dir ~= nil then
work_dir = config.process.work_dir
end
local dir = utils.pathjoin(self.chdir, work_dir)
self.net_box_uri = find_advertise_uri(config, self.alias, dir)
end
end

if self.alias == nil then
self.alias = DEFAULT_ALIAS
end
Expand Down Expand Up @@ -157,7 +270,7 @@ function Server:initialize()
self.net_box_uri = 'localhost:' .. self.net_box_port
end
end
local parsed_net_box_uri = uri.parse(self.net_box_uri)
local parsed_net_box_uri = urilib.parse(self.net_box_uri)
if parsed_net_box_uri.host == 'unix/' then
-- Linux uses max 108 bytes for Unix domain socket paths, which means a 107 characters
-- string ended by a null terminator. Other systems use 104 bytes and 103 characters strings.
Expand All @@ -170,7 +283,7 @@ function Server:initialize()
end
end
if type(self.net_box_uri) == 'table' then
self.net_box_uri = uri.format(parsed_net_box_uri, true)
self.net_box_uri = urilib.format(parsed_net_box_uri, true)
end

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

local wait_until_ready
if self.coverage_report then
wait_until_ready = self.original_command == DEFAULT_INSTANCE
if self.config_file then
wait_until_ready = self.net_box_uri ~= nil
Totktonada marked this conversation as resolved.
Show resolved Hide resolved
else
wait_until_ready = self.command == DEFAULT_INSTANCE
if self.coverage_report then
wait_until_ready = self.original_command == DEFAULT_INSTANCE
else
wait_until_ready = self.command == DEFAULT_INSTANCE
end
end
if opts ~= nil and opts.wait_until_ready ~= nil then
wait_until_ready = opts.wait_until_ready
Expand Down Expand Up @@ -499,10 +616,18 @@ end
--- Wait until the server is ready after the start.
-- A server is considered ready when its `_G.ready` variable becomes `true`.
function Server:wait_until_ready()
local expr
if self.config_file ~= nil then
expr = "return require('config'):info().status == 'ready' or " ..
"require('config'):info().status == 'check_warnings'"
else
expr = 'return _G.ready'
end

wait_for_condition('server is ready', self, function()
local ok, is_ready = pcall(function()
self:connect_net_box()
return self.net_box:eval('return _G.ready') == true
return self.net_box:eval(expr) == true
end)
return ok and is_ready
end)
Expand Down
22 changes: 22 additions & 0 deletions luatest/utils.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local digest = require('digest')
local fio = require('fio')
local fun = require('fun')
local yaml = require('yaml')

Expand Down Expand Up @@ -196,4 +197,25 @@ function utils.table_pack(...)
return {n = select('#', ...), ...}
end

-- Join paths in an intuitive way.
-- If a component is nil, it is skipped.
-- If a component is an absolute path, it skips all the previous
-- components.
-- The wrapper is written for two components for simplicity.
function utils.pathjoin(a, b)
-- No first path -- skip it.
if a == nil then
return b
end
-- No second path -- skip it.
if b == nil then
return a
end
-- The absolute path is checked explicitly due to gh-8816.
if b:startswith('/') then
return b
end
return fio.pathjoin(a, b)
end

return utils