mongo 2.7.2 → 2.8.0.rc0

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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -3
  4. data/lib/mongo/address.rb +17 -20
  5. data/lib/mongo/address/ipv4.rb +6 -3
  6. data/lib/mongo/address/ipv6.rb +6 -3
  7. data/lib/mongo/address/unix.rb +5 -2
  8. data/lib/mongo/auth.rb +15 -2
  9. data/lib/mongo/auth/cr/conversation.rb +4 -2
  10. data/lib/mongo/auth/ldap/conversation.rb +4 -2
  11. data/lib/mongo/auth/scram.rb +3 -7
  12. data/lib/mongo/auth/scram/conversation.rb +28 -19
  13. data/lib/mongo/auth/user.rb +45 -10
  14. data/lib/mongo/auth/x509/conversation.rb +4 -2
  15. data/lib/mongo/cluster.rb +9 -17
  16. data/lib/mongo/error.rb +2 -0
  17. data/lib/mongo/error/missing_password.rb +29 -0
  18. data/lib/mongo/error/operation_failure.rb +7 -3
  19. data/lib/mongo/error/parser.rb +2 -1
  20. data/lib/mongo/error/sdam_error_detection.rb +54 -0
  21. data/lib/mongo/operation/aggregate/command.rb +1 -16
  22. data/lib/mongo/operation/aggregate/op_msg.rb +1 -1
  23. data/lib/mongo/operation/collections_info.rb +2 -3
  24. data/lib/mongo/operation/delete/command.rb +2 -15
  25. data/lib/mongo/operation/delete/legacy.rb +1 -16
  26. data/lib/mongo/operation/explain/command.rb +1 -16
  27. data/lib/mongo/operation/explain/legacy.rb +1 -16
  28. data/lib/mongo/operation/find/command.rb +1 -16
  29. data/lib/mongo/operation/find/legacy.rb +1 -16
  30. data/lib/mongo/operation/get_more/command.rb +1 -16
  31. data/lib/mongo/operation/indexes/command.rb +1 -16
  32. data/lib/mongo/operation/indexes/legacy.rb +4 -16
  33. data/lib/mongo/operation/list_collections/command.rb +1 -16
  34. data/lib/mongo/operation/map_reduce/command.rb +1 -16
  35. data/lib/mongo/operation/parallel_scan/command.rb +1 -16
  36. data/lib/mongo/operation/result.rb +3 -0
  37. data/lib/mongo/operation/shared/executable.rb +4 -0
  38. data/lib/mongo/operation/shared/polymorphic_lookup.rb +1 -1
  39. data/lib/mongo/operation/shared/polymorphic_result.rb +8 -1
  40. data/lib/mongo/operation/shared/result/aggregatable.rb +0 -5
  41. data/lib/mongo/operation/update/command.rb +2 -15
  42. data/lib/mongo/operation/update/legacy.rb +1 -16
  43. data/lib/mongo/operation/users_info/command.rb +1 -16
  44. data/lib/mongo/retryable.rb +22 -10
  45. data/lib/mongo/server.rb +10 -1
  46. data/lib/mongo/server/app_metadata.rb +7 -2
  47. data/lib/mongo/server/connectable.rb +0 -6
  48. data/lib/mongo/server/connection.rb +86 -135
  49. data/lib/mongo/server/connection_base.rb +133 -0
  50. data/lib/mongo/server/connection_pool.rb +11 -24
  51. data/lib/mongo/server/connection_pool/queue.rb +41 -41
  52. data/lib/mongo/server/description.rb +1 -1
  53. data/lib/mongo/server/monitor.rb +4 -4
  54. data/lib/mongo/server/monitor/connection.rb +26 -7
  55. data/lib/mongo/server/pending_connection.rb +36 -0
  56. data/lib/mongo/server_selector/selectable.rb +9 -1
  57. data/lib/mongo/session.rb +0 -1
  58. data/lib/mongo/socket.rb +23 -6
  59. data/lib/mongo/socket/ssl.rb +11 -18
  60. data/lib/mongo/socket/tcp.rb +13 -14
  61. data/lib/mongo/socket/unix.rb +9 -27
  62. data/lib/mongo/uri.rb +1 -1
  63. data/lib/mongo/version.rb +1 -1
  64. data/spec/integration/auth_spec.rb +160 -0
  65. data/spec/integration/retryable_writes_spec.rb +55 -58
  66. data/spec/integration/sdam_error_handling_spec.rb +115 -0
  67. data/spec/mongo/address/ipv4_spec.rb +4 -0
  68. data/spec/mongo/address/ipv6_spec.rb +4 -0
  69. data/spec/mongo/auth/scram/conversation_spec.rb +6 -5
  70. data/spec/mongo/auth/scram/negotiation_spec.rb +25 -36
  71. data/spec/mongo/auth/scram_spec.rb +2 -2
  72. data/spec/mongo/auth/user_spec.rb +97 -0
  73. data/spec/mongo/client_construction_spec.rb +1 -1
  74. data/spec/mongo/error/operation_failure_spec.rb +125 -1
  75. data/spec/mongo/retryable_spec.rb +17 -8
  76. data/spec/mongo/server/connection_pool/queue_spec.rb +24 -10
  77. data/spec/mongo/server/connection_pool_spec.rb +30 -117
  78. data/spec/mongo/server/connection_spec.rb +147 -25
  79. data/spec/mongo/server/description_spec.rb +0 -14
  80. data/spec/mongo/server/monitor/connection_spec.rb +22 -0
  81. data/spec/mongo/server_selector_spec.rb +1 -0
  82. data/spec/mongo/server_spec.rb +6 -6
  83. data/spec/mongo/socket/ssl_spec.rb +48 -116
  84. data/spec/mongo/socket/tcp_spec.rb +22 -0
  85. data/spec/mongo/socket/unix_spec.rb +9 -9
  86. data/spec/mongo/socket_spec.rb +15 -3
  87. data/spec/spec_tests/server_selection_spec.rb +2 -0
  88. data/spec/support/client_registry.rb +8 -2
  89. data/spec/support/common_shortcuts.rb +20 -1
  90. data/spec/support/constraints.rb +10 -2
  91. data/spec/support/lite_constraints.rb +8 -0
  92. data/spec/support/spec_config.rb +9 -1
  93. metadata +14 -4
  94. metadata.gz.sig +0 -0
