mongo 2.11.1 → 2.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +2 -1
- data.tar.gz.sig +0 -0
- data/Rakefile +24 -0
- data/lib/mongo/auth.rb +30 -10
- data/lib/mongo/auth/cr.rb +1 -0
- data/lib/mongo/auth/cr/conversation.rb +13 -13
- data/lib/mongo/auth/ldap.rb +2 -1
- data/lib/mongo/auth/ldap/conversation.rb +9 -12
- data/lib/mongo/auth/scram.rb +1 -0
- data/lib/mongo/auth/scram/conversation.rb +36 -27
- data/lib/mongo/auth/x509.rb +2 -1
- data/lib/mongo/auth/x509/conversation.rb +9 -9
- data/lib/mongo/client.rb +17 -6
- data/lib/mongo/cluster.rb +65 -49
- data/lib/mongo/cluster/sdam_flow.rb +87 -3
- data/lib/mongo/database.rb +1 -1
- data/lib/mongo/server.rb +13 -6
- data/lib/mongo/server/connection.rb +12 -4
- data/lib/mongo/server/connection_base.rb +7 -4
- data/lib/mongo/server/description.rb +34 -21
- data/lib/mongo/session.rb +10 -10
- data/lib/mongo/version.rb +1 -1
- data/spec/README.md +13 -0
- data/spec/integration/auth_spec.rb +27 -8
- data/spec/integration/client_construction_spec.rb +14 -0
- data/spec/mongo/auth/ldap/conversation_spec.rb +1 -1
- data/spec/mongo/auth/scram/conversation_spec.rb +23 -14
- data/spec/mongo/auth/x509/conversation_spec.rb +1 -1
- data/spec/mongo/client_construction_spec.rb +1 -21
- data/spec/mongo/cluster_spec.rb +38 -0
- data/spec/mongo/collection/view/map_reduce_spec.rb +1 -1
- data/spec/mongo/server/connection_spec.rb +67 -0
- data/spec/runners/sdam/verifier.rb +6 -3
- data/spec/spec_tests/data/sdam/rs/primary_address_change.yml +29 -0
- data/spec/spec_tests/data/sdam/rs/primary_mismatched_me.yml +27 -23
- data/spec/spec_tests/data/sdam/rs/primary_to_no_primary_mismatched_me.yml +56 -79
- data/spec/spec_tests/data/sdam/sharded/primary_address_change.yml +21 -0
- data/spec/spec_tests/data/sdam/sharded/primary_mismatched_me.yml +22 -0
- data/spec/spec_tests/data/sdam/single/primary_address_change.yml +24 -0
- data/spec/spec_tests/data/sdam/single/primary_mismatched_me.yml +25 -0
- data/spec/spec_tests/data/sdam_monitoring/replica_set_with_me_mismatch.yml +159 -0
- data/spec/spec_tests/data/sdam_monitoring/{replica_set_other_seed.yml → replica_set_with_primary_change.yml} +97 -101
- data/spec/spec_tests/data/sdam_monitoring/replica_set_with_primary_removal.yml +22 -18
- data/spec/spec_tests/data/sdam_monitoring/standalone_to_rs_with_me_mismatch.yml +90 -0
- data/spec/support/cluster_config.rb +36 -0
- data/spec/support/constraints.rb +18 -18
- data/spec/support/server_discovery_and_monitoring.rb +2 -0
- metadata +18 -4
- metadata.gz.sig +0 -0
data/lib/mongo/client.rb
CHANGED
@@ -756,8 +756,8 @@ module Mongo
|
|
756
756
|
#
|
757
757
|
# @since 2.5.0
|
758
758
|
def start_session(options = {})
|
759
|
-
|
760
|
-
|
759
|
+
get_session(options.merge(implicit: false)) or
|
760
|
+
raise Error::InvalidSession.new(Session::SESSIONS_NOT_SUPPORTED)
|
761
761
|
end
|
762
762
|
|
763
763
|
# As of version 3.6 of the MongoDB server, a ``$changeStream`` pipeline stage is supported
|
@@ -827,14 +827,25 @@ module Mongo
|
|
827
827
|
# The session is implicit unless options[:implicit] is given.
|
828
828
|
# If deployment does not support session, returns nil.
|
829
829
|
#
|
830
|
-
# @
|
831
|
-
#
|
830
|
+
# @return [ Session | nil ] Session object or nil if sessions are not
|
831
|
+
# supported by the deployment.
|
832
832
|
def get_session(options = {})
|
833
|
-
|
833
|
+
if options[:session]
|
834
|
+
return options[:session].validate!(self)
|
835
|
+
end
|
836
|
+
|
837
|
+
if cluster.sessions_supported?
|
838
|
+
Session.new(cluster.session_pool.checkout, self, { implicit: true }.merge(options))
|
839
|
+
end
|
834
840
|
end
|
835
841
|
|
836
842
|
def with_session(options = {}, &block)
|
837
|
-
|
843
|
+
session = get_session(options)
|
844
|
+
yield(session)
|
845
|
+
ensure
|
846
|
+
if session && session.implicit?
|
847
|
+
session.end_session
|
848
|
+
end
|
838
849
|
end
|
839
850
|
|
840
851
|
def initialize_copy(original)
|
data/lib/mongo/cluster.rb
CHANGED
@@ -162,12 +162,9 @@ module Mongo
|
|
162
162
|
return
|
163
163
|
end
|
164
164
|
|
165
|
-
#
|
166
|
-
|
167
|
-
|
168
|
-
servers.each do |server|
|
169
|
-
server.start_monitoring
|
170
|
-
end
|
165
|
+
# Update instance variables prior to starting monitoring threads.
|
166
|
+
@connecting = false
|
167
|
+
@connected = true
|
171
168
|
|
172
169
|
if options[:cleanup] != false
|
173
170
|
@cursor_reaper = CursorReaper.new
|
@@ -182,8 +179,12 @@ module Mongo
|
|
182
179
|
@periodic_executor.run!
|
183
180
|
end
|
184
181
|
|
185
|
-
|
186
|
-
|
182
|
+
# Need to record start time prior to starting monitoring
|
183
|
+
start_time = Time.now
|
184
|
+
|
185
|
+
servers.each do |server|
|
186
|
+
server.start_monitoring
|
187
|
+
end
|
187
188
|
|
188
189
|
if options[:scan] != false
|
189
190
|
server_selection_timeout = options[:server_selection_timeout] || ServerSelector::SERVER_SELECTION_TIMEOUT
|
@@ -743,30 +744,49 @@ module Mongo
|
|
743
744
|
# server.remove('127.0.0.1:27017')
|
744
745
|
#
|
745
746
|
# @param [ String ] host The host/port or socket address.
|
747
|
+
# @param [ true | false ] disconnect Whether to disconnect the servers
|
748
|
+
# being removed. For internal driver use only.
|
749
|
+
#
|
750
|
+
# @return [ Array<Server> | true | false ] If disconnect is any value other
|
751
|
+
# than false, including nil, returns whether any servers were removed.
|
752
|
+
# If disconnect is false, returns an array of servers that were removed
|
753
|
+
# (and should be disconnected by the caller).
|
746
754
|
#
|
747
|
-
# @return
|
755
|
+
# @note The return value of this method is not part of the driver's
|
756
|
+
# public API.
|
748
757
|
#
|
749
|
-
# @since 2.0.0
|
750
|
-
def remove(host)
|
758
|
+
# @since 2.0.0
|
759
|
+
def remove(host, disconnect: true)
|
751
760
|
address = Address.new(host)
|
752
761
|
removed_servers = @servers.select { |s| s.address == address }
|
753
762
|
@update_lock.synchronize { @servers = @servers - removed_servers }
|
754
|
-
|
755
|
-
|
756
|
-
server
|
757
|
-
publish_sdam_event(
|
758
|
-
Monitoring::SERVER_CLOSED,
|
759
|
-
Monitoring::Event::ServerClosed.new(address, topology)
|
760
|
-
)
|
763
|
+
if disconnect != false
|
764
|
+
removed_servers.each do |server|
|
765
|
+
disconnect_server_if_connected(server)
|
761
766
|
end
|
762
767
|
end
|
763
|
-
|
768
|
+
if disconnect != false
|
769
|
+
removed_servers.any?
|
770
|
+
else
|
771
|
+
removed_servers
|
772
|
+
end
|
764
773
|
end
|
765
774
|
|
766
775
|
# @api private
|
767
776
|
def update_topology(new_topology)
|
768
777
|
old_topology = topology
|
769
778
|
@topology = new_topology
|
779
|
+
|
780
|
+
# If new topology has data bearing servers, we know for sure whether
|
781
|
+
# sessions are supported - update our cached value.
|
782
|
+
# If new topology has no data bearing servers, leave the old value
|
783
|
+
# as it is and sessions_supported? method will perform server selection
|
784
|
+
# to try to determine session support accurately, falling back to the
|
785
|
+
# last known value.
|
786
|
+
if topology.data_bearing_servers?
|
787
|
+
@sessions_supported = !!topology.logical_session_timeout
|
788
|
+
end
|
789
|
+
|
770
790
|
publish_sdam_event(
|
771
791
|
Monitoring::TOPOLOGY_CHANGED,
|
772
792
|
Monitoring::Event::TopologyChanged.new(old_topology, topology)
|
@@ -778,53 +798,49 @@ module Mongo
|
|
778
798
|
@update_lock.synchronize { @servers.dup }
|
779
799
|
end
|
780
800
|
|
781
|
-
private
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
# servers at the time of the call.
|
790
|
-
def get_session(client, options = {})
|
791
|
-
return options[:session].validate!(self) if options[:session]
|
792
|
-
if sessions_supported?
|
793
|
-
Session.new(@session_pool.checkout, client, { implicit: true }.merge(options))
|
801
|
+
# @api private
|
802
|
+
def disconnect_server_if_connected(server)
|
803
|
+
if server.connected?
|
804
|
+
server.disconnect!
|
805
|
+
publish_sdam_event(
|
806
|
+
Monitoring::SERVER_CLOSED,
|
807
|
+
Monitoring::Event::ServerClosed.new(server.address, topology)
|
808
|
+
)
|
794
809
|
end
|
795
810
|
end
|
796
811
|
|
797
|
-
|
798
|
-
|
799
|
-
yield(session)
|
800
|
-
ensure
|
801
|
-
session.end_session if (session && session.implicit?)
|
802
|
-
end
|
803
|
-
|
804
|
-
# Returns whether the deployment (as this term is defined in the sessions
|
805
|
-
# spec) supports sessions.
|
812
|
+
# Returns whether the deployment that the driver is connected to supports
|
813
|
+
# sessions.
|
806
814
|
#
|
807
|
-
#
|
808
|
-
#
|
809
|
-
#
|
815
|
+
# Session support may change over time, for example due to servers in the
|
816
|
+
# deployment being upgraded or downgraded. This method returns the
|
817
|
+
# current information if the client is connected to at least one data
|
818
|
+
# bearing server. If the client is currently not connected to any data
|
819
|
+
# bearing servers, this method returns the last known value for whether
|
820
|
+
# the deployment supports sessions.
|
810
821
|
#
|
811
|
-
# @
|
812
|
-
#
|
813
|
-
# can change depending on how many servers have been contacted, if
|
814
|
-
# the servers are configured differently.
|
822
|
+
# @return [ true | false ] Whether deployment supports sessions.
|
823
|
+
# @api private
|
815
824
|
def sessions_supported?
|
816
825
|
if topology.data_bearing_servers?
|
817
826
|
return !!topology.logical_session_timeout
|
818
827
|
end
|
819
828
|
|
829
|
+
# No data bearing servers known - perform server selection to try to
|
830
|
+
# get a response from at least one of them, to return an accurate
|
831
|
+
# assessment of whether sessions are currently supported.
|
820
832
|
begin
|
821
833
|
ServerSelector.get(mode: :primary_preferred).select_server(self)
|
822
834
|
!!topology.logical_session_timeout
|
823
835
|
rescue Error::NoServerAvailable
|
824
|
-
|
836
|
+
# We haven't been able to contact any servers - use last known
|
837
|
+
# value for esssion support.
|
838
|
+
@sessions_supported || false
|
825
839
|
end
|
826
840
|
end
|
827
841
|
|
842
|
+
private
|
843
|
+
|
828
844
|
# @api private
|
829
845
|
def start_stop_srv_monitor
|
830
846
|
# SRV URI is either always given or not for a given cluster, if one
|
@@ -30,6 +30,7 @@ class Mongo::Cluster
|
|
30
30
|
@topology = cluster.topology
|
31
31
|
@original_desc = @previous_desc = previous_desc
|
32
32
|
@updated_desc = updated_desc
|
33
|
+
@servers_to_disconnect = []
|
33
34
|
end
|
34
35
|
|
35
36
|
attr_reader :cluster
|
@@ -73,6 +74,60 @@ class Mongo::Cluster
|
|
73
74
|
end
|
74
75
|
|
75
76
|
def server_description_changed
|
77
|
+
if updated_desc.me_mismatch? && updated_desc.primary? &&
|
78
|
+
(topology.unknown? || topology.replica_set?)
|
79
|
+
then
|
80
|
+
# When the driver receives a description claiming to be a primary,
|
81
|
+
# we are obligated by spec tests to add and remove hosts in that
|
82
|
+
# description even if it also has a me mismatch. The me mismatch
|
83
|
+
# scenario though presents a number of problems:
|
84
|
+
#
|
85
|
+
# 1. Effectively, the server's address changes, meaning we cannot
|
86
|
+
# update the description of the server whose description change we
|
87
|
+
# are processing (instead servers are added and removed), but we
|
88
|
+
# behave to an extent as if we are updating the description, which
|
89
|
+
# causes a bunch of awkwardness.
|
90
|
+
# 2. The server for which we are processing the response will be
|
91
|
+
# removed from topology, which may cause the current thread to terminate
|
92
|
+
# prior to running the entire sdam flow. To deal with this we separate
|
93
|
+
# the removal event publication from actually removing the server
|
94
|
+
# from topology, which again complicates the flow.
|
95
|
+
|
96
|
+
# Primary-with-me-mismatch response could be the first one we receive
|
97
|
+
# when the topology is still unknown. Change to RS without primary
|
98
|
+
# in this case.
|
99
|
+
if topology.unknown?
|
100
|
+
@topology = Topology::ReplicaSetNoPrimary.new(
|
101
|
+
topology.options.merge(replica_set_name: updated_desc.replica_set_name),
|
102
|
+
topology.monitoring, self)
|
103
|
+
end
|
104
|
+
|
105
|
+
servers = add_servers_from_desc(updated_desc)
|
106
|
+
# Spec tests require us to remove servers based on data in descrptions
|
107
|
+
# with me mismatches. The driver will be more resilient if it only
|
108
|
+
# removed servers from descriptions with matching mes.
|
109
|
+
remove_servers_not_in_desc(updated_desc)
|
110
|
+
|
111
|
+
servers.each do |server|
|
112
|
+
server.start_monitoring
|
113
|
+
end
|
114
|
+
|
115
|
+
# The rest of sdam flow assumes the server being removed is not the one
|
116
|
+
# whose description we are processing, and publishes description update
|
117
|
+
# event. Since we are removing the server whose response we are
|
118
|
+
# processing, do not publish description change event but mark it
|
119
|
+
# published (by assigning to @previous_desc).
|
120
|
+
do_remove(updated_desc.address.to_s)
|
121
|
+
@previous_desc = updated_desc
|
122
|
+
|
123
|
+
# We may have removed the current primary, check if there is a primary.
|
124
|
+
check_if_has_primary
|
125
|
+
# Publish topology change event.
|
126
|
+
commit_changes
|
127
|
+
disconnect_servers
|
128
|
+
return
|
129
|
+
end
|
130
|
+
|
76
131
|
unless update_server_descriptions
|
77
132
|
# All of the transitions require that server whose updated_desc we are
|
78
133
|
# processing is still in the cluster (i.e., was not removed as a result
|
@@ -142,6 +197,7 @@ class Mongo::Cluster
|
|
142
197
|
end
|
143
198
|
|
144
199
|
commit_changes
|
200
|
+
disconnect_servers
|
145
201
|
end
|
146
202
|
|
147
203
|
# Transitions from unknown to single topology type, when a standalone
|
@@ -337,9 +393,14 @@ class Mongo::Cluster
|
|
337
393
|
end.flatten
|
338
394
|
servers_list.each do |server|
|
339
395
|
unless updated_desc_address_strs.include?(address_str = server.address.to_s)
|
396
|
+
updated_host = updated_desc.address.to_s
|
397
|
+
if updated_desc.me && updated_desc.me != updated_host
|
398
|
+
updated_host += " (self-identified as #{updated_desc.me})"
|
399
|
+
end
|
340
400
|
log_warn(
|
341
401
|
"Removing server #{address_str} because it is not in hosts reported by primary " +
|
342
|
-
"#{
|
402
|
+
"#{updated_host}. Reported hosts are: " +
|
403
|
+
updated_desc.hosts.join(', ')
|
343
404
|
)
|
344
405
|
do_remove(address_str)
|
345
406
|
end
|
@@ -356,7 +417,21 @@ class Mongo::Cluster
|
|
356
417
|
# Removes specified server from topology and warns if the topology ends
|
357
418
|
# up with an empty server list as a result
|
358
419
|
def do_remove(address_str)
|
359
|
-
cluster.remove(address_str)
|
420
|
+
servers = cluster.remove(address_str, disconnect: false)
|
421
|
+
servers.each do |server|
|
422
|
+
# We need to publish server closed event here, but we cannot close
|
423
|
+
# the server because it could be the server owning the monitor in
|
424
|
+
# whose thread this flow is presently executing, in which case closing
|
425
|
+
# the server can terminate the thread and leave SDAM processing
|
426
|
+
# incomplete. Thus we have to remove the server from the cluster,
|
427
|
+
# publish the event, but do not call disconnect on the server until
|
428
|
+
# the very end when all processing has completed.
|
429
|
+
publish_sdam_event(
|
430
|
+
Mongo::Monitoring::SERVER_CLOSED,
|
431
|
+
Mongo::Monitoring::Event::ServerClosed.new(server.address, cluster.topology)
|
432
|
+
)
|
433
|
+
end
|
434
|
+
@servers_to_disconnect += servers
|
360
435
|
if servers_list.empty?
|
361
436
|
log_warn(
|
362
437
|
"Topology now has no servers - this is likely a misconfiguration of the cluster and/or the application"
|
@@ -451,6 +526,15 @@ class Mongo::Cluster
|
|
451
526
|
cluster.update_topology(topology)
|
452
527
|
end
|
453
528
|
|
529
|
+
def disconnect_servers
|
530
|
+
while server = @servers_to_disconnect.shift
|
531
|
+
if server.connected?
|
532
|
+
# Do not publish server closed event, as this was already done
|
533
|
+
server.disconnect!
|
534
|
+
end
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
454
538
|
# If the server being processed is identified as data bearing, creates the
|
455
539
|
# server's connection pool so it can start populating
|
456
540
|
def start_pool_if_data_bearing
|
@@ -468,7 +552,7 @@ class Mongo::Cluster
|
|
468
552
|
# invoking this method.
|
469
553
|
def check_if_has_primary
|
470
554
|
unless topology.replica_set?
|
471
|
-
raise ArgumentError,
|
555
|
+
raise ArgumentError, "check_if_has_primary should only be called when topology is replica set, but it is #{topology.class.name.sub(/.*::/, '')}"
|
472
556
|
end
|
473
557
|
|
474
558
|
primary = servers_list.detect do |server|
|
data/lib/mongo/database.rb
CHANGED
@@ -157,7 +157,7 @@ module Mongo
|
|
157
157
|
# @option opts :read [ Hash ] The read preference for this command.
|
158
158
|
# @option opts :session [ Session ] The session to use for this command.
|
159
159
|
#
|
160
|
-
# @return [
|
160
|
+
# @return [ Mongo::Operation::Result ] The result of the command execution.
|
161
161
|
def command(operation, opts = {})
|
162
162
|
txn_read_pref = if opts[:session] && opts[:session].in_transaction?
|
163
163
|
opts[:session].txn_read_preference
|
data/lib/mongo/server.rb
CHANGED
@@ -306,12 +306,11 @@ module Mongo
|
|
306
306
|
"#<Mongo::Server:0x#{object_id} address=#{address.host}:#{address.port}>"
|
307
307
|
end
|
308
308
|
|
309
|
-
# @
|
309
|
+
# @return [ String ] String representing server status (e.g. PRIMARY).
|
310
310
|
#
|
311
|
-
# @api
|
312
|
-
|
313
|
-
|
314
|
-
status = case
|
311
|
+
# @api private
|
312
|
+
def status
|
313
|
+
case
|
315
314
|
when primary?
|
316
315
|
'PRIMARY'
|
317
316
|
when secondary?
|
@@ -331,8 +330,16 @@ module Mongo
|
|
331
330
|
else
|
332
331
|
# Since the summary method is often used for debugging, do not raise
|
333
332
|
# an exception in case none of the expected types matched
|
334
|
-
|
333
|
+
nil
|
335
334
|
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# @note This method is experimental and subject to change.
|
338
|
+
#
|
339
|
+
# @api experimental
|
340
|
+
# @since 2.7.0
|
341
|
+
def summary
|
342
|
+
status = self.status || ''
|
336
343
|
if replica_set_name
|
337
344
|
status += " replica_set=#{replica_set_name}"
|
338
345
|
end
|
@@ -103,6 +103,12 @@ module Mongo
|
|
103
103
|
)
|
104
104
|
end
|
105
105
|
|
106
|
+
# @return [ Server::Description ] The server description obtained from
|
107
|
+
# the handshake on this connection.
|
108
|
+
#
|
109
|
+
# @api private
|
110
|
+
attr_reader :description
|
111
|
+
|
106
112
|
# @return [ Time ] The last time the connection was checked back into a pool.
|
107
113
|
#
|
108
114
|
# @since 2.5.0
|
@@ -186,8 +192,10 @@ module Mongo
|
|
186
192
|
|
187
193
|
begin
|
188
194
|
handshake!(socket)
|
189
|
-
|
190
|
-
|
195
|
+
unless description.arbiter?
|
196
|
+
pending_connection = PendingConnection.new(socket, @server, monitoring, options.merge(id: id))
|
197
|
+
authenticate!(pending_connection)
|
198
|
+
end
|
191
199
|
rescue Exception
|
192
200
|
socket.close
|
193
201
|
raise
|
@@ -356,8 +364,8 @@ module Mongo
|
|
356
364
|
@auth_mechanism = nil
|
357
365
|
end
|
358
366
|
|
359
|
-
|
360
|
-
@server.cluster.run_sdam_flow(@server.description,
|
367
|
+
@description = Description.new(address, response, average_rtt)
|
368
|
+
@server.cluster.run_sdam_flow(@server.description, @description)
|
361
369
|
end
|
362
370
|
|
363
371
|
def authenticate!(pending_connection)
|
@@ -29,12 +29,15 @@ module Mongo
|
|
29
29
|
# @return [ Hash ] options The passed in options.
|
30
30
|
attr_reader :options
|
31
31
|
|
32
|
+
# @return [ Server ] The server that this connection is for.
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
attr_reader :server
|
36
|
+
|
32
37
|
# @return [ Mongo::Address ] address The address to connect to.
|
33
|
-
|
34
|
-
@server.address
|
35
|
-
end
|
38
|
+
def_delegators :server, :address
|
36
39
|
|
37
|
-
def_delegators
|
40
|
+
def_delegators :server,
|
38
41
|
:features,
|
39
42
|
:max_bson_object_size,
|
40
43
|
:max_message_size,
|