mongo 1.5.2 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/Rakefile +24 -8
  2. data/docs/HISTORY.md +15 -0
  3. data/docs/READ_PREFERENCE.md +1 -1
  4. data/docs/RELEASES.md +18 -4
  5. data/docs/REPLICA_SETS.md +5 -5
  6. data/docs/WRITE_CONCERN.md +1 -1
  7. data/lib/mongo/collection.rb +49 -10
  8. data/lib/mongo/connection.rb +7 -24
  9. data/lib/mongo/cursor.rb +3 -1
  10. data/lib/mongo/db.rb +5 -1
  11. data/lib/mongo/exceptions.rb +0 -3
  12. data/lib/mongo/gridfs/grid.rb +1 -1
  13. data/lib/mongo/gridfs/grid_file_system.rb +11 -3
  14. data/lib/mongo/networking.rb +7 -3
  15. data/lib/mongo/repl_set_connection.rb +58 -82
  16. data/lib/mongo/util/logging.rb +26 -18
  17. data/lib/mongo/util/node.rb +11 -2
  18. data/lib/mongo/util/pool_manager.rb +7 -5
  19. data/lib/mongo/util/support.rb +2 -2
  20. data/lib/mongo/util/uri_parser.rb +74 -36
  21. data/lib/mongo/version.rb +1 -1
  22. data/mongo.gemspec +2 -2
  23. data/test/auxillary/authentication_test.rb +8 -0
  24. data/test/auxillary/repl_set_auth_test.rb +1 -2
  25. data/test/bson/ordered_hash_test.rb +1 -1
  26. data/test/bson/test_helper.rb +2 -1
  27. data/test/collection_test.rb +71 -0
  28. data/test/connection_test.rb +9 -0
  29. data/test/db_test.rb +7 -0
  30. data/test/grid_file_system_test.rb +12 -0
  31. data/test/load/thin/load.rb +1 -1
  32. data/test/replica_sets/basic_test.rb +36 -26
  33. data/test/replica_sets/complex_connect_test.rb +44 -0
  34. data/test/replica_sets/connect_test.rb +48 -22
  35. data/test/replica_sets/count_test.rb +4 -6
  36. data/test/replica_sets/insert_test.rb +13 -14
  37. data/test/replica_sets/pooled_insert_test.rb +9 -10
  38. data/test/replica_sets/query_test.rb +4 -4
  39. data/test/replica_sets/read_preference_test.rb +48 -14
  40. data/test/replica_sets/refresh_test.rb +43 -42
  41. data/test/replica_sets/refresh_with_threads_test.rb +10 -9
  42. data/test/replica_sets/replication_ack_test.rb +3 -3
  43. data/test/replica_sets/rs_test_helper.rb +17 -11
  44. data/test/test_helper.rb +2 -1
  45. data/test/tools/repl_set_manager.rb +63 -39
  46. data/test/unit/collection_test.rb +2 -1
  47. data/test/unit/connection_test.rb +22 -0
  48. data/test/unit/cursor_test.rb +6 -3
  49. data/test/unit/read_test.rb +1 -1
  50. data/test/uri_test.rb +17 -2
  51. metadata +151 -167
@@ -37,9 +37,6 @@ module Mongo
37
37
  end
38
38
  end
39
39
 
40
- # Raised when configuration options cause connections, queries, etc., to fail.
41
- class ConfigurationError < MongoRubyError; end
42
-
43
40
  # Raised on fatal errors to GridFS.
44
41
  class GridError < MongoRubyError; end
45
42
 
@@ -70,7 +70,7 @@ module Mongo
70
70
  opts = opts.dup
71
71
  filename = opts[:filename]
72
72
  opts.merge!(default_grid_io_opts)
73
- file = GridIO.new(@files, @chunks, filename, 'w', opts=opts)
73
+ file = GridIO.new(@files, @chunks, filename, 'w', opts)
74
74
  file.write(data)
