mongo 2.11.1 → 2.11.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -1
  3. data.tar.gz.sig +2 -1
  4. data/Rakefile +24 -0
  5. data/lib/mongo/address.rb +53 -37
  6. data/lib/mongo/auth.rb +30 -10
  7. data/lib/mongo/auth/cr.rb +1 -0
  8. data/lib/mongo/auth/cr/conversation.rb +13 -13
  9. data/lib/mongo/auth/ldap.rb +2 -1
  10. data/lib/mongo/auth/ldap/conversation.rb +9 -12
  11. data/lib/mongo/auth/scram.rb +1 -0
  12. data/lib/mongo/auth/scram/conversation.rb +36 -27
  13. data/lib/mongo/auth/user.rb +7 -1
  14. data/lib/mongo/auth/x509.rb +2 -1
  15. data/lib/mongo/auth/x509/conversation.rb +9 -9
  16. data/lib/mongo/bulk_write/transformable.rb +3 -3
  17. data/lib/mongo/client.rb +17 -6
  18. data/lib/mongo/cluster.rb +67 -49
  19. data/lib/mongo/cluster/sdam_flow.rb +87 -3
  20. data/lib/mongo/collection/view/readable.rb +3 -1
  21. data/lib/mongo/collection/view/writable.rb +3 -3
  22. data/lib/mongo/cursor/builder/kill_cursors_command.rb +8 -1
  23. data/lib/mongo/cursor/builder/op_kill_cursors.rb +8 -1
  24. data/lib/mongo/database.rb +1 -1
  25. data/lib/mongo/grid/file.rb +5 -0
  26. data/lib/mongo/grid/file/chunk.rb +2 -0
  27. data/lib/mongo/grid/fs_bucket.rb +15 -13
  28. data/lib/mongo/grid/stream/write.rb +9 -3
  29. data/lib/mongo/protocol/serializers.rb +12 -2
  30. data/lib/mongo/server.rb +13 -6
  31. data/lib/mongo/server/connection.rb +15 -8
  32. data/lib/mongo/server/connection_base.rb +7 -4
  33. data/lib/mongo/server/description.rb +34 -21
  34. data/lib/mongo/server/monitor.rb +1 -1
  35. data/lib/mongo/server/monitor/connection.rb +2 -3
  36. data/lib/mongo/session.rb +10 -10
  37. data/lib/mongo/socket.rb +10 -1
  38. data/lib/mongo/uri.rb +1 -1
  39. data/lib/mongo/version.rb +1 -1
  40. data/mongo.gemspec +1 -1
  41. data/spec/README.md +13 -0
  42. data/spec/integration/auth_spec.rb +27 -8
  43. data/spec/integration/bson_symbol_spec.rb +34 -0
  44. data/spec/integration/client_construction_spec.rb +14 -0
  45. data/spec/integration/client_options_spec.rb +5 -5
  46. data/spec/integration/connection_spec.rb +57 -9
  47. data/spec/integration/crud_spec.rb +45 -0
  48. data/spec/integration/cursor_reaping_spec.rb +2 -1
  49. data/spec/integration/grid_fs_bucket_spec.rb +48 -0
  50. data/spec/integration/retryable_errors_spec.rb +2 -2
  51. data/spec/integration/zlib_compression_spec.rb +25 -0
  52. data/spec/lite_spec_helper.rb +1 -0
  53. data/spec/mongo/address_spec.rb +19 -13
  54. data/spec/mongo/auth/ldap/conversation_spec.rb +1 -1
  55. data/spec/mongo/auth/scram/conversation_spec.rb +25 -14
  56. data/spec/mongo/auth/user/view_spec.rb +39 -7
  57. data/spec/mongo/auth/user_spec.rb +12 -0
  58. data/spec/mongo/auth/x509/conversation_spec.rb +1 -1
  59. data/spec/mongo/bulk_write_spec.rb +2 -2
  60. data/spec/mongo/client_construction_spec.rb +3 -22
  61. data/spec/mongo/cluster_spec.rb +57 -0
  62. data/spec/mongo/collection/view/map_reduce_spec.rb +1 -1
  63. data/spec/mongo/collection_spec.rb +26 -2
  64. data/spec/mongo/cursor/builder/op_kill_cursors_spec.rb +56 -0
  65. data/spec/mongo/server/connection_spec.rb +76 -8
  66. data/spec/mongo/server/monitor/connection_spec.rb +14 -7
  67. data/spec/mongo/socket/ssl_spec.rb +132 -98
  68. data/spec/mongo/socket/tcp_spec.rb +1 -9
  69. data/spec/mongo/uri_spec.rb +1 -1
  70. data/spec/runners/sdam/verifier.rb +6 -3
  71. data/spec/spec_tests/data/sdam/rs/primary_address_change.yml +29 -0
  72. data/spec/spec_tests/data/sdam/rs/primary_mismatched_me.yml +27 -23
  73. data/spec/spec_tests/data/sdam/rs/primary_to_no_primary_mismatched_me.yml +56 -79
  74. data/spec/spec_tests/data/sdam/sharded/primary_address_change.yml +21 -0
  75. data/spec/spec_tests/data/sdam/sharded/primary_mismatched_me.yml +22 -0
  76. data/spec/spec_tests/data/sdam/single/primary_address_change.yml +24 -0
  77. data/spec/spec_tests/data/sdam/single/primary_mismatched_me.yml +25 -0
  78. data/spec/spec_tests/data/sdam_monitoring/replica_set_with_me_mismatch.yml +159 -0
  79. data/spec/spec_tests/data/sdam_monitoring/{replica_set_other_seed.yml → replica_set_with_primary_change.yml} +97 -101
  80. data/spec/spec_tests/data/sdam_monitoring/replica_set_with_primary_removal.yml +22 -18
  81. data/spec/spec_tests/data/sdam_monitoring/standalone_to_rs_with_me_mismatch.yml +90 -0
  82. data/spec/support/cluster_config.rb +36 -0
  83. data/spec/support/cluster_tools.rb +5 -3
  84. data/spec/support/command_monitoring.rb +1 -1
  85. data/spec/support/constraints.rb +18 -18
  86. data/spec/support/lite_constraints.rb +8 -0
  87. data/spec/support/server_discovery_and_monitoring.rb +2 -0
  88. data/spec/support/spec_config.rb +3 -3
  89. data/spec/support/utils.rb +11 -1
  90. metadata +661 -637
  91. metadata.gz.sig +0 -0
