From 9bf46d2bb159da04617335bc89fa41abf908e0b6 Mon Sep 17 00:00:00 2001 From: Bernie Hackett Date: Wed, 6 May 2015 09:14:55 -0700 Subject: [PATCH] PYTHON-920, PYTHON-921 - Fix metadata helpers with direct connection. With this change metadata helpers (database_names, collection_names, options, and index_information) no longer require a non-primary read preference or the slave_okay option to run them against a directly connected secondary or slave. This was the easiest way to make the driver behave consistently across MongoDB versions and helpers. This is also consistent with the server selection spec, though we are not implementing that spec for PyMongo 2.x. --- pymongo/collection.py | 13 ++++++++++--- pymongo/database.py | 8 ++++++-- pymongo/mongo_client.py | 9 +++++++-- test/test_read_preferences.py | 18 ++++++++++++++++++ 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/pymongo/collection.py b/pymongo/collection.py index cacf5f3dd..1c5ae0ce7 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -1270,10 +1270,12 @@ class Collection(common.BaseObject): client = self.database.connection client._ensure_connected(True) + slave_okay = not client._rs_client and not client.is_mongos if client.max_wire_version > 2: res, addr = self.__database._command( "listIndexes", self.__name, as_class=SON, - cursor={}, read_preference=ReadPreference.PRIMARY) + cursor={}, slave_okay=slave_okay, + read_preference=ReadPreference.PRIMARY) # MongoDB 2.8rc2 if "indexes" in res: raw = res["indexes"] @@ -1283,6 +1285,7 @@ class Collection(common.BaseObject): else: raw = self.__database.system.indexes.find({"ns": self.__full_name}, {"ns": 0}, as_class=SON, + slave_okay=slave_okay, _must_use_master=True) info = {} for index in raw: @@ -1303,12 +1306,14 @@ class Collection(common.BaseObject): client._ensure_connected(True) result = None + slave_okay = not client._rs_client and not client.is_mongos if client.max_wire_version > 2: res, addr = self.__database._command( "listCollections", cursor={}, filter={"name": self.__name}, - read_preference=ReadPreference.PRIMARY) + read_preference=ReadPreference.PRIMARY, + slave_okay=slave_okay) # MongoDB 2.8rc2 if "collections" in res: results = res["collections"] @@ -1320,7 +1325,9 @@ class Collection(common.BaseObject): break else: result = self.__database.system.namespaces.find_one( - {"name": self.__full_name}, _must_use_master=True) + {"name": self.__full_name}, + slave_okay=slave_okay, + _must_use_master=True) if not result: return {} diff --git a/pymongo/database.py b/pymongo/database.py index f8ab8cb17..b42579425 100644 --- a/pymongo/database.py +++ b/pymongo/database.py @@ -448,10 +448,12 @@ class Database(common.BaseObject): client = self.connection client._ensure_connected(True) + slave_okay = not client._rs_client and not client.is_mongos if client.max_wire_version > 2: res, addr = self._command("listCollections", cursor={}, - read_preference=ReadPreference.PRIMARY) + read_preference=ReadPreference.PRIMARY, + slave_okay=slave_okay) # MongoDB 2.8rc2 if "collections" in res: results = res["collections"] @@ -461,7 +463,9 @@ class Database(common.BaseObject): names = [result["name"] for result in results] else: names = [result["name"] for result - in self["system.namespaces"].find(_must_use_master=True)] + in self["system.namespaces"].find( + slave_okay=slave_okay, + _must_use_master=True)] names = [n[len(self.__name) + 1:] for n in names if n.startswith(self.__name + ".") and "$" not in n] diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index 3a41c5de3..b762a31aa 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -1373,9 +1373,14 @@ class MongoClient(common.BaseObject): def database_names(self): """Get a list of the names of all databases on the connected server. """ + # SERVER-15994 changed listDatabases to require slaveOk when run + # against a secondary / slave. Passing slave_okay=True makes things + # consistent across server versions. return [db["name"] for db in - self.admin.command("listDatabases", - read_preference=ReadPreference.PRIMARY)["databases"]] + self.admin.command( + "listDatabases", + read_preference=ReadPreference.PRIMARY, + slave_okay=not self.is_mongos)["databases"]] def drop_database(self, name_or_database): """Drop a database. diff --git a/test/test_read_preferences.py b/test/test_read_preferences.py index bd0f30f3e..2bc9c811a 100644 --- a/test/test_read_preferences.py +++ b/test/test_read_preferences.py @@ -82,6 +82,24 @@ class TestReadPreferencesBase(TestReplicaSetClientBase): expected, used)) +class TestSlaveOkayMetadataCommands(TestReadPreferencesBase): + + def test_slave_okay_metadata_commands(self): + + secondaries = iter(self._get_client().secondaries) + host, port = secondaries.next() + # Direct connection to a secondary. + client = MongoClient(host, port) + self.assertFalse(client.is_primary) + self.assertEqual(client.read_preference, ReadPreference.PRIMARY) + + # No error. + client.database_names() + client.pymongo_test.collection_names() + client.pymongo_test.test.options() + client.pymongo_test.test.index_information() + + class TestReadPreferences(TestReadPreferencesBase): def test_mode_validation(self): # 'modes' are imported from read_preferences.py