75
75
  file.close
76
76
  file.files_id
@@ -71,6 +71,9 @@ module Mongo
71
71
  # GridFileSystem#delete.
72
72
  # @option opts [Boolean] :safe (false) When safe mode is enabled, the chunks sent to the server
73
73
  # will be validated using an md5 hash. If validation fails, an exception will be raised.
74
+ # @option opts [Integer] :versions (false) deletes all versions which exceed the number specified to
75
+ # retain ordered by uploadDate. This option only works in 'w' mode. Certain precautions must be taken when
76
+ # deleting GridFS files. See the notes under GridFileSystem#delete.
74
77
  #
75
78
  # @example
76
79
  #
@@ -97,7 +100,12 @@ module Mongo
97
100
  def open(filename, mode, opts={})
98
101
  opts = opts.dup
99
102
  opts.merge!(default_grid_io_opts(filename))
100
- del = opts.delete(:delete_old) && mode == 'w'
103
+ if mode == 'w'
104
+ versions = opts.delete(:versions)
105
+ if opts.delete(:delete_old) || (versions && versions < 1)
106
+ versions = 1
107
+ end
108
+ end
101
109
  file = GridIO.new(@files, @chunks, filename, mode, opts)
102
110
  return file unless block_given?
103
111
  result = nil
@@ -105,9 +113,9 @@ module Mongo
105
113
  result = yield file
106
114
  ensure
107
115
  id = file.close
108
- if del
116
+ if versions
109
117
  self.delete do
110
- @files.find({'filename' => filename, '_id' => {'$ne' => id}}, :fields => ['_id'])
118
+ @files.find({'filename' => filename, '_id' => {'$ne' => id}}, :fields => ['_id'], :sort => ['uploadDate', -1], :skip => (versions -1))
111
119
  end
112
120
  end
113
121
  end
@@ -87,9 +87,13 @@ module Mongo
87
87
  end
88
88
 
89
89
  if num_received == 1 && (error = docs[0]['err'] || docs[0]['errmsg'])
90
- close if error == "not master"
91
- error = "wtimeout" if error == "timeout"
92
- raise OperationFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
90
+ if error.include?("not master")
91
+ close
92
+ raise ConnectionFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
93
+ else
94
+ error = "wtimeout" if error == "timeout"
95
+ raise OperationFailure.new(docs[0]['code'].to_s + ': ' + error, docs[0]['code'], docs[0])
96
+ end
93
97
  end
94
98
 
95
99
  docs[0]
@@ -20,7 +20,6 @@ module Mongo
20
20
 
21
21
  # Instantiates and manages connections to a MongoDB replica set.
22
22
  class ReplSetConnection < Connection
23
- CLEANUP_INTERVAL = 300
24
23
 
25
24
  attr_reader :replica_set_name, :seeds, :refresh_interval, :refresh_mode,
26
25
  :refresh_version
@@ -32,26 +31,25 @@ module Mongo
32
31
  # Connection#arbiters. This is useful if your application needs to connect manually to nodes other
33
32
  # than the primary.
34
33
  #
35
- # @param [Array] args A list of host-port pairs to be used as seed nodes followed by a
36
- # hash containing any options. See the examples below for exactly how to use the constructor.
34
+ # @param [Array] seeds "host:port" strings
37
35
  #
38
- # @option options [String] :rs_name (nil) The name of the replica set to connect to. You
36
+ # @option opts [String] :rs_name (nil) The name of the replica set to connect to. You
39
37
  # can use this option to verify that you're connecting to the right replica set.
40
- # @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
38
+ # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options
41
39
  # propogated to DB objects instantiated off of this Connection. This
42
40
  # default can be overridden upon instantiation of any DB by explicity setting a :safe value
43
41
  # on initialization.