@@ -151,6 +151,8 @@ module Mongo
151
151
  # authorized for.
152
152
  # @option options [ String ] :user The user name.
153
153
  # @option options [ String ] :password The user's password.
154
+ # @option options [ String ] :pwd Legacy option for the user's password.
155
+ # If :password and :pwd are both specified, :password takes precedence.
154
156
  # @option options [ Symbol ] :auth_mech The authorization mechanism.
155
157
  # @option options [ Array<String>, Array<Hash> ] roles The user roles.
156
158
  # @option options [ String ] :client_key The user's client key cached from a previous
@@ -196,7 +198,11 @@ module Mongo
196
198
  #
197
199
  # @since 2.0.0
198
200
  def spec
199
- { pwd: password, roles: roles }
201
+ {roles: roles}.tap do |spec|
202
+ if password
203
+ spec[:pwd] = password
204
+ end
205
+ end
200
206
  end
201
207
 
202
208
  private
@@ -20,6 +20,7 @@ module Mongo
20
20
  # Defines behavior for X.509 authentication.
21
21
  #
22
22
  # @since 2.0.0
23
+ # @api private
23
24
  class X509
24
25
 
25
26
  # The authentication mechinism string.
@@ -67,7 +68,7 @@ module Mongo
67
68
  conversation = Conversation.new(user)
68
69
  reply = connection.dispatch([ conversation.start(connection) ])
69
70
  connection.update_cluster_time(Operation::Result.new(reply))
70
- conversation.finalize(reply)
71
+ conversation.finalize(reply, connection)
71
72
  end
72
73
  end
73
74
  end
@@ -42,26 +42,26 @@ module Mongo
42
42
  #
43
43
  # @param [ Protocol::Message ] reply The reply of the previous
44
44
  # message.
45
+ # @param [ Server::Connection ] connection The connection being
46
+ # authenticated.
45
47
  #
46
48
  # @return [ Protocol::Query ] The next message to send.
47
49
  #
48
50
  # @since 2.0.0
49
- def finalize(reply)
50
- validate!(reply)
51
+ def finalize(reply, connection)
52
+ validate!(reply, connection.server)
51
53
  end
52
54
 
53
55
  # Start the X.509 conversation. This returns the first message that
54
56
  # needs to be sent to the server.
55
57
  #
56
- # @example Start the conversation.
57
- # conversation.start
58
- #
59
- # @param [ Mongo::Server::Connection ] connection The connection being authenticated.
58
+ # @param [ Server::Connection ] connection The connection being
59
+ # authenticated.
60
60
  #
61
61
  # @return [ Protocol::Query ] The first X.509 conversation message.
62
62
  #
63
63
  # @since 2.0.0
