Skip to content

Commit ebb7b1f

Browse files
committed
remove op_query
1 parent 8dc7efa commit ebb7b1f

3 files changed

Lines changed: 67 additions & 19 deletions

File tree

pymongo/asynchronous/pool.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
MAX_MESSAGE_SIZE,
4747
MAX_WIRE_VERSION,
4848
MAX_WRITE_BATCH_SIZE,
49+
MIN_SUPPORTED_SERVER_VERSION,
50+
MIN_SUPPORTED_WIRE_VERSION,
4951
ORDERED_TYPES,
5052
)
5153
from pymongo.errors import ( # type:ignore[attr-defined]
@@ -235,13 +237,12 @@ async def unpin(self) -> None:
235237
await self.close_conn(ConnectionClosedReason.STALE)
236238

237239
def hello_cmd(self) -> dict[str, Any]:
238-
# Handshake spec requires us to use OP_MSG+hello command for the
239-
# initial handshake in load balanced or stable API mode.
240+
# As of PYTHON-5713, always use OP_MSG for the handshake since all
241+
# supported servers (MongoDB 4.2+, wire version >= 8) support it.
242+
self.op_msg_enabled = True
240243
if self.opts.server_api or self.hello_ok or self.opts.load_balanced:
241-
self.op_msg_enabled = True
242244
return {HelloCompat.CMD: 1}
243-
else:
244-
return {HelloCompat.LEGACY_CMD: 1, "helloOk": True}
245+
return {HelloCompat.LEGACY_CMD: 1, "helloOk": True}
245246

246247
async def hello(self) -> Hello[dict[str, Any]]:
247248
return await self._hello(None, None)
@@ -291,6 +292,19 @@ async def _hello(
291292
if performing_handshake:
292293
self.connect_rtt = time.monotonic() - start
293294
hello = Hello(doc, awaitable=awaitable)
295+
# OP_MSG requires wire version 6+.
296+
if hello.max_wire_version < 6:
297+
raise ConfigurationError(
298+
"Server at %s:%d reports wire version %d, but this version of "
299+
"PyMongo requires at least %d (MongoDB %s)."
300+
% (
301+
self.address[0],
302+
self.address[1] or 0,
303+
hello.max_wire_version,
304+
MIN_SUPPORTED_WIRE_VERSION,
305+
MIN_SUPPORTED_SERVER_VERSION,
306+
)
307+
)
294308
self.is_writable = hello.is_writable
295309
self.max_wire_version = hello.max_wire_version
296310
self.max_bson_size = hello.max_bson_size

pymongo/synchronous/pool.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
MAX_MESSAGE_SIZE,
4444
MAX_WIRE_VERSION,
4545
MAX_WRITE_BATCH_SIZE,
46+
MIN_SUPPORTED_SERVER_VERSION,
47+
MIN_SUPPORTED_WIRE_VERSION,
4648
ORDERED_TYPES,
4749
)
4850
from pymongo.errors import ( # type:ignore[attr-defined]
@@ -235,13 +237,12 @@ def unpin(self) -> None:
235237
self.close_conn(ConnectionClosedReason.STALE)
236238

237239
def hello_cmd(self) -> dict[str, Any]:
238-
# Handshake spec requires us to use OP_MSG+hello command for the
239-
# initial handshake in load balanced or stable API mode.
240+
# As of PYTHON-5713, always use OP_MSG for the handshake since all
241+
# supported servers (MongoDB 4.2+, wire version >= 8) support it.
242+
self.op_msg_enabled = True
240243
if self.opts.server_api or self.hello_ok or self.opts.load_balanced:
241-
self.op_msg_enabled = True
242244
return {HelloCompat.CMD: 1}
243-
else:
244-
return {HelloCompat.LEGACY_CMD: 1, "helloOk": True}
245+
return {HelloCompat.LEGACY_CMD: 1, "helloOk": True}
245246

246247
def hello(self) -> Hello[dict[str, Any]]:
247248
return self._hello(None, None)
@@ -291,6 +292,19 @@ def _hello(
291292
if performing_handshake:
292293
self.connect_rtt = time.monotonic() - start
293294
hello = Hello(doc, awaitable=awaitable)
295+
# OP_MSG requires wire version 6+.
296+
if hello.max_wire_version < 6:
297+
raise ConfigurationError(
298+
"Server at %s:%d reports wire version %d, but this version of "
299+
"PyMongo requires at least %d (MongoDB %s)."
300+
% (
301+
self.address[0],
302+
self.address[1] or 0,
303+
hello.max_wire_version,
304+
MIN_SUPPORTED_WIRE_VERSION,
305+
MIN_SUPPORTED_SERVER_VERSION,
306+
)
307+
)
294308
self.is_writable = hello.is_writable
295309
self.max_wire_version = hello.max_wire_version
296310
self.max_bson_size = hello.max_bson_size

test/mockupdb/test_handshake.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@
1313
# limitations under the License.
1414
from __future__ import annotations
1515

16+
import re
1617
import unittest
1718

1819
import pytest
1920

2021
try:
21-
from mockupdb import Command, MockupDB, OpMsg, OpMsgReply, OpQuery, OpReply, absent, go
22+
from mockupdb import Command, MockupDB, OpMsg, OpMsgReply, OpReply, absent, go
2223

2324
_HAVE_MOCKUPDB = True
2425
except ImportError:
@@ -28,8 +29,8 @@
2829
from bson.objectid import ObjectId
2930
from pymongo import MongoClient, has_c
3031
from pymongo import version as pymongo_version
31-
from pymongo.common import MIN_SUPPORTED_WIRE_VERSION
32-
from pymongo.errors import OperationFailure
32+
from pymongo.common import MIN_SUPPORTED_SERVER_VERSION, MIN_SUPPORTED_WIRE_VERSION
33+
from pymongo.errors import ConfigurationError, OperationFailure, ServerSelectionTimeoutError
3334
from pymongo.server_api import ServerApi, ServerApiVersion
3435

3536
pytestmark = pytest.mark.mockupdb
@@ -53,7 +54,7 @@ def _check_handshake_data(request):
5354

5455
class TestHandshake(unittest.TestCase):
5556
def hello_with_option_helper(self, protocol, **kwargs):
56-
hello = "ismaster" if isinstance(protocol(), OpQuery) else "hello"
57+
hello = "hello" if ("apiVersion" in kwargs or "loadBalanced" in kwargs) else "ismaster"
5758
# `db.command("hello"|"ismaster")` commands are the same for primaries and
5859
# secondaries, so we only need one server.
5960
primary = MockupDB()
@@ -165,7 +166,7 @@ def test_client_handshake_data(self):
165166
future = go(client.db.command, "whatever")
166167

167168
for request in primary:
168-
if request.matches(Command("ismaster")):
169+
if request.matches("ismaster"):
169170
if request.client_port == heartbeat.client_port:
170171
# This is the monitor again, keep going.
171172
request.ok(primary_response)
@@ -242,11 +243,10 @@ def test_handshake_versioned_api(self):
242243
self.hello_with_option_helper(Command, apiVersion="1")
243244

244245
def test_handshake_not_either(self):
245-
# If we don't specify either option then it should be using
246-
# OP_QUERY for the initial step of the handshake.
247-
self.hello_with_option_helper(Command)
246+
# As of PYTHON-5713, always use OP_MSG for the initial handshake.
247+
self.hello_with_option_helper(OpMsg)
248248
with self.assertRaisesRegex(AssertionError, "does not match"):
249-
self.hello_with_option_helper(OpMsg)
249+
self.hello_with_option_helper(Command)
250250

251251
def test_handshake_max_wire(self):
252252
server = MockupDB()
@@ -292,6 +292,26 @@ def responder(request):
292292
self.found_auth_msg, "Could not find authentication command with correct protocol"
293293
)
294294

295+
def test_handshake_op_msg_not_supported(self):
296+
# If a server responds with maxWireVersion < 6 (no OP_MSG support),
297+
# the wire version error must surface to the user.
298+
server = MockupDB()
299+
server.autoresponds("ismaster", ok=1, ismaster=True, minWireVersion=0, maxWireVersion=5)
300+
server.run()
301+
self.addCleanup(server.stop)
302+
303+
client = MongoClient(server.uri, serverSelectionTimeoutMS=500)
304+
self.addCleanup(client.close)
305+
306+
# The ConfigurationError from _hello() is stored as the server's error
307+
# and surfaces inside ServerSelectionTimeoutError.
308+
expected = re.escape(
309+
"reports wire version 5, but this version of PyMongo requires at least "
310+
"%d (MongoDB %s)." % (MIN_SUPPORTED_WIRE_VERSION, MIN_SUPPORTED_SERVER_VERSION)
311+
)
312+
with self.assertRaisesRegex(ServerSelectionTimeoutError, expected):
313+
client.db.command("ping")
314+
295315

296316
if __name__ == "__main__":
297317
unittest.main()

0 commit comments

Comments
 (0)