mongo 2.11.1 → 2.11.2
Sign up to get free protection for your applications and to get access to all the features.
- 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,
|