diff --git a/src/box/lua/config/applier/box_cfg.lua b/src/box/lua/config/applier/box_cfg.lua index c7310100cf15e44ec71f4a414df0bf15081b0678..f24c0874322819d0845b35691dbba46c76bfdd4e 100644 --- a/src/box/lua/config/applier/box_cfg.lua +++ b/src/box/lua/config/applier/box_cfg.lua @@ -1,7 +1,11 @@ +local urilib = require('uri') local log = require('internal.config.utils.log') +local instance_config = require('internal.config.instance_config') local function peer_uris(configdata) local names = configdata:names() + local err_msg_prefix = ('box_cfg.apply: unable to build replicaset %q ' .. + 'of group %q'):format(names.replicaset_name, names.group_name) local peers = configdata:peers() if #peers <= 1 then @@ -11,12 +15,29 @@ local function peer_uris(configdata) local uris = {} for _, peer_name in ipairs(peers) do local iproto = configdata:get('iproto', {peer = peer_name}) or {} - local uri = iproto.advertise or iproto.listen + + local uri + if iproto.advertise ~= nil then + uri = iproto.advertise + elseif iproto.listen ~= nil then + for _, u in ipairs(urilib.parse_many(iproto.listen)) do + if instance_config:uri_is_suitable_to_connect(u) then + uri = urilib.format(u) + break + end + end + + if uri == nil then + error(('%s: instance %q has no iproto.advertise option and ' .. + 'neither of the iproto.listen URIs are suitable to ' .. + 'create a client socket'):format(err_msg_prefix, + peer_name), 0) + end + end + if uri == nil then - error(('box_cfg.apply: unable to build replicaset %q of group ' .. - '%q: instance %q has neither iproto.advertise nor ' .. - 'iproto.listen options'):format(names.replicaset_name, - names.group_name, peer_name), 0) + error(('%s: instance %q has neither iproto.advertise nor ' .. + 'iproto.listen options'):format(err_msg_prefix, peer_name), 0) end table.insert(uris, uri) end diff --git a/src/box/lua/config/instance_config.lua b/src/box/lua/config/instance_config.lua index e0dd3497a968734126161ec5ac54fd0212312f2e..19a8af0092cd41cd1e5f017036110902a7294b39 100644 --- a/src/box/lua/config/instance_config.lua +++ b/src/box/lua/config/instance_config.lua @@ -141,7 +141,7 @@ end -- -- It means 'bind to a random free port' for the bind() call, -- but it has no meaning at the connect() call on a client. -local function uri_is_suitable_to_connect(uri) +local function uri_is_suitable_to_connect(_, uri) assert(uri ~= nil) if uri.ipv4 == '0.0.0.0' then @@ -461,7 +461,7 @@ return schema.new('instance_config', schema.record({ end w.error('Unable to parse an URI: %s', err) end - local ok, err = uri_is_suitable_to_connect(uri) + local ok, err = uri_is_suitable_to_connect(nil, uri) if not ok then w.error(err) end @@ -953,4 +953,8 @@ return schema.new('instance_config', schema.record({ -- node as an annotation. It simplifies accesses from other -- code. config_version = CONFIG_VERSION, -})) +}), { + methods = { + uri_is_suitable_to_connect = uri_is_suitable_to_connect, + }, +}) diff --git a/test/config-luatest/basic_test.lua b/test/config-luatest/basic_test.lua index dae384d46de7409f35507100d80a78594649f7fc..bddfe533c5bc7f995b0f475eae4b2d789888d47c 100644 --- a/test/config-luatest/basic_test.lua +++ b/test/config-luatest/basic_test.lua @@ -125,3 +125,109 @@ g.test_no_advertise_no_listen = function(g) 'fatal error, exiting the event loop', }) end + +g.test_no_advertise_unsuitable_listen = function(g) + local dir = treegen.prepare_directory(g, {}, {}) + local instance_002 = { + iproto = { + -- This option is set in the particular cases below. + -- listen = <...>, + }, + } + local config = { + credentials = { + users = { + guest = { + roles = {'super'}, + }, + }, + }, + groups = { + ['group-001'] = { + replicasets = { + ['replicaset-001'] = { + instances = { + ['instance-001'] = { + database = { + rw = true, + }, + iproto = { + listen = + 'unix/:./{{ instance_name }}.iproto', + }, + }, + ['instance-002'] = instance_002, + ['instance-003'] = {}, + }, + }, + }, + }, + }, + } + + local exp_stderr = 'LuajitError: box_cfg.apply: unable to build ' .. + 'replicaset "replicaset-001" of group "group-001": instance ' .. + '"instance-002" has no iproto.advertise option and neither ' .. + 'of the iproto.listen URIs are suitable to create a client ' .. + 'socket\nfatal error, exiting the event loop' + for _, listen in ipairs({ + '0.0.0.0:3301', + '[::]:3301', + 'localhost:0', + }) do + instance_002.iproto.listen = listen + local config_file = treegen.write_script(dir, 'config.yaml', + yaml.encode(config)) + local env = {} + local args = {'--name', 'instance-001', '--config', config_file} + local opts = {nojson = true, stderr = true} + local res = justrun.tarantool(dir, env, args, opts) + t.assert_equals(res, { + exit_code = 1, + stderr = exp_stderr, + }) + end +end + +g.test_no_advertise_second_listen_suitable = function() + local config = { + credentials = { + users = { + guest = { + roles = {'super'}, + }, + client = { + password = { + plain = 'secret', + }, + roles = {'super'}, + }, + }, + }, + iproto = { + listen = 'localhost:0,unix/:./{{ instance_name }}.iproto', + }, + groups = { + ['group-001'] = { + replicasets = { + ['replicaset-001'] = { + instances = { + ['instance-001'] = { + database = { + rw = true, + }, + }, + ['instance-002'] = {}, + ['instance-003'] = {}, + }, + }, + }, + }, + }, + } + + local dir = treegen.prepare_directory(g, {}, {}) + local config_file = treegen.write_script(dir, 'config.yaml', + yaml.encode(config)) + helpers.start_example_replicaset(g, dir, config_file) +end diff --git a/test/luatest_helpers/server.lua b/test/luatest_helpers/server.lua index 43c0f37b6af7b5abdca92358a22998401a830456..2440b95624037d9c577966d62f3cca29d1758dda 100644 --- a/test/luatest_helpers/server.lua +++ b/test/luatest_helpers/server.lua @@ -1,5 +1,6 @@ local fun = require('fun') local yaml = require('yaml') +local urilib = require('uri') local fio = require('fio') local luatest = require('luatest') @@ -50,7 +51,19 @@ local function find_advertise_uri(config, instance_name, dir) uri = uri:gsub('unix/:%./', ('unix/:%s/'):format(dir)) end - return uri + local uris, err = urilib.parse_many(uri) + if uris == nil then + error(err) + end + for _, u in ipairs(uris) do + local suitable = u.ipv4 ~= '0.0.0.0' and u.ipv6 ~= '::' and + u.service ~= '0' + if suitable then + return urilib.format(u, true) + end + end + + error(('No suitable URIs to connect found in %s'):format(uri)) end local Server = luatest.Server:inherit({})