Skip to content

Commit

Permalink
Adapt the server to use the declarative config
Browse files Browse the repository at this point in the history
You can use a declarative config file to configure the server instance
via following parameters:
  - @string[opt] config_file Declarative YAML configuration for server
    instance.
  - @tab[opt] remote_config If `config_file` is not passed, this config
    value is used to deduce the advertise URI to connect net.box to the
    instance.

Close tarantool#367
  • Loading branch information
Oleg Chaplashkin committed Jul 10, 2024
1 parent 82e8777 commit 5352040
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 1 deletion.
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 declarative configuration in `server.lua` (gh-367).

## 1.0.1

Expand Down
133 changes: 132 additions & 1 deletion luatest/server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ local fio = require('fio')
local fun = require('fun')
local http_client = require('http.client')
local json = require('json')
local yaml = require('yaml')
local net_box = require('net.box')
local tarantool = require('tarantool')
local uri = require('uri')
Expand Down Expand Up @@ -39,14 +40,17 @@ 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',
net_box_credentials = '?table',

alias = '?string',

coverage_report = '?boolean',
coverage_report = '?boolean'
},
}

Expand Down Expand Up @@ -93,6 +97,10 @@ 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 server
-- instance. Used to deduce advertise URI to connect net.box to the instance.
-- @tab[opt] object.remote_config If `config_file` is not passed, this config
-- value is used to deduce the 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 +135,114 @@ 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)
local urilib = require('uri')
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 @@ -297,6 +411,11 @@ end
function Server:start(opts)
checks('table', {wait_until_ready = '?boolean'})

opts = opts or {}
if self.config_file and opts.wait_until_ready == nil then
opts.wait_until_ready = self.net_box_uri ~= nil
end

self:initialize()

self:make_workdir()
Expand Down Expand Up @@ -550,6 +669,18 @@ function Server:connect_net_box()
error(connection.error)
end
self.net_box = connection

if self.config_file ~= nil then
-- Replace the ready condition.
local saved_eval = self.net_box.eval
self.net_box.eval = function(self, expr, args, opts)
if expr == 'return _G.ready' then
expr = "return require('config'):info().status == 'ready' or " ..
"require('config'):info().status == 'check_warnings'"
end
return saved_eval(self, expr, args, opts)
end
end
end

local function is_header_set(headers, name)
Expand Down
22 changes: 22 additions & 0 deletions luatest/utils.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local digest = require('digest')
local fun = require('fun')
local fio = require('fio')
local yaml = require('yaml')

local utils = {}
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

0 comments on commit 5352040

Please sign in to comment.