Skip to content

Commit 3351a74

Browse files
committed
conn: create from socket fd
This patch adds the ability to create Tarantool connection using an existing socket fd. Closes #304
1 parent 3c8eb9d commit 3351a74

File tree

6 files changed

+99
-19
lines changed

6 files changed

+99
-19
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## Unreleased
8+
9+
### Added
10+
- The ability to connect to the Tarantool using an existing socket fd (#304).
11+
712
## 1.1.2 - 2023-09-20
813

914
### Fixed

tarantool/connection.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ def __init__(self, host, port,
623623
Unix sockets.
624624
:type host: :obj:`str` or :obj:`None`
625625
626-
:param port: Server port or Unix socket path.
626+
:param port: Server port, socket fd or Unix socket path.
627627
:type port: :obj:`int` or :obj:`str`
628628
629629
:param user: User name for authentication on the Tarantool
@@ -941,9 +941,13 @@ def connect_unix(self):
941941
self.connected = True
942942
if self._socket:
943943
self._socket.close()
944-
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
944+
if isinstance(self.port, int):
945+
# Use an existing fd to create a socket, take ownership.
946+
self._socket = socket.socket(fileno=self.port)
947+
else:
948+
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
949+
self._socket.connect(self.port)
945950
self._socket.settimeout(self.connection_timeout)
946-
self._socket.connect(self.port)
947951
self._socket.settimeout(self.socket_timeout)
948952
except socket.error as exc:
949953
self.connected = False

tarantool/mesh_connection.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -139,23 +139,23 @@ def format_error(address, err):
139139
result[key] = val
140140

141141
if isinstance(result['port'], int):
142-
# Looks like an inet address.
142+
# Looks like an inet address or socket fd.
143143

144144
# Validate host.
145-
if 'host' not in result or result['host'] is None:
146-
return format_error(result,
147-
'host is mandatory for an inet result')
148-
if not isinstance(result['host'], str):
149-
return format_error(result,
150-
'host must be a string for an inet result')
145+
if ('host' in result and result['host'] is not None
146+
and not isinstance(result['host'], str)):
147+
return format_error(result, 'host must be a string for an inet result, '
148+
'or None for a socket fd result')
151149

152150
# Validate port.
153151
if not isinstance(result['port'], int):
154152
return format_error(result,
155-
'port must be an int for an inet result')
156-
if result['port'] < 1 or result['port'] > 65535:
157-
return format_error(result, 'port must be in range [1, 65535] '
158-
'for an inet result')
153+
'port must be an int for an inet/socket fd result')
154+
if 'host' in result and isinstance(result['host'], str):
155+
# Check that the port is correct.
156+
if result['port'] < 1 or result['port'] > 65535:
157+
return format_error(result, 'port must be in range [1, 65535] '
158+
'for an inet result')
159159

160160
# Looks okay.
161161
return result, None
@@ -447,7 +447,8 @@ def __init__(self, host=None, port=None,
447447
# Don't change user provided arguments.
448448
addrs = addrs[:]
449449

450-
if host and port:
450+
if port:
451+
# host can be None in the case of socket fd or Unix socket.
451452
addrs.insert(0, {'host': host,
452453
'port': port,
453454
'transport': transport,

test/suites/test_dml.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
This module tests basic data operations.
33
"""
4+
import socket
45
# pylint: disable=missing-class-docstring,missing-function-docstring,protected-access,fixme,too-many-public-methods,duplicate-code
56

67
import sys
@@ -430,6 +431,20 @@ def test_16_extra_error_info_fields(self):
430431
else:
431432
self.fail('Expected error')
432433

434+
@unittest.skipIf(sys.platform.startswith("win"),
435+
"Skip UNIX socket tests on Windows since it uses remote server")
436+
def test_17_socket_fd(self):
437+
# pylint: disable=E1101
438+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
439+
sock.connect(self.sock_srv.args['primary'])
440+
441+
sock_con = tarantool.connect(self.sock_srv.host, sock.fileno())
442+
sock.detach()
443+
try:
444+
self.assertEqual(sock_con.ping(notime=True), "Success")
445+
finally:
446+
sock_con.close()
447+
433448
@classmethod
434449
def tearDownClass(cls):
435450
cls.con.close()

test/suites/test_mesh.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
This module tests work with a cluster of Tarantool servers through
33
MeshConnection.
44
"""
5+
import socket
56
# pylint: disable=missing-class-docstring,missing-function-docstring
67

78
import sys
@@ -19,8 +20,8 @@
1920
from .utils import assert_admin_success
2021

2122

22-
def create_server(_id):
23-
srv = TarantoolServer()
23+
def create_server(_id, create_unix_socket=False):
24+
srv = TarantoolServer(create_unix_socket=create_unix_socket)
2425
srv.script = 'test/suites/box.lua'
2526
srv.start()
2627
resp = srv.admin(f"""
@@ -85,6 +86,11 @@ def setUp(self):
8586
self.define_cluster_function(self.get_all_nodes_func_name,
8687
self.servers)
8788

89+
if not sys.platform.startswith("win"):
90+
self.sock_srv = create_server(3, create_unix_socket=True)
91+
else:
92+
self.sock_srv = None
93+
8894
def test_00_basic(self):
8995
def assert_srv_id(con, srv_id):
9096
with warnings.catch_warnings():
@@ -315,9 +321,31 @@ def test_07_discovery_exclude_address(self):
315321
finally:
316322
con.close()
317323

324+
@unittest.skipIf(sys.platform.startswith("win"),
325+
"Skip UNIX socket tests on Windows since it uses remote server")
326+
def test_08_socket_fd(self):
327+
path = self.sock_srv.args['primary']
328+
# pylint: disable=E1101
329+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
330+
sock.connect(path)
331+
332+
sock_con = tarantool.MeshConnection(
333+
self.sock_srv.host, sock.fileno(), user='test', password='test'
334+
)
335+
sock.detach()
336+
337+
try:
338+
self.assertEqual(sock_con.ping(notime=True), "Success")
339+
finally:
340+
sock_con.close()
341+
318342
def tearDown(self):
319343
self.srv.stop()
320344
self.srv.clean()
321345

322346
self.srv2.stop()
323347
self.srv2.clean()
348+
349+
if self.sock_srv is not None:
350+
self.sock_srv.stop()
351+
self.sock_srv.clean()

test/suites/test_pool.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
This module tests work with a cluster of Tarantool servers through
33
ConnectionPool.
44
"""
5+
import socket
56
# pylint: disable=missing-class-docstring,missing-function-docstring,too-many-public-methods,too-many-locals,duplicate-code,bad-option-value,no-self-use
67

78
import sys
@@ -23,8 +24,8 @@
2324
from .utils import assert_admin_success
2425

2526

26-
def create_server(_id):
27-
srv = TarantoolServer()
27+
def create_server(_id, create_unix_socket=False):
28+
srv = TarantoolServer(create_unix_socket=create_unix_socket)
2829
srv.script = 'test/suites/box.lua'
2930
srv.start()
3031
resp = srv.admin(f"""
@@ -107,6 +108,11 @@ def setUp(self):
107108
self.servers.append(srv)
108109
self.addrs.append({'host': srv.host, 'port': srv.args['primary']})
109110

111+
if not sys.platform.startswith("win"):
112+
self.sock_srv = create_server(self.servers_count, create_unix_socket=True)
113+
else:
114+
self.sock_srv = None
115+
110116
def test_00_basic(self):
111117
self.set_cluster_ro([False, False, True, False, True])
112118

@@ -581,10 +587,31 @@ def test_16_is_closed(self):
581587

582588
self.assertEqual(self.pool.is_closed(), True)
583589

590+
@unittest.skipIf(sys.platform.startswith("win"),
591+
"Skip UNIX socket tests on Windows since it uses remote server")
592+
def test_17_socket_fd(self):
593+
# pylint: disable=E1101
594+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
595+
sock.connect(self.sock_srv.args['primary'])
596+
597+
self.pool = tarantool.ConnectionPool(
598+
addrs=[{'host': None, 'port': sock.fileno()}],
599+
user='test',
600+
password='test'
601+
)
602+
sock.detach()
603+
try:
604+
self.assertEqual(self.pool.ping(notime=True, mode=tarantool.Mode.ANY), "Success")
605+
finally:
606+
self.pool.close()
607+
584608
def tearDown(self):
585609
if self.pool:
586610
self.pool.close()
587611

588612
for srv in self.servers:
589613
srv.stop()
590614
srv.clean()
615+
if self.sock_srv is not None:
616+
self.sock_srv.stop()
617+
self.sock_srv.clean()

0 commit comments

Comments
 (0)