Skip to content

Commit 3cba982

Browse files
committed
PYTHON-1332 - Test lsid with all commands
1 parent fdf4436 commit 3cba982

13 files changed

+459
-199
lines changed

doc/changelog.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ Highlights include:
2020

2121
Breaking changes include:
2222

23+
- Certain commands, such as ``ismaster`` and ``ping``, that could be used
24+
to check whether a server is available without requiring authentication, now
25+
raise :exc:`~pymongo.errors.OperationFailure` if executed without
26+
authenticating on a MongoDB 3.6+ server started with ``--auth``. (This is
27+
because all commands are now sent with a session id, whether a
28+
:class:`~pymongo.client_session.ClientSession` is used or not, and all
29+
commands with a session id require auth.)
2330
- BSON binary subtype 4 is decoded using RFC-4122 byte order regardless
2431
of the UUID representation. This is a change in behavior for applications
2532
that use UUID representation :data:`bson.binary.JAVA_LEGACY` or

test/__init__.py

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import os
1919
import socket
2020
import sys
21+
import time
2122
import warnings
2223

2324
try:
@@ -88,14 +89,14 @@ def is_server_resolvable():
8889

8990

9091
def _connect(host, port, **kwargs):
91-
try:
92-
client = pymongo.MongoClient(
93-
host, port, serverSelectionTimeoutMS=100, **kwargs)
94-
client.admin.command('ismaster') # Can we connect?
95-
# If connected, then return client with default timeout
96-
return pymongo.MongoClient(host, port, **kwargs)
97-
except pymongo.errors.ConnectionFailure:
98-
return None
92+
client = pymongo.MongoClient(host, port, **kwargs)
93+
start = time.time()
94+
while not client.nodes:
95+
time.sleep(0.05)
96+
if time.time() - start > 0.1:
97+
return None
98+
99+
return client
99100

100101

101102
class client_knobs(object):
@@ -147,11 +148,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
147148

148149

149150
def _all_users(db):
150-
if Version.from_client(db.client).at_least(2, 5, 3, -1):
151-
return set(u['user']
152-
for u in db.command('usersInfo').get('users', []))
153-
else:
154-
return set(u['user'] for u in db.system.users.find())
151+
return set(u['user'] for u in db.command('usersInfo').get('users', []))
155152

156153

157154
class ClientContext(object):
@@ -190,32 +187,6 @@ def __init__(self):
190187

191188
if self.client:
192189
self.connected = True
193-
ismaster = self.client.admin.command('ismaster')
194-
self.sessions_enabled = 'logicalSessionTimeoutMinutes' in ismaster
195-
196-
if 'setName' in ismaster:
197-
self.replica_set_name = ismaster['setName']
198-
self.is_rs = True
199-
# It doesn't matter which member we use as the seed here.
200-
self.client = pymongo.MongoClient(
201-
host,
202-
port,
203-
replicaSet=self.replica_set_name,
204-
**self.ssl_client_options)
205-
# Get the authoritative ismaster result from the primary.
206-
self.ismaster = self.client.admin.command('ismaster')
207-
nodes = [partition_node(node.lower())
208-
for node in self.ismaster.get('hosts', [])]
209-
nodes.extend([partition_node(node.lower())
210-
for node in self.ismaster.get('passives', [])])
211-
nodes.extend([partition_node(node.lower())
212-
for node in self.ismaster.get('arbiters', [])])
213-
self.nodes = set(nodes)
214-
else:
215-
self.ismaster = ismaster
216-
self.nodes = set([(host, port)])
217-
self.w = len(self.ismaster.get("hosts", [])) or 1
218-
self.version = Version.from_client(self.client)
219190

220191
try:
221192
self.cmd_line = self.client.admin.command('getCmdLineOpts')
@@ -232,10 +203,7 @@ def __init__(self):
232203
if self.auth_enabled:
233204
# See if db_user already exists.
234205
if not self._check_user_provided():
235-
roles = {}
236-
if self.version.at_least(2, 5, 3, -1):
237-
roles = {'roles': ['root']}
238-
self.client.admin.add_user(db_user, db_pwd, **roles)
206+
self.client.admin.add_user(db_user, db_pwd, roles=['root'])
239207