64
- def start(connection = nil)
64
+ def start(connection)
65
65
  login = LOGIN.merge(mechanism: X509::MECHANISM)
66
66
  login[:user] = user.name if user.name
67
67
  if connection && connection.features.op_msg_enabled?
@@ -103,9 +103,9 @@ module Mongo
103
103
 
104
104
  private
105
105
 
106
- def validate!(reply)
106
+ def validate!(reply, server)
107
107
  if reply.documents[0][Operation::Result::OK] != 1
108
- raise Unauthorized.new(user, used_mechanism: MECHANISM)
108
+ raise Unauthorized.new(user, used_mechanism: MECHANISM, server: server)
109
109
  end
110
110
  @reply = reply
111
111
  end
@@ -92,7 +92,7 @@ module Mongo
92
92
  Operation::U => doc[:replacement],
93
93
  }.tap do |d|
94
94
  if doc[:upsert]
95
- d[:upsert] = true
95
+ d['upsert'] = true
96
96
  end
97
97
  d[Operation::COLLATION] = doc[:collation] if doc[:collation]
98
98
  end
@@ -108,7 +108,7 @@ module Mongo
108
108
  Operation::MULTI => true,
109
109
  }.tap do |d|
110
110
  if doc[:upsert]
111
- d[:upsert] = true
111
+ d['upsert'] = true
112
112
  end
113
113
  d[Operation::COLLATION] = doc[:collation] if doc[:collation]
114
114
  d[Operation::ARRAY_FILTERS] = doc[:array_filters] if doc[:array_filters]
@@ -124,7 +124,7 @@ module Mongo
124
124
  Operation::U => doc[:update],
125
125
  }.tap do |d|
126
126
  if doc[:upsert]
127
- d[:upsert] = true
127
+ d['upsert'] = true
128
128
  end
129
129
  d[Operation::COLLATION] = doc[:collation] if doc[:collation]
130
130
  d[Operation::ARRAY_FILTERS] = doc[:array_filters] if doc[:array_filters]
@@ -756,8 +756,8 @@ module Mongo
756
756
  #
757
757
  # @since 2.5.0
758
758
  def start_session(options = {})
759
- cluster.send(:get_session, self, options.merge(implicit: false)) ||
760
- (raise Error::InvalidSession.new(Session::SESSIONS_NOT_SUPPORTED))
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
- # @note This method will return nil if deployment has no data-bearing
831
- # servers at the time of the call.
830
+ # @return [ Session | nil ] Session object or nil if sessions are not
831
+ # supported by the deployment.
832
832
  def get_session(options = {})
833
- cluster.send(:get_session, self, options)
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
- cluster.send(:with_session, self, options, &block)
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)
@@ -109,6 +109,8 @@ module Mongo
109
109
  options[:cleanup] = false
110
110
  end
111
111
 
112
+ seeds = seeds.uniq
113
+
112
114
  @servers = []
113
115
  @monitoring = monitoring
114
116
  @event_listeners = Event::Listeners.new
@@ -162,12 +164,9 @@ module Mongo
162
164
  return
163
165
  end
164
166
 
165
- # Need to record start time prior to starting monitoring
166
- start_time = Time.now
167
-
168
- servers.each do |server|
169
- server.start_monitoring
170
- end
167
+ # Update instance variables prior to starting monitoring threads.
168
+ @connecting = false
169
+ @connected = true
171
170
 
172
171
  if options[:cleanup] != false
173
172
  @cursor_reaper = CursorReaper.new
@@ -182,8 +181,12 @@ module Mongo
182
181
  @periodic_executor.run!
183
182
  end
184
183
 
185
- @connecting = false
186
- @connected = true
184
+ # Need to record start time prior to starting monitoring
185
+ start_time = Time.now
186
+
187
+ servers.each do |server|
188
+ server.start_monitoring
189
+ end
187
190
 
188
191
  if options[:scan] != false
189
192
  server_selection_timeout = options[:server_selection_timeout] || ServerSelector::SERVER_SELECTION_TIMEOUT
@@ -743,30 +746,49 @@ module Mongo
743
746
  # server.remove('127.0.0.1:27017')
744
747
  #
745
748
  # @param [ String ] host The host/port or socket address.
749
+ # @param [ true | false ] disconnect Whether to disconnect the servers
750
+ # being removed. For internal driver use only.
751
+ #
752
+ # @return [ Array<Server> | true | false ] If disconnect is any value other
753
+ # than false, including nil, returns whether any servers were removed.
754
+ # If disconnect is false, returns an array of servers that were removed
755
+ # (and should be disconnected by the caller).
746
756
  #
