From 6e4608b9cd3f19a039f7cd7eb44663d97be7f92e Mon Sep 17 00:00:00 2001 From: aherlihy Date: Wed, 3 Jun 2015 16:02:28 -0400 Subject: [PATCH] PYTHON-890 - Support localThresholdMS URI option. --- doc/api/pymongo/mongo_client.rst | 1 + doc/api/pymongo/mongo_replica_set_client.rst | 1 + pymongo/common.py | 1 + pymongo/mongo_client.py | 21 +++++++ pymongo/mongo_replica_set_client.py | 20 ++++++- test/test_client.py | 57 +++++++++++++++++++ test/test_mongos_ha.py | 38 +++++++++++++ test/test_read_preferences.py | 21 +++++++ test/test_replica_set_client.py | 58 ++++++++++++++++++++ test/test_uri_parser.py | 2 + 10 files changed, 218 insertions(+), 2 deletions(-) diff --git a/doc/api/pymongo/mongo_client.rst b/doc/api/pymongo/mongo_client.rst index 11e287ace..13c9fa824 100644 --- a/doc/api/pymongo/mongo_client.rst +++ b/doc/api/pymongo/mongo_client.rst @@ -33,6 +33,7 @@ .. autoattribute:: read_preference .. autoattribute:: tag_sets .. autoattribute:: secondary_acceptable_latency_ms + .. autoattribute:: local_threshold_ms .. autoattribute:: write_concern .. autoattribute:: uuid_subtype .. autoattribute:: is_locked diff --git a/doc/api/pymongo/mongo_replica_set_client.rst b/doc/api/pymongo/mongo_replica_set_client.rst index 3a53a9dd7..9e6a35f5c 100644 --- a/doc/api/pymongo/mongo_replica_set_client.rst +++ b/doc/api/pymongo/mongo_replica_set_client.rst @@ -34,6 +34,7 @@ .. autoattribute:: read_preference .. autoattribute:: tag_sets .. autoattribute:: secondary_acceptable_latency_ms + .. autoattribute:: local_threshold_ms .. autoattribute:: write_concern .. autoattribute:: uuid_subtype .. automethod:: database_names diff --git a/pymongo/common.py b/pymongo/common.py index 2342b71bc..0f35c86c1 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -328,6 +328,7 @@ VALIDATORS = { 'tag_sets': validate_tag_sets, 'secondaryacceptablelatencyms': validate_positive_float_or_zero, 'secondary_acceptable_latency_ms': validate_positive_float_or_zero, + 'localthresholdms': validate_positive_float_or_zero, 'auto_start_request': validate_boolean, 'use_greenlets': validate_boolean, 'authmechanism': validate_auth_mechanism, diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index 388989eb1..f1eec20ef 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -206,6 +206,14 @@ class MongoClient(common.BaseObject): set, ``{}``, means "read from any member that matches the mode, ignoring tags. Defaults to ``[{}]``, meaning "ignore members' tags." + - `secondaryAcceptableLatencyMS`: (integer) Any replica-set member + whose ping time is within secondary_acceptable_latency_ms of the + nearest member may accept reads. Default 15 milliseconds. + **Ignored by mongos** and must be configured on the command line. + See the localThreshold_ option for more information. + - `localThresholdMS`: (integer) Alias for + secondaryAcceptableLatencyMS. Takes precedence over + secondaryAcceptableLatencyMS. | **SSL configuration:** @@ -294,6 +302,11 @@ class MongoClient(common.BaseObject): options[option] = value options.update(opts) + # localthresholdms takes precedence over secondaryacceptablelatencyms. + if "localthresholdms" in options: + options["secondaryacceptablelatencyms"] = ( + options["localthresholdms"]) + common.validate_boolean('tz_aware', tz_aware) uuid_representation = options.pop('uuidrepresentation', PYTHON_LEGACY) options['codec_options'] = CodecOptions( @@ -688,6 +701,14 @@ class MongoClient(common.BaseObject): return self.__member_property( 'max_write_batch_size', common.MAX_WRITE_BATCH_SIZE) + @property + def local_threshold_ms(self): + """Alias for secondary_acceptable_latency_ms. + + .. versionadded:: 2.9 + """ + return self.secondary_acceptable_latency_ms + def __simple_command(self, sock_info, dbname, spec): """Send a command to the server. May raise AutoReconnect. """ diff --git a/pymongo/mongo_replica_set_client.py b/pymongo/mongo_replica_set_client.py index a9eee746c..dc8b1c9a2 100644 --- a/pymongo/mongo_replica_set_client.py +++ b/pymongo/mongo_replica_set_client.py @@ -550,11 +550,14 @@ class MongoReplicaSetClient(common.BaseObject): ignoring tags." :class:`MongoReplicaSetClient` tries each set of tags in turn until it finds a set of tags with at least one matching member. Defaults to ``[{}]``, meaning "ignore members' tags." - - `secondary_acceptable_latency_ms`: (integer) Any replica-set member - whose ping time is within secondary_acceptable_latency_ms of the + - `secondaryAcceptableLatencyMS`: (integer) Any replica-set member + whose ping time is within secondaryAcceptableLatencyMS of the nearest member may accept reads. Default 15 milliseconds. **Ignored by mongos** and must be configured on the command line. See the localThreshold_ option for more information. + - `localThresholdMS`: (integer) Alias for + secondaryAcceptableLatencyMS. Takes precedence over + secondaryAcceptableLatencyMS. | **SSL configuration:** @@ -689,6 +692,11 @@ class MongoReplicaSetClient(common.BaseObject): "2.6 you must install the ssl package " "from PyPI.") + # localThresholdMS takes precedence over secondaryAcceptableLatencyMS + if "localthresholdms" in self.__opts: + self.__opts["secondaryacceptablelatencyms"] = ( + self.__opts["localthresholdms"]) + super(MongoReplicaSetClient, self).__init__(**self.__opts) if self.slave_okay: warnings.warn("slave_okay is deprecated. Please " @@ -1017,6 +1025,14 @@ class MongoReplicaSetClient(common.BaseObject): """**DEPRECATED** Is auto_start_request enabled?""" return self.__auto_start_request + @property + def local_threshold_ms(self): + """Alias for secondary_acceptable_latency_ms. + + .. versionadded:: 2.9 + """ + return self.secondary_acceptable_latency_ms + def __simple_command(self, sock_info, dbname, spec): """Send a command to the server. Returns (response, ping_time in seconds). diff --git a/test/test_client.py b/test/test_client.py index 9997422bb..6a2548f11 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -331,6 +331,63 @@ class TestClient(unittest.TestCase, TestRequestMixin): finally: ctx.exit() + def test_backport_localthresholdms_uri(self): + uri = "mongodb://%s:%s" % (host, port) + lt_uri = "mongodb://%s:%d/?localThresholdMS=10" % (host, port) + sl_uri = ("mongodb://%s:%d/?secondaryAcceptableLatencyMS=10" % + (host, port)) + lt_sl_uri = ("mongodb://%s:%d/?localThresholdMS=10;" + "secondaryAcceptableLatencyMS=8" % (host, port)) + + # Just localThresholdMS + client = MongoClient(uri) + self.assertEqual(client.secondary_acceptable_latency_ms, 15) + self.assertEqual(client.local_threshold_ms, 15) + client = MongoClient(uri, localThresholdMS=10) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(uri, localThresholdMS=10, + secondaryAcceptableLatencyMS=8) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + + # URI options take precedence over kwargs but localThresholdMS takes + # precedence over secondaryAcceptableLatencyMS always. Test to make + # sure the precedence is correct between URI vs. kwargs. + client = MongoClient(lt_uri) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(lt_uri, localThresholdMS=8) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(lt_uri, secondaryAcceptableLatencyMS=8) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(lt_uri, localThresholdMS=8, + secondaryAcceptableLatencyMS=6) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + + client = MongoClient(sl_uri, secondaryAcceptableLatencyMS=8) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(sl_uri, localThresholdMS=10) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(sl_uri, localThresholdMS=10, + secondaryAcceptableLatencyMS=6) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + + client = MongoClient(lt_sl_uri) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(lt_sl_uri, localThresholdMS=8, + secondaryAcceptableLatencyMS=4) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + + def test_get_default_database(self): c = MongoClient("mongodb://%s:%d/foo" % (host, port), _connect=False) self.assertEqual(Database(c, 'foo'), c.get_default_database()) diff --git a/test/test_mongos_ha.py b/test/test_mongos_ha.py index 9faa40edc..88e775c72 100644 --- a/test/test_mongos_ha.py +++ b/test/test_mongos_ha.py @@ -154,6 +154,44 @@ class TestMongosHA(unittest.TestCase): # No error client.db.collection.find_one() + def test_backport_localthresholdms_kwarg(self): + # Test that localThresholdMS takes precedence over + # secondaryAcceptableLatencyMS. + client = MockClient( + standalones=[], + members=[], + mongoses=['a:1', 'b:2', 'c:3'], + host='a:1,b:2,c:3', + localThresholdMS=7, + secondaryAcceptableLatencyMS=0) + + self.assertEqual(7, client.secondary_acceptable_latency_ms) + self.assertEqual(7, client.local_threshold_ms) + # No error + client.db.collection.find_one() + + client = MockClient( + standalones=[], + members=[], + mongoses=['a:1', 'b:2', 'c:3'], + host='a:1,b:2,c:3', + localThresholdMS=0, + secondaryAcceptableLatencyMS=15) + + self.assertEqual(0, client.secondary_acceptable_latency_ms) + self.assertEqual(0, client.local_threshold_ms) + + # Test that using localThresholdMS works in the same way as using + # secondaryAcceptableLatencyMS. + client.db.collection.find_one() + # Our chosen mongos goes down. + client.kill_host('%s:%s' % (client.host, client.port)) + try: + client.db.collection.find_one() + except: + pass + # No error + client.db.collection.find_one() if __name__ == "__main__": unittest.main() diff --git a/test/test_read_preferences.py b/test/test_read_preferences.py index 7334488a9..f9d673239 100644 --- a/test/test_read_preferences.py +++ b/test/test_read_preferences.py @@ -166,6 +166,27 @@ class TestReadPreferences(TestReadPreferencesBase): self._get_client, secondaryacceptablelatencyms=-1) + def test_backport_latency_validation(self): + self.assertEqual(17, self._get_client( + localThresholdMS=17 + ).secondary_acceptable_latency_ms) + + self.assertEqual(42, self._get_client( + secondaryAcceptableLatencyMS=42 + ).local_threshold_ms) + + self.assertEqual(666, self._get_client( + localThresholdMS=666 + ).local_threshold_ms) + + self.assertEqual(0, self._get_client( + localthresholdms=0 + ).local_threshold_ms) + + self.assertRaises(ConfigurationError, + self._get_client, + localthresholdms=-1) + def test_primary(self): self.assertReadsFrom('primary', read_preference=ReadPreference.PRIMARY) diff --git a/test/test_replica_set_client.py b/test/test_replica_set_client.py index aaf947a3c..02e1017fb 100644 --- a/test/test_replica_set_client.py +++ b/test/test_replica_set_client.py @@ -743,6 +743,64 @@ class TestReplicaSetClient(TestReplicaSetClientBase, TestRequestMixin): self.assertTrue("pymongo_test_bernie" in dbs) client.close() + def test_backport_repl_localthresholdms_uri(self): + uri = ("mongodb://%s/?replicaSet=%s" % (pair, self.name)) + lt_uri = ("mongodb://%s/?replicaSet=%s;localThresholdMS=10" + % (pair, self.name)) + sl_uri = ("mongodb://%s/?replicaSet=%s;secondaryAcceptableLatencyMS=10" + % (pair, self.name)) + sl_lt_uri = ("mongodb://%s/?replicaSet=%s;localThresholdMS=10;" + "secondaryAcceptableLatencyMS=8" % (pair, self.name)) + + # Just localThresholdMS + client = MongoReplicaSetClient(uri) + self.assertEqual(client.secondary_acceptable_latency_ms, 15) + self.assertEqual(client.local_threshold_ms, 15) + client = MongoReplicaSetClient(uri, localThresholdMS=10) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(uri, localThresholdMS=10, + secondaryAcceptableLatencyMS=8) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + + # URI options take precedence over kwargs but localThresholdMS takes + # precedence over secondaryAcceptableLatencyMS always. Test to make + # sure the precedence is correct between URI vs. kwargs. + client = MongoReplicaSetClient(lt_uri) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoReplicaSetClient(lt_uri, localThresholdMS=8) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(lt_uri, secondaryAcceptableLatencyMS=8) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(lt_uri, secondaryAcceptableLatencyMS=8, + localThresholdMS=6) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + + client = MongoClient(sl_uri, secondaryAcceptableLatencyMS=8) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(sl_uri, localThresholdMS=10) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(sl_uri, localThresholdMS=10, + secondaryAcceptableLatencyMS=6) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + + client = MongoClient(sl_lt_uri) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + client = MongoClient(sl_lt_uri, localThresholdMS=8, + secondaryAcceptableLatencyMS=4) + self.assertEqual(client.secondary_acceptable_latency_ms, 10) + self.assertEqual(client.local_threshold_ms, 10) + + def _test_kill_cursor_explicit(self, read_pref): c = self._get_client(read_preference=read_pref) db = c.pymongo_test diff --git a/test/test_uri_parser.py b/test/test_uri_parser.py index 381124446..499688e22 100644 --- a/test/test_uri_parser.py +++ b/test/test_uri_parser.py @@ -138,6 +138,8 @@ class TestURI(unittest.TestCase): self.assertEqual({'authsource': 'foobar'}, split_options('authSource=foobar')) # maxPoolSize isn't yet a documented URI option. self.assertRaises(ConfigurationError, split_options, 'maxpoolsize=50') + self.assertEqual({'localthresholdms': 300}, + split_options('localThresholdMS=300')) def test_parse_uri(self): self.assertRaises(InvalidURI, parse_uri, "http://foobar.com")