Skip to content

Commit 4f5e06d

Browse files
authored
RUBY-2746 RUBY-2851 RUBY-2737 Provide options to limit number of mongos servers used in connecting to sharded clusters (#2380)
* RUBY-2746 add spec tests * RUBY-2746 add another spec test * RUBY-2746 implemented srvServiceName, 5 tests still failing * RUBY-2746 added srvMaxHosts implementation and fix test for it * RUBY-2746 add type that raises error on no parse, ALL TESTS PASSING * RUBY-2746 move uri validation for replica set * RUBY-2746 uri options passing, still waiting on bad input failure direction * RUBY-2746 remove offending tests and integer_raise * RUBY-2746 clean up old test files and combine runners * RUBY-2746 add support for ruby options * RUBY-2746 add tests for ruby options * RUBY-2746 cleanup comments * RUBY-2746 fix tests, naming, method signatures, set srv_max_hosts to nil on 0 * RUBY-2746 don't set srv_max_hosts on 0 * RUBY-2746 add docs for new options * RUBY-2746 fix bug, add test for srv_max_hosts * RUBY-2746 add tests for different valus of srvMaxHosts * RUBY-2746 add srvServiceName test * RUBY-2746 fix spec tests, make srv_max_hosts accept 0 * RUBY-2746 fix srv tests, disregard sorting * RUBY-2746 fix tests sorting * RUBY-2746 update comments and docs
1 parent 7c3b6cb commit 4f5e06d

29 files changed

+658
-13
lines changed

docs/reference/create-client.txt

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ routers explicitly, the driver will not discover remaining routers that
235235
may be configured and will not attempt to connect to them.
236236

237237
The driver will automatically balance the operation load among the routers
238-
it is aware of.
238+
it is aware of.
239239

240240
To connect to a MongoDB Atlas cluster which is deployed as a sharded cluster,
241241
connect to the URI:
@@ -537,7 +537,7 @@ Ruby Options
537537
* - ``:max_idle_time``
538538
- The maximum time, in seconds, that a connection can be idle before it
539539
is closed by the connection pool.
540-
540+
541541
*Warning:* when connected to a load balancer, the driver uses existing
542542
connections for iterating cursors (which includes change streams)
543543
and executing transactions. Setting an idle time via this option may
@@ -669,11 +669,11 @@ Ruby Options
669669
- ``:version`` (String)
670670
- ``:strict`` (true or false)
671671
- ``:deprecation_errors`` (true or false)
672-
672+
673673
Note that the server API version can only be specified as a Ruby option,
674674
not as a URI option, and it cannot be overridden for database and
675675
collection objects.
676-
676+
677677
If server API version is changed on a client (such as via the ``with``
678678
call), the entire API version hash is replaced with the new specification
679679
(the old and the new individual fields are NOT merged).
@@ -694,6 +694,24 @@ Ruby Options
694694
- ``Float``
695695
- none
696696

697+
* - ``:srv_max_hosts``
698+
- The maximum number of mongoses that the driver will communicate with
699+
for sharded topologies. If this option is set to 0, there will
700+
be no maximum number of mongoses. If the given URI resolves
701+
to more hosts than ``:srv_max_hosts``, the client will ramdomly
702+
choose an ``:srv_max_hosts`` sized subset of hosts. Note that the
703+
hosts that the driver ignores during client construction will never
704+
be used. If the hosts chosen by the driver become unavailable, the
705+
client will quit working completely, even though the deployment has
706+
other functional mongoses.
707+
- ``Integer``
708+
- 0
709+
710+
* - ``:srv_service_name``
711+
- The service name to use in the SRV DNS query.
712+
- ``String``
713+
- mongodb
714+
697715
* - ``:ssl``
698716
- Tell the client to connect to the servers via TLS.
699717
- ``Boolean``
@@ -1012,6 +1030,12 @@ URI options are explained in detail in the :manual:`Connection URI reference
10121030
invalid values (e.g. negative and non-numeric values), invalid values
10131031
provided via this URI option are ignored with a warning.
10141032

1033+
* - srvMaxHosts=Integer
1034+
- ``:srv_max_hosts => Integer``
1035+
1036+
* - srvServiceName=String
1037+
- ``:srv_service_name => String``
1038+
10151039
* - ssl=Boolean
10161040
- ``:ssl => true|false``
10171041

@@ -1874,7 +1898,7 @@ the highest compression at the same CPU consumption compared to the other
18741898
compressors. For maximum server compatibility all three compressors can be
18751899
specified, e.g. as ``compressors: ["zstd", "snappy", "zlib"]``.
18761900

1877-
``zstd`` compressor requires the
1901+
``zstd`` compressor requires the
18781902
`zstd-ruby <https://rubygems.org/gems/zstd-ruby>`_ library to be installed.
18791903
``snappy`` compressor requires the
18801904
`snappy <https://rubygems.org/gems/snappy>`_ library to be installed.

lib/mongo/client.rb

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ class Client
9292
:server_api,
9393
:server_selection_timeout,
9494
:socket_timeout,
95+
:srv_max_hosts,
96+
:srv_service_name,
9597
:ssl,
9698
:ssl_ca_cert,
9799
:ssl_ca_cert_object,
@@ -340,6 +342,13 @@ def hash
340342
# for selecting a server for an operation.
341343
# @option options [ Float ] :socket_timeout The timeout, in seconds, to
342344
# execute operations on a socket.
345+
# @option options [ Integer ] :srv_max_hosts The maximum number of mongoses
346+
# that the driver will communicate with for sharded topologies. If this
347+
# option is 0, then there will be no maximum number of mongoses. If the
348+
# given URI resolves to more hosts than ``:srv_max_hosts``, the client
349+
# will ramdomly choose an ``:srv_max_hosts`` sized subset of hosts.
350+
# @option options [ String ] :srv_service_name The service name to use in
351+
# the SRV DNS query.
343352
# @option options [ true, false ] :ssl Whether to use TLS.
344353
# @option options [ String ] :ssl_ca_cert The file containing concatenated
345354
# certificate authority certificates used to validate certs passed from the
@@ -532,7 +541,7 @@ def initialize(addresses_or_uri, options = nil)
532541
end
533542
=end
534543
@options.freeze
535-
validate_options!(addresses)
544+
validate_options!(addresses, is_srv: uri.is_a?(URI::SRVProtocol))
536545
validate_authentication_options!
537546

538547
database_options = @options.dup
@@ -1226,6 +1235,12 @@ def validate_new_options!(opts)
12261235
end
12271236

12281237
_options[key] = compressors unless compressors.empty?
1238+
elsif key == :srv_max_hosts
1239+
if v && (!v.is_a?(Integer) || v < 0)
1240+
log_warn("#{v} is not a valid integer for srv_max_hosts")
1241+
else
1242+
_options[key] = v
1243+
end
12291244
else
12301245
_options[key] = v
12311246
end
@@ -1239,7 +1254,7 @@ def validate_new_options!(opts)
12391254
# Validates all options after they are set on the client.
12401255
# This method is intended to catch combinations of options which are
12411256
# not allowed.
1242-
def validate_options!(addresses = nil)
1257+
def validate_options!(addresses = nil, is_srv: nil)
12431258
if options[:write] && options[:write_concern] && options[:write] != options[:write_concern]
12441259
raise ArgumentError, "If :write and :write_concern are both given, they must be identical: #{options.inspect}"
12451260
end
@@ -1349,6 +1364,26 @@ def validate_options!(addresses = nil)
13491364
end
13501365
end
13511366
end
1367+
1368+
if options[:srv_max_hosts] && options[:srv_max_hosts] > 0
1369+
if options[:replica_set]
1370+
raise ArgumentError, ":srv_max_hosts > 0 cannot be used with :replica_set option"
1371+
end
1372+
1373+
if options[:load_balanced]
1374+
raise ArgumentError, ":srv_max_hosts > 0 cannot be used with :load_balanced=true"
1375+
end
1376+
end
1377+
1378+
unless is_srv.nil? || is_srv
1379+
if options[:srv_max_hosts]
1380+
raise ArgumentError, ":srv_max_hosts cannot be used on non-SRV URI"
1381+
end
1382+
1383+
if options[:srv_service_name]
1384+
raise ArgumentError, ":srv_service_name cannot be used on non-SRV URI"
1385+
end
1386+
end
13521387
end
13531388

13541389
# Validates all authentication-related options after they are set on the client

lib/mongo/srv/resolver.rb

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ class Resolver
2929
# before querying SRV records.
3030
RECORD_PREFIX = '_mongodb._tcp.'.freeze
3131

32+
# Generates the record prefix with a custom SRV service name if it is
33+
# provided.
34+
#
35+
# @option srv_service_name [ String | nil ] The SRV service name to use
36+
# in the record prefix.
37+
# @return [ String ] The generated record prefix.
38+
def record_prefix(srv_service_name=nil)
39+
return srv_service_name ? "_#{srv_service_name}._tcp." : RECORD_PREFIX
40+
end
41+
3242
# Creates a new Resolver.
3343
#
3444
# @option opts [ Float ] :timeout The timeout, in seconds, to use for
@@ -51,13 +61,18 @@ def timeout
5161
options[:timeout] || Monitor::DEFAULT_TIMEOUT
5262
end
5363

54-
# Obtains all of the SRV records for a given hostname.
64+
# Obtains all of the SRV records for a given hostname. If a srv_max_hosts
65+
# is specified and it is greater than 0, return maximum srv_max_hosts records.
5566
#
5667
# In the event that a record with a mismatched domain is found or no
5768
# records are found, if the :raise_on_invalid option is true,
5869
# an exception will be raised, otherwise a warning will be logged.
5970
#
6071
# @param [ String ] hostname The hostname whose records should be obtained.
72+
# @param [ String | nil ] srv_service_name The SRV service name for the DNS query.
73+
# If nil, 'mongodb' is used.
74+
# @param [ Integer | nil ] srv_max_hosts The maximum number of records to return.
75+
# If this value is nil, return all of the records.
6176
#
6277
# @raise [ Mongo::Error::MismatchedDomain ] If the :raise_in_invalid
6378
# Resolver option is true and a record with a domain name that does
@@ -66,8 +81,8 @@ def timeout
6681
# option is true and no records are found.
6782
#
6883
# @return [ Mongo::Srv::Result ] SRV lookup result.
69-
def get_records(hostname)
70-
query_name = RECORD_PREFIX + hostname
84+
def get_records(hostname, srv_service_name=nil, srv_max_hosts=nil)
85+
query_name = record_prefix(srv_service_name) + hostname
7186
resources = @resolver.getresources(query_name, Resolv::DNS::Resource::IN::SRV)
7287

7388
# Collect all of the records into a Result object, raising an error
@@ -97,6 +112,12 @@ def get_records(hostname)
97112
end
98113
end
99114

115+
# if srv_max_hosts is in [1, #addresses)
116+
if (1...result.address_strs.length).include? srv_max_hosts
117+
sampled_records = resources.shuffle.first(srv_max_hosts)
118+
result = Srv::Result.new(hostname)
119+
sampled_records.each { |record| result.add_record(record) }
120+
end
100121
result
101122
end
102123

lib/mongo/uri.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,26 @@ def validate_uri_options!
538538
raise_invalid_error_no_fmt!("loadBalanced=true cannot be used with replicaSet option")
539539
end
540540
end
541+
542+
unless self.is_a?(URI::SRVProtocol)
543+
if uri_options[:srv_max_hosts]
544+
raise_invalid_error_no_fmt!("srvMaxHosts cannot be used on non-SRV URI")
545+
end
546+
547+
if uri_options[:srv_service_name]
548+
raise_invalid_error_no_fmt!("srvServiceName cannot be used on non-SRV URI")
549+
end
550+
end
551+
552+
if uri_options[:srv_max_hosts] && uri_options[:srv_max_hosts] > 0
553+
if uri_options[:replica_set]
554+
raise_invalid_error_no_fmt!("srvMaxHosts > 0 cannot be used with replicaSet option")
555+
end
556+
557+
if options[:load_balanced]
558+
raise_invalid_error_no_fmt!("srvMaxHosts > 0 cannot be used with loadBalanced=true")
559+
end
560+
end
541561
end
542562
end
543563
end

lib/mongo/uri/options_mapper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ def self.uri_option(uri_key, name, **extra)
269269
uri_option 'directConnection', :direct_connection, type: :bool
270270
uri_option 'connect', :connect, type: :symbol
271271
uri_option 'loadBalanced', :load_balanced, type: :bool
272+
uri_option 'srvMaxHosts', :srv_max_hosts, type: :integer
273+
uri_option 'srvServiceName', :srv_service_name
272274

273275
# Auth Options
274276
uri_option 'authSource', :auth_source

lib/mongo/uri/srv_protocol.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def parse!(remaining)
147147
validate_srv_hostname(hostname)
148148
@query_hostname = hostname
149149

150-
@srv_result = resolver.get_records(hostname)
150+
@srv_result = resolver.get_records(hostname, uri_options[:srv_service_name], uri_options[:srv_max_hosts])
151151
if srv_result.empty?
152152
raise Error::NoSRVRecords.new(NO_SRV_RECORDS % hostname)
153153
end

0 commit comments

Comments
 (0)