Skip to content

Commit 5fd7905

Browse files
committed
Free port auto resolving for TarantoolServer
This change replaces manual choosing an iproto port for TarantoolServer by the auto resolving mechanism. In two words, test-run always provides '127.0.0.1:0' as a value for LISTEN env variable that is used in a lua file to start a tarantool instance. In this way, the port will be picked automatically, and we are getting the real value of it via the admin console by executing `box.info.listen` that is available for tarantool version >= 2.4.1, and special lua script intended for tarantool version < 2.4.1. Part of #141
1 parent eac237e commit 5fd7905

File tree

2 files changed

+78
-16
lines changed

2 files changed

+78
-16
lines changed

lib/preprocessor.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def __init__(self, suite_ini, default_server, create_server, params={},
5959
self.curcon = [self.connections['default']]
6060
nmsp = Namespace()
6161
setattr(nmsp, 'admin', default_server.admin.uri)
62-
setattr(nmsp, 'listen', default_server.iproto.uri)
62+
setattr(nmsp, 'listen', default_server.listen_uri)
6363
setattr(self.environ, 'default', nmsp)
6464
# for propagating 'current_test' to non-default servers
6565
self.default_server_no_connect = kwargs.get(
@@ -282,13 +282,13 @@ def server_create(self, ctype, sname, opts):
282282
copy_to
283283
))
284284
nmsp = Namespace()
285-
setattr(nmsp, 'admin', temp.admin.port)
286-
setattr(nmsp, 'listen', temp.iproto.port)
285+
setattr(nmsp, 'admin', temp.admin.uri)
286+
setattr(nmsp, 'listen', temp.listen_uri)
287287
if temp.rpl_master:
288-
setattr(nmsp, 'master', temp.rpl_master.iproto.port)
288+
setattr(nmsp, 'master', temp.rpl_master.iproto.uri)
289289
setattr(self.environ, sname, nmsp)
290290
if 'return_listen_uri' in opts and opts['return_listen_uri'] == 'True':
291-
return self.servers[sname].iproto.uri
291+
return self.servers[sname].listen_uri
292292

293293
def server_deploy(self, ctype, sname, opts):
294294
self.servers[sname].install()
@@ -342,6 +342,9 @@ def server_restart(self, ctype, sname, opts):
342342
# remove proxy
343343
self.server_stop('stop', 'proxy', {})
344344

345+
def server_get_iproto_uri(self, ctype, sname, opts):
346+
return self.servers[sname].iproto.uri
347+
345348
def server(self, ctype, sname, opts):
346349
attr = 'server_%s' % ctype
347350
if hasattr(self, attr):

lib/tarantool_server.py

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
from lib.server import DEFAULT_SNAPSHOT_NAME
3939
from lib.test import Test
4040
from lib.utils import bytes_to_str
41-
from lib.utils import find_port
4241
from lib.utils import extract_schema_from_snapshot
4342
from lib.utils import format_process
4443
from lib.utils import safe_makedirs
@@ -598,7 +597,7 @@ def _admin(self, port):
598597
del self.admin
599598
if not hasattr(self, 'tests_type'):
600599
self.tests_type = 'lua'
601-
self.admin = CON_SWITCH[self.tests_type]('localhost', port)
600+
self.admin = CON_SWITCH[self.tests_type](self.localhost, port)
602601

603602
@property
604603
def _iproto(self):
@@ -610,7 +609,7 @@ def _iproto(self):
610609
def _iproto(self, port):
611610
if hasattr(self, 'iproto'):
612611
del self.iproto
613-
self.iproto = BoxConnection('localhost', port)
612+
self.iproto = BoxConnection(self.localhost, port)
614613

615614
@property
616615
def log_des(self):
@@ -672,9 +671,8 @@ def __init__(self, _ini=None, test_suite=None):
672671
self.name = "default"
673672
self.conf = {}
674673
self.status = None
675-
# -----InitBasicVars-----#
676674
self.core = ini['core']
677-
675+
self.localhost = '127.0.0.1'
678676
self.gdb = ini['gdb']
679677
self.lldb = ini['lldb']
680678
self.script = ini['script']
@@ -686,10 +684,8 @@ def __init__(self, _ini=None, test_suite=None):
686684
self.crash_detector = None
687685
# use this option with inspector to enable crashes in test
688686
self.crash_enabled = False
689-
690687
# set in from a test let test-run ignore server's crashes
691688
self.crash_expected = False
692-
693689
# filled in {Test,FuncTest,LuaTest,PythonTest}.execute()
694690
# or passed through execfile() for PythonTest
695691
self.current_test = None
@@ -772,9 +768,10 @@ def install(self, silent=True):
772768
if self.use_unix_sockets_iproto:
773769
path = os.path.join(self.vardir, self.name + ".i")
774770
warn_unix_socket(path)
771+
self.listen_uri = path
775772
self._iproto = path
776773
else:
777-
self._iproto = find_port()
774+
self.listen_uri = self.localhost + ':0'
778775