240208
self.client = _connect(host,
241209
port,
@@ -247,6 +215,43 @@ def __init__(self):
247215
# May not have this if OperationFailure was raised earlier.
248216
self.cmd_line = self.client.admin.command('getCmdLineOpts')
249217

218+
self.ismaster = ismaster = self.client.admin.command('isMaster')
219+
self.sessions_enabled = 'logicalSessionTimeoutMinutes' in ismaster
220+
221+
if 'setName' in ismaster:
222+
self.replica_set_name = ismaster['setName']
223+
self.is_rs = True
224+
if self.auth_enabled:
225+
# It doesn't matter which member we use as the seed here.
226+
self.client = pymongo.MongoClient(
227+
host,
228+
port,
229+
username=db_user,
230+
password=db_pwd,
231+
replicaSet=self.replica_set_name,
232+
**self.ssl_client_options)
233+
else:
234+
self.client = pymongo.MongoClient(
235+
host,
236+
port,
237+
replicaSet=self.replica_set_name,
238+
**self.ssl_client_options)
239+
240+
# Get the authoritative ismaster result from the primary.
241+
self.ismaster = self.client.admin.command('ismaster')
242+
nodes = [partition_node(node.lower())
243+
for node in self.ismaster.get('hosts', [])]
244+
nodes.extend([partition_node(node.lower())
245+
for node in self.ismaster.get('passives', [])])
246+
nodes.extend([partition_node(node.lower())
247+
for node in self.ismaster.get('arbiters', [])])
248+
self.nodes = set(nodes)
249+
else:
250+
self.ismaster = ismaster
251+
self.nodes = set([(host, port)])
252+
self.w = len(self.ismaster.get("hosts", [])) or 1
253+
self.version = Version.from_client(self.client)
254+
250255
if 'enableTestCommands=1' in self.cmd_line['argv']:
251256
self.test_commands_enabled = True
252257
elif 'parsed' in self.cmd_line:
@@ -487,6 +492,7 @@ def require_sessions(self, func):
487492
"Sessions not supported",
488493
func=func)
489494

495+
490496
# Reusable client context
491497
client_context = ClientContext()
492498

@@ -499,6 +505,10 @@ class IntegrationTest(unittest.TestCase):
499505
def setUpClass(cls):
500506
cls.client = client_context.client
501507
cls.db = cls.client.pymongo_test
508+
if client_context.auth_enabled:
509+
cls.credentials = {'username': db_user, 'password': db_pwd}
510+
else:
511+
cls.credentials = {}
502512

503513

504514
# Use assertRaisesRegex if available, otherwise use Python 2.7's

test/test_change_stream.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from pymongo.command_cursor import CommandCursor
3434
from pymongo.errors import (InvalidOperation, OperationFailure,
3535
ServerSelectionTimeoutError)
36+
from pymongo.message import _CursorAddress
3637
from pymongo.read_concern import ReadConcern
3738

3839
from test import client_context, unittest, IntegrationTest
@@ -189,7 +190,8 @@ def test_resume_on_error(self):
189190
self.insert_and_check(change_stream, {'_id': 1})
190191
# Cause a cursor not found error on the next getMore.
191192
cursor = change_stream._cursor
192-
self.client._close_cursor_now(cursor.cursor_id, cursor.address)
193+
address = _CursorAddress(cursor.address, self.coll.full_name)
194+
self.client._close_cursor_now(cursor.cursor_id, address)
193195
self.insert_and_check(change_stream, {'_id': 2})
194196

195197
def test_does_not_resume_on_server_error(self):
@@ -235,7 +237,8 @@ def raise_error():
235237
self.insert_and_check(change_stream, {'_id': 1})
236238
# Cause a cursor not found error on the next getMore.
237239
cursor = change_stream._cursor
238-
self.client._close_cursor_now(cursor.cursor_id, cursor.address)
240+
address = _CursorAddress(cursor.address, self.coll.full_name)
241+
self.client._close_cursor_now(cursor.cursor_id, address)
239242
cursor.close = raise_error
240243
self.insert_and_check(change_stream, {'_id': 2})
241244