@@ -18,7 +18,7 @@ module Mongo
18
18
  # This class models the socket connections for servers and their behavior.
19
19
  #
20
20
  # @since 2.0.0
21
- class Connection
21
+ class Connection < ConnectionBase
22
22
  include Connectable
23
23
  include Monitoring::Publishable
24
24
  include Retryable
@@ -66,7 +66,12 @@ module Mongo
66
66
  # @deprecated No longer necessary with Server Selection specification.
67
67
  PING_OP_MSG_BYTES = PING_OP_MSG_MESSAGE.serialize.to_s.freeze
68
68
 
69
- # Initialize a new socket connection from the client to the server.
69
+ # Creates a new connection object to the specified target address
70
+ # with the specified options.
71
+ #
72
+ # The constructor does not perform any I/O (and thus does not create
73
+ # sockets, handshakes nor authenticates); call connect! method on the
74
+ # connection object to create the network connection.
70
75
  #
71
76
  # @api private
72
77
  #
@@ -84,11 +89,10 @@ module Mongo
84
89
  #
85
90
  # @since 2.0.0
86
91
  def initialize(server, options = {})
87
- @address = server.address
88
92
  @monitoring = server.monitoring
89
93
  @options = options.freeze
90
94
  @server = server
91
- @ssl_options = options.reject { |k, v| !k.to_s.start_with?(SSL) }
95
+ @ssl_options = options.select { |k, v| k.to_s.start_with?(SSL) }.freeze
92
96
  @socket = nil
93
97
  @last_checkin = nil
94
98
  @auth_mechanism = nil
