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