44
- # @option options [:primary, :secondary] :read (:primary) The default read preference for Mongo::DB
42
+ # @option opts [:primary, :secondary] :read (:primary) The default read preference for Mongo::DB
45
43
  # objects created from this connection object. If +:secondary+ is chosen, reads will be sent
46
44
  # to one of the closest available secondary nodes. If a secondary node cannot be located, the
47
45
  # read will be sent to the primary.
48
- # @option options [Logger] :logger (nil) Logger instance to receive driver operation log.
49
- # @option options [Integer] :pool_size (1) The maximum number of socket connections allowed per
46
+ # @option opts [Logger] :logger (nil) Logger instance to receive driver operation log.
47
+ # @option opts [Integer] :pool_size (1) The maximum number of socket connections allowed per
50
48
  # connection pool. Note: this setting is relevant only for multi-threaded applications.
51
- # @option options [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
49
+ # @option opts [Float] :pool_timeout (5.0) When all of the connections a pool are checked out,
52
50
  # this is the number of seconds to wait for a new connection to be released before throwing an exception.
53
51
  # Note: this setting is relevant only for multi-threaded applications.
54
- # @option opts [Float] :op_timeout (30) The number of seconds to wait for a read operation to time out.
52
+ # @option opts [Float] :op_timeout (nil) The number of seconds to wait for a read operation to time out.
55
53
  # @option opts [Float] :connect_timeout (30) The number of seconds to wait before timing out a
56
54
  # connection attempt.
57
55
  # @option opts [Boolean] :ssl (false) If true, create the connection to the server using SSL.
@@ -63,17 +61,17 @@ module Mongo
63
61
  # between calls to check the replica set's state.
64
62
  # @option opts [Boolean] :require_primary (true) If true, require a primary node for the connection
65
63
  # to succeed. Otherwise, connection will succeed as long as there's at least one secondary node.
64
+ # Note: that the number of seed nodes does not have to be equal to the number of replica set members.
65
+ # The purpose of seed nodes is to permit the driver to find at least one replica set member even if a member is down.
66
66
  #
67
- # @example Connect to a replica set and provide two seed nodes. Note that the number of seed nodes does
68
- # not have to be equal to the number of replica set members. The purpose of seed nodes is to permit
69
- # the driver to find at least one replica set member even if a member is down.
70
- # ReplSetConnection.new(['localhost', 30000], ['localhost', 30001])
67
+ # @example Connect to a replica set and provide two seed nodes.
68
+ # ReplSetConnection.new(['localhost:30000', 'localhost:30001'])
71
69
  #
72
70
  # @example Connect to a replica set providing two seed nodes and ensuring a connection to the replica set named 'prod':
73
- # ReplSetConnection.new(['localhost', 30000], ['localhost', 30001], :rs_name => 'prod')
71
+ # ReplSetConnection.new(['localhost:30000', 'localhost:30001'], :name => 'prod')
74
72
  #
75
73
  # @example Connect to a replica set providing two seed nodes and allowing reads from a secondary node:
76
- # ReplSetConnection.new(['localhost', 30000], ['localhost', 30001], :read_secondary => true)
74
+ # ReplSetConnection.new(['localhost:30000', 'localhost:30001'], :read => :secondary)
77
75
  #
78
76
  # @see http://api.mongodb.org/ruby/current/file.REPLICA_SETS.html Replica sets in Ruby
79
77
  #
@@ -85,23 +83,33 @@ module Mongo
85
83
  else
86
84
  opts = {}
87
85
  end
88
-
86
+
89
87
  unless args.length > 0
90
88
  raise MongoArgumentError, "A ReplSetConnection requires at least one seed node."
91
89
  end
90
+
91
+ # This is temporary until support for the old format is dropped
92
+ @seeds = []
93
+ if args.first.last.is_a?(Integer)
94
+ warn "Initiating a ReplSetConnection with seeds passed as individual [host, port] array arguments is deprecated."
95
+ warn "Please specify hosts as an array of 'host:port' strings; the old format will be removed in v2.0"
96
+ @seeds = args
97
+ else
98
+ args.first.map do |host_port|
99
+ seed = host_port.split(":")
100
+ seed[1] = seed[1].to_i
101
+ seeds << seed
102
+ end
103
+ end
92
104
 