779776
# these sockets will be created by tarantool itself
780777
path = os.path.join(self.vardir, self.name + '.control')
@@ -872,7 +869,7 @@ def start(self, silent=True, wait=True, wait_load=True, rais=True, args=[],
872869
color_log(prefix_each_line(' | ', self.version()) + '\n',
873870
schema='version')
874871

875-
os.putenv("LISTEN", self.iproto.uri)
872+
os.putenv("LISTEN", self.listen_uri)
876873
os.putenv("ADMIN", self.admin.uri)
877874
if self.rpl_master:
878875
os.putenv("MASTER", self.rpl_master.iproto.uri)
@@ -933,7 +930,12 @@ def start(self, silent=True, wait=True, wait_load=True, rais=True, args=[],
933930

934931
port = self.admin.port
935932
self.admin.disconnect()
936-
self.admin = CON_SWITCH[self.tests_type]('localhost', port)
933+
self.admin = CON_SWITCH[self.tests_type](self.localhost, port)
934+
935+
if not self.use_unix_sockets_iproto:
936+
if wait and wait_load and not self.crash_expected:
937+
self._iproto = self.get_iproto_port()
938+
937939
self.status = 'started'
938940

939941
# Verify that the schema actually was not upgraded.
@@ -1144,7 +1146,7 @@ def wait_until_started(self, wait_load=True, deadline=None):
11441146
self.wait_load(deadline)
11451147
while not deadline or time.time() < deadline:
11461148
try:
1147-
temp = AdminConnection('localhost', self.admin.port)
1149+
temp = AdminConnection(self.localhost, self.admin.port)
11481150
if not wait_load:
11491151
ans = yaml.safe_load(temp.execute("2 + 2"))
11501152
color_log(" | Successful connection check; don't wait for "
@@ -1270,3 +1272,60 @@ def wait_lsn(self, node_id, lsn):
12701272

12711273
def get_log(self):
12721274
return TarantoolLog(self.logfile).positioning()
1275+
1276+
def get_iproto_port(self):
1277+
# Check the `box.cfg.listen` option, if it wasn't defined, just return.
1278+
res = yaml.safe_load(self.admin('box.cfg.listen', silent=True))[0]
1279+
if res is None:
1280+
return
1281+
1282+
# If `box.info.listen` (available for tarantool version >= 2.4.1) gives
1283+
# `nil`, use a simple script intended for tarantool version < 2.4.1 to
1284+
# get the listening socket of the instance. First, the script catches
1285+
# both server (listening) and client (sending) sockets (they usually
1286+
# occur when starting an instance as a replica). Then it finds the
1287+
# listening socket among caught sockets.
1288+
script = """
1289+
local ffi = require('ffi')
1290+
local socket = require('socket')
1291+
local uri = require('uri')
1292+
local res = box.info.listen
1293+
if res then
1294+
local listen_uri = uri.parse(res)
1295+
return {{host = listen_uri.host, port = listen_uri.service}}
1296+
else
1297+
res = {{}}
1298+
local val = ffi.new('int[1]')
1299+
local len = ffi.new('size_t[1]', ffi.sizeof('int'))
1300+
for fd = 0, 65535 do
1301+
local addrinfo = socket.internal.name(fd)
1302+
local is_matched = addrinfo ~= nil and
1303+
addrinfo.host == '{localhost}' and
1304+
addrinfo.family == 'AF_INET' and
1305+
addrinfo.type == 'SOCK_STREAM' and
1306+
addrinfo.protocol == 'tcp' and
1307+
type(addrinfo.port) == 'number'
1308+
if is_matched then
1309+
local lvl = socket.internal.SOL_SOCKET
1310+
ffi.C.getsockopt(fd, lvl,
1311+
socket.internal.SO_OPT[lvl].SO_REUSEADDR.iname,
1312+
val, len)
1313+
if val[0] > 0 then
1314+
table.insert(res, addrinfo)
1315+
end
1316+
end
1317+
end
1318+
if #res ~= 1 then
1319+
error(("Zero or more than one listening TCP sockets: %s")
1320+
:format(#res))
1321+
end
1322+
return {{host = res[1].host, port = res[1].port}}
1323+
end
1324+
""".format(localhost=self.localhost)
1325+
res = yaml.safe_load(self.admin(script, silent=True))[0]
1326+
if res.get('error'):
1327+
color_stdout("Failed to get iproto port: {}\n".format(res['error']),
1328+
schema='error')
1329+
raise TarantoolStartError(self.name)
1330+
1331+
return int(res['port'])

0 commit comments

Comments
 (0)