Skip to content

Commit fdbe38f

Browse files
committed
PYTHON-2671 Support loadBalanced URI option (#614)
Add workaround in test_dns until PYTHON-2679 is completed. (cherry picked from commit 2c41c6f)
1 parent 1224db3 commit fdbe38f

40 files changed

+137
-22
lines changed

pymongo/client_options.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ def __init__(self, username, password, database, options):
173173
self.__server_selector = options.get(
174174
'server_selector', any_server_selector)
175175
self.__auto_encryption_opts = options.get('auto_encryption_opts')
176+
self.__load_balanced = options.get('loadbalanced')
176177

177178
@property
178179
def _options(self):
@@ -257,3 +258,8 @@ def retry_reads(self):
257258
def auto_encryption_opts(self):
258259
"""A :class:`~pymongo.encryption.AutoEncryptionOpts` or None."""
259260
return self.__auto_encryption_opts
261+
262+
@property
263+
def load_balanced(self):
264+
"""True if the client was configured to connect to a load balancer."""
265+
return self.__load_balanced

pymongo/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,7 @@ def validate_tzinfo(dummy, value):
627627
'replicaset': validate_string_or_none,
628628
'retryreads': validate_boolean_or_string,
629629
'retrywrites': validate_boolean_or_string,
630+
'loadbalanced': validate_boolean_or_string,
630631
'serverselectiontimeoutms': validate_timeout_or_zero,
631632
'sockettimeoutms': validate_timeout_or_none_or_zero,
632633
'ssl_keyfile': validate_readable,

pymongo/mongo_client.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@
7373
from pymongo.settings import TopologySettings
7474
from pymongo.uri_parser import (_handle_option_deprecations,
7575
_handle_security_options,
76-
_normalize_options)
76+
_normalize_options,
77+
_check_options)
7778
from pymongo.write_concern import DEFAULT_WRITE_CONCERN
7879

7980

@@ -700,11 +701,7 @@ def __init__(
700701
opts = _handle_security_options(opts)
701702
# Normalize combined options.
702703
opts = _normalize_options(opts)
703-
704-
# Ensure directConnection was not True if there are multiple seeds.
705-
if len(seeds) > 1 and opts.get('directconnection'):
706-
raise ConfigurationError(
707-
"Cannot specify multiple hosts with directConnection=true")
704+
_check_options(seeds, opts)
708705

709706
# Username and password passed as kwargs override user info in URI.
710707
username = opts.get("username", username)
@@ -752,7 +749,9 @@ def __init__(
752749
server_selector=options.server_selector,
753750
heartbeat_frequency=options.heartbeat_frequency,
754751
fqdn=fqdn,
755-
direct_connection=options.direct_connection)
752+
direct_connection=options.direct_connection,
753+
load_balanced=options.load_balanced,
754+
)
756755

757756
self._topology = Topology(self._topology_settings)
758757

pymongo/settings.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ def __init__(self,
3939
heartbeat_frequency=common.HEARTBEAT_FREQUENCY,
4040
server_selector=None,
4141
fqdn=None,
42-
direct_connection=None):
42+
direct_connection=None,
43+
load_balanced=None):
4344
"""Represent MongoClient's configuration.
4445
4546
Take a list of (host, port) pairs and optional replica set name.
@@ -65,6 +66,7 @@ def __init__(self,
6566
self._direct = (len(self._seeds) == 1 and not self.replica_set_name)
6667
else:
6768
self._direct = direct_connection
69+
self._load_balanced = load_balanced
6870

6971
self._topology_id = ObjectId()
7072
# Store the allocation traceback to catch unclosed clients in the
@@ -124,6 +126,11 @@ def direct(self):
124126
"""
125127
return self._direct
126128

129+
@property
130+
def load_balanced(self):
131+
"""True if the client was configured to connect to a load balancer."""
132+
return self._load_balanced
133+
127134
def get_topology_type(self):
128135
if self.direct:
129136
return TOPOLOGY_TYPE.Single

pymongo/uri_parser.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,26 @@ def split_hosts(hosts, default_port=DEFAULT_PORT):
371371
_BAD_DB_CHARS = re.compile('[' + re.escape(r'/ "$') + ']')
372372