@@ -109,17 +113,9 @@ module Mongo
109
113
  options[:generation]
110
114
  end
111
115
 
112
- def_delegators :@server,
113
- :features,
114
- :max_bson_object_size,
115
- :max_message_size,
116
- :mongos?,
117
- :app_metadata,
118
- :compressor,
119
- :cluster_time,
120
- :update_cluster_time
121
-
122
- # Tell the underlying socket to establish a connection to the host.
116
+ # Establishes a network connection to the target address.
117
+ #
118
+ # If the connection is already established, this method does nothing.
123
119
  #
124
120
  # @example Connect to the host.
125
121
  # connection.connect!
@@ -131,18 +127,15 @@ module Mongo
131
127
  #
132
128
  # @since 2.0.0
133
129
  def connect!
134
- unless socket && socket.connectable?
135
- begin
136
- # Need to assign to the instance variable here because
137
- # I/O done by #handshake! and #connect! reference the socket
138
- @socket = address.socket(socket_timeout, ssl_options)
139
- address.connect_socket!(socket)
140
- handshake!
141
- authenticate!
142
- rescue Exception
143
- @socket = nil
144
- raise
145
- end
130
+ unless @socket
131
+ socket = address.socket(socket_timeout, ssl_options,
132
+ connect_timeout: address.connect_timeout)
133
+ handshake!(socket)
134
+ pending_connection = PendingConnection.new(socket, @server, monitoring, options)
135
+ authenticate!(pending_connection)
136
+ # When @socket is assigned, the socket should have handshaken and
137
+ # authenticated and be usable.
138
+ @socket = socket
146
139
  end
147
140
  true
148
141
  end
@@ -168,36 +161,6 @@ module Mongo
168
161
  true
169
162
  end
170
163
 
171
- # Dispatch a single message to the connection. If the message
172
- # requires a response, a reply will be returned.
173
- #
174
- # @example Dispatch the message.
175
- # connection.dispatch([ insert ])
176
- #
177
- # @note This method is named dispatch since 'send' is a core Ruby method on
178
- # all objects.
179
- #
180
- # @note For backwards compatibility, this method accepts the messages
181
- # as an array. However, exactly one message must be given per invocation.
182
- #
183
- # @param [ Array<Message> ] messages A one-element array containing
184
- # the message to dispatch.
185
- # @param [ Integer ] operation_id The operation id to link messages.
186
- #
187
- # @return [ Protocol::Message | nil ] The reply if needed.
188
- #
189
- # @since 2.0.0
190
- def dispatch(messages, operation_id = nil)
191
- # The monitoring code does not correctly handle multiple messages,
192
- # and the driver internally does not send more than one message at
193
- # a time ever. Thus prohibit multiple message use for now.
194
- if messages.length != 1
195
- raise ArgumentError, 'Can only dispatch one message at a time'
196
- end
197
- message = messages.first
198
- deliver(message)
199
- end
200
-
201
164
  # Ping the connection to see if the server is responding to commands.
202
165
  # This is non-blocking on the server side.
203
166
  #
@@ -249,92 +212,80 @@ module Mongo
249
212
 
250
213
  private
251
214
 
252
- def deliver(message)
253
- buffer = serialize(message)
254
- ensure_connected do |socket|
255
- operation_id = Monitoring.next_operation_id
256
- command_started(address, operation_id, message.payload)
257
- start = Time.now
258
- result = nil
259
- begin
260
- socket.write(buffer.to_s)
261
- result = if message.replyable?
262
- Protocol::Message.deserialize(socket, max_message_size, message.request_id)
263
- else
264
- nil
265
- end
266
- rescue Exception => e
267
- total_duration = Time.now - start
268
- command_failed(nil, address, operation_id, message.payload, e.message, total_duration)
269
- raise
270
- else
271
- total_duration = Time.now - start
272
- command_completed(result, address, operation_id, message.payload, total_duration)
273
- end
274
- result
275
- end
276
- end
277
-
278
- def handshake!
279
- unless socket && socket.connectable?
215
+ def handshake!(socket)
216
+ unless socket
280
217
  raise Error::HandshakeError, "Cannot handshake because there is no usable socket"