93
- # The original, immutable list of seed node.
94
105
  # TODO: add a method for replacing this list of node.
95
- @seeds = args
96
106
  @seeds.freeze
97
107
 
98
- # TODO: get rid of this
99
- @nodes = @seeds.dup
100
-
101
108
  # Refresh
102
109
  @refresh_mode = opts.fetch(:refresh_mode, false)
103
110
  @refresh_interval = opts[:refresh_interval] || 90
104
111
  @last_refresh = Time.now
112
+ @refresh_version = 0
105
113
 
106
114
  # No connection manager by default.
107
115
  @manager = nil
@@ -127,7 +135,6 @@ module Mongo
127
135
  end
128
136
 
129
137
  @connected = false
130
- @refresh_version = 0
131
138
 
132
139
  # Replica set name
133
140
  if opts[:rs_name]
@@ -212,7 +219,6 @@ module Mongo
212
219
  @manager = background_manager
213
220
  old_manager.close(:soft => true)
214
221
  @refresh_version += 1
215
-
216
222
  return true
217
223
  end
218
224
 
@@ -312,10 +318,8 @@ module Mongo
312
318
  else
313
319
  connect
314
320
  end
315
-
316
321
  begin
317
322
  socket = get_socket_from_pool(self.read_pool)
318
-
319
323
  if !socket
320
324
  connect
321
325
  socket = get_socket_from_pool(self.primary_pool)
@@ -331,6 +335,26 @@ module Mongo
331
335
  raise ConnectionFailure.new("Could not connect to a node for reading.")
332
336
  end
333
337
  end
338
+
339
+ def checkout_secondary
340
+ if connected?
341
+ sync_refresh
342
+ else
343
+ connect
344
+ end
345
+ begin
346
+ socket = get_socket_from_pool(self.secondary_pool)
347
+ rescue => ex
348
+ checkin(socket) if socket
349
+ raise ex
350
+ end
351
+
352
+ if socket
353
+ socket
354
+ else
355
+ raise ConnectionFailure.new("Could not connect to a secondary for reading.")
356
+ end
357
+ end
334
358
 
335
359
  # Checkout a socket for writing (i.e., a primary node).
336
360
  def checkout_writer
@@ -419,6 +443,10 @@ module Mongo
419
443
  def read_pool
420
444
  @manager ? @manager.read_pool : nil
421
445
  end
446
+
447
+ def secondary_pool
448
+ @manager ? @manager.secondary_pool : nil
449
+ end
422
450
 
423
451
  def secondary_pools
424
452
  @manager ? @manager.secondary_pools : []
@@ -440,64 +468,12 @@ module Mongo
440
468
 
441
469
  # Generic initialization code.
442
470
  def setup(opts)
443
- # Default maximum BSON object size
444
- @max_bson_size = Mongo::DEFAULT_MAX_BSON_SIZE
445
-
446
471
  @safe_mutex_lock = Mutex.new
447
472
  @safe_mutexes = Hash.new {|hash, key| hash[key] = Mutex.new}
