Skip to content

Commit 2bedbd9

Browse files
api: allow to require protocol version and features
Allow to set required_protocol_version and required_features on connection initialization to ensure that a Tarantool server provides expected features. The approach is similar to go-tarantool [1]. We do not check client protocol version and features, similar to the core Tarantool [1, 2]. 1. tarantool/go-tarantool#226 2. tarantool/tarantool#7953 Closes #267
1 parent 8a093c6 commit 2bedbd9

File tree

4 files changed

+88
-1
lines changed

4 files changed

+88
-1
lines changed

Diff for: CHANGELOG.md

+5
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+
- Allow to require specific server protocol version and features (#267).
11+
712
## 1.0.0 - 2023-04-17
813

914
### Changed

Diff for: tarantool/connection.py

+33-1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@
6666
IPROTO_FEATURE_TRANSACTIONS,
6767
IPROTO_FEATURE_ERROR_EXTENSION,
6868
IPROTO_FEATURE_WATCHERS,
69+
IPROTO_FEATURE_PAGINATION,
70+
IPROTO_FEATURE_SPACE_AND_INDEX_NAMES,
71+
IPROTO_FEATURE_WATCH_ONCE,
6972
IPROTO_CHUNK,
7073
AUTH_TYPE_CHAP_SHA1,
7174
AUTH_TYPE_PAP_SHA256,
@@ -608,7 +611,9 @@ def __init__(self, host, port,
608611
packer_factory=default_packer_factory,
609612
unpacker_factory=default_unpacker_factory,
610613
auth_type=None,
611-
fetch_schema=True):
614+
fetch_schema=True,
615+
required_protocol_version=None,
616+
required_features=None):
612617
"""
613618
:param host: Server hostname or IP address. Use ``None`` for
614619
Unix sockets.
@@ -777,6 +782,14 @@ def __init__(self, host, port,
777782
:meth:`~tarantool.Connection.space`.
778783
:type fetch_schema: :obj:`bool`, optional
779784
785+
:param required_protocol_version: Minimal protocol version that
786+
should be supported by Tarantool server.
787+
:type required_protocol_version: :obj:`int` or :obj:`None`, optional
788+
789+
:param required_features: List of protocol features that
790+
should be supported by Tarantool server.
791+
:type required_features: :obj:`list` or :obj:`None`, optional
792+
780793
:raise: :exc:`~tarantool.error.ConfigurationError`,
781794
:meth:`~tarantool.Connection.connect` exceptions
782795
@@ -831,6 +844,9 @@ def __init__(self, host, port,
831844
IPROTO_FEATURE_TRANSACTIONS: False,
832845
IPROTO_FEATURE_ERROR_EXTENSION: False,
833846
IPROTO_FEATURE_WATCHERS: False,
847+
IPROTO_FEATURE_PAGINATION: False,
848+
IPROTO_FEATURE_SPACE_AND_INDEX_NAMES: False,
849+
IPROTO_FEATURE_WATCH_ONCE: False,
834850
}
835851
self._packer_factory_impl = packer_factory
836852
self._unpacker_factory_impl = unpacker_factory
@@ -843,6 +859,8 @@ def __init__(self, host, port,
843859
self._client_features = copy(CONNECTOR_FEATURES)
844860
self._server_protocol_version = None
845861
self._server_features = None
862+
self.required_protocol_version = required_protocol_version
863+
self.required_features = copy(required_features)
846864

847865
if connect_now:
848866
self.connect()
@@ -2076,6 +2094,20 @@ def _check_features(self):
20762094
if exc.code != ER_UNKNOWN_REQUEST_TYPE:
20772095
raise exc
20782096

2097+
if self.required_protocol_version is not None:
2098+
if self._server_protocol_version is None \
2099+
or self._server_protocol_version < self.required_protocol_version:
2100+
raise ConfigurationError(f'Server protocol version is {self._server_protocol_version}, '
2101+
f'protocol version {self.required_protocol_version} '
2102+
'is required')
2103+
2104+
if self.required_features is not None:
2105+
failed_features = [val for val in self.required_features
2106+
if val not in self._server_features]
2107+
if len(failed_features) > 0:
2108+
str_features = ', '.join([str(v) for v in failed_features])
2109+
raise ConfigurationError(f'Server missing protocol features with id {str_features}')
2110+
20792111
if self._server_protocol_version is not None:
20802112
self._protocol_version = min(self._server_protocol_version,
20812113
self._client_protocol_version)

Diff for: test/suites/lib/skip.py

+16
Original file line numberDiff line numberDiff line change
@@ -277,3 +277,19 @@ def skip_or_run_constraints_test(func):
277277

278278
return skip_or_run_test_tarantool(func, '2.10.0',
279279
'does not support schema constraints')
280+
281+
282+
def skip_or_run_iproto_basic_features_test(func):
283+
"""
284+
Decorator to skip or run tests related to iproto ID requests,
285+
protocol version and features.
286+
287+
Tarantool supports iproto ID requests only since 2.10.0 version.
288+
Protocol version is 3 for Tarantool 2.10.0,
289+
IPROTO_FEATURE_STREAMS, IPROTO_FEATURE_TRANSACTIONS
290+
and IPROTO_FEATURE_ERROR_EXTENSION are supported in Tarantool 2.10.0.
291+
See https://github.com/tarantool/tarantool/issues/6253
292+
"""
293+
294+
return skip_or_run_test_tarantool(func, '2.10.0',
295+
'does not support iproto ID and iproto basic features')

Diff for: test/suites/test_protocol.py

+34
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,15 @@
1515
IPROTO_FEATURE_TRANSACTIONS,
1616
IPROTO_FEATURE_ERROR_EXTENSION,
1717
IPROTO_FEATURE_WATCHERS,
18+
IPROTO_FEATURE_PAGINATION,
19+
IPROTO_FEATURE_SPACE_AND_INDEX_NAMES,
20+
IPROTO_FEATURE_WATCH_ONCE,
1821
)
22+
from tarantool.error import NetworkError
1923
from tarantool.utils import greeting_decode, version_id
2024

2125
from .lib.tarantool_server import TarantoolServer
26+
from .lib.skip import skip_or_run_iproto_basic_features_test
2227

2328

2429
class TestSuiteProtocol(unittest.TestCase):
@@ -91,6 +96,35 @@ def test_04_protocol(self):
9196
self.assertEqual(self.con._features[IPROTO_FEATURE_STREAMS], False)
9297
self.assertEqual(self.con._features[IPROTO_FEATURE_TRANSACTIONS], False)
9398
self.assertEqual(self.con._features[IPROTO_FEATURE_WATCHERS], False)
99+
self.assertEqual(self.con._features[IPROTO_FEATURE_PAGINATION], False)
100+
self.assertEqual(self.con._features[IPROTO_FEATURE_SPACE_AND_INDEX_NAMES], False)
101+
self.assertEqual(self.con._features[IPROTO_FEATURE_WATCH_ONCE], False)
102+
103+
@skip_or_run_iproto_basic_features_test
104+
def test_protocol_requirement(self):
105+
try:
106+
con = tarantool.Connection(self.srv.host, self.srv.args['primary'],
107+
required_protocol_version=3,
108+
required_features=[IPROTO_FEATURE_STREAMS,
109+
IPROTO_FEATURE_TRANSACTIONS,
110+
IPROTO_FEATURE_ERROR_EXTENSION])
111+
con.close()
112+
except Exception as exc: # pylint: disable=bad-option-value,broad-exception-caught,broad-except
113+
self.fail(f'Connection create have raised Exception: {repr(exc)}')
114+
115+
def test_protocol_version_requirement_fail(self):
116+
with self.assertRaisesRegex(NetworkError, # ConfigurationError is wrapped in NetworkError
117+
'protocol version 100500 is required'):
118+
con = tarantool.Connection(self.srv.host, self.srv.args['primary'],
119+
required_protocol_version=100500)
120+
con.close()
121+
122+
def test_protocol_features_requirement_fail(self):
123+
with self.assertRaisesRegex(NetworkError, # ConfigurationError is wrapped in NetworkError
124+
'Server missing protocol features with id 100500, 500100'):
125+
con = tarantool.Connection(self.srv.host, self.srv.args['primary'],
126+
required_features=[100500, 500100])
127+
con.close()
94128

95129
@classmethod
96130
def tearDownClass(cls):

0 commit comments

Comments
 (0)