281
218
  end
282
219
 
220
+ response = average_rtt = nil
283
221
  @server.handle_handshake_failure! do
284
- response, exc, rtt, average_rtt =
285
- @server.monitor.round_trip_time_averager.measure do
286
- socket.write(app_metadata.ismaster_bytes)
287
- Protocol::Message.deserialize(socket, max_message_size).documents[0]
288
- end
222
+ begin
223
+ response, exc, rtt, average_rtt =
224
+ @server.monitor.round_trip_time_averager.measure do
225
+ socket.write(app_metadata.ismaster_bytes)
226
+ Protocol::Message.deserialize(socket, max_message_size).documents[0]
227
+ end
289
228
 
290
- if exc
291
- raise exc
229
+ if exc
230
+ raise exc
231
+ end
232
+ rescue => e
233
+ log_warn("Failed to handshake with #{address}: #{e.class}: #{e}")
234
+ raise
292
235
  end
236
+ end
293
237
 
294
- if response["ok"] == 1
295
- # Auth mechanism is entirely dependent on the contents of
296
- # ismaster response *for this connection*.
297
- # Ismaster received by the monitoring connection should advertise
298
- # the same wire protocol, but if it doesn't, we use whatever
299
- # the monitoring connection advertised for filling out the
300
- # server description and whatever the non-monitoring connection
301
- # (that's this one) advertised for performing auth on that
302
- # connection.
303
- @auth_mechanism = if response['saslSupportedMechs']
304
- if response['saslSupportedMechs'].include?(Mongo::Auth::SCRAM::SCRAM_SHA_256_MECHANISM)
305
- :scram256
306
- else
307
- :scram
308
- end
238
+ post_handshake(response, average_rtt)
239
+ end
240
+
241
+ # This is a separate method to keep the nesting level down.
242
+ def post_handshake(response, average_rtt)
243
+ if response["ok"] == 1
244
+ # Auth mechanism is entirely dependent on the contents of
245
+ # ismaster response *for this connection*.
246
+ # Ismaster received by the monitoring connection should advertise
247
+ # the same wire protocol, but if it doesn't, we use whatever
248
+ # the monitoring connection advertised for filling out the
249
+ # server description and whatever the non-monitoring connection
250
+ # (that's this one) advertised for performing auth on that
251
+ # connection.
252
+ @auth_mechanism = if response['saslSupportedMechs']
253
+ if response['saslSupportedMechs'].include?(Mongo::Auth::SCRAM::SCRAM_SHA_256_MECHANISM)
254
+ :scram256
309
255
  else
310
- # MongoDB servers < 2.6 are no longer suported.
311
- # Wire versions should always be returned in ismaster.
312
- # See also https://jira.mongodb.org/browse/RUBY-1584.
313
- min_wire_version = response[Description::MIN_WIRE_VERSION]
314
- max_wire_version = response[Description::MAX_WIRE_VERSION]
315
- features = Description::Features.new(min_wire_version..max_wire_version)
316
- if features.scram_sha_1_enabled?
317
- :scram
318
- else
319
- :mongodb_cr
320
- end
256
+ :scram
321
257
  end
322
258
  else
323
- @auth_mechanism = nil
259
+ # MongoDB servers < 2.6 are no longer suported.
260
+ # Wire versions should always be returned in ismaster.
261
+ # See also https://jira.mongodb.org/browse/RUBY-1584.
262
+ min_wire_version = response[Description::MIN_WIRE_VERSION]
263
+ max_wire_version = response[Description::MAX_WIRE_VERSION]
264
+ features = Description::Features.new(min_wire_version..max_wire_version)
265
+ if features.scram_sha_1_enabled?
266
+ :scram
267
+ else
268
+ :mongodb_cr
269
+ end
324
270
  end