448
-
449
- # Determine whether to use SSL.
450
- @ssl = opts.fetch(:ssl, false)
451
- if @ssl
452
- @socket_class = Mongo::SSLSocket
453
- else
454
- @socket_class = ::TCPSocket
455
- end
456
-
457
- # Authentication objects
458
- @auths = opts.fetch(:auths, [])
459
-
460
- # Lock for request ids.
461
- @id_lock = Mutex.new
462
-
463
- # Pool size and timeout.
464
- @pool_size = opts[:pool_size] || 1
465
- if opts[:timeout]
466
- warn "The :timeout option has been deprecated " +
467
- "and will be removed in the 2.0 release. Use :pool_timeout instead."
468
- end
469
- @pool_timeout = opts[:pool_timeout] || opts[:timeout] || 5.0
470
-
471
- # Timeout on socket read operation.
472
- @op_timeout = opts[:op_timeout] || 30
473
-
474
- # Timeout on socket connect.
475
- @connect_timeout = opts[:connect_timeout] || 30
476
-
477
- # Mutex for synchronizing pool access
478
- # TODO: remove this.
479
- @connection_mutex = Mutex.new
480
-
481
- # Global safe option. This is false by default.
482
- @safe = opts[:safe] || false
483
-
484
- # Condition variable for signal and wait
485
- @queue = ConditionVariable.new
486
-
487
- @logger = opts[:logger] || nil
488
-
489
- # Clean up connections to dead threads.
490
- @last_cleanup = Time.now
491
- @cleanup_lock = Mutex.new
492
-
493
- if @logger
494
- write_logging_startup_message
495
- end
496
-
497
- @last_refresh = Time.now
498
-
499
- should_connect = opts.fetch(:connect, true)
500
- connect if should_connect
473
+
474
+ opts[:connect_timeout] = opts[:connect_timeout] || 30
475
+
476
+ super opts
501
477
  end
502
478
 
503
479
  # Checkout a socket connected to a node with one of
@@ -1,8 +1,10 @@
1
1
  module Mongo
2
2
  module Logging
3
3
 
4
+ DEBUG_LEVEL = defined?(Logger) ? Logger::DEBUG : 0
5
+
4
6
  def write_logging_startup_message
