def test_aggregate(self): server = MockupDB() server.autoresponds('ismaster', ismaster=True, msg='isdbgrid', minWireVersion=2, maxWireVersion=6) self.addCleanup(server.stop) server.run() client = MongoClient(server.uri) self.addCleanup(client.close) collection = client.test.collection with going(collection.aggregate, []): command = server.receives(aggregate='collection', pipeline=[]) self.assertFalse(command.slave_ok, 'SlaveOkay set') command.ok(result=[{}]) secondary_collection = collection.with_options( read_preference=ReadPreference.SECONDARY) with going(secondary_collection.aggregate, []): command = server.receives(OpMsg({"aggregate": "collection", "pipeline": [], '$readPreference': {'mode': 'secondary'}})) command.ok(result=[{}]) self.assertTrue(command.slave_ok, 'SlaveOkay not set')
def test_list_indexes_command(self): server = MockupDB(auto_ismaster={'maxWireVersion': 6}) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) self.addCleanup(client.close) with going(client.test.collection.list_indexes) as cursor: request = server.receives( listIndexes='collection', namespace='test') request.reply({'cursor': { 'firstBatch': [{'name': 'index_0'}], 'id': 123}}) with going(list, cursor()) as indexes: request = server.receives(getMore=123, namespace='test', collection='collection') request.reply({'cursor': { 'nextBatch': [{'name': 'index_1'}], 'id': 0}}) self.assertEqual([{'name': 'index_0'}, {'name': 'index_1'}], indexes()) for index_info in indexes(): self.assertIsInstance(index_info, SON)
def test_projection(self): q = {} fields = {'foo': True} # OP_QUERY, server = MockupDB(auto_ismaster=True, min_wire_version=0, max_wire_version=3) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) cursor = client.test.collection.find(q, fields) with going(next, cursor): request = server.receives(OpQuery(q, fields=fields)) request.reply([], cursor_id=0) # "find" command. server = MockupDB(auto_ismaster=True, min_wire_version=0, max_wire_version=4) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) cursor = client.test.collection.find(q, fields) cmd = Command( SON([('find', 'collection'), ('filter', q), ('projection', fields)])) with going(next, cursor): request = server.receives(cmd) request.ok(cursor={'id': 0, 'firstBatch': []})
def test_aggregate(self): server = MockupDB() server.autoresponds('ismaster', ismaster=True, msg='isdbgrid', minWireVersion=2, maxWireVersion=5) self.addCleanup(server.stop) server.run() client = MongoClient(server.uri) self.addCleanup(client.close) collection = client.test.collection with going(collection.aggregate, []): command = server.receives(aggregate='collection', pipeline=[]) self.assertFalse(command.slave_ok, 'SlaveOkay set') self.assertNotIn('$readPreference', command) command.ok(result=[{}]) secondary_collection = collection.with_options( read_preference=ReadPreference.SECONDARY) with going(secondary_collection.aggregate, []): command = server.receives({ '$query': SON([('aggregate', 'collection'), ('pipeline', []), ('cursor', {})]), '$readPreference': { 'mode': 'secondary' } }) command.ok(result=[{}]) self.assertTrue(command.slave_ok, 'SlaveOkay not set')
def test_list_indexes_command(self): server = MockupDB(auto_ismaster={'maxWireVersion': 3}) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) self.addCleanup(client.close) with going(client.test.collection.list_indexes) as cursor: request = server.receives(listIndexes='collection', namespace='test') request.reply( {'cursor': { 'firstBatch': [{ 'name': 'index_0' }], 'id': 123 }}) with going(list, cursor()) as indexes: request = server.receives(OpGetMore, namespace='test.collection', cursor_id=123) request.reply([{'name': 'index_1'}], cursor_id=0) self.assertEqual([{'name': 'index_0'}, {'name': 'index_1'}], indexes()) self.check_indexes(indexes())
def test_getmore_sharded(self): servers = [MockupDB(), MockupDB()] # Collect queries to either server in one queue. q = Queue() for server in servers: server.subscribe(q.put) server.autoresponds('ismaster', ismaster=True, msg='isdbgrid') server.run() self.addCleanup(server.stop) client = MongoClient('mongodb://%s:%d,%s:%d' % (servers[0].host, servers[0].port, servers[1].host, servers[1].port)) self.addCleanup(client.close) collection = client.db.collection cursor = collection.find() with going(next, cursor): query = q.get(timeout=1) query.replies({}, cursor_id=123) # 10 batches, all getMores go to same server. for i in range(1, 10): with going(next, cursor): getmore = q.get(timeout=1) self.assertEqual(query.server, getmore.server) getmore.replies({}, starting_from=i, cursor_id=123)
def test_network_disconnect_primary(self): # Application operation fails against primary. Test that topology # type changes from ReplicaSetWithPrimary to ReplicaSetNoPrimary. # http://bit.ly/1B5ttuL primary, secondary = servers = [MockupDB() for _ in range(2)] for server in servers: server.run() self.addCleanup(server.stop) hosts = [server.address_string for server in servers] primary_response = OpReply(ismaster=True, setName='rs', hosts=hosts, minWireVersion=2, maxWireVersion=6) primary.autoresponds('ismaster', primary_response) secondary.autoresponds( 'ismaster', ismaster=False, secondary=True, setName='rs', hosts=hosts, minWireVersion=2, maxWireVersion=6) client = MongoClient(primary.uri, replicaSet='rs') self.addCleanup(client.close) wait_until(lambda: client.primary == primary.address, 'discover primary') topology = client._topology self.assertEqual(TOPOLOGY_TYPE.ReplicaSetWithPrimary, topology.description.topology_type) # Open a socket in the application pool (calls ismaster). with going(client.db.command, 'buildinfo'): primary.receives('buildinfo').ok() # The primary hangs replying to ismaster. ismaster_future = Future() primary.autoresponds('ismaster', lambda r: r.ok(ismaster_future.result())) # Network error on application operation. with self.assertRaises(ConnectionFailure): with going(client.db.command, 'buildinfo'): primary.receives('buildinfo').hangup() # Topology type is updated. self.assertEqual(TOPOLOGY_TYPE.ReplicaSetNoPrimary, topology.description.topology_type) # Let ismasters through again. ismaster_future.set_result(primary_response) # Demand a primary. with going(client.db.command, 'buildinfo'): wait_until(lambda: client.primary == primary.address, 'rediscover primary') primary.receives('buildinfo').ok() self.assertEqual(TOPOLOGY_TYPE.ReplicaSetWithPrimary, topology.description.topology_type)
def cluster_time_conversation(self, callback, replies): cluster_time = Timestamp(0, 0) server = MockupDB() # First test all commands include $clusterTime with wire version 6. responder = server.autoresponds( 'ismaster', { 'minWireVersion': 0, 'maxWireVersion': 6, '$clusterTime': { 'clusterTime': cluster_time } }) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) self.addCleanup(client.close) with going(callback, client): for reply in replies: request = server.receives() self.assertIn('$clusterTime', request) self.assertEqual(request['$clusterTime']['clusterTime'], cluster_time) cluster_time = Timestamp(cluster_time.time, cluster_time.inc + 1) reply['$clusterTime'] = {'clusterTime': cluster_time} request.reply(reply) # Now test that no commands include $clusterTime with wire version 5, # even though the isMaster reply still has $clusterTime. server.cancel_responder(responder) server.autoresponds( 'ismaster', { 'minWireVersion': 0, 'maxWireVersion': 5, '$clusterTime': { 'clusterTime': cluster_time } }) client = MongoClient(server.uri) self.addCleanup(client.close) with going(callback, client): for reply in replies: request = server.receives() self.assertNotIn('$clusterTime', request) request.reply(reply)
def test_delete_many(self): with going(self.collection.delete_many, {}): self.server.receives( OpMsg(SON([('delete', 'collection'), ('writeConcern', { 'w': 0 })]), flags=2))
def test_replace_one(self): with going(self.collection.replace_one, {}, {}): self.server.receives( OpMsg(SON([('update', 'collection'), ('writeConcern', { 'w': 0 })]), flags=2))
def test_iteration(self): server = MockupDB(auto_ismaster={'maxWireVersion': 3}) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) def send_three_docs(): for i in range(3): client.test.test.insert({'_id': i}) with going(send_three_docs): j = 0 # The "for request in server" statement is the point of this test. for request in server: self.assertTrue( request.matches({ 'insert': 'test', 'documents': [{ '_id': j }] })) request.ok() j += 1 if j == 3: break
def test_aggregate(self): server = MockupDB(auto_ismaster={'maxWireVersion': 3}) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) with going(client.test.collection.aggregate, []) as cursor: request = server.receives(Command) request.reply({'cursor': { 'id': 123, 'firstBatch': [{'a': 1}]}}) with going(list, cursor()) as docs: request = server.receives(OpGetMore, cursor_id=123) request.reply({'a': 2}, starting_from=-3, cursor_id=0) self.assertEqual([{'a': 1}, {'a': 2}], docs())
def test_replace_one(self): with going(self.collection.replace_one, {}, {}) as future: self.server.receives(OpUpdate({}, {}, flags=0)) request = self.server.receives(Command('getlasterror')) request.replies_to_gle(upserted=1) self.assertEqual(1, future().upserted_id)
def test(self): pref = make_read_preference(read_pref_mode_from_name(mode), tag_sets=None) client = self.setup_client(read_preference=pref) if operation.op_type == 'always-use-secondary': expected_server = self.secondary expected_pref = ReadPreference.SECONDARY elif operation.op_type == 'must-use-primary': expected_server = self.primary expected_pref = ReadPreference.PRIMARY elif operation.op_type == 'may-use-secondary': if mode in ('primary', 'primaryPreferred'): expected_server = self.primary else: expected_server = self.secondary expected_pref = pref else: self.fail('unrecognized op_type %r' % operation.op_type) # For single mongod we send primaryPreferred instead of primary. if expected_pref == ReadPreference.PRIMARY and self.single_mongod: expected_pref = ReadPreference.PRIMARY_PREFERRED with going(operation.function, client) as future: request = expected_server.receive() request.reply(operation.reply) future() # No error. self.assertEqual(expected_pref.document, request.doc.get('$readPreference')) self.assertNotIn('$query', request.doc)
def _test_killCursors_namespace(self, cursor_op, command): with going(cursor_op): request = self.server.receives(**{ command: 'collection', 'namespace': 'test' }) # Respond with a different namespace. request.reply({ 'cursor': { 'firstBatch': [{ 'doc': 1 }], 'id': 123, 'ns': 'different_db.different.coll' } }) # Client uses the namespace we returned for killCursors. request = self.server.receives( **{ 'killCursors': 'different.coll', 'cursors': [123], '$db': 'different_db' }) request.reply({ 'ok': 1, 'cursorsKilled': [123], 'cursorsNotFound': [], 'cursorsAlive': [], 'cursorsUnknown': [] })
def test(self): ismaster_with_version = ismaster.copy() ismaster_with_version['maxWireVersion'] = operation.wire_version self.server.autoresponds('ismaster', **ismaster_with_version) if operation.op_type == 'always-use-secondary': slave_ok = True elif operation.op_type == 'may-use-secondary': slave_ok = mode != 'primary' or server_type != 'mongos' elif operation.op_type == 'must-use-primary': slave_ok = server_type != 'mongos' else: assert False, 'unrecognized op_type %r' % operation.op_type pref = make_read_preference(read_pref_mode_from_name(mode), tag_sets=None) client = MongoClient(self.server.uri, read_preference=pref) self.addCleanup(client.close) with going(operation.function, client): request = self.server.receive() request.reply(operation.reply) self.assertEqual(topology_type_name(client), 'Single') if slave_ok: self.assertTrue(request.slave_ok, 'SlaveOkay not set') else: self.assertFalse(request.slave_ok, 'SlaveOkay set')
def test_query_and_read_mode_sharded_op_msg(self): """Test OP_MSG sends non-primary $readPreference and never $query.""" server = MockupDB() server.autoresponds('ismaster', ismaster=True, msg='isdbgrid', minWireVersion=2, maxWireVersion=6) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) self.addCleanup(client.close) read_prefs = ( Primary(), SecondaryPreferred(), PrimaryPreferred(), Secondary(), Nearest(), SecondaryPreferred([{'tag': 'value'}]),) for query in ({'a': 1}, {'$query': {'a': 1}},): for mode in read_prefs: collection = client.db.get_collection('test', read_preference=mode) cursor = collection.find(query.copy()) with going(next, cursor): request = server.receives() # Command is not nested in $query. request.assert_matches(OpMsg( SON([('find', 'test'), ('filter', {'a': 1}), ('$readPreference', mode.document)]))) request.replies({'cursor': {'id': 0, 'firstBatch': [{}]}})
def test(self): server = MockupDB() self.addCleanup(server.stop) server.run() server.autoresponds('ismaster', ismaster=True, msg='isdbgrid', minWireVersion=2, maxWireVersion=6) pref = make_read_preference(read_pref_mode_from_name(mode), tag_sets=None) client = MongoClient(server.uri, read_preference=pref) self.addCleanup(client.close) with going(operation.function, client): request = server.receive() request.reply(operation.reply) if operation.op_type == 'always-use-secondary': self.assertEqual(ReadPreference.SECONDARY.document, request.doc.get('$readPreference')) slave_ok = mode != 'primary' elif operation.op_type == 'must-use-primary': slave_ok = False elif operation.op_type == 'may-use-secondary': slave_ok = mode != 'primary' self.assertEqual(pref.document, request.doc.get('$readPreference')) else: self.fail('unrecognized op_type %r' % operation.op_type) if slave_ok: self.assertTrue(request.slave_ok, 'SlaveOkay not set') else: self.assertFalse(request.slave_ok, 'SlaveOkay set')
def test_rsghost(self): rsother_response = { 'ok': 1.0, 'ismaster': False, 'secondary': False, 'info': 'Does not have a valid replica set config', 'isreplicaset': True, 'maxBsonObjectSize': 16777216, 'maxMessageSizeBytes': 48000000, 'maxWriteBatchSize': 100000, 'localTime': datetime.datetime(2021, 11, 30, 0, 53, 4, 99000), 'logicalSessionTimeoutMinutes': 30, 'connectionId': 3, 'minWireVersion': 0, 'maxWireVersion': 15, 'readOnly': False } server = MockupDB(auto_ismaster=rsother_response) server.run() self.addCleanup(server.stop) # Default auto discovery yields a server selection timeout. with MongoClient(server.uri, serverSelectionTimeoutMS=250) as client: with self.assertRaises(ServerSelectionTimeoutError): client.test.command('ping') # Direct connection succeeds. with MongoClient(server.uri, directConnection=True) as client: with going(client.test.command, 'ping'): request = server.receives(ping=1) request.reply()
def test_update_many(self): with going(self.collection.update_many, {}, {'$unset': 'a'}): self.server.receives( OpMsg(SON([('update', 'collection'), ('ordered', True), ('writeConcern', { 'w': 0 })]), flags=2))
def test_insert_many(self): collection = self.collection.with_options( write_concern=WriteConcern(0)) flags = INSERT_FLAGS['ContinueOnError'] docs = [{'_id': 1}, {'_id': 2}] with going(collection.insert_many, docs, ordered=False): self.server.receives(OpInsert(docs, flags=flags))
def test_delete_many(self): with going(self.collection.delete_many, {}) as future: delete = self.server.receives(OpDelete({}, flags=0)) self.assertEqual(0, delete.flags) gle = self.server.receives(Command('getlasterror')) gle.replies_to_gle(n=2) self.assertEqual(2, future().deleted_count)
def test_delete_one(self): flags = DELETE_FLAGS['SingleRemove'] with going(self.collection.delete_one, {}) as future: delete = self.server.receives(OpDelete({}, flags=flags)) self.assertEqual(1, delete.flags) gle = self.server.receives(Command('getlasterror')) gle.replies_to_gle(n=1) self.assertEqual(1, future().deleted_count)
def test_indexes_query(self): server = MockupDB(auto_ismaster=True) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) self.addCleanup(client.close) with going(client.test.collection.list_indexes) as cursor: request = server.receives(OpQuery, namespace='test.system.indexes') request.reply([{'name': 'index_0'}], cursor_id=123) with going(list, cursor()) as indexes: request = server.receives(OpGetMore, namespace='test.system.indexes', cursor_id=123) request.reply([{'name': 'index_1'}], starting_from=1, cursor_id=0) self.check_indexes(indexes())
def _test_write_operation(self, op): coll = self.client.db.coll with going(op.function, coll) as future: request = self.server.receives() request.assert_matches(op.request) if op.reply is not None: request.reply(op.reply) future() # No error.
def test_update_many(self): flags = UPDATE_FLAGS['MultiUpdate'] with going(self.collection.update_many, {}, {'$unset': 'a'}) as future: update = self.server.receives(OpUpdate({}, {}, flags=flags)) self.assertEqual(2, update.flags) gle = self.server.receives(Command('getlasterror')) gle.replies_to_gle(upserted=1) self.assertEqual(1, future().upserted_id)
def test_insert_many(self): collection = self.collection.with_options( write_concern=WriteConcern(0)) flags = INSERT_FLAGS['ContinueOnError'] docs = [{'_id': 1}, {'_id': 2}] with going(collection.insert_many, docs, ordered=False) as future: self.server.receives(OpInsert(docs, flags=flags)) self.assertEqual([1, 2], future().inserted_ids)
def test_mongos(self): mongos = MockupDB() mongos.autoresponds('ismaster', maxWireVersion=5, ismaster=True, msg='isdbgrid') mongos.run() self.addCleanup(mongos.stop) # No maxStalenessSeconds. uri = 'mongodb://localhost:%d/?readPreference=secondary' % mongos.port client = MongoClient(uri) self.addCleanup(client.close) with going(client.db.coll.find_one) as future: request = mongos.receives() self.assertNotIn('maxStalenessSeconds', request.doc['$readPreference']) self.assertTrue(request.slave_okay) request.ok(cursor={'firstBatch': [], 'id': 0}) # find_one succeeds with no result. self.assertIsNone(future()) # Set maxStalenessSeconds to 1. Client has no minimum with mongos, # we let mongos enforce the 90-second minimum and return an error: # SERVER-27146. uri = 'mongodb://localhost:%d/?readPreference=secondary' \ '&maxStalenessSeconds=1' % mongos.port client = MongoClient(uri) self.addCleanup(client.close) with going(client.db.coll.find_one) as future: request = mongos.receives() self.assertEqual( 1, request.doc['$readPreference']['maxStalenessSeconds']) self.assertTrue(request.slave_okay) request.ok(cursor={'firstBatch': [], 'id': 0}) self.assertIsNone(future())
def test(self): self.setup_server() assert not operation.op_type == 'always-use-secondary' client = MongoClient(self.primary.uri, replicaSet='rs') self.addCleanup(client.close) with going(operation.function, client): request = self.primary.receive() request.reply(operation.reply) self.assertFalse(request.slave_ok, 'SlaveOkay set read mode "primary"')
def test_ok(self): server = MockupDB(auto_ismaster={'maxWireVersion': 3}) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) with going(client.test.command, {'foo': 1}) as future: server.receives().ok(3) response = future() self.assertEqual(3, response['ok'])
def test_datetime(self): server = MockupDB(auto_ismaster=True) server.run() client = MongoClient(server.uri) # Python datetimes have microsecond precision, BSON only millisecond. # Ensure this datetime matches itself despite the truncation. dt = datetime.datetime(2018, 12, 1, 6, 6, 6, 12345) doc = SON([('_id', 1), ('dt', dt)]) with going(client.db.collection.insert_one, doc): server.receives(OpMsg('insert', 'collection', documents=[doc])).ok()
def test_insert_many(self): collection = self.collection.with_options( write_concern=WriteConcern(0)) docs = [{'_id': 1}, {'_id': 2}] with going(collection.insert_many, docs, ordered=False): self.server.receives( OpMsg(SON([('insert', 'collection'), ('ordered', False), ('writeConcern', { 'w': 0 })]), flags=2))
def test_query(self): server = MockupDB(auto_ismaster=True) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) cursor = client.test.collection.find() with going(list, cursor) as docs: request = server.receives(OpQuery) request.reply({'a': 1}, cursor_id=123, starting_from=-7) request = server.receives(OpGetMore, cursor_id=123) request.reply({'a': 2}, starting_from=-3, cursor_id=0) self.assertEqual([{'a': 1}, {'a': 2}], docs())
def test_insert_command_manipulate_false(self): # Test same three aspects as test_op_insert_manipulate_false does, # with the "insert" command. server = MockupDB(auto_ismaster={'maxWireVersion': 2}) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) self.addCleanup(client.close) doc = {} with going(client.db.coll.insert, doc, manipulate=False) as future: r = server.receives(Command("insert", "coll", documents=[{}])) # MockupDB doesn't understand "absent" in subdocuments yet. self.assertFalse('_id' in r.doc['documents'][0]) r.ok() self.assertFalse('_id' in doc) self.assertIsNone(future()) docs = [{}] # One doc in a list. with going(client.db.coll.insert, docs, manipulate=False) as future: r = server.receives(Command("insert", "coll", documents=[{}])) self.assertFalse('_id' in r.doc['documents'][0]) r.ok() self.assertFalse('_id' in docs[0]) self.assertEqual(future(), [None]) docs = [{}, {}] # Two docs. with going(client.db.coll.insert, docs, manipulate=False) as future: r = server.receives(Command("insert", "coll", documents=[{}, {}])) self.assertFalse('_id' in r.doc['documents'][0]) self.assertFalse('_id' in r.doc['documents'][1]) r.ok() self.assertFalse('_id' in docs[0]) self.assertFalse('_id' in docs[1]) self.assertEqual(future(), [None, None])
def test_nested_errors(self): def thrower(): raise AssertionError("thrown") with capture_stderr() as stderr: with self.assertRaises(ZeroDivisionError): with going(thrower) as future: 1 / 0 self.assertIn('error in going(', stderr.getvalue()) self.assertIn('AssertionError: thrown', stderr.getvalue()) # Future keeps raising. self.assertRaises(AssertionError, future) self.assertRaises(AssertionError, future)
def test_iteration(self): server = MockupDB(auto_ismaster={'maxWireVersion': 3}) server.run() self.addCleanup(server.stop) client = MongoClient(server.uri) def send_three_docs(): for i in range(3): client.test.test.insert({'_id': i}) with going(send_three_docs): j = 0 # The "for request in server" statement is the point of this test. for request in server: self.assertTrue(request.matches({'insert': 'test', 'documents': [{'_id': j}]})) request.ok() j += 1 if j == 3: break
def test_insert_one(self): with going(self.collection.insert_one, {'_id': 1}) as future: self.server.receives(OpInsert({'_id': 1}, flags=0)) self.server.receives(Command('getlasterror')).replies_to_gle() self.assertEqual(1, future().inserted_id)