325
-
326
- new_description = Description.new(@server.description.address, response, average_rtt)
327
- @server.monitor.publish(Event::DESCRIPTION_CHANGED, @server.description, new_description)
271
+ else
272
+ @auth_mechanism = nil
328
273
  end
274
+
275
+ new_description = Description.new(address, response, average_rtt)
276
+ @server.monitor.publish(Event::DESCRIPTION_CHANGED, @server.description, new_description)
329
277
  end
330
278
 
331
- def authenticate!
279
+ def authenticate!(pending_connection)
332
280
  if options[:user] || options[:auth_mech]
333
- user = Auth::User.new(Options::Redacted.new(:auth_mech => default_mechanism, :client_key => @client_key).merge(options))
281
+ user = Auth::User.new(Options::Redacted.new(:auth_mech => default_mechanism).merge(options))
334
282
  @server.handle_auth_failure! do
335
- reply = Auth.get(user).login(self)
336
- @client_key ||= user.send(:client_key) if user.mechanism == :scram
337
- reply
283
+ begin
284
+ Auth.get(user).login(pending_connection)
285
+ rescue => e
286
+ log_warn("Failed to handshake with #{address}: #{e.class}: #{e}")
287
+ raise
288
+ end
338
289
  end
339
290
  end
340
291
  end
@@ -343,15 +294,15 @@ module Mongo
343
294
  @auth_mechanism || (@server.features.scram_sha_1_enabled? ? :scram : :mongodb_cr)
344
295
  end
345
296
 
346
- def serialize(message, buffer = BSON::ByteBuffer.new)
347
- start_size = 0
348
- message.compress!(compressor, options[:zlib_compression_level]).serialize(buffer, max_bson_object_size)
349
- if max_message_size &&
350
- (buffer.length - start_size) > max_message_size
351
- then
352
- raise Error::MaxMessageSize.new(max_message_size)
297
+ def deliver(message)
298
+ begin
299
+ super
300
+ # Important: timeout errors are not handled here
301
+ rescue Error::SocketError
302
+ @server.unknown!
303
+ @server.pool.disconnect!
304
+ raise
353
305
  end
354
- buffer
355
306
  end
356
307
  end
357
308
  end