5
- log(:warn, "Please note that logging negatively impacts client-side performance. " +
7
+ log(:debug, "Please note that logging negatively impacts client-side performance. " +
6
8
  "You should set your logging level no lower than :info in production.")
7
9
  end
8
10
 
@@ -10,37 +12,43 @@ module Mongo
10
12
  def log(level, msg)
11
13
  return unless @logger
12
14
  case level
13
- when :debug then
14
- @logger.debug "MONGODB [DEBUG] #{msg}"
15
- when :warn then
16
- @logger.warn "MONGODB [WARNING] #{msg}"
17
- when :error then
18
- @logger.error "MONGODB [ERROR] #{msg}"
19
15
  when :fatal then
20
16
  @logger.fatal "MONGODB [FATAL] #{msg}"
21
- else
17
+ when :error then
18
+ @logger.error "MONGODB [ERROR] #{msg}"
19
+ when :warn then
20
+ @logger.warn "MONGODB [WARNING] #{msg}"
21
+ when :info then
22
22
  @logger.info "MONGODB [INFO] #{msg}"
23
+ when :debug then
24
+ @logger.debug "MONGODB [DEBUG] #{msg}"
25
+ else
26
+ @logger.debug "MONGODB [DEBUG] #{msg}"
23
27
  end
24
28
  end
25
29
 
26
30
  # Execute the block and log the operation described by name and payload.
27
31
  def instrument(name, payload = {}, &blk)
32
+ start_time = Time.now
28
33
  res = yield
29
- log_operation(name, payload)
34
+ if @logger && (@logger.level == DEBUG_LEVEL)
35
+ log_operation(name, payload, start_time)
36
+ end
30
37
  res
31
38
  end
32
39
 
33
40
  protected
34
41
 
35
- def log_operation(name, payload)
36
- @logger ||= nil
37
- return unless @logger
38
- msg = "#{payload[:database]}['#{payload[:collection]}'].#{name}("
39
- msg += payload.values_at(:selector, :document, :documents, :fields ).compact.map(&:inspect).join(', ') + ")"
40
- msg += ".skip(#{payload[:skip]})" if payload[:skip]
41
- msg += ".limit(#{payload[:limit]})" if payload[:limit]
42
- msg += ".sort(#{payload[:order]})" if payload[:order]
43
- @logger.debug "MONGODB #{msg}"
42
+ def log_operation(name, payload, start_time)
43
+ msg = "MONGODB "
44
+ msg << "(#{((Time.now - start_time) * 1000).to_i}ms) "
45
+ msg << "#{payload[:database]}['#{payload[:collection]}'].#{name}("
46
+ msg << payload.values_at(:selector, :document, :documents, :fields ).compact.map(&:inspect).join(', ') + ")"
47
+ msg << ".skip(#{payload[:skip]})" if payload[:skip]
48
+ msg << ".limit(#{payload[:limit]})" if payload[:limit]
49
+ msg << ".sort(#{payload[:order]})" if payload[:order]
50
+
51
+ @logger.debug(msg)
44
52
  end
45
53
 
46
54
  end
@@ -101,7 +101,12 @@ module Mongo
101
101
  rescue ConnectionFailure, OperationFailure, OperationTimeout, SocketError, SystemCallError, IOError => ex
102
102
  @connection.log(:warn, "Attempted connection to node #{host_string} raised " +
103
103
  "#{ex.class}: #{ex.message}")
104
- @socket.close unless @socket.closed?
104
+
105
+ # Socket may already be nil from issuing command
106
+ if @socket && !@socket.closed?
107
+ @socket.close
108
+ end
109
+
105
110
  return nil
106
111
  end
107
112
 
@@ -166,12 +171,16 @@ module Mongo
166
171
  [host, port]
167
172
  end
168
173
 
169
- # Ensure that this node is a member of a replica set.
174
+ # Ensure that this node is a healty member of a replica set.
170
175
  def check_set_membership(config)
171
176
  if !config['hosts']
172
177
  message = "Will not connect to #{host_string} because it's not a member " +
173
178
  "of a replica set."
174
179
  raise ConnectionFailure, message
180
+ elsif config['hosts'].length == 1 && !config['ismaster'] &&
181
+ !config['secondary']
182
+ message = "Attempting to connect to an unhealthy, single-node replica set."
183
+ raise ConnectionFailure, message
175
184
  end
176
185
  end
177
186
 
@@ -2,8 +2,8 @@ module Mongo
2
2
  class PoolManager
3
3
 
4
4
  attr_reader :connection, :arbiters, :primary, :secondaries, :primary_pool,
5
- :read_pool, :secondary_pools, :hosts, :nodes, :max_bson_size,
6
- :tags_to_pools, :tag_map, :members
5
+ :read_pool, :secondary_pool, :secondary_pools, :hosts, :nodes,
6
+ :max_bson_size, :tags_to_pools, :tag_map, :members
7
7
 
8
8
  # Create a new set of connection pools.
9
9
  #
@@ -135,12 +135,12 @@ module Mongo
135
135
  end
136
136
 
137
137
  def initialize_data
138
- @seeds = []
139
138
  @primary = nil
140
139
  @primary_pool = nil
141
140
  @read_pool = nil
142
141
  @arbiters = []
143
142
  @secondaries = []
143
+ @secondary_pool = nil
144
144
  @secondary_pools = []
145
145
  @hosts = Set.new
146
146
  @members = Set.new
@@ -234,11 +234,13 @@ module Mongo
234
234
  # time to figure out which nodes to choose from.
235
235
  def set_read_pool
236
236
  if @secondary_pools.empty?
237
- @read_pool = @primary_pool
237
+ @read_pool = @primary_pool
238
238
  elsif @secondary_pools.size == 1
239
- @read_pool = @secondary_pools[0]
239
+ @read_pool = @secondary_pools[0]
240
+ @secondary_pool = @read_pool
240
241
  else
241
242
  @read_pool = nearby_pool_from_set(@secondary_pools)
243
+ @secondary_pool = @read_pool
242
244
  end
243
245
  end
244
246