test/test_client.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -354,23 +354,27 @@ def test_constants(self):
354354
port are not overloaded.
355355
"""
356356
host, port = client_context.host, client_context.port
357+
kwargs = client_context.ssl_client_options.copy()
358+
if client_context.auth_enabled:
359+
kwargs['username'] = db_user
360+
kwargs['password'] = db_pwd
361+
357362
# Set bad defaults.
358363
MongoClient.HOST = "somedomainthatdoesntexist.org"
359364
MongoClient.PORT = 123456789
360365
with self.assertRaises(AutoReconnect):
361366
connected(MongoClient(serverSelectionTimeoutMS=10,
362-
**client_context.ssl_client_options))
367+
**kwargs))
363368

364369
# Override the defaults. No error.
365-
connected(MongoClient(host, port,
366-
**client_context.ssl_client_options))
370+
connected(MongoClient(host, port, **kwargs))
367371

368372
# Set good defaults.
369373
MongoClient.HOST = host
370374
MongoClient.PORT = port
371375

372376
# No error.
373-
connected(MongoClient(**client_context.ssl_client_options))
377+
connected(MongoClient(**kwargs))
374378

375379
def test_init_disconnected(self):
376380
host, port = client_context.host, client_context.port
@@ -1051,7 +1055,8 @@ def test_stale_getmore(self):
10511055
serverSelectionTimeoutMS=100)
10521056
client._send_message_with_response(
10531057
operation=message._GetMore('pymongo_test', 'collection',
1054-
101, 1234, client.codec_options),
1058+
101, 1234, client.codec_options,
1059+
None),
10551060
address=('not-a-member', 27017))
10561061

10571062
def test_heartbeat_frequency_ms(self):

test/test_collection.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2252,8 +2252,8 @@ def test_find_regex(self):
22522252
self.assertTrue(isinstance(doc['r'], Regex))
22532253

22542254
def test_find_command_generation(self):
2255-
cmd = _gen_find_command(
2256-
'coll', {'$query': {'foo': 1}, '$dumb': 2}, None, 0, 0, 0, None)
2255+
cmd = _gen_find_command('coll', {'$query': {'foo': 1}, '$dumb': 2},
2256+
None, 0, 0, 0, None, None)
22572257
self.assertEqual(
22582258
cmd.to_dict(),
22592259
SON([('find', 'coll'),

test/test_common.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from bson.objectid import ObjectId
2626
from pymongo.errors import OperationFailure
2727
from pymongo.write_concern import WriteConcern
28-
from test import client_context, unittest, IntegrationTest
28+
from test import client_context, IntegrationTest
2929
from test.utils import connected, rs_or_single_client, single_client
3030

3131

@@ -195,7 +195,8 @@ def test_mongo_client(self):
195195

196196
# Equality tests
197197
direct = connected(single_client(w=0))
198-
direct2 = connected(single_client("mongodb://%s/?w=0" % (pair,)))
198+
direct2 = connected(single_client("mongodb://%s/?w=0" % (pair,),
199+
**self.credentials))
199200
self.assertEqual(direct, direct2)
200201
self.assertFalse(direct != direct2)
201202

test/test_cursor.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
from bson.py3compat import PY3
2828
from bson.son import SON
2929
from pymongo import (monitoring,
30-
MongoClient,
3130
ASCENDING,
3231
DESCENDING,
3332
ALL,
@@ -52,12 +51,7 @@
5251
long = int
5352

5453

55-
class TestCursorNoConnect(unittest.TestCase):
56-
57-
@classmethod
58-
def setUpClass(cls):
59-
cls.db = MongoClient(connect=False).test
60-
54+
class TestCursor(IntegrationTest):
6155
def test_deepcopy_cursor_littered_with_regexes(self):
6256
cursor = self.db.test.find({
6357
"x": re.compile("^hmmm.*"),
@@ -134,9 +128,6 @@ def test_add_remove_option(self):
134128
cursor.remove_option(128)
135129
self.assertEqual(0, cursor._Cursor__query_flags)
136130

137-
138-
class TestCursor(IntegrationTest):
139-
140131
def test_add_remove_option_exhaust(self):
141132
# Exhaust - which mongos doesn't support
142133
if client_context.is_mongos:

test/test_cursor_manager.py

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
sys.path[0:0] = [""]
2121

22-
from pymongo.command_cursor import CommandCursor
2322
from pymongo.cursor_manager import CursorManager
2423
from pymongo.errors import CursorNotFound
2524
from pymongo.message import _CursorAddress
@@ -97,37 +96,6 @@ def raises_cursor_not_found():
9796
wait_until(raises_cursor_not_found, 'close cursor')
9897
self.assertTrue(self.close_was_called)
9998

100-
def test_cursor_transfer(self):
101-
102-
# This is just a test, don't try this at home...
103-
104-
client = rs_or_single_client()
105-
db = client.pymongo_test
106-
107-
db.test.delete_many({})
108-
db.test.insert_many([{'_id': i} for i in range(200)])
109-
110-
class CManager(CursorManager):
111-
def __init__(self, client):
112-
super(CManager, self).__init__(client)
113-
114-
def close(self, dummy, dummy2):
115-
# Do absolutely nothing...
116-
pass
117-
118-
client.set_cursor_manager(CManager)
119-
docs = []
120-
cursor = db.test.find().batch_size(10)
121-
docs.append(next(cursor))
122-
cursor.close()
123-
docs.extend(cursor)
124-
self.assertEqual(len(docs), 10)
125-
cmd_cursor = {'id': cursor.cursor_id, 'firstBatch': []}
126-
ccursor = CommandCursor(cursor.collection, cmd_cursor,
127-
cursor.address, retrieved=cursor.retrieved)
128-
docs.extend(ccursor)
129-
self.assertEqual(len(docs), 200)
130-
13199

132100
if __name__ == "__main__":
133101
unittest.main()

test/test_grid_file.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,12 @@ def test_grid_out_cursor_options(self):
232232

233233
cursor = GridOutCursor(self.db.fs, {})
234234
cursor_clone = cursor.clone()
235-
self.assertEqual(cursor_clone.__dict__, cursor.__dict__)
235+
236+
cursor_dict = cursor.__dict__.copy()
237+
cursor_dict.pop('_Cursor__session')
238+
cursor_clone_dict = cursor_clone.__dict__.copy()
239+
cursor_clone_dict.pop('_Cursor__session')
240+
self.assertEqual(cursor_dict, cursor_clone_dict)
236241

237242
self.assertRaises(NotImplementedError, cursor.add_option, 0)
238243
self.assertRaises(NotImplementedError, cursor.remove_option, 0)

test/test_replica_set_client.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
"""Test the mongo_replica_set_client module."""
1616

17-
import socket
1817
import sys
1918
import warnings
2019
import time
@@ -161,32 +160,6 @@ def test_properties(self):
161160
self.assertEqual(c.max_bson_size, 16777216)
162161
c.close()
163162

164-
@client_context.require_secondaries_count(1)
165-
def test_auto_reconnect_exception_when_read_preference_is_secondary(self):
166-
c = MongoClient(
167-
client_context.pair,
168-
replicaSet=self.name,
169-
serverSelectionTimeoutMS=100)
170-
db = c.pymongo_test
171-
172-
def raise_socket_error(*args, **kwargs):
173-
raise socket.error
174-
175-
# In Jython socket.socket is a function, not a class.
176-
sock = socket.socket()
177-
klass = sock.__class__
178-
old_sendall = klass.sendall
179-
klass.sendall = raise_socket_error
180-
181-
try:
182-
cursor = db.get_collection(
183-
"test", read_preference=ReadPreference.SECONDARY).find()
184-
self.assertRaises(AutoReconnect, cursor.next)
185-
finally:
186-
klass.sendall = old_sendall
187-
# Silence resource warnings.
188-
sock.close()
189-
190163
@client_context.require_secondaries_count(1)
191164
def test_timeout_does_not_mark_member_down(self):
192165
# If a query times out, the client shouldn't mark the member "down".

0 commit comments

Comments
 (0)