@@ -0,0 +1,133 @@
1
+ # Copyright (C) 2019 MongoDB, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ class Server
17
+
18
+ # This class encapsulates common connection functionality.
19
+ #
20
+ # @note Although methods of this module are part of the public API,
21
+ # the fact that these methods are defined on this module and not on
22
+ # the classes which include this module is not part of the public API.
23
+ #
24
+ # @api semipublic
25
+ class ConnectionBase
26
+ extend Forwardable
27
+ include Monitoring::Publishable
28
+
29
+ # @return [ Hash ] options The passed in options.
30
+ attr_reader :options
31
+
32
+ # @return [ Mongo::Address ] address The address to connect to.
33
+ def address
34
+ @server.address
35
+ end
36
+
37
+ def_delegators :@server,
38
+ :features,
39
+ :max_bson_object_size,
40
+ :max_message_size,
41
+ :mongos?,
42
+ :compressor,
43
+ :cluster_time,
44
+ :update_cluster_time
45
+
46
+ def app_metadata
47
+ @app_metadata ||= begin
48
+ same = true
49
+ AppMetadata::AUTH_OPTION_KEYS.each do |key|
50
+ if @server.options[key] != options[key]
51
+ same = false
52
+ break
53
+ end
54
+ end
55
+ if same
56
+ @server.app_metadata
57
+ else
58
+ AppMetadata.new(options)
59
+ end
60
+ end
61
+ end
62
+
63
+ # Dispatch a single message to the connection. If the message
64
+ # requires a response, a reply will be returned.
65
+ #
66
+ # @example Dispatch the message.
67
+ # connection.dispatch([ insert ])
68
+ #
69
+ # @note This method is named dispatch since 'send' is a core Ruby method on
70
+ # all objects.
71
+ #
72
+ # @note For backwards compatibility, this method accepts the messages
73
+ # as an array. However, exactly one message must be given per invocation.
74
+ #
75
+ # @param [ Array<Message> ] messages A one-element array containing
76
+ # the message to dispatch.
77
+ # @param [ Integer ] operation_id The operation id to link messages.
78
+ #
79
+ # @return [ Protocol::Message | nil ] The reply if needed.
80
+ #
81
+ # @since 2.0.0
82
+ def dispatch(messages, operation_id = nil)
83
+ # The monitoring code does not correctly handle multiple messages,
84
+ # and the driver internally does not send more than one message at
85
+ # a time ever. Thus prohibit multiple message use for now.
86
+ if messages.length != 1
87
+ raise ArgumentError, 'Can only dispatch one message at a time'
88
+ end
89
+ message = messages.first
90
+ deliver(message)
91
+ end
92
+
93
+ private
94
+
95
+ def deliver(message)
96
+ buffer = serialize(message)
97
+ ensure_connected do |socket|
98
+ operation_id = Monitoring.next_operation_id
99
+ command_started(address, operation_id, message.payload)
100
+ start = Time.now
101
+ result = nil
102
+ begin
103
+ socket.write(buffer.to_s)
104
+ result = if message.replyable?
105
+ Protocol::Message.deserialize(socket, max_message_size, message.request_id)
106
+ else
107
+ nil
108
+ end
109
+ rescue Exception => e
110
+ total_duration = Time.now - start
111
+ command_failed(nil, address, operation_id, message.payload, e.message, total_duration)
112
+ raise
113
+ else
114
+ total_duration = Time.now - start
115
+ command_completed(result, address, operation_id, message.payload, total_duration)
116
+ end
117
+ result
118
+ end
119
+ end
120
+
121
+ def serialize(message, buffer = BSON::ByteBuffer.new)
122
+ start_size = 0
123
+ message.compress!(compressor, options[:zlib_compression_level]).serialize(buffer, max_bson_object_size)
124
+ if max_message_size &&
125
+ (buffer.length - start_size) > max_message_size
126
+ then
127
+ raise Error::MaxMessageSize.new(max_message_size)
128
+ end
129
+ buffer
130
+ end
131
+ end
132
+ end
133
+ end
@@ -42,8 +42,13 @@ module Mongo
42
42
  #
43
43
  # @since 2.0.0
44
44
  def initialize(options = {}, &block)
45
- @options = options.freeze
46
- @queue = Queue.new(options, &block)
45
+ @options = options.dup.freeze
46
+ @queue = queue = Queue.new(@options, &block)
47
+
48
+ finalizer = proc do
49
+ queue.disconnect!
50
+ end
51
+ ObjectSpace.define_finalizer(self, finalizer)
47
52
  end
48
53
 
49
54
  # @return [ Hash ] options The pool options.
@@ -76,7 +81,10 @@ module Mongo
76
81
  queue.dequeue
77
82
  end
78
83
 
79
- # Disconnect the connection pool.
84
+ # Closes all idle connections in the pool and schedules currently checked
85
+ # out connections to be closed when they are checked back into the pool.
86
+ # The pool remains operational and can create new connections when
87
+ # requested.
80
88
  #
81
89
  # @example Disconnect the connection pool.
82
90
  # pool.disconnect!
@@ -120,27 +128,6 @@ module Mongo
120
128
  protected
121
129
 
122
130
  attr_reader :queue
123
-
124
- private
125
-
126
- class << self
127
-
128
- # Creates a new connection pool for the provided server.
129
- #
130
- # @example Create a new connection pool.
131
- # Mongo::Server::ConnectionPool.get(server)
132
- #
133
- # @param [ Mongo::Server ] server The server.
134
- #
135
- # @return [ Mongo::Server::ConnectionPool ] The connection pool.
136
- #
137
- # @since 2.0.0
138
- def get(server)
139
- ConnectionPool.new(server.options) do |generation|
140
- Connection.new(server, server.options.merge(generation: generation))
141
- end
142
- end
143
- end
144
131
  end
145
132
  end
146
133
  end