747
- # @return [ true|false ] Whether any servers were removed.
757
+ # @note The return value of this method is not part of the driver's
758
+ # public API.
748
759
  #
749
- # @since 2.0.0, return value added in 2.7.0
750
- def remove(host)
760
+ # @since 2.0.0
761
+ def remove(host, disconnect: true)
751
762
  address = Address.new(host)
752
763
  removed_servers = @servers.select { |s| s.address == address }
753
764
  @update_lock.synchronize { @servers = @servers - removed_servers }
754
- removed_servers.each do |server|
755
- if server.connected?
756
- server.disconnect!
757
- publish_sdam_event(
758
- Monitoring::SERVER_CLOSED,
759
- Monitoring::Event::ServerClosed.new(address, topology)
760
- )
765
+ if disconnect != false
766
+ removed_servers.each do |server|
767
+ disconnect_server_if_connected(server)
761
768
  end
762
769
  end
763
- removed_servers.any?
770
+ if disconnect != false
771
+ removed_servers.any?
772
+ else
773
+ removed_servers
774
+ end
764
775
  end
765
776
 
766
777
  # @api private
767
778
  def update_topology(new_topology)
768
779
  old_topology = topology
769
780
  @topology = new_topology
781
+
782
+ # If new topology has data bearing servers, we know for sure whether
783
+ # sessions are supported - update our cached value.
784
+ # If new topology has no data bearing servers, leave the old value
785
+ # as it is and sessions_supported? method will perform server selection
786
+ # to try to determine session support accurately, falling back to the
787
+ # last known value.
788
+ if topology.data_bearing_servers?
789
+ @sessions_supported = !!topology.logical_session_timeout
790
+ end
791
+
770
792
  publish_sdam_event(
771
793
  Monitoring::TOPOLOGY_CHANGED,
772
794
  Monitoring::Event::TopologyChanged.new(old_topology, topology)
@@ -778,53 +800,49 @@ module Mongo
778
800
  @update_lock.synchronize { @servers.dup }
779
801
  end
780
802
 
781
- private
782
-
783
- # If options[:session] is set, validates that session and returns it.
784
- # If deployment supports sessions, creates a new session and returns it.
785
- # The session is implicit unless options[:implicit] is given.
786
- # If deployment does not support session, returns nil.
787
- #
788
- # @note This method will return nil if deployment has no data-bearing
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))
803
+ # @api private
804
+ def disconnect_server_if_connected(server)
805
+ if server.connected?
806
+ server.disconnect!
807
+ publish_sdam_event(
808
+ Monitoring::SERVER_CLOSED,
809
+ Monitoring::Event::ServerClosed.new(server.address, topology)
810
+ )
794
811
  end
795
812
  end
796
813
 
797
- def with_session(client, options = {})
798
- session = get_session(client, options)
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.
814
+ # Returns whether the deployment that the driver is connected to supports
815
+ # sessions.
806
816
  #
807
- # @note If the cluster has no data bearing servers, for example because
808
- # the deployment is in the middle of a failover, this method returns
809
- # false.
817
+ # Session support may change over time, for example due to servers in the
818
+ # deployment being upgraded or downgraded. This method returns the
819
+ # current information if the client is connected to at least one data
820
+ # bearing server. If the client is currently not connected to any data
821
+ # bearing servers, this method returns the last known value for whether
822
+ # the deployment supports sessions.
810
823
  #
811
- # @note This method returns as soon as the driver connects to any single
812
- # server in the deployment. Whether deployment overall supports sessions
813
- # can change depending on how many servers have been contacted, if
814
- # the servers are configured differently.
824
+ # @return [ true | false ] Whether deployment supports sessions.
825
+ # @api private
815
826
  def sessions_supported?
816
827
  if topology.data_bearing_servers?
817
828
  return !!topology.logical_session_timeout
818
829
  end
819
830
 
831
+ # No data bearing servers known - perform server selection to try to
832
+ # get a response from at least one of them, to return an accurate
833
+ # assessment of whether sessions are currently supported.
820
834
  begin
821
835
  ServerSelector.get(mode: :primary_preferred).select_server(self)
822
836
  !!topology.logical_session_timeout
823
837
  rescue Error::NoServerAvailable
824
- false
838
+ # We haven't been able to contact any servers - use last known
839
+ # value for esssion support.
840
+ @sessions_supported || false
825
841
  end
826
842
  end
827
843
 
844
+ private
845
+
828
846
  # @api private
829
847
  def start_stop_srv_monitor
830
848
  # 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
- "#{updated_desc.address}"
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, 'check_if_has_primary should only be called when topology is replica set'
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|