mongo 2.7.2 → 2.8.0.rc0

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