373373
_ALLOWED_TXT_OPTS = frozenset(
374-
['authsource', 'authSource', 'replicaset', 'replicaSet'])
374+
['authsource', 'authSource', 'replicaset', 'replicaSet', 'loadbalanced',
375+
'loadBalanced'])
376+
377+
378+
def _check_options(nodes, options):
379+
# Ensure directConnection was not True if there are multiple seeds.
380+
if len(nodes) > 1 and options.get('directconnection'):
381+
raise ConfigurationError(
382+
'Cannot specify multiple hosts with directConnection=true')
383+
384+
if options.get('loadbalanced'):
385+
if len(nodes) > 1:
386+
raise ConfigurationError(
387+
'Cannot specify multiple hosts with loadBalanced=true')
388+
if options.get('directconnection'):
389+
raise ConfigurationError(
390+
'Cannot specify directConnection=true with loadBalanced=true')
391+
if options.get('replicaset'):
392+
raise ConfigurationError(
393+
'Cannot specify replicaSet with loadBalanced=true')
375394

376395

377396
def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False,
@@ -509,17 +528,17 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False,
509528
dns_options, validate, warn, normalize)
510529
if set(parsed_dns_options) - _ALLOWED_TXT_OPTS:
511530
raise ConfigurationError(
512-
"Only authSource and replicaSet are supported from DNS")
531+
"Only authSource, replicaSet, and loadBalanced are "
532+
"supported from DNS")
513533
for opt, val in parsed_dns_options.items():
514534
if opt not in options:
515535
options[opt] = val
516536
if "ssl" not in options:
517537
options["ssl"] = True if validate else 'true'
518538
else:
519539
nodes = split_hosts(hosts, default_port=default_port)
520-
if len(nodes) > 1 and options.get('directConnection'):
521-
raise ConfigurationError(
522-
"Cannot specify multiple hosts with directConnection=true")
540+
541+
_check_options(nodes, options)
523542

524543
return {
525544
'nodelist': nodes,

test/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ def __init__(self):
216216
self.client = None
217217
self.conn_lock = threading.Lock()
218218
self.is_data_lake = False
219+
self.load_balancer = False
219220
if COMPRESSORS:
220221
self.default_client_options["compressors"] = COMPRESSORS
221222
if MONGODB_API_VERSION:
@@ -605,6 +606,12 @@ def require_no_standalone(self, func):
605606
"Must be connected to a replica set or mongos",
606607
func=func)
607608

609+
def require_load_balancer(self, func):
610+
"""Run a test only if the client is connected to a load balancer."""
611+
return self._require(lambda: self.load_balancer,
612+
"Must be connected to a load balancer",
613+
func=func)
614+
608615
def check_auth_with_sharding(self, func):
609616
"""Skip a test when connected to mongos < 2.0 and running with auth."""
610617
condition = lambda: not (self.auth_enabled and
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"uri": "mongodb+srv://test20.test.build.10gen.cc/?directConnection=false",
3+
"seeds": [
4+
"localhost.test.build.10gen.cc:27017"
5+
],
6+
"hosts": [
7+
"localhost.test.build.10gen.cc:27017"
8+
],
9+
"options": {
10+
"loadBalanced": true,
11+
"ssl": true,
12+
"directConnection": false
13+
}
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"uri": "mongodb+srv://test20.test.build.10gen.cc/?replicaSet=replset",
3+
"seeds": [],
4+
"hosts": [],
5+
"error": true,
6+
"comment": "Should fail because loadBalanced=true is incompatible with replicaSet"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"uri": "mongodb+srv://test1.test.build.10gen.cc/?loadBalanced=true",
3+
"seeds": [],
4+
"hosts": [],
5+
"error": true,
6+
"comment": "Should fail because loadBalanced is true but the SRV record resolves to multiple hosts"
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"uri": "mongodb+srv://test20.test.build.10gen.cc/",
3+
"seeds": [
4+
"localhost.test.build.10gen.cc:27017"
5+
],
6+
"hosts": [
7+
"localhost.test.build.10gen.cc:27017"
8+
],
9+
"options": {
10+
"loadBalanced": true,
11+
"ssl": true
12+
}
13+
}

0 commit comments

Comments
 (0)