diff --git a/.gitignore b/.gitignore index 22df448ccf..65424f0e99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ docs .yardoc +coverage *.gem nbproject *.bundle diff --git a/.travis.yml b/.travis.yml index 5151313a1e..016a755b4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,7 @@ script: bundle exec rake test:ruby notifications: email: false - #TODO: flowdock: [api token] + flowdock: 1da4416b8ff98d1880986472428b1b1b services: - mongodb diff --git a/Gemfile b/Gemfile index 25c048faf6..23ddf41ba6 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,7 @@ group :deploy do end group :testing do + gem 'simplecov' gem 'test-unit' gem 'mocha', '0.12.7' gem 'shoulda' diff --git a/README.md b/README.md index e0c70c7c0b..b529e4173a 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Here's a quick code sample. Again, see the [MongoDB Ruby Tutorial](https://githu require 'rubygems' require 'mongo' -@client = Mongo::Client.new('localhost', 27017, :safe => true) +@client = Mongo::Client.new('localhost', 27017) @db = @client['sample-db'] @coll = @db['test'] diff --git a/bin/mongo_console b/bin/mongo_console index b3953c4e3c..0549f0dece 100755 --- a/bin/mongo_console +++ b/bin/mongo_console @@ -14,7 +14,7 @@ port = org_argv[1] || ENV['MONGO_RUBY_DRIVER_PORT'] || Client::DEFAULT_PORT dbnm = org_argv[2] || ENV['MONGO_RUBY_DRIVER_DB'] || 'ruby-mongo-console' puts "Connecting to #{host}:#{port} (CLIENT) on with database #{dbnm} (DB)" -CLIENT = Client.new(host, port, :safe => true) +CLIENT = Client.new(host, port) DB = CLIENT.db(dbnm) puts "Starting IRB session..." diff --git a/examples/admin.rb b/examples/admin.rb index 10b6c3bc0f..8e11f32fac 100644 --- a/examples/admin.rb +++ b/examples/admin.rb @@ -9,7 +9,7 @@ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Client::DEFAULT_PORT puts "Connecting to #{host}:#{port}" -client = Mongo::Client.new(host, port, :safe => true) +client = Mongo::Client.new(host, port) db = client.db('ruby-mongo-examples') coll = db.create_collection('test') diff --git a/examples/capped.rb b/examples/capped.rb index be832a7345..c17809e4b3 100644 --- a/examples/capped.rb +++ b/examples/capped.rb @@ -7,7 +7,7 @@ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Client::DEFAULT_PORT puts "Connecting to #{host}:#{port}" -db = Client.new(host, port, :safe => true).db('ruby-mongo-examples') +db = Client.new(host, port).db('ruby-mongo-examples') db.drop_collection('test') # A capped collection has a max size and, optionally, a max number of records. diff --git a/examples/cursor.rb b/examples/cursor.rb index 59bdb101db..e82af392ab 100644 --- a/examples/cursor.rb +++ b/examples/cursor.rb @@ -9,7 +9,7 @@ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Client::DEFAULT_PORT puts "Connecting to #{host}:#{port}" -db = Client.new(host, port, :safe => true).db('ruby-mongo-examples') +db = Client.new(host, port).db('ruby-mongo-examples') coll = db.collection('test') # Erase all records from collection, if any diff --git a/examples/gridfs.rb b/examples/gridfs.rb index 4b1eb5f4c1..13b2768983 100644 --- a/examples/gridfs.rb +++ b/examples/gridfs.rb @@ -10,7 +10,7 @@ def assert port = ENV['MONGO_RUBY_DRIVER_PORT'] || Client::DEFAULT_PORT puts "Connecting to #{host}:#{port}" -db = Client.new(host, port, :safe => true).db('ruby-mongo-examples') +db = Client.new(host, port).db('ruby-mongo-examples') data = "hello, world!" diff --git a/examples/index_test.rb b/examples/index_test.rb index 5a8795f98e..ab5612650f 100644 --- a/examples/index_test.rb +++ b/examples/index_test.rb @@ -8,7 +8,7 @@ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Client::DEFAULT_PORT puts ">> Connecting to #{host}:#{port}" -db = Client.new(host, port, :safe => true).db('ruby-mongo-index_test') +db = Client.new(host, port).db('ruby-mongo-index_test') class Exception def errmsg diff --git a/examples/info.rb b/examples/info.rb index 8393f656a1..a0b6b53516 100644 --- a/examples/info.rb +++ b/examples/info.rb @@ -8,7 +8,7 @@ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Client::DEFAULT_PORT puts "Connecting to #{host}:#{port}" -db = Client.new(host, port, :safe => true).db('ruby-mongo-examples') +db = Client.new(host, port).db('ruby-mongo-examples') coll = db.collection('test') # Erase all records from collection, if any diff --git a/examples/queries.rb b/examples/queries.rb index 5e185a0ee1..3c1ce6c222 100644 --- a/examples/queries.rb +++ b/examples/queries.rb @@ -9,7 +9,7 @@ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Client::DEFAULT_PORT puts "Connecting to #{host}:#{port}" -db = Client.new(host, port, :safe => true).db('ruby-mongo-examples') +db = Client.new(host, port).db('ruby-mongo-examples') coll = db.collection('test') # Remove all records, if any diff --git a/examples/simple.rb b/examples/simple.rb index f1de48197c..8dfdbb921f 100644 --- a/examples/simple.rb +++ b/examples/simple.rb @@ -9,7 +9,7 @@ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Client::DEFAULT_PORT puts "Connecting to #{host}:#{port}" -db = Client.new(host, port, :safe => true).db('ruby-mongo-examples') +db = Client.new(host, port).db('ruby-mongo-examples') coll = db.collection('test') # Erase all records from collection, if any diff --git a/examples/strict.rb b/examples/strict.rb index c58da4be73..76a7610fa3 100644 --- a/examples/strict.rb +++ b/examples/strict.rb @@ -8,7 +8,7 @@ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Client::DEFAULT_PORT puts "Connecting to #{host}:#{port}" -db = Client.new(host, port, :safe => true).db('ruby-mongo-examples') +db = Client.new(host, port).db('ruby-mongo-examples') db.drop_collection('does-not-exist') db.create_collection('test') diff --git a/examples/types.rb b/examples/types.rb index d09d8f6a24..a8ee1ce2a5 100644 --- a/examples/types.rb +++ b/examples/types.rb @@ -9,7 +9,7 @@ port = ENV['MONGO_RUBY_DRIVER_PORT'] || Client::DEFAULT_PORT puts "Connecting to #{host}:#{port}" -db = Client.new(host, port, :safe => true).db('ruby-mongo-examples') +db = Client.new(host, port).db('ruby-mongo-examples') coll = db.collection('test') # Remove all records, if any diff --git a/examples/web/thin/load.rb b/examples/web/thin/load.rb index fbe5ca27f1..9f882d4da1 100644 --- a/examples/web/thin/load.rb +++ b/examples/web/thin/load.rb @@ -1,7 +1,7 @@ require File.join(File.dirname(__FILE__), '..', '..', '..', 'lib', 'mongo') require 'logger' -$con = Mongo::ReplSetClient.new(['localhost:30000', 'localhost:30001'], :safe => true, :read => :secondary, :refresh_mode => :sync, :refresh_interval => 30) +$con = Mongo::ReplSetClient.new(['localhost:30000', 'localhost:30001'], :read => :secondary, :refresh_mode => :sync, :refresh_interval => 30) $db = $con['foo'] class Load < Sinatra::Base diff --git a/examples/web/unicorn/load.rb b/examples/web/unicorn/load.rb index 0b675876b0..4e93cff4a6 100644 --- a/examples/web/unicorn/load.rb +++ b/examples/web/unicorn/load.rb @@ -1,6 +1,6 @@ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'mongo') -$client = Mongo::Client.new('localhost', 27017, :safe => true) +$client = Mongo::Client.new('localhost', 27017) $db = $client['foo'] class Load < Sinatra::Base diff --git a/examples/web/unicorn/unicorn.rb.template b/examples/web/unicorn/unicorn.rb.template index d832838652..6c0b2d162c 100644 --- a/examples/web/unicorn/unicorn.rb.template +++ b/examples/web/unicorn/unicorn.rb.template @@ -23,7 +23,7 @@ stdout_path "#{@dir}log/unicorn.stdout.log" # NOTE: You need this when using forking web servers! after_fork do |server, worker| $client.close if $client - $client = Mongo::Client.new('localhost', 27017, :safe => true) + $client = Mongo::Client.new('localhost', 27017) $db = $client['foo'] STDERR << "FORKED #{server} #{worker}" end diff --git a/lib/bson/version.rb b/lib/bson/version.rb deleted file mode 100644 index 8257562d8d..0000000000 --- a/lib/bson/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module BSON - VERSION = "1.7.1" -end diff --git a/lib/mongo.rb b/lib/mongo.rb index 6dbc0a0b7c..7a266e9ab4 100644 --- a/lib/mongo.rb +++ b/lib/mongo.rb @@ -52,6 +52,7 @@ module Constants require 'mongo/util/conversions' require 'mongo/util/support' +require 'mongo/util/write_concern' require 'mongo/util/core_ext' require 'mongo/util/logging' require 'mongo/util/node' @@ -63,15 +64,16 @@ module Constants require 'mongo/util/tcp_socket' require 'mongo/util/uri_parser' -require 'mongo/collection' + require 'mongo/networking' require 'mongo/client' require 'mongo/repl_set_client' require 'mongo/sharded_client' +require 'mongo/legacy' +require 'mongo/collection' require 'mongo/cursor' require 'mongo/db' require 'mongo/exceptions' -require 'mongo/legacy' require 'mongo/gridfs/grid_ext' require 'mongo/gridfs/grid' require 'mongo/gridfs/grid_io' diff --git a/lib/mongo/client.rb b/lib/mongo/client.rb index 9b27e98314..a7ffafa8cd 100644 --- a/lib/mongo/client.rb +++ b/lib/mongo/client.rb @@ -19,28 +19,44 @@ require 'set' require 'socket' require 'thread' + module Mongo # Instantiates and manages self.connections to MongoDB. class Client include Mongo::Logging include Mongo::Networking + include Mongo::WriteConcern - TCPSocket = Mongo::TCPSocket - Mutex = ::Mutex - ConditionVariable = ::ConditionVariable + TCPSocket = Mongo::TCPSocket + Mutex = ::Mutex + ConditionVariable = ::ConditionVariable - DEFAULT_HOST = 'localhost' - DEFAULT_PORT = 27017 - DEFAULT_DB_NAME = 'test' - GENERIC_OPTS = [:ssl, :auths, :pool_size, :pool_timeout, :timeout, :op_timeout, :connect_timeout, :safe, :logger, :connect] - CONNECTION_OPTS = [:slave_ok] + DEFAULT_HOST = 'localhost' + DEFAULT_PORT = 27017 + DEFAULT_DB_NAME = 'test' + GENERIC_OPTS = [:ssl, :auths, :logger, :connect] + TIMEOUT_OPTS = [:timeout, :op_timeout, :connect_timeout] + POOL_OPTS = [:pool_size, :pool_timeout] + WRITE_CONCERN_OPTS = [:w, :j, :fsync, :wtimeout] + CLIENT_ONLY_OPTS = [:slave_ok] mongo_thread_local_accessor :connections - attr_reader :logger, :size, :auths, :primary, :safe, :host_to_try, - :pool_size, :connect_timeout, :pool_timeout, - :primary_pool, :socket_class, :op_timeout, :tag_sets, :acceptable_latency + attr_reader :logger, + :size, + :auths, + :primary, + :write_concern, + :host_to_try, + :pool_size, + :connect_timeout, + :pool_timeout, + :primary_pool, + :socket_class, + :op_timeout, + :tag_sets, + :acceptable_latency # Create a connection to single MongoDB instance. # @@ -59,10 +75,10 @@ class Client # @param [String, Hash] host # @param [Integer] port specify a port number here if only one host is being specified. # - # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options - # propagated to DB objects instantiated off of this Client. This - # default can be overridden upon instantiation of any DB by explicitly setting a :safe value - # on initialization. + # @option opts [Hash] :w (1), :j (false), :wtimeout (false), :fsync (false) Set the default write concern + # options propagated to DB objects instantiated off of this Client. + # This default can be overridden upon instantiation of any DB by explicitly setting an options hash + # on initialization. It can also be overridden at instantiation of a collection or at the time of a write operation. # @option opts [Boolean] :slave_ok (false) Must be set to +true+ when connecting # to a single, slave node. # @option opts [Logger, #debug] :logger (nil) A Logger instance for debugging driver ops. Note that @@ -104,7 +120,7 @@ def initialize(host=nil, port=nil, opts={}) if parser.replicaset? raise MongoArgumentError, "Mongo::Client.new called with no arguments, but ENV['MONGODB_URI'] implies a replica set." end - opts = parser.connection_options.merge! opts + opts.merge!(parser.connection_options) @host_to_try = [parser.host, parser.port] elsif host.is_a?(String) && (port || DEFAULT_PORT).respond_to?(:to_i) @host_to_try = [host, (port || DEFAULT_PORT).to_i] @@ -169,7 +185,8 @@ def self.multi(nodes, opts={}) # Initialize a connection to MongoDB using the MongoDB URI spec. # - # Since Client.new cannot be used with any ENV["MONGODB_URI"] that has multiple hosts (implying a replicaset), you may use this when the type of your connection varies by environment and should be determined solely from ENV["MONGODB_URI"]. + # Since Client.new cannot be used with any ENV["MONGODB_URI"] that has multiple hosts (implying a replicaset), + # you may use this when the type of your connection varies by environment and should be determined solely from ENV["MONGODB_URI"]. # # @param uri [String] # A string of the format mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database] @@ -530,7 +547,11 @@ def checkin(socket) protected def valid_opts - GENERIC_OPTS + CONNECTION_OPTS + GENERIC_OPTS + + CLIENT_ONLY_OPTS + + POOL_OPTS + + WRITE_CONCERN_OPTS + + TIMEOUT_OPTS end def check_opts(opts) @@ -544,10 +565,10 @@ def check_opts(opts) # Parse option hash def setup(opts) # slave_ok can be true only if one node is specified - @slave_ok = opts[:slave_ok] + @slave_ok = opts.delete(:slave_ok) # Determine whether to use SSL. - @ssl = opts.fetch(:ssl, false) + @ssl = opts.delete(:ssl) if @ssl @socket_class = Mongo::SSLSocket else @@ -555,27 +576,27 @@ def setup(opts) end # Authentication objects - @auths = opts.fetch(:auths, []) + @auths = opts.delete(:auths) || [] # Pool size and timeout. - @pool_size = opts[:pool_size] || 1 + @pool_size = opts.delete(:pool_size) || 1 if opts[:timeout] warn "The :timeout option has been deprecated " + "and will be removed in the 2.0 release. Use :pool_timeout instead." end - @pool_timeout = opts[:pool_timeout] || opts[:timeout] || 5.0 + @pool_timeout = opts.delete(:pool_timeout) || opts.delete(:timeout) || 5.0 # Timeout on socket read operation. - @op_timeout = opts[:op_timeout] || nil + @op_timeout = opts.delete(:op_timeout) || nil # Timeout on socket connect. - @connect_timeout = opts[:connect_timeout] || nil - - # Global safe option. This is false by default. - @safe = opts[:safe] || false + @connect_timeout = opts.delete(:connect_timeout) || nil @logger = opts.fetch(:logger, nil) + # Connection level write concern options. + @write_concern = get_write_concern(opts) + if @logger write_logging_startup_message end diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 5fee9258af..387e2b7fa9 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -20,11 +20,18 @@ module Mongo # A named collection of documents in a database. class Collection include Mongo::Logging + include Mongo::WriteConcern - attr_reader :db, :name, :pk_factory, :hint, :safe + attr_reader :db, + :name, + :pk_factory, + :hint, + :write_concern # Read Preference - attr_accessor :read_preference, :tag_sets, :acceptable_latency + attr_accessor :read_preference, + :tag_sets, + :acceptable_latency # Initialize a collection object. # @@ -34,10 +41,10 @@ class Collection # @option opts [:create_pk] :pk (BSON::ObjectId) A primary key factory to use # other than the default BSON::ObjectId. # - # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options + # @option opts [Hash] :w, :j, :wtimeout, :fsync Set the default write concern # for +insert+, +update+, and +remove+ method called on this Collection instance. If no - # value is provided, the default value set on this instance's DB will be used. This - # default can be overridden for any invocation of +insert+, +update+, or +remove+. + # value is provided, the default values set on this instance's DB will be used. These option + # values can be overridden for any invocation of +insert+, +update+, or +remove+. # @option options [:primary, :secondary] :read The default read preference for queries # initiates from this connection object. If +:secondary+ is chosen, reads will be sent # to one of the closest available secondary nodes. If a secondary node cannot be located, the @@ -93,7 +100,7 @@ def initialize(name, db, opts={}) @cache_time = @db.cache_time @cache = Hash.new(0) unless pk_factory - @safe = opts.fetch(:safe, @db.safe) + @write_concern = get_write_concern(opts, db) if value = opts[:read] Mongo::Support.validate_read_preference(value) else @@ -312,22 +319,24 @@ def find_one(spec_or_object_id=nil, opts={}) # # @return [ObjectId] the _id of the saved document. # - # @option opts [Boolean, Hash] :safe (+false+) - # run the operation in safe mode, which runs a +getlasterror+ command on the - # database to report any assertion. In addition, a hash can be provided to - # run an fsync and/or wait for replication (>= 1.5.1). Safe - # options provided here will override any safe options set on this collection, + # @option opts [Hash] :w, :j, :wtimeout, :fsync Set the write concern for this operation. + # :w > 0 will run a +getlasterror+ command on the database to report any assertion. + # :j will confirm a write has been committed to the journal + # :wtimeout specifies how long to wait for write confirmation + # :fsync will confirm that a write has been fsynced + # options provided here will override any write concern options set on this collection, # its database object, or the current connection. See the options # for +DB#get_last_error+. # - # @raise [Mongo::OperationFailure] will be raised iff safe mode is enabled and the operation fails. + # @raise [Mongo::OperationFailure] will be raised iff :w > 0 and the operation fails. def save(doc, opts={}) + write_concern = get_write_concern(opts, self) if doc.has_key?(:_id) || doc.has_key?('_id') id = doc[:_id] || doc['_id'] - update({:_id => id}, doc, :upsert => true, :safe => opts.fetch(:safe, @safe)) + update({:_id => id}, doc, :upsert => true, :write_concern => write_concern) id else - insert(doc, :safe => opts.fetch(:safe, @safe)) + insert(doc, write_concern) end end @@ -343,33 +352,34 @@ def save(doc, opts={}) # 2nd, a list of invalid documents. # Return this result format only when :collect_on_error is true. # - # @option opts [Boolean, Hash] :safe (+false+) - # run the operation in safe mode, which runs a +getlasterror+ command on the - # database to report any assertion. In addition, a hash can be provided to - # run an fsync and/or wait for replication of the insert (>= 1.5.1). Safe - # options provided here will override any safe options set on this collection, + # @option opts [Hash] :w, :j, :wtimeout, :fsync Set the write concern for this operation. + # :w > 0 will run a +getlasterror+ command on the database to report any assertion. + # :j will confirm a write has been committed to the journal + # :wtimeout specifies how long to wait for write confirmation + # :fsync will confirm that a write has been fsynced + # options provided here will override any write concern options set on this collection, # its database object, or the current connection. See the options # for +DB#get_last_error+. # # @option opts [Boolean] :continue_on_error (+false+) If true, then # continue a bulk insert even if one of the documents inserted # triggers a database assertion (as in a duplicate insert, for instance). - # If not using safe mode, the list of ids returned will + # If not acknowledging writes, the list of ids returned will # include the object ids of all documents attempted on insert, even - # if some are rejected on error. When safe mode is - # enabled, any error will raise an OperationFailure exception. + # if some are rejected on error. When acknowledging writes, any error will raise an + # OperationFailure exception. # MongoDB v2.0+. # @option opts [Boolean] :collect_on_error (+false+) if true, then # collects invalid documents as an array. Note that this option changes the result format. # - # @raise [Mongo::OperationFailure] will be raised iff safe mode is enabled and the operation fails. + # @raise [Mongo::OperationFailure] will be raised iff :w > 0 and the operation fails. # # @core insert insert-instance_method def insert(doc_or_docs, opts={}) doc_or_docs = [doc_or_docs] unless doc_or_docs.is_a?(Array) doc_or_docs.collect! { |doc| @pk_factory.create_pk(doc) } - safe = opts.fetch(:safe, @safe) - result = insert_documents(doc_or_docs, @name, true, safe, opts) + write_concern = get_write_concern(opts, self) + result = insert_documents(doc_or_docs, @name, true, write_concern, opts) result.size > 1 ? result : result.first end alias_method :<<, :insert @@ -379,13 +389,14 @@ def insert(doc_or_docs, opts={}) # @param [Hash] selector # If specified, only matching documents will be removed. # - # @option opts [Boolean, Hash] :safe (+false+) - # run the operation in safe mode, which runs a +getlasterror+ command on the - # database to report any assertion. In addition, a hash can be provided to - # run an fsync and/or wait for replication of the remove (>= 1.5.1). Safe - # options provided here will override any safe options set on this collection, + # @option opts [Hash] :w, :j, :wtimeout, :fsync Set the write concern for this operation. + # :w > 0 will run a +getlasterror+ command on the database to report any assertion. + # :j will confirm a write has been committed to the journal + # :wtimeout specifies how long to wait for write confirmation + # :fsync will confirm that a write has been fsynced + # options provided here will override any write concern options set on this collection, # its database object, or the current connection. See the options - # for +DB#get_last_error+. + # for +DB#get_last_error+. # # @example remove all documents from the 'users' collection: # users.remove @@ -394,23 +405,22 @@ def insert(doc_or_docs, opts={}) # @example remove only documents that have expired: # users.remove({:expire => {"$lte" => Time.now}}) # - # @return [Hash, true] Returns a Hash containing the last error object if running in safe mode. + # @return [Hash, true] Returns a Hash containing the last error object if acknowledging writes # Otherwise, returns true. # - # @raise [Mongo::OperationFailure] will be raised iff safe mode is enabled and the operation fails. + # @raise [Mongo::OperationFailure] will be raised iff :w > 0 and the operation fails. # # @core remove remove-instance_method def remove(selector={}, opts={}) - # Initial byte is 0. - safe = opts.fetch(:safe, @safe) + write_concern = get_write_concern(opts, self) message = BSON::ByteBuffer.new("\0\0\0\0") BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}") message.put_int(0) message.put_binary(BSON::BSON_CODER.serialize(selector, false, true, @connection.max_bson_size).to_s) instrument(:remove, :database => @db.name, :collection => @name, :selector => selector) do - if safe - @connection.send_message_with_safe_check(Mongo::Constants::OP_DELETE, message, @db.name, nil, safe) + if Mongo::WriteConcern.gle?(write_concern) + @connection.send_message_with_acknowledge(Mongo::Constants::OP_DELETE, message, @db.name, nil, write_concern) else @connection.send_message(Mongo::Constants::OP_DELETE, message) true @@ -432,23 +442,24 @@ def remove(selector={}, opts={}) # @option opts [Boolean] :upsert (+false+) if true, performs an upsert (update or insert) # @option opts [Boolean] :multi (+false+) update all documents matching the selector, as opposed to # just the first matching document. Note: only works in MongoDB 1.1.3 or later. - # @option opts [Boolean, Hash] :safe (+false+) - # run the operation in safe mode, which runs a +getlasterror+ command on the - # database to report any assertion. In addition, a hash can be provided to - # run an fsync and/or wait for replication of the update (>= 1.5.1). Safe - # options provided here will override any safe options set on this collection, + # @option opts [Hash] :w, :j, :wtimeout, :fsync Set the write concern for this operation. + # :w > 0 will run a +getlasterror+ command on the database to report any assertion. + # :j will confirm a write has been committed to the journal + # :wtimeout specifies how long to wait for write confirmation + # :fsync will confirm that a write has been fsynced + # options provided here will override any write concern options set on this collection, # its database object, or the current connection. See the options - # for +DB#get_last_error+. + # for +DB#get_last_error+. # - # @return [Hash, true] Returns a Hash containing the last error object if running in safe mode. + # @return [Hash, true] Returns a Hash containing the last error object if acknowledging writes. # Otherwise, returns true. # - # @raise [Mongo::OperationFailure] will be raised iff safe mode is enabled and the operation fails. + # @raise [Mongo::OperationFailure] will be raised iff :w > 0 and the operation fails. # # @core update update-instance_method def update(selector, document, opts={}) # Initial byte is 0. - safe = opts.fetch(:safe, @safe) + write_concern = get_write_concern(opts, self) message = BSON::ByteBuffer.new("\0\0\0\0") BSON::BSON_RUBY.serialize_cstr(message, "#{@db.name}.#{@name}") update_options = 0 @@ -463,8 +474,8 @@ def update(selector, document, opts={}) message.put_binary(BSON::BSON_CODER.serialize(document, check_keys, true, @connection.max_bson_size).to_s) instrument(:update, :database => @db.name, :collection => @name, :selector => selector, :document => document) do - if safe - @connection.send_message_with_safe_check(Mongo::Constants::OP_UPDATE, message, @db.name, nil, safe) + if Mongo::WriteConcern.gle?(write_concern) + @connection.send_message_with_acknowledge(Mongo::Constants::OP_UPDATE, message, @db.name, nil, write_concern) else @connection.send_message(Mongo::Constants::OP_UPDATE, message) end @@ -987,7 +998,7 @@ def generate_indexes(field_spec, name, opts) selector.merge!(opts) begin - insert_documents([selector], Mongo::DB::SYSTEM_INDEX_COLLECTION, false, true) + insert_documents([selector], Mongo::DB::SYSTEM_INDEX_COLLECTION, false, {:w => 1}) rescue Mongo::OperationFailure => e if selector[:dropDups] && e.message =~ /^11000/ @@ -1004,7 +1015,7 @@ def generate_indexes(field_spec, name, opts) # Sends a Mongo::Constants::OP_INSERT message to the database. # Takes an array of +documents+, an optional +collection_name+, and a # +check_keys+ setting. - def insert_documents(documents, collection_name=@name, check_keys=true, safe=false, flags={}) + def insert_documents(documents, collection_name=@name, check_keys=true, write_concern={}, flags={}) if flags[:continue_on_error] message = BSON::ByteBuffer.new message.put_int(1) @@ -1036,8 +1047,8 @@ def insert_documents(documents, collection_name=@name, check_keys=true, safe=fal raise InvalidOperation, "Exceded maximum insert size of 16,777,216 bytes" if message.size > @connection.max_bson_size instrument(:insert, :database => @db.name, :collection => collection_name, :documents => documents) do - if safe - @connection.send_message_with_safe_check(Mongo::Constants::OP_INSERT, message, @db.name, nil, safe) + if Mongo::WriteConcern.gle?(write_concern) + @connection.send_message_with_acknowledge(Mongo::Constants::OP_INSERT, message, @db.name, nil, write_concern) else @connection.send_message(Mongo::Constants::OP_INSERT, message) end diff --git a/lib/mongo/db.rb b/lib/mongo/db.rb index 49dfff3174..5e8207b095 100644 --- a/lib/mongo/db.rb +++ b/lib/mongo/db.rb @@ -23,13 +23,14 @@ module Mongo # A MongoDB database. class DB + include Mongo::WriteConcern - SYSTEM_NAMESPACE_COLLECTION = "system.namespaces" - SYSTEM_INDEX_COLLECTION = "system.indexes" - SYSTEM_PROFILE_COLLECTION = "system.profile" - SYSTEM_USER_COLLECTION = "system.users" - SYSTEM_JS_COLLECTION = "system.js" - SYSTEM_COMMAND_COLLECTION = "$cmd" + SYSTEM_NAMESPACE_COLLECTION = 'system.namespaces' + SYSTEM_INDEX_COLLECTION = 'system.indexes' + SYSTEM_PROFILE_COLLECTION = 'system.profile' + SYSTEM_USER_COLLECTION = 'system.users' + SYSTEM_JS_COLLECTION = 'system.js' + SYSTEM_COMMAND_COLLECTION = '$cmd' # Counter for generating unique request ids. @@current_request_id = 0 @@ -44,8 +45,8 @@ class DB # Returns the value of the +strict+ flag. def strict?; @strict; end - # The name of the database and the local safe option. - attr_reader :name, :safe + # The name of the database and the local write concern options. + attr_reader :name, :write_concern # The Mongo::Client instance connecting to the MongoDB server. attr_reader :connection @@ -70,10 +71,10 @@ def strict?; @strict; end # fields the factory wishes to inject. (NOTE: if the object already has a primary key, # the factory should not inject a new key). # - # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options - # propagated to Collection objects instantiated off of this DB. If no - # value is provided, the default value set on this instance's Client object will be used. This - # default can be overridden upon instantiation of any collection by explicitly setting a :safe value + # @option opts [Hash] :w, :j, :wtimeout, :fsync Set the default write concern for this database. + # Propagated to Collection objects instantiated off of this DB. If no + # options are provided, the default write concern set on this instance's Client object will be used. This + # default can be overridden upon instantiation of any collection by explicitly setting write concern values # on initialization # # @option opts [Integer] :cache_time (300) Set the time that all ensure_index calls should cache the command. @@ -84,7 +85,9 @@ def initialize(name, client, opts={}) @connection = client @strict = opts[:strict] @pk_factory = opts[:pk] - @safe = opts.fetch(:safe, @connection.safe) + + @write_concern = get_write_concern(opts, client) + if value = opts[:read] Mongo::Support.validate_read_preference(value) else @@ -173,7 +176,7 @@ def add_stored_function(function_name, code) # @return [Boolean] def remove_stored_function(function_name) if self[SYSTEM_JS_COLLECTION].find_one({"_id" => function_name}) - self[SYSTEM_JS_COLLECTION].remove({"_id" => function_name}, :safe => true) + self[SYSTEM_JS_COLLECTION].remove({"_id" => function_name}, :w => 1) else return false end @@ -205,7 +208,7 @@ def add_user(username, password, read_only = false) # @return [Boolean] def remove_user(username) if self[SYSTEM_USER_COLLECTION].find_one({:user => username}) - self[SYSTEM_USER_COLLECTION].remove({:user => username}, :safe => true) + self[SYSTEM_USER_COLLECTION].remove({:user => username}, :w => 1) else return false end @@ -323,7 +326,6 @@ def collection(name, opts={}) "Currently in strict mode." else opts = opts.dup - opts[:safe] = opts.fetch(:safe, @safe) opts.merge!(:pk => @pk_factory) unless opts[:pk] Collection.new(name, self, opts) end diff --git a/lib/mongo/gridfs/grid.rb b/lib/mongo/gridfs/grid.rb index 2f6349f121..57f07c263b 100644 --- a/lib/mongo/gridfs/grid.rb +++ b/lib/mongo/gridfs/grid.rb @@ -62,8 +62,9 @@ def initialize(db, fs_name=DEFAULT_FS_NAME) # the content type will may be inferred from the filename extension if the mime-types gem can be # loaded. Otherwise, the content type 'binary/octet-stream' will be used. # @option opts [Integer] (262144) :chunk_size size of file chunks in bytes. - # @option opts [Boolean] :safe (false) When safe mode is enabled, the chunks sent to the server - # will be validated using an md5 hash. If validation fails, an exception will be raised. + # @option opts [Boolean] :w (1), :j (false), :wtimeout (false), :fsync (false) Set the default write concern + # When :w > 0, the chunks sent to the server are validated using an md5 hash. + # If validation fails, an exception will be raised. # # @return [BSON::ObjectId] the file's id. def put(data, opts={}) diff --git a/lib/mongo/gridfs/grid_file_system.rb b/lib/mongo/gridfs/grid_file_system.rb index f1e3bceb4a..013d31713a 100644 --- a/lib/mongo/gridfs/grid_file_system.rb +++ b/lib/mongo/gridfs/grid_file_system.rb @@ -69,7 +69,8 @@ def initialize(db, fs_name=Grid::DEFAULT_FS_NAME) # @option opts [Boolean] :delete_old (false) ensure that old versions of the file are deleted. This option # only work in 'w' mode. Certain precautions must be taken when deleting GridFS files. See the notes under # GridFileSystem#delete. - # @option opts [Boolean] :safe (false) When safe mode is enabled, the chunks sent to the server + # @option opts [Boolean] :w (1), :j (false), :wtimeout (false), :fsync (false) Set the default write concern + # When :w > 0, the chunks sent to the server # will be validated using an md5 hash. If validation fails, an exception will be raised. # @option opts [Integer] :versions (false) deletes all versions which exceed the number specified to # retain ordered by uploadDate. This option only works in 'w' mode. Certain precautions must be taken when diff --git a/lib/mongo/gridfs/grid_io.rb b/lib/mongo/gridfs/grid_io.rb index 8b1290f245..cdf4014360 100644 --- a/lib/mongo/gridfs/grid_io.rb +++ b/lib/mongo/gridfs/grid_io.rb @@ -48,7 +48,8 @@ class GridIO # @option opts [String] :content_type ('binary/octet-stream') If no content type is specified, # the content type will may be inferred from the filename extension if the mime-types gem can be # loaded. Otherwise, the content type 'binary/octet-stream' will be used. - # @option opts [Boolean] :safe (false) When safe mode is enabled, the chunks sent to the server + # @option opts [Boolean] :w (1), :j (false), :wtimeout (false), :fsync (false) Set the default write concern + # When :w > 0, the chunks sent to the server # will be validated using an md5 hash. If validation fails, an exception will be raised. def initialize(files, chunks, filename, mode, opts={}) @files = files diff --git a/lib/mongo/legacy.rb b/lib/mongo/legacy.rb index c98bc13e3f..863c34d729 100644 --- a/lib/mongo/legacy.rb +++ b/lib/mongo/legacy.rb @@ -17,26 +17,65 @@ # ++ module Mongo - # @deprecated Use Mongo::Client instead. + module LegacyWriteConcern + @legacy_write_concern = true + + def safe=(value) + @write_concern = value + end + + def safe + if @write_concern[:w] == 0 + return false + elsif @write_concern[:w] == 1 + return true + else + return @write_concern + end + end + + def self.from_uri(uri = ENV['MONGODB_URI'], extra_opts={}) + parser = URIParser.new uri + parser.connection(extra_opts, true) + end + end +end + +module Mongo + # @deprecated Use Mongo::Client instead. Support will be removed after v2.0 class Connection < Client + include Mongo::LegacyWriteConcern + def initialize(host=nil, port=nil, opts={}) - warn '[DEPRECATED] Mongo::Connection has been replaced with Mongo::Client.' + write_concern_from_legacy(opts) super end end - # @deprecated Use Mongo::ReplSetClient instead. + # @deprecated Use Mongo::ReplSetClient instead. Support will be removed after v2.0 class ReplSetConnection < ReplSetClient + include Mongo::LegacyWriteConcern + def initialize(*args) - warn '[DEPRECATED] Mongo::ReplSetConnection has been replaced with Mongo::ReplSetClient.' + if args.last.is_a?(Hash) + opts = args.pop + write_concern_from_legacy(opts) + args.push(opts) + end super end end - # @deprecated Use Mongo::ShardedClient instead. + # @deprecated Use Mongo::ShardedClient instead. Support will be removed after v2.0 class ShardedConnection < ShardedClient + include Mongo::LegacyWriteConcern + def initialize(*args) - warn '[DEPRECATED] Mongo::ShardedConnection has been replaced with Mongo::ShardedClient.' + if args.last.is_a?(Hash) + opts = args.pop + write_concern_from_legacy(opts) + args.push(opts) + end super end end diff --git a/lib/mongo/networking.rb b/lib/mongo/networking.rb index 9a5dfdf4ce..7bc084cdcf 100644 --- a/lib/mongo/networking.rb +++ b/lib/mongo/networking.rb @@ -56,7 +56,7 @@ def send_message(operation, message, opts={}) # @see DB#get_last_error for valid last error params. # # @return [Hash] The document returned by the call to getlasterror. - def send_message_with_safe_check(operation, message, db_name, log_message=nil, last_error_params=false) + def send_message_with_acknowledge(operation, message, db_name, log_message=nil, last_error_params=false) docs = num_received = cursor_id = '' add_message_headers(message, operation) @@ -210,14 +210,23 @@ def read_documents(number_received, sock) end # Constructs a getlasterror message. This method is used exclusively by - # Client#send_message_with_safe_check. + # Client#send_message_with_acknowledge. # # Because it modifies message by reference, we don't need to return it. def build_last_error_message(message, db_name, opts) + + # flags bit vector message.put_int(0) + + # namespace BSON::BSON_RUBY.serialize_cstr(message, "#{db_name}.$cmd") + + # number to skip message.put_int(0) + + # numer to return (-1 closes cursor) message.put_int(-1) + cmd = BSON::OrderedHash.new cmd[:getlasterror] = 1 if opts.is_a?(Hash) diff --git a/lib/mongo/repl_set_client.rb b/lib/mongo/repl_set_client.rb index 00e2fc29ca..7282bf39cd 100644 --- a/lib/mongo/repl_set_client.rb +++ b/lib/mongo/repl_set_client.rb @@ -21,11 +21,25 @@ module Mongo # Instantiates and manages connections to a MongoDB replica set. class ReplSetClient < Client - REPL_SET_OPTS = [:read, :refresh_mode, :refresh_interval, :read_secondary, - :rs_name, :name, :tag_sets, :secondary_acceptable_latency_ms] - - attr_reader :replica_set_name, :seeds, :refresh_interval, :refresh_mode, - :refresh_version, :manager, :tag_sets, :acceptable_latency + REPL_SET_OPTS = [ + :read, + :refresh_mode, + :refresh_interval, + :read_secondary, + :rs_name, + :name, + :tag_sets, + :secondary_acceptable_latency_ms + ] + + attr_reader :replica_set_name, + :seeds, + :refresh_interval, + :refresh_mode, + :refresh_version, + :manager, + :tag_sets, + :acceptable_latency # Create a connection to a MongoDB replica set. # @@ -39,9 +53,9 @@ class ReplSetClient < Client # @overload initialize(seeds=ENV["MONGODB_URI"], opts={}) # @param [Array, Array] seeds # - # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options + # @option opts [Hash] :w (1), :j (false), :wtimeout (false), :fsync (false) Set the default write concern # propagated to DB objects instantiated off of this Client. This - # default can be overridden upon instantiation of any DB by explicitly setting a :safe value + # default can be overridden upon instantiation of any DB by explicitly setting write concern values # on initialization. # @option opts [:primary, :primary_preferred, :secondary, :secondary_preferred, :nearest] :read_preference (:primary) # A "read preference" determines the candidate replica set members to which a query or command can be sent. @@ -150,7 +164,7 @@ def initialize(*args) end def valid_opts - GENERIC_OPTS + REPL_SET_OPTS + super + REPL_SET_OPTS - CLIENT_ONLY_OPTS end def inspect @@ -437,8 +451,8 @@ def max_bson_size # Parse option hash def setup(opts) # Refresh - @refresh_mode = opts.fetch(:refresh_mode, false) - @refresh_interval = opts.fetch(:refresh_interval, 90) + @refresh_mode = opts.delete(:refresh_mode) || false + @refresh_interval = opts.delete(:refresh_interval) || 90 if @refresh_mode && @refresh_interval < 60 @refresh_interval = 60 unless ENV['TEST_MODE'] = 'TRUE' @@ -456,26 +470,26 @@ def setup(opts) if opts[:read_secondary] warn ":read_secondary options has now been deprecated and will " + "be removed in driver v2.0. Use the :read option instead." - @read_secondary = opts.fetch(:read_secondary, false) + @read_secondary = opts.delete(:read_secondary) || false @read = :secondary_preferred else - @read = opts.fetch(:read, :primary) + @read = opts.delete(:read) || :primary Mongo::Support.validate_read_preference(@read) end - @tag_sets = opts.fetch(:tag_sets, []) - @acceptable_latency = opts.fetch(:secondary_acceptable_latency_ms, 15) + @tag_sets = opts.delete(:tag_sets) || [] + @acceptable_latency = opts.delete(:secondary_acceptable_latency_ms) || 15 # Replica set name if opts[:rs_name] warn ":rs_name option has been deprecated and will be removed in v2.0. " + "Please use :name instead." - @replica_set_name = opts[:rs_name] + @replica_set_name = opts.delete(:rs_name) else - @replica_set_name = opts[:name] + @replica_set_name = opts.delete(:name) end - opts[:connect_timeout] = opts[:connect_timeout] || 30 + opts[:connect_timeout] = opts.delete(:connect_timeout) || 30 super opts end diff --git a/lib/mongo/sharded_client.rb b/lib/mongo/sharded_client.rb index 7175054216..4564af8e47 100644 --- a/lib/mongo/sharded_client.rb +++ b/lib/mongo/sharded_client.rb @@ -34,9 +34,9 @@ class ShardedClient < ReplSetClient # # @option opts [String] :name (nil) The name of the sharded cluster to connect to. You # can use this option to verify that you're connecting to the right sharded cluster. - # @option opts [Boolean, Hash] :safe (false) Set the default safe-mode options + # @option opts [Hash] ::w (1), :j (false), :wtimeout (false), :fsync (false) Set the default write concern # propagated to DB objects instantiated off of this Client. This - # default can be overridden upon instantiation of any DB by explicitly setting a :safe value + # default can be overridden upon instantiation of any DB by explicitly setting a write concern values # on initialization. # @option opts [Logger] :logger (nil) Logger instance to receive driver operation log. # @option opts [Integer] :pool_size (1) The maximum number of socket connections allowed per diff --git a/lib/mongo/util/ssl_socket.rb b/lib/mongo/util/ssl_socket.rb index c3beab63e9..b99220086b 100644 --- a/lib/mongo/util/ssl_socket.rb +++ b/lib/mongo/util/ssl_socket.rb @@ -20,7 +20,7 @@ def initialize(host, port, op_timeout=nil, connect_timeout=nil) @ssl = OpenSSL::SSL::SSLSocket.new(@socket) @ssl.sync_close = true - + connect end diff --git a/lib/mongo/util/support.rb b/lib/mongo/util/support.rb index 5324c4824d..2926cc5631 100644 --- a/lib/mongo/util/support.rb +++ b/lib/mongo/util/support.rb @@ -20,15 +20,31 @@ module Mongo module Support + include Mongo::Conversions extend self - READ_PREFERENCES = [:primary, :primary_preferred, :secondary, :secondary_preferred, :nearest] + READ_PREFERENCES = [ + :primary, + :primary_preferred, + :secondary, + :secondary_preferred, + :nearest + ] # Commands that may be sent to replica-set secondaries, depending on # read preference and tags. All other commands are always run on the primary. - SECONDARY_OK_COMMANDS = ['group', 'aggregate', 'collstats', 'dbstats', 'count', 'distinct', - 'geonear', 'geosearch', 'geowalk'] + SECONDARY_OK_COMMANDS = [ + 'group', + 'aggregate', + 'collstats', + 'dbstats', + 'count', + 'distinct', + 'geonear', + 'geosearch', + 'geowalk' + ] # Generate an MD5 for authentication. # diff --git a/lib/mongo/util/uri_parser.rb b/lib/mongo/util/uri_parser.rb index 12d038ef2f..339e65c8dd 100644 --- a/lib/mongo/util/uri_parser.rb +++ b/lib/mongo/util/uri_parser.rb @@ -35,7 +35,20 @@ class URIParser MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]" SPEC_ATTRS = [:nodes, :auths] - OPT_ATTRS = [:connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync, :journal, :connecttimeoutms, :sockettimeoutms, :wtimeoutms] + + OPT_ATTRS = [ + :connect, + :replicaset, + :slaveok, + :safe, + :w, + :wtimeout, + :fsync, + :journal, + :connecttimeoutms, + :sockettimeoutms, + :wtimeoutms + ] OPT_VALID = {:connect => lambda {|arg| ['direct', 'replicaset', 'true', 'false', true, false].include?(arg)}, :replicaset => lambda {|arg| arg.length > 0}, @@ -76,7 +89,18 @@ class URIParser :wtimeoutms => lambda {|arg| arg.to_i } } - attr_reader :auths, :connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync, :journal, :connecttimeoutms, :sockettimeoutms, :wtimeoutms + attr_reader :auths, + :connect, + :replicaset, + :slaveok, + :safe, + :w, + :wtimeout, + :fsync, + :journal, + :connecttimeoutms, + :sockettimeoutms, + :wtimeoutms # Parse a MongoDB URI. This method is used by Client.from_uri. # Returns an array of nodes and an array of db authorizations, if applicable. @@ -105,12 +129,20 @@ def initialize(uri) # @note Don't confuse this with attribute getter method #connect. # # @return [Client,ReplSetClient] - def connection(extra_opts) + def connection(extra_opts, legacy=false) opts = connection_options.merge! extra_opts - if replicaset? - ReplSetClient.new(nodes, opts) + if(legacy) + if replicaset? + ReplSetConnection.new(nodes, opts) + else + Connection.new(host, port, opts) + end else - Client.new(host, port, opts) + if replicaset? + ReplSetClient.new(nodes, opts) + else + Client.new(host, port, opts) + end end end @@ -152,32 +184,16 @@ def port def connection_options opts = {} - if (@w || @journal || @wtimeout || @fsync || @wtimeoutms) && !@safe - raise MongoArgumentError, "Safe must be true if w, journal, wtimeoutMS, or fsync is specified" + if @wtimeout + warn "Using wtimeout in a URI is deprecated, please use wtimeoutMS. It will be removed in v2.0." + opts[:wtimeout] = @wtimeout end + opts[:wtimeout] = @wtimeoutms - if @safe - if @w || @journal || @wtimeout || @fsync || @wtimeoutms - safe_opts = {} - safe_opts[:w] = @w if @w - safe_opts[:j] = @journal if @journal - - if @wtimeout - warn "Using wtimeout in a URI is deprecated, please use wtimeoutMS. It will be removed in v2.0." - safe_opts[:wtimeout] = @wtimeout - end - - if @wtimeoutms - safe_opts[:wtimeout] = @wtimeoutms - end - - safe_opts[:fsync] = @fsync if @fsync - else - safe_opts = true - end - - opts[:safe] = safe_opts - end + opts[:w] = 1 if @safe + opts[:w] = @w if @w + opts[:j] = @journal + opts[:fsync] = @fsync if @connecttimeoutms opts[:connect_timeout] = @connecttimeoutms diff --git a/lib/mongo/util/write_concern.rb b/lib/mongo/util/write_concern.rb new file mode 100644 index 0000000000..63228fa78f --- /dev/null +++ b/lib/mongo/util/write_concern.rb @@ -0,0 +1,67 @@ +# encoding: UTF-8 + +# -- +# Copyright (C) 2008-2011 10gen Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ++ + +module Mongo + module WriteConcern + + attr_reader :legacy_write_concern + + @@safe_warn = nil + def write_concern_from_legacy(opts) + # Warn if 'safe' parameter is being used, + if opts.key?(:safe) && !@@safe_warn && !ENV['TEST_MODE'] + warn "[DEPRECATED] The 'safe' write concern option has been deprecated in favor of 'w'." + @@safe_warn = true + end + + # nil: set :w => 0 + # false: set :w => 0 + # true: set :w => 1 + # hash: set :w => 0 and merge with opts + + unless opts.has_key?(:w) + opts[:w] = 0 # legacy default, unacknowledged + safe = opts.delete(:safe) + if(safe && safe.is_a?(Hash)) + opts.merge!(safe) + elsif(safe == true) + opts[:w] = 1 + end + end + end + + # todo: throw exception for conflicting write concern options + def get_write_concern(opts, parent=nil) + write_concern_from_legacy(opts) if opts.key?(:safe) || @legacy_write_concern + write_concern = { + :w => 1, + :j => false, + :fsync => false, + :wtimeout => false + } + write_concern.merge!(parent.write_concern) if parent + write_concern.merge!(opts.select {|k| write_concern.keys.include?(k)}) + write_concern + end + + def self.gle?(write_concern) + write_concern[:w] > 0 || write_concern[:j] || write_concern[:fsync] || write_concern[:wtimeout] + end + + end +end \ No newline at end of file diff --git a/lib/mongo/version.rb b/lib/mongo/version.rb deleted file mode 100644 index a222744177..0000000000 --- a/lib/mongo/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module Mongo - VERSION = "1.7.0" -end diff --git a/tasks/testing.rake b/tasks/testing.rake index 1659c2de99..8a91f49fd5 100644 --- a/tasks/testing.rake +++ b/tasks/testing.rake @@ -17,6 +17,12 @@ namespace :test do desc "Runs default test suites" task :ruby do + SimpleCov.start do + add_group "Mongo", 'lib/mongo' + add_group "BSON", 'lib/bson' + add_filter "/test/" + end + if ENV['TEST'] Rake::Task['test:functional'].invoke else @@ -55,12 +61,12 @@ namespace :test do require 'mongo' client = Mongo::Client.new( ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost', - ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Client::DEFAULT_PORT, :safe => true) + ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::Client::DEFAULT_PORT) client.database_names.each {|name| client.drop_database(name) if name =~ /^ruby-test/ } if File.directory?('data') puts "[CLEAN-UP] Removing replica set data files..." - Dir.delete('data') + FileUtils.rm_rf 'data' end end diff --git a/test/auxillary/authentication_test.rb b/test/auxillary/authentication_test.rb index 188d33d40a..c1e63e7eb4 100644 --- a/test/auxillary/authentication_test.rb +++ b/test/auxillary/authentication_test.rb @@ -28,18 +28,18 @@ def test_authenticate @admin.logout assert_raise Mongo::OperationFailure do - @db1['stuff'].insert({:a => 2}, :safe => true) + @db1['stuff'].insert({:a => 2}) end assert_raise Mongo::OperationFailure do - @db2['stuff'].insert({:a => 2}, :safe => true) + @db2['stuff'].insert({:a => 2}) end @db1.authenticate('user1', 'secret') @db2.authenticate('user2', 'secret') - assert @db1['stuff'].insert({:a => 2}, :safe => true) - assert @db2['stuff'].insert({:a => 2}, :safe => true) + assert @db1['stuff'].insert({:a => 2}) + assert @db2['stuff'].insert({:a => 2}) puts "Please bounce the server." gets @@ -50,24 +50,24 @@ def test_authenticate rescue Mongo::ConnectionFailure end - assert @db1['stuff'].insert({:a => 2}, :safe => true) - assert @db2['stuff'].insert({:a => 2}, :safe => true) - assert @db2['stuff'].find({}, :safe => true) + assert @db1['stuff'].insert({:a => 2}) + assert @db2['stuff'].insert({:a => 2}) + assert @db2['stuff'].find({}) @db1.logout assert_raise Mongo::OperationFailure do - @db1['stuff'].insert({:a => 2}, :safe => true) + @db1['stuff'].insert({:a => 2}) end @db2.logout assert_raise Mongo::OperationFailure do - assert @db2['stuff'].insert({:a => 2}, :safe => true) + assert @db2['stuff'].insert({:a => 2}) end @db2.authenticate('userRO', 'secret') - assert @db2['stuff'].find({}, :safe => true) + assert @db2['stuff'].find({}) assert_raise Mongo::OperationFailure do - assert @db2['stuff'].insert({:a => 2}, :safe => true) + assert @db2['stuff'].insert({:a => 2}) end end diff --git a/test/auxillary/repl_set_auth_test.rb b/test/auxillary/repl_set_auth_test.rb index 695c291630..e15f7697e3 100644 --- a/test/auxillary/repl_set_auth_test.rb +++ b/test/auxillary/repl_set_auth_test.rb @@ -21,14 +21,14 @@ def test_repl_set_auth # Ensure that insert fails assert_raise_error Mongo::OperationFailure, "unauthorized" do - @client['foo']['stuff'].insert({:a => 2}, :safe => {:w => 3}) + @client['foo']['stuff'].insert({:a => 2}, {:w => 3}) end # Then authenticate assert @client['admin'].authenticate("me", "secret") # Insert should succeed now - assert @client['foo']['stuff'].insert({:a => 2}, :safe => {:w => 3}) + assert @client['foo']['stuff'].insert({:a => 2}, {:w => 3}) # So should a query assert @client['foo']['stuff'].find_one diff --git a/test/auxillary/threaded_authentication_test.rb b/test/auxillary/threaded_authentication_test.rb index f34cb53fd5..6309ea5400 100644 --- a/test/auxillary/threaded_authentication_test.rb +++ b/test/auxillary/threaded_authentication_test.rb @@ -43,13 +43,13 @@ def test_authenticate threaded_exec do assert_raise Mongo::OperationFailure do - @db1['stuff'].insert({:a => 2}, :safe => true) + @db1['stuff'].insert({:a => 2}) end end threaded_exec do assert_raise Mongo::OperationFailure do - @db2['stuff'].insert({:a => 2}, :safe => true) + @db2['stuff'].insert({:a => 2}) end end @@ -57,11 +57,11 @@ def test_authenticate @db2.authenticate('user2', 'secret') threaded_exec do - assert @db1['stuff'].insert({:a => 2}, :safe => true) + assert @db1['stuff'].insert({:a => 2}) end threaded_exec do - assert @db2['stuff'].insert({:a => 2}, :safe => true) + assert @db2['stuff'].insert({:a => 2}) end puts "Please bounce the server." @@ -74,24 +74,24 @@ def test_authenticate end threaded_exec do - assert @db1['stuff'].insert({:a => 2}, :safe => true) + assert @db1['stuff'].insert({:a => 2}) end threaded_exec do - assert @db2['stuff'].insert({:a => 2}, :safe => true) + assert @db2['stuff'].insert({:a => 2}) end @db1.logout threaded_exec do assert_raise Mongo::OperationFailure do - @db1['stuff'].insert({:a => 2}, :safe => true) + @db1['stuff'].insert({:a => 2}) end end @db2.logout threaded_exec do assert_raise Mongo::OperationFailure do - assert @db2['stuff'].insert({:a => 2}, :safe => true) + assert @db2['stuff'].insert({:a => 2}) end end end diff --git a/test/functional/collection_test.rb b/test/functional/collection_test.rb index 6c18e7362c..4f7bb73249 100644 --- a/test/functional/collection_test.rb +++ b/test/functional/collection_test.rb @@ -92,23 +92,23 @@ def test_rename_collection end def test_nil_id - assert_equal 5, @@test.insert({"_id" => 5, "foo" => "bar"}, {:safe => true}) - assert_equal 5, @@test.save({"_id" => 5, "foo" => "baz"}, {:safe => true}) + assert_equal 5, @@test.insert({"_id" => 5, "foo" => "bar"}) + assert_equal 5, @@test.save({"_id" => 5, "foo" => "baz"}) assert_equal nil, @@test.find_one("foo" => "bar") assert_equal "baz", @@test.find_one(:_id => 5)["foo"] assert_raise OperationFailure do - @@test.insert({"_id" => 5, "foo" => "bar"}, {:safe => true}) + @@test.insert({"_id" => 5, "foo" => "bar"}) end - assert_equal nil, @@test.insert({"_id" => nil, "foo" => "bar"}, {:safe => true}) - assert_equal nil, @@test.save({"_id" => nil, "foo" => "baz"}, {:safe => true}) + assert_equal nil, @@test.insert({"_id" => nil, "foo" => "bar"}) + assert_equal nil, @@test.save({"_id" => nil, "foo" => "baz"}) assert_equal nil, @@test.find_one("foo" => "bar") assert_equal "baz", @@test.find_one(:_id => nil)["foo"] assert_raise OperationFailure do - @@test.insert({"_id" => nil, "foo" => "bar"}, {:safe => true}) + @@test.insert({"_id" => nil, "foo" => "bar"}) end assert_raise OperationFailure do - @@test.insert({:_id => nil, "foo" => "bar"}, {:safe => true}) + @@test.insert({:_id => nil, "foo" => "bar"}) end end @@ -146,11 +146,11 @@ def test_safe_insert @@test.create_index("hello", :unique => true) a = {"hello" => "world"} @@test.insert(a) - @@test.insert(a) + @@test.insert(a, :w => 0) assert(@@db.get_last_error['err'].include?("11000")) assert_raise OperationFailure do - @@test.insert(a, :safe => true) + @@test.insert(a) end end @@ -174,7 +174,7 @@ def test_bulk_insert_with_continue_on_error docs << {:foo => 2} docs << {:foo => 3} assert_raise OperationFailure do - @@test.insert(docs, :safe => true) + @@test.insert(docs) end assert_equal 1, @@test.count @@test.remove @@ -185,7 +185,7 @@ def test_bulk_insert_with_continue_on_error docs << {:foo => 2} docs << {:foo => 3} assert_raise OperationFailure do - @@test.insert(docs, :safe => true, :continue_on_error => true) + @@test.insert(docs, :continue_on_error => true) end assert_equal 3, @@test.count @@ -255,26 +255,26 @@ def test_maximum_insert_size end end - if @@version >= "1.5.1" - def test_safe_mode_with_advanced_safe_with_invalid_options - assert_raise_error ArgumentError, "Unknown key(s): wtime" do - @@test.insert({:foo => 1}, :safe => {:w => 2, :wtime => 1, :fsync => true}) - end - assert_raise_error ArgumentError, "Unknown key(s): wtime" do - @@test.update({:foo => 1}, {:foo => 2}, :safe => {:w => 2, :wtime => 1, :fsync => true}) - end - - assert_raise_error ArgumentError, "Unknown key(s): wtime" do - @@test.remove({:foo => 2}, :safe => {:w => 2, :wtime => 1, :fsync => true}) - end - end - end + #if @@version >= "1.5.1" + # def test_safe_mode_with_advanced_safe_with_invalid_options + # assert_raise_error ArgumentError, "Unknown key(s): wtime" do + # @@test.insert({:foo => 1}, :w => 2, :wtime => 1, :fsync => true) + # end + # assert_raise_error ArgumentError, "Unknown key(s): wtime" do + # @@test.update({:foo => 1}, {:foo => 2}, :w => 2, :wtime => 1, :fsync => true) + # end + # + # assert_raise_error ArgumentError, "Unknown key(s): wtime" do + # @@test.remove({:foo => 2}, :w => 2, :wtime => 1, :fsync => true) + # end + # end + #end if @@version >= "2.0.0" def test_safe_mode_with_journal_commit_option - @@test.insert({:foo => 1}, :safe => {:j => true}) - @@test.update({:foo => 1}, {:foo => 2}, :safe => {:j => true}) - @@test.remove({:foo => 2}, :safe => {:j => true}) + @@test.insert({:foo => 1}, :j => true) + @@test.update({:foo => 1}, {:foo => 2}, :j => true) + @@test.remove({:foo => 2}, :j => true) end end @@ -332,7 +332,7 @@ def test_safe_update # Can't change an index. assert_raise OperationFailure do - @@test.update({}, {"$inc" => {"x" => 1}}, :safe => true) + @@test.update({}, {"$inc" => {"x" => 1}}) end @@test.drop end @@ -348,7 +348,7 @@ def test_safe_update # Can't duplicate an index. assert_raise OperationFailure do - @@test.update({}, {"x" => 10}, :safe => true) + @@test.update({}, {"x" => 10}) end @@test.drop end @@ -358,10 +358,10 @@ def test_safe_save @@test.create_index("hello", :unique => true) @@test.save("hello" => "world") - @@test.save("hello" => "world") + @@test.save({"hello" => "world"}, :w => 0) assert_raise OperationFailure do - @@test.save({"hello" => "world"}, :safe => true) + @@test.save({"hello" => "world"}) end @@test.drop end @@ -374,7 +374,7 @@ def test_mocked_safe_remove @client.stubs(:receive).returns([[{'ok' => 0, 'err' => 'failed'}], 1, 0]) assert_raise OperationFailure do - @test.remove({}, :safe => true) + @test.remove({}) end @test.drop end @@ -385,12 +385,12 @@ def test_safe_remove @test = @db['test-safe-remove'] @test.remove @test.save({:a => 50}) - assert_equal 1, @test.remove({}, :safe => true)["n"] + assert_equal 1, @test.remove({})["n"] @test.drop end def test_remove_return_value - assert_equal true, @@test.remove({}) + assert_equal true, @@test.remove({}, :w => 0) end def test_count diff --git a/test/functional/connection_test.rb b/test/functional/connection_test.rb index f610632f5a..4905221a7c 100644 --- a/test/functional/connection_test.rb +++ b/test/functional/connection_test.rb @@ -361,7 +361,7 @@ def test_connection_activity end end - should "close the connection on send_message_with_safe_check for major exceptions" do + should "close the connection on send_message_with_acknowledge for major exceptions" do @con.expects(:checkout_writer).raises(SystemStackError) @con.expects(:close) begin diff --git a/test/functional/cursor_fail_test.rb b/test/functional/cursor_fail_test.rb index 4a807a4947..4813196288 100644 --- a/test/functional/cursor_fail_test.rb +++ b/test/functional/cursor_fail_test.rb @@ -11,37 +11,11 @@ class CursorFailTest < Test::Unit::TestCase @@version = @@connection.server_version def setup - @@coll.remove({}, :safe => true) - @@coll.insert({'a' => 1}, :safe => true) # collection not created until it's used + @@coll.remove({}) + @@coll.insert({'a' => 1}) # collection not created until it's used @@coll_full_name = "#{MONGO_TEST_DB}.test" end - #def test_refill_via_get_more - # assert_equal 1, @@coll.count - # 1000.times { |i| - # assert_equal 1 + i, @@coll.count - # @@coll.insert({'a' => i}, :safe => true) - # } - # - # assert_equal 1001, @@coll.count - # count = 0 - # @@coll.find.each { |obj| - # count += obj['a'] - # } - # assert_equal 1001, @@coll.count - - # # do the same thing again for debugging - # assert_equal 1001, @@coll.count - # count2 = 0 - # @@coll.find.each { |obj| - # count2 += obj['a'] - # } - # assert_equal 1001, @@coll.count - # - # assert_equal count, count2 - # assert_equal 499501, count - #end - def test_refill_via_get_more_alt_coll coll = @@db.collection('test-alt-coll') coll.remove diff --git a/test/functional/cursor_test.rb b/test/functional/cursor_test.rb index 35ae485b2a..c84a9bc1c9 100644 --- a/test/functional/cursor_test.rb +++ b/test/functional/cursor_test.rb @@ -195,15 +195,15 @@ def test_id_range_queries t1 = Time.now t1_id = ObjectId.from_time(t1) - @@coll.save({:t => 't1'}, {:safe => true}) - @@coll.save({:t => 't1'}, {:safe => true}) - @@coll.save({:t => 't1'}, {:safe => true}) + @@coll.save({:t => 't1'}) + @@coll.save({:t => 't1'}) + @@coll.save({:t => 't1'}) sleep(2) t2 = Time.now t2_id = ObjectId.from_time(t2) - @@coll.save({:t => 't2'}, {:safe => true}) - @@coll.save({:t => 't2'}, {:safe => true}) - @@coll.save({:t => 't2'}, {:safe => true}) + @@coll.save({:t => 't2'}) + @@coll.save({:t => 't2'}) + @@coll.save({:t => 't2'}) assert_equal 3, @@coll.find({'_id' => {'$gt' => t1_id, '$lt' => t2_id}}).count @@coll.find({'_id' => {'$gt' => t2_id}}).each do |doc| diff --git a/test/functional/db_api_test.rb b/test/functional/db_api_test.rb index 9a2ac947d9..01c4f53f3b 100644 --- a/test/functional/db_api_test.rb +++ b/test/functional/db_api_test.rb @@ -329,8 +329,7 @@ def test_collection_options_are_passed_to_the_existing_ones @@db.create_collection('foobar') - opts = {:safe => true} - coll = @@db.create_collection('foobar', opts) + coll = @@db.create_collection('foobar') assert_equal true, coll.safe end @@ -429,9 +428,9 @@ def test_index_on_subfield end def test_array - @@coll.remove({'$atomic' => true}, :safe => true) - @@coll.insert({'b' => [1, 2, 3]}, :safe => true) - @@coll.insert({'b' => [1, 2, 3]}, :safe => true) + @@coll.remove({'$atomic' => true}) + @@coll.insert({'b' => [1, 2, 3]}) + @@coll.insert({'b' => [1, 2, 3]}) rows = @@coll.find({}, {:fields => ['b']}).to_a assert_equal 2, rows.length assert_equal [1, 2, 3], rows[1]['b'] diff --git a/test/functional/grid_file_system_test.rb b/test/functional/grid_file_system_test.rb index dc338e25a7..9280fc291c 100644 --- a/test/functional/grid_file_system_test.rb +++ b/test/functional/grid_file_system_test.rb @@ -16,7 +16,7 @@ class GridFileSystemTest < Test::Unit::TestCase setup do @chunks_data = "CHUNKS" * 50000 @grid = GridFileSystem.new(@db) - @opts = {:safe => true} + @opts = {:w => 1} @original_opts = @opts.dup @grid.open('sample.file', 'w', @opts) do |f| f.write @chunks_data diff --git a/test/functional/grid_io_test.rb b/test/functional/grid_io_test.rb index d39a5a3ea8..a59077306f 100644 --- a/test/functional/grid_io_test.rb +++ b/test/functional/grid_io_test.rb @@ -186,7 +186,7 @@ class GridIOTest < Test::Unit::TestCase context "Grid MD5 check" do should "run in safe mode" do - file = GridIO.new(@files, @chunks, 'smallfile', 'w', :safe => true) + file = GridIO.new(@files, @chunks, 'smallfile', 'w') file.write("DATA" * 100) assert file.close assert_equal file.server_md5, file.client_md5 @@ -194,7 +194,7 @@ class GridIOTest < Test::Unit::TestCase should "validate with a large file" do io = File.open(File.join(TEST_DATA, 'sample_file.pdf'), 'r') - file = GridIO.new(@files, @chunks, 'bigfile', 'w', :safe => true) + file = GridIO.new(@files, @chunks, 'bigfile', 'w') file.write(io) assert file.close assert_equal file.server_md5, file.client_md5 @@ -203,7 +203,7 @@ class GridIOTest < Test::Unit::TestCase should "raise an exception when check fails" do io = File.open(File.join(TEST_DATA, 'sample_file.pdf'), 'r') @db.stubs(:command).returns({'md5' => '12345'}) - file = GridIO.new(@files, @chunks, 'bigfile', 'w', :safe => true) + file = GridIO.new(@files, @chunks, 'bigfile', 'w') file.write(io) assert_raise GridMD5Failure do assert file.close diff --git a/test/functional/grid_test.rb b/test/functional/grid_test.rb index 948675f1ae..7ba7d26991 100644 --- a/test/functional/grid_test.rb +++ b/test/functional/grid_test.rb @@ -59,12 +59,6 @@ class GridTest < Test::Unit::TestCase assert_equal 'sample', file['filename'] end - #should "not be able to overwrite an exising file" do - # assert_raise GridError do - # @grid.put(@data, :filename => 'sample', :_id => @id, :safe => true) - # end - #end - should "return nil if it doesn't exist" do assert_nil @grid.exist?(:metadata => 'foo') end diff --git a/test/functional/safe_test.rb b/test/functional/safe_test.rb index 48d6640cf5..12d9182fb9 100644 --- a/test/functional/safe_test.rb +++ b/test/functional/safe_test.rb @@ -4,65 +4,65 @@ class SafeTest < Test::Unit::TestCase context "Safe mode propogation: " do setup do - @con = standard_connection(:safe => {:w => 1}) - @db = @con[MONGO_TEST_DB] - @col = @db['test-safe'] - @col.create_index([[:a, 1]], :unique => true) - @col.remove + @connection = standard_connection({:safe => true}, true) # Legacy + @db = @connection[MONGO_TEST_DB] + @collection = @db['test-safe'] + @collection.create_index([[:a, 1]], :unique => true) + @collection.remove end should "propogate safe option on insert" do - @col.insert({:a => 1}) + @collection.insert({:a => 1}) assert_raise_error(OperationFailure, "duplicate key") do - @col.insert({:a => 1}) + @collection.insert({:a => 1}) end end should "allow safe override on insert" do - @col.insert({:a => 1}) - @col.insert({:a => 1}, :safe => false) + @collection.insert({:a => 1}) + @collection.insert({:a => 1}, :safe => false) end should "propogate safe option on update" do - @col.insert({:a => 1}) - @col.insert({:a => 2}) + @collection.insert({:a => 1}) + @collection.insert({:a => 2}) assert_raise_error(OperationFailure, "duplicate key") do - @col.update({:a => 2}, {:a => 1}) + @collection.update({:a => 2}, {:a => 1}) end end should "allow safe override on update" do - @col.insert({:a => 1}) - @col.insert({:a => 2}) - @col.update({:a => 2}, {:a => 1}, :safe => false) + @collection.insert({:a => 1}) + @collection.insert({:a => 2}) + @collection.update({:a => 2}, {:a => 1}, :safe => false) end end context "Safe error objects" do setup do - @con = standard_connection - @db = @con[MONGO_TEST_DB] - @col = @db['test'] - @col.remove - @col.insert({:a => 1}) - @col.insert({:a => 1}) - @col.insert({:a => 1}) + @connection = standard_connection({:safe => true}, true) # Legacy + @db = @connection[MONGO_TEST_DB] + @collection = @db['test'] + @collection.remove + @collection.insert({:a => 1}) + @collection.insert({:a => 1}) + @collection.insert({:a => 1}) end should "return object on update" do - response = @col.update({:a => 1}, {"$set" => {:a => 2}}, - :multi => true, :safe => true) + response = @collection.update({:a => 1}, {"$set" => {:a => 2}}, + :multi => true) assert response['updatedExisting'] assert_equal 3, response['n'] end should "return object on remove" do - response = @col.remove({}, :safe => true) + response = @collection.remove({}) assert_equal 3, response['n'] end end -end +end \ No newline at end of file diff --git a/test/functional/write_concern.rb b/test/functional/write_concern.rb new file mode 100644 index 0000000000..1fc227440d --- /dev/null +++ b/test/functional/write_concern.rb @@ -0,0 +1,70 @@ +require 'test_helper' +include Mongo + +class WriteConcern < Test::Unit::TestCase + context "Write concern propogation: " do + setup do + @con = standard_connection + @db = @con[MONGO_TEST_DB] + @col = @db['test-safe'] + @col.create_index([[:a, 1]], :unique => true) + @col.remove + end + + #TODO: add write concern tests for remove + + should "propogate write concern options on insert" do + @col.insert({:a => 1}) + + assert_raise_error(OperationFailure, "duplicate key") do + @col.insert({:a => 1}) + end + end + + should "allow write concern override on insert" do + @col.insert({:a => 1}) + @col.insert({:a => 1}, :w => 0) + end + + should "propogate write concern option on update" do + @col.insert({:a => 1}) + @col.insert({:a => 2}) + + assert_raise_error(OperationFailure, "duplicate key") do + @col.update({:a => 2}, {:a => 1}) + end + end + + should "allow write concern override on update" do + @col.insert({:a => 1}) + @col.insert({:a => 2}) + @col.update({:a => 2}, {:a => 1}, :w => 0) + end + end + + context "Write concern error objects" do + setup do + @con = standard_connection + @db = @con[MONGO_TEST_DB] + @col = @db['test'] + @col.remove + @col.insert({:a => 1}) + @col.insert({:a => 1}) + @col.insert({:a => 1}) + end + + should "return object on update" do + response = @col.update({:a => 1}, {"$set" => {:a => 2}}, + :multi => true) + + assert response['updatedExisting'] + assert_equal 3, response['n'] + end + + should "return object on remove" do + response = @col.remove({}) + assert_equal 3, response['n'] + end + end + +end diff --git a/test/replica_set/basic_test.rb b/test/replica_set/basic_test.rb index f90a444232..e466b56c0e 100644 --- a/test/replica_set/basic_test.rb +++ b/test/replica_set/basic_test.rb @@ -30,15 +30,15 @@ def test_connect def test_safe_option client = Mongo::ReplSetClient.new(@rs.repl_set_seeds, :name => @rs.repl_set_name) assert client.connected? - assert !client.safe + assert client.write_concern[:w] > 0 client.close - client = Mongo::ReplSetClient.new(@rs.repl_set_seeds, :name => @rs.repl_set_name, :safe => false) + client = Mongo::ReplSetClient.new(@rs.repl_set_seeds, :name => @rs.repl_set_name, :w => 0) assert client.connected? - assert !client.safe + assert client.write_concern[:w] < 1 client.close - client = Mongo::ReplSetClient.new(@rs.repl_set_seeds, :name => @rs.repl_set_name, :safe => true) + client = Mongo::ReplSetClient.new(@rs.repl_set_seeds, :name => @rs.repl_set_name, :w => 2) assert client.connected? - assert client.safe + assert client.write_concern[:w] > 0 client.close end @@ -98,11 +98,11 @@ def test_accessors end end - should "close the connection on send_message_with_safe_check for major exceptions" do + should "close the connection on send_message_with_acknowledge for major exceptions" do @client.expects(:checkout_writer).raises(SystemStackError) @client.expects(:close) begin - @coll.insert({:foo => "bar"}, :safe => true) + @coll.insert({:foo => "bar"}) rescue SystemStackError end end diff --git a/test/replica_set/connect_test.rb b/test/replica_set/client_test.rb similarity index 86% rename from test/replica_set/connect_test.rb rename to test/replica_set/client_test.rb index 759170f28e..8f6b2360c0 100644 --- a/test/replica_set/connect_test.rb +++ b/test/replica_set/client_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class ConnectTest < Test::Unit::TestCase +class ClientTest < Test::Unit::TestCase def setup ensure_cluster(:rs) @@ -17,7 +17,7 @@ def self.shutdown end # To reset after (test) failure - # $ rm -fr data + # rake test:cleanup def step_down_command # Adding force=true to avoid 'no secondaries within 10 seconds of my optime' errors @@ -40,7 +40,7 @@ def test_connect_with_deprecated_multi [host, @rs.replicas[2].port], ], :name => @rs.repl_set_name) end - assert @client.is_a?(ReplSetClient) + assert !@client.nil? assert @client.connected? end @@ -70,7 +70,7 @@ def test_connect_with_last_secondary_node_terminated def test_connect_with_primary_stepped_down @client = ReplSetClient.new @rs.repl_set_seeds - @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 3}}) + @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:w => 3}) assert @client[MONGO_TEST_DB]['bar'].find_one primary = Mongo::Client.new(@client.primary_pool.host, @client.primary_pool.port) @@ -87,7 +87,7 @@ def test_connect_with_primary_stepped_down def test_connect_with_primary_killed @client = ReplSetClient.new @rs.repl_set_seeds assert @client.connected? - @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 3}}) + @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:w => 3}) assert @client[MONGO_TEST_DB]['bar'].find_one @rs.primary.kill(Signal.list['KILL']) @@ -107,13 +107,13 @@ def test_save_with_primary_stepped_down end rescue_connection_failure do - @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 3}}) + @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:w => 3}) end end #def test_connect_with_first_node_removed # @client = ReplSetClient.new @rs.repl_set_seeds - # @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 3}}) + # @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:w => 3}) # # old_primary = [@client.primary_pool.host, @client.primary_pool.port] # old_primary_conn = Mongo::Client.new(*old_primary) @@ -183,14 +183,14 @@ def test_save_with_primary_stepped_down def test_connect_with_connection_string @client = Client.from_uri("mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name}") - assert @client.is_a?(ReplSetClient) + assert !@client.nil? assert @client.connected? end def test_connect_with_connection_string_in_env_var ENV['MONGODB_URI'] = "mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name}" @client = ReplSetClient.new - assert @client.is_a?(ReplSetClient) + assert !@client.nil? assert_equal 2, @client.seeds.length assert_equal @rs.replicas[0].host, @client.seeds[0][0] assert_equal @rs.replicas[1].host, @client.seeds[1][0] @@ -203,7 +203,7 @@ def test_connect_with_connection_string_in_env_var def test_connect_with_connection_string_in_implicit_mongodb_uri ENV['MONGODB_URI'] = "mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name}" @client = Client.from_uri - assert @client.is_a?(ReplSetClient) + assert !@client.nil? assert_equal 2, @client.seeds.length assert_equal @rs.replicas[0].host, @client.seeds[0][0] assert_equal @rs.replicas[1].host, @client.seeds[1][0] @@ -226,30 +226,30 @@ def test_connect_with_old_seed_format end def test_connect_with_full_connection_string - @client = Client.from_uri("mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name};safe=true;w=2;fsync=true;slaveok=true") - assert @client.is_a?(ReplSetClient) + @client = Client.from_uri("mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name};w=2;fsync=true;slaveok=true") + assert !@client.nil? assert @client.connected? - assert_equal 2, @client.safe[:w] - assert @client.safe[:fsync] + assert_equal 2, @client.write_concern[:w] + assert @client.write_concern[:fsync] assert @client.read_pool end def test_connect_with_full_connection_string_in_env_var - ENV['MONGODB_URI'] = "mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name};safe=true;w=2;fsync=true;slaveok=true" + ENV['MONGODB_URI'] = "mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name};w=2;fsync=true;slaveok=true" @client = ReplSetClient.new - assert @client.is_a?(ReplSetClient) + assert !@client.nil? assert @client.connected? - assert_equal 2, @client.safe[:w] - assert @client.safe[:fsync] + assert_equal 2, @client.write_concern[:w] + assert @client.write_concern[:fsync] assert @client.read_pool end def test_connect_options_override_env_var - ENV['MONGODB_URI'] = "mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name};safe=true;w=2;fsync=true;slaveok=true" - @client = ReplSetClient.new({:safe => false}) - assert @client.is_a?(ReplSetClient) + ENV['MONGODB_URI'] = "mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name};w=2;fsync=true;slaveok=true" + @client = ReplSetClient.new({:w => 0}) + assert !@client.nil? assert @client.connected? - assert_equal @client.safe, false + assert_equal 0, @client.write_concern[:w] end end diff --git a/test/replica_set/connection_test.rb b/test/replica_set/connection_test.rb new file mode 100644 index 0000000000..af5886ed1e --- /dev/null +++ b/test/replica_set/connection_test.rb @@ -0,0 +1,255 @@ +require 'test_helper' + +class ConnectionTest < Test::Unit::TestCase + + def setup + ensure_cluster(:rs) + @connection = nil + end + + def teardown + @connection.close if @connection + end + + def self.shutdown + @@cluster.stop + @@cluster.clobber + end + + # To reset after (test) failure + # rake test:cleanup + + def step_down_command + # Adding force=true to avoid 'no secondaries within 10 seconds of my optime' errors + step_down_command = BSON::OrderedHash.new + step_down_command[:replSetStepDown] = 60 + step_down_command[:force] = true + step_down_command + end + + # TODO: test connect timeout. + + def test_connect_with_deprecated_multi + #replica_host_ports = @rs.replicas.collect{|replica| [replica.host, replica.port]} + host = @rs.replicas.first.host + silently do + @connection = Connection.multi([ + # guaranteed to have one data-holding member + [host, @rs.replicas[0].port], + [host, @rs.replicas[1].port], + [host, @rs.replicas[2].port], + ], :name => @rs.repl_set_name) + end + assert !@connection.nil? + assert @connection.connected? + end + + def test_connect_bad_name + assert_raise_error(ReplicaSetConnectionError, "-wrong") do + @connection = ReplSetConnection.new(@rs.repl_set_seeds, :safe => true, :name => @rs.repl_set_name + "-wrong") + end + end + + def test_connect_with_first_secondary_node_terminated + @rs.secondaries.first.stop + + rescue_connection_failure do + @connection = ReplSetConnection.new @rs.repl_set_seeds + end + assert @connection.connected? + end + + def test_connect_with_last_secondary_node_terminated + @rs.secondaries.last.stop + + rescue_connection_failure do + @connection = ReplSetConnection.new @rs.repl_set_seeds + end + assert @connection.connected? + end + + def test_connect_with_primary_stepped_down + @connection = ReplSetConnection.new @rs.repl_set_seeds + @connection[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 3}}) + assert @connection[MONGO_TEST_DB]['bar'].find_one + + primary = Mongo::Connection.new(@connection.primary_pool.host, @connection.primary_pool.port) + assert_raise Mongo::ConnectionFailure do + primary['admin'].command(step_down_command) + end + assert @connection.connected? + + rescue_connection_failure do + @connection[MONGO_TEST_DB]['bar'].find_one + end + end + + def test_connect_with_primary_killed + @connection = ReplSetConnection.new @rs.repl_set_seeds + assert @connection.connected? + @connection[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 3}}) + assert @connection[MONGO_TEST_DB]['bar'].find_one + + @rs.primary.kill(Signal.list['KILL']) + + rescue_connection_failure do + @connection[MONGO_TEST_DB]['bar'].find_one + end + end + + def test_save_with_primary_stepped_down + @connection = ReplSetConnection.new @rs.repl_set_seeds + assert @connection.connected? + + primary = Mongo::Connection.new(@connection.primary_pool.host, @connection.primary_pool.port) + assert_raise Mongo::ConnectionFailure do + primary['admin'].command(step_down_command) + end + + rescue_connection_failure do + @connection[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 3}}) + end + end + + #def test_connect_with_first_node_removed + # @connection = ReplSetConnection.new @rs.repl_set_seeds + # @connection[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 3}}) + # + # old_primary = [@connection.primary_pool.host, @connection.primary_pool.port] + # old_primary_conn = Mongo::Connection.new(*old_primary) + # assert_raise Mongo::ConnectionFailure do + # old_primary_conn['admin'].command(step_down_command) + # end + # + # # Wait for new primary + # rescue_connection_failure do + # sleep 1 until @rs.get_node_with_state(1) + # end + # + # new_primary = @rs.get_all_host_pairs_with_state(1).first + # new_primary_conn = Mongo::Connection.new(*new_primary) + # + # config = nil + # + # # Remove old primary from replset + # rescue_connection_failure do + # config = @connection['local']['system.replset'].find_one + # end + # + # old_member = config['members'].select {|m| m['host'] == old_primary.join(':')}.first + # config['members'].reject! {|m| m['host'] == old_primary.join(':')} + # config['version'] += 1 + # + # begin + # new_primary_conn['admin'].command({'replSetReconfig' => config}) + # rescue Mongo::ConnectionFailure + # end + # + # # Wait for the dust to settle + # rescue_connection_failure do + # assert @connection[MONGO_TEST_DB]['bar'].find_one + # end + # + # # Make sure a new connection skips the old primary + # @new_conn = ReplSetConnection.new @rs.repl_set_seeds + # @new_conn.connect + # new_nodes = [@new_conn.primary] + @new_conn.secondaries + # assert !(new_nodes).include?(old_primary) + # + # # Add the old primary back + # config['members'] << old_member + # config['version'] += 1 + # + # begin + # new_primary_conn['admin'].command({'replSetReconfig' => config}) + # rescue Mongo::ConnectionFailure + # end + #end + + #def test_connect_with_hung_first_node + # hung_node = nil + # begin + # hung_node = IO.popen('nc -lk 127.0.0.1 29999 >/dev/null 2>&1') + # + # @connection = ReplSetConnection.new(['localhost:29999'] + @rs.repl_set_seeds, + # :connect_timeout => 2) + # @connection.connect + # assert ['localhost:29999'] != @connection.primary + # assert !@connection.secondaries.include?('localhost:29999') + # ensure + # Process.kill("KILL", hung_node.pid) if hung_node + # end + #end + + def test_connect_with_connection_string + @connection = Connection.from_uri("mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name}") + assert !@connection.nil? + assert @connection.connected? + end + + def test_connect_with_connection_string_in_env_var + ENV['MONGODB_URI'] = "mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name}" + @connection = ReplSetConnection.new + assert !@connection.nil? + assert_equal 2, @connection.seeds.length + assert_equal @rs.replicas[0].host, @connection.seeds[0][0] + assert_equal @rs.replicas[1].host, @connection.seeds[1][0] + assert_equal @rs.replicas[0].port, @connection.seeds[0][1] + assert_equal @rs.replicas[1].port, @connection.seeds[1][1] + assert_equal @rs.repl_set_name, @connection.replica_set_name + assert @connection.connected? + end + + def test_connect_with_connection_string_in_implicit_mongodb_uri + ENV['MONGODB_URI'] = "mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name}" + @connection = Connection.from_uri + assert !@connection.nil? + assert_equal 2, @connection.seeds.length + assert_equal @rs.replicas[0].host, @connection.seeds[0][0] + assert_equal @rs.replicas[1].host, @connection.seeds[1][0] + assert_equal @rs.replicas[0].port, @connection.seeds[0][1] + assert_equal @rs.replicas[1].port, @connection.seeds[1][1] + assert_equal @rs.repl_set_name, @connection.replica_set_name + assert @connection.connected? + end + + def test_connect_with_new_seed_format + @connection = ReplSetConnection.new @rs.repl_set_seeds + assert @connection.connected? + end + + def test_connect_with_old_seed_format + silently do + @connection = ReplSetConnection.new(@rs.replicas[0].host_port_a, @rs.replicas[1].host_port_a) + end + assert @connection.connected? + end + + def test_connect_with_full_connection_string + @connection = Connection.from_uri("mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name};safe=true;w=2;fsync=true;slaveok=true") + assert !@connection.nil? + assert @connection.connected? + assert_equal 2, @connection.write_concern[:w] + assert @connection.write_concern[:fsync] + assert @connection.read_pool + end + + def test_connect_with_full_connection_string_in_env_var + ENV['MONGODB_URI'] = "mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name};safe=true;w=2;fsync=true;slaveok=true" + @connection = ReplSetConnection.new + assert !@connection.nil? + assert @connection.connected? + assert_equal 2, @connection.write_concern[:w] + assert @connection.write_concern[:fsync] + assert @connection.read_pool + end + + def test_connect_options_override_env_var + ENV['MONGODB_URI'] = "mongodb://#{@rs.replicas[0].host_port},#{@rs.replicas[1].host_port}?replicaset=#{@rs.repl_set_name};safe=true;w=2;fsync=true;slaveok=true" + @connection = ReplSetConnection.new({:safe => {:w => 1}}) + assert !@connection.nil? + assert @connection.connected? + assert_equal 1, @connection.write_concern[:w] + end + +end diff --git a/test/replica_set/count_test.rb b/test/replica_set/count_test.rb index 66087fe2bb..c451431ccb 100644 --- a/test/replica_set/count_test.rb +++ b/test/replica_set/count_test.rb @@ -22,22 +22,22 @@ def self.shutdown end def test_correct_count_after_insertion_reconnect - @coll.insert({:a => 20}, :safe => {:w => 2, :wtimeout => 10000}) + @coll.insert({:a => 20}, :w => 2, :wtimeout => 10000) assert_equal 1, @coll.count # Kill the current master node @rs.primary.stop rescue_connection_failure do - @coll.insert({:a => 30}, :safe => true) + @coll.insert({:a => 30}) end - @coll.insert({:a => 40}, :safe => true) + @coll.insert({:a => 40}) assert_equal 3, @coll.count, "Second count failed" end def test_count_command_sent_to_primary - @coll.insert({:a => 20}, :safe => {:w => 2, :wtimeout => 10000}) + @coll.insert({:a => 20}, :w => 2, :wtimeout => 10000) count_before = @primary['admin'].command({:serverStatus => 1})['opcounters']['command'] assert_equal 1, @coll.count count_after = @primary['admin'].command({:serverStatus => 1})['opcounters']['command'] diff --git a/test/replica_set/cursor_test.rb b/test/replica_set/cursor_test.rb index e90d4f3f5b..fb08c169d8 100644 --- a/test/replica_set/cursor_test.rb +++ b/test/replica_set/cursor_test.rb @@ -34,9 +34,9 @@ def setup_client(read=:primary) @db.drop_collection("cursor_tests") @coll = @db.collection("cursor_tests") - @coll.insert({:a => 1}, :safe => true, :w => 3) - @coll.insert({:b => 2}, :safe => true, :w => 3) - @coll.insert({:c => 3}, :safe => true, :w => 3) + @coll.insert({:a => 1}, :w => 3) + @coll.insert({:b => 2}, :w => 3) + @coll.insert({:c => 3}, :w => 3) # Pin reader @coll.find_one diff --git a/test/replica_set/insert_test.rb b/test/replica_set/insert_test.rb index 12b4f2f884..326228ce21 100644 --- a/test/replica_set/insert_test.rb +++ b/test/replica_set/insert_test.rb @@ -20,18 +20,18 @@ def self.shutdown end def test_insert - @coll.save({:a => 20}, :safe => {:w => 2}) + @coll.save({:a => 20}, :w => 2) @rs.primary.stop rescue_connection_failure do - @coll.save({:a => 30}, :safe => {:w => 2}) + @coll.save({:a => 30}, :w => 2) end - @coll.save({:a => 40}, :safe => {:w => 2}) - @coll.save({:a => 50}, :safe => {:w => 2}) - @coll.save({:a => 60}, :safe => {:w => 2}) - @coll.save({:a => 70}, :safe => {:w => 2}) + @coll.save({:a => 40}, :w => 2) + @coll.save({:a => 50}, :w => 2) + @coll.save({:a => 60}, :w => 2) + @coll.save({:a => 70}, :w => 2) # Restart the old master and wait for sync @rs.start @@ -45,7 +45,7 @@ def test_insert end end - @coll.save({:a => 80}, :safe => {:w => 2}) + @coll.save({:a => 80}, :w => 2) @coll.find.each {|r| results << r} [20, 30, 40, 50, 60, 70, 80].each do |a| assert results.any? {|r| r['a'] == a}, "Could not find record for a => #{a} on second find" diff --git a/test/replica_set/old/basic_test.rb b/test/replica_set/old/basic_test.rb deleted file mode 100644 index d3e322d1b7..0000000000 --- a/test/replica_set/old/basic_test.rb +++ /dev/null @@ -1,119 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' - -class BasicTest < Test::Unit::TestCase - - def setup - ensure_rs - end - - def teardown - @rs.restart_killed_nodes - @client.close if defined?(@conn) && @conn - end - - def test_connect - @client = ReplSetClient.new(build_seeds(3), :name => @rs.name) - assert @client.connected? - - assert_equal @rs.primary, @client.primary - assert_equal @rs.secondaries.sort, @client.secondaries.sort - assert_equal @rs.arbiters.sort, @client.arbiters.sort - - @client = ReplSetClient.new(["#{@rs.host}:#{@rs.ports[1]}","#{@rs.host}:#{@rs.ports[0]}"], - :name => @rs.name) - assert @client.connected? - end - - def test_multiple_concurrent_replica_set_connection - @conn1 = ReplSetClient.new(build_seeds(3), :name => @rs.name) - @conn2 = ReplSetClient.new(build_seeds(3), :name => @rs.name) - assert @conn1.connected? - assert @conn2.connected? - - assert @conn1.manager != @conn2.manager - assert @conn1.local_manager != @conn2.local_manager - end - - def test_cache_original_seed_nodes - seeds = build_seeds(3) << "#{@rs.host}:19356" - @client = ReplSetClient.new(seeds, :name => @rs.name) - assert @client.connected? - assert @client.seeds.include?([@rs.host, 19356]), "Original seed nodes not cached!" - assert_equal [@rs.host, 19356], @client.seeds.last, "Original seed nodes not cached!" - end - - def test_accessors - seeds = build_seeds(3) - args = {:name => @rs.name} - @client = ReplSetClient.new(seeds, args) - - assert_equal @client.host, @rs.primary[0] - assert_equal @client.port, @rs.primary[1] - assert_equal @client.host, @client.primary_pool.host - assert_equal @client.port, @client.primary_pool.port - assert_equal 2, @client.secondaries.length - assert_equal 0, @client.arbiters.length - assert_equal 2, @client.secondary_pools.length - assert_equal @rs.name, @client.replica_set_name - assert @client.secondary_pools.include?(@client.read_pool(:secondary)) - assert_equal 90, @client.refresh_interval - assert_equal @client.refresh_mode, false - end - - context "Socket pools" do - context "checking out writers" do - setup do - seeds = build_seeds(3) - args = {:name => @rs.name} - @con = ReplSetClient.new(seeds, args) - @coll = @con[MONGO_TEST_DB]['test-connection-exceptions'] - end - - should "close the connection on send_message for major exceptions" do - @con.expects(:checkout_writer).raises(SystemStackError) - @con.expects(:close) - begin - @coll.insert({:foo => "bar"}) - rescue SystemStackError - end - end - - should "close the connection on send_message_with_safe_check for major exceptions" do - @con.expects(:checkout_writer).raises(SystemStackError) - @con.expects(:close) - begin - @coll.insert({:foo => "bar"}, :safe => true) - rescue SystemStackError - end - end - - should "close the connection on receive_message for major exceptions" do - @con.expects(:checkout_reader).raises(SystemStackError) - @con.expects(:close) - begin - @coll.find({}, :read => :primary).next - rescue SystemStackError - end - end - end - - context "checking out readers" do - setup do - seeds = build_seeds(3) - args = {:name => @rs.name} - @con = ReplSetClient.new(seeds, args) - @coll = @con[MONGO_TEST_DB]['test-connection-exceptions'] - end - - should "close the connection on receive_message for major exceptions" do - @con.expects(:checkout_reader).raises(SystemStackError) - @con.expects(:close) - begin - @coll.find({}, :read => :secondary).next - rescue SystemStackError - end - end - end - end -end diff --git a/test/replica_set/old/complex_connect_test.rb b/test/replica_set/old/complex_connect_test.rb deleted file mode 100644 index 7f0b25440e..0000000000 --- a/test/replica_set/old/complex_connect_test.rb +++ /dev/null @@ -1,57 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' - -class ComplexConnectTest < Test::Unit::TestCase - - def setup - ensure_rs - end - - def teardown - @rs.restart_killed_nodes - @client.close if defined?(@conn) && @conn - end - - def test_complex_connect - primary = Client.new(@rs.host, @rs.ports[0]) - - @client = ReplSetClient.new([ - "#{@rs.host}:#{@rs.ports[2]}", - "#{@rs.host}:#{@rs.ports[1]}", - "#{@rs.host}:#{@rs.ports[0]}", - ]) - - version = @client.server_version - - @client['test']['foo'].insert({:a => 1}) - assert @client['test']['foo'].find_one - - config = primary['local']['system.replset'].find_one - config['version'] += 1 - config['members'].delete_if do |member| - member['host'].include?(@rs.ports[2].to_s) - end - - assert_raise ConnectionFailure do - primary['admin'].command({:replSetReconfig => config}) - end - @rs.ensure_up - - force_stepdown = BSON::OrderedHash.new - force_stepdown[:replSetStepDown] = 1 - force_stepdown[:force] = true - - assert_raise ConnectionFailure do - primary['admin'].command(force_stepdown) - end - - # isMaster is currently broken in 2.1+ when called on removed nodes - if version < "2.1" - rescue_connection_failure do - assert @client['test']['foo'].find_one - end - - assert @client['test']['foo'].find_one - end - end -end diff --git a/test/replica_set/old/complex_read_preference_test.rb b/test/replica_set/old/complex_read_preference_test.rb deleted file mode 100644 index 1e051150d9..0000000000 --- a/test/replica_set/old/complex_read_preference_test.rb +++ /dev/null @@ -1,237 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' -require 'logger' - -# Tags for members: -# 0 => {"dc" => "ny", "rack" => "a", "db" => "main"} -# 1 => {"dc" => "ny", "rack" => "b", "db" => "main"} -# 2 => {"dc" => "sf", "rack" => "a", "db" => "main"} - -class ComplexReadPreferenceTest < Test::Unit::TestCase - def setup - ensure_rs - - # Insert data - conn = Client.new(@rs.host, @rs.primary[1]) - db = conn.db(MONGO_TEST_DB) - coll = db.collection("test-sets") - coll.save({:a => 20}, :safe => {:w => 2}) - end - - def test_primary_with_tags - # Test specifying a tag set with default read preference of primary throws and error - conn = make_connection({:tag_sets => {"rack" => "a"}}) - assert_raise_error MongoArgumentError, "Read preferecy :primary cannot be combined with tags" do - conn.read_pool - end - end - - def test_tags - return true if @rs.version < "2.1" - - assert_read_pool(:primary, {}, 0) - assert_read_pool(:primary_preferred, {}, 0) - assert_read_pool(:secondary, {}, [1,2]) - assert_read_pool(:secondary_preferred, {}, [1,2]) - - # Test tag_sets are ignored on primary - assert_read_pool(:primary_preferred, - {"rack" => "b"}, 0) - - # Test single tag - assert_read_pool(:secondary, - {"rack" => "a"}, 2) - assert_read_pool(:secondary, - {"rack" => "b"}, 1) - assert_read_pool(:secondary, - {"db" => "main"}, [1, 2]) - - # Test multiple tags - assert_read_pool(:secondary, - {"db" => "main", "rack" => "a"}, 2) - assert_read_pool(:secondary, - {"dc" => "ny", "rack" => "b", "db" => "main"}, 1) - - # Test multiple tags failing - assert_fail_pool(:secondary, - {"dc" => "ny", "rack" => "a"}) - assert_fail_pool(:secondary, - {"dc" => "ny", "rack" => "b", "db" => "main", "xtra" => "?"}) - - # Test symbol is converted to string for key - assert_read_pool(:secondary, - {:db => "main", "rack" => "b"}, 1) - assert_read_pool(:secondary, - {:db => "main", :rack => "b"}, 1) - assert_read_pool(:secondary, - {"db" => "main", :rack => "b"}, 1) - - # Test secondary_preferred - assert_read_pool(:secondary_preferred, - {"dc" => "ny"}, 1) - assert_read_pool(:secondary_preferred, - {"dc" => "sf"}, 2) - assert_read_pool(:secondary_preferred, - {"dc" => "china"}, 0) - - # Test secondary_preferred with no matching member - assert_read_pool(:secondary_preferred, - {"dc" => "bad"}, 0) - assert_read_pool(:secondary_preferred, - {"db" => "main", "dc" => "china"}, 0) - assert_read_pool(:secondary_preferred, - {"db" => "ny", "rack" => "a"}, 0) - end - - def test_tag_sets - return true if @rs.version < "2.1" - - # Test primary_preferred overrides any tags when primary is available - assert_read_pool(:primary_preferred, [ - {"dc" => "sf"} - ], 0) - - # Test first tag_set takes priority over the second - assert_read_pool(:secondary, [ - {"dc" => "sf"}, - {"dc" => "ny"} - ], 2) - assert_read_pool(:secondary, [ - {"dc" => "ny"}, - {"dc" => "sf"} - ], 1) - assert_read_pool(:secondary_preferred, [ - {"dc" => "sf"}, - {"dc" => "ny"} - ], 2) - assert_read_pool(:secondary_preferred, [ - {"dc" => "ny"}, - {"dc" => "sf"} - ], 1) - - # Test tags not matching any member throw an error - assert_fail_pool(:secondary, [ - {"dc" => "ny", "rack" => "a"}, - {"dc" => "sf", "rack" => "b"}, - ]) - - # Test bad tags get skipped over - assert_read_pool(:secondary_preferred, [ - {"bad" => "tag"}, - {"dc" => "sf"} - ], 2) - - # Test less selective tags - assert_read_pool(:secondary, [ - {"dc" => "ny", "rack" => "b", "db" => "alt"}, - {"dc" => "ny", "rack" => "a"}, - {"dc" => "sf"} - ], 2) - assert_read_pool(:secondary_preferred, [ - {"dc" => "ny", "rack" => "b", "db" => "alt"}, - {"dc" => "ny", "rack" => "a"}, - {"dc" => "sf"} - ], 2) - assert_read_pool(:secondary_preferred, [ - {"dc" => "ny", "rack" => "a"}, - {"dc" => "sf", "rack" => "b"}, - {"db" => "main"} - ], [1,2]) - - # Test secondary preferred gives primary if no tags match - assert_read_pool(:secondary_preferred, [ - {"dc" => "ny", "rack" => "a"}, - {"dc" => "sf", "rack" => "b"} - ], 0) - assert_read_pool(:secondary_preferred, [ - {"dc" => "ny", "rack" => "a"}, - {"dc" => "sf", "rack" => "b"}, - {"dc" => "ny", "rack" => "b"}, - ], 1) - - # Basic nearest test - assert_read_pool(:nearest, [ - {"dc" => "ny", "rack" => "a"}, - {"dc" => "sf", "rack" => "b"}, - {"db" => "main"} - ], [0,1,2]) - end - - def test_nearest - # Test refresh happens on connection after interval has passed - conn = make_connection( - :read => :secondary_preferred, - :refresh_mode => :sync, - :refresh_interval => 1, - :secondary_acceptable_latency_ms => 10 - ) - pools = conn.manager.pools - - # Connection should select node with 110 ping every time - set_pings(pools, [100,110,130]) - sleep(2) - - assert conn.read_pool == pools[1] - - # Connection should select node with 100 ping every time - set_pings(pools, [100,120,100]) - sleep(2) - - assert conn.read_pool == pools[2] - end - - def test_tags_and_nearest - return true if @rs.version < "2.1" - - # Test connection's read pool matches tags - assert_read_pool(:secondary_preferred, {"dc" => "sf"}, 2, [100,110,130]) - - # Test connection's read pool picks near pool (both match tags) - assert_read_pool(:secondary_preferred, {"db" => "main"}, 1, [100,110,130]) - assert_read_pool(:secondary_preferred, {"db" => "main"}, 2, [100,130,110]) - assert_read_pool(:secondary_preferred, {"db" => "fake"}, 0, [100,130,110]) - end - - private - - def set_pings(pools, pings) - pools.sort! { |a,b| a.port <=> b.port } - pools.each_with_index do |pool, index| - pool.stubs(:ping_time).returns(pings[index]) - end - end - - def make_connection(opts = {}) - ReplSetClient.new(build_seeds(3), opts) - end - - def assert_read_pool(mode=:primary, tags=[], node_nums=[0], pings=[], latency=10) - if pings.empty? - conn = make_connection({:read => mode, :tag_sets => tags}) - else - conn = make_connection({ - :read => mode, - :tag_sets => tags, - :refresh_mode => :sync, - :refresh_interval => 1, - :secondary_acceptable_latency_ms => latency - }) - - set_pings(conn.manager.pools, pings) - sleep(2) - end - - assert conn[MONGO_TEST_DB]['test-sets'].find_one - - target_ports = [*node_nums].collect {|num| @rs.ports[num]} - - assert target_ports.member?(conn.read_pool.port) - end - - def assert_fail_pool(mode=:primary, tags={}) - assert_raise_error ConnectionFailure, "No replica set member available for query " + - "with read preference matching mode #{mode} and tags matching #{tags}." do - make_connection({:read => mode, :tag_sets => tags}).read_pool - end - end -end \ No newline at end of file diff --git a/test/replica_set/old/connect_test.rb b/test/replica_set/old/connect_test.rb deleted file mode 100644 index 5d8855245c..0000000000 --- a/test/replica_set/old/connect_test.rb +++ /dev/null @@ -1,248 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' - -class ConnectTest < Test::Unit::TestCase - def setup - @old_mongodb_uri = ENV['MONGODB_URI'] - ensure_rs - end - - def teardown - @rs.restart_killed_nodes - @client.close if defined?(@client) && @client - ENV['MONGODB_URI'] = @old_mongodb_uri - end - - def step_down_command - # Adding force=true to avoid 'no secondaries within 10 seconds of my optime' errors - step_down_command = BSON::OrderedHash.new - step_down_command[:replSetStepDown] = 60 - step_down_command[:force] = true - step_down_command - end - - # TODO: test connect timeout. - - def test_connect_with_deprecated_multi - silently do - @client = Client.multi([[@rs.host, @rs.ports[0]], [@rs.host, @rs.ports[1]]], :name => @rs.name) - end - assert @client.is_a?(ReplSetClient) - assert @client.connected? - end - - def test_connect_bad_name - assert_raise_error(ReplicaSetConnectionError, "-wrong") do - @client = ReplSetClient.new(build_seeds(3), :name => @rs.name + "-wrong") - end - end - - def test_connect_with_secondary_node_terminated - @rs.kill_secondary - - rescue_connection_failure do - @client = ReplSetClient.new build_seeds(3) - end - assert @client.connected? - end - - def test_connect_with_third_node_terminated - @rs.kill(@rs.get_node_from_port(@rs.ports[2])) - - rescue_connection_failure do - @client = ReplSetClient.new build_seeds(3) - end - assert @client.connected? - end - - def test_connect_with_primary_stepped_down - @client = ReplSetClient.new build_seeds(3) - @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 3}}) - assert @client[MONGO_TEST_DB]['bar'].find_one - - primary = Mongo::Client.new(@client.primary_pool.host, @client.primary_pool.port) - assert_raise Mongo::ConnectionFailure do - primary['admin'].command(step_down_command) - end - assert @client.connected? - - rescue_connection_failure do - @client[MONGO_TEST_DB]['bar'].find_one - end - end - - def test_connect_with_primary_killed - @client = ReplSetClient.new build_seeds(3) - @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 3}}) - assert @client[MONGO_TEST_DB]['bar'].find_one - - @rs.kill_primary(Signal.list['KILL']) - - rescue_connection_failure do - @client[MONGO_TEST_DB]['bar'].find_one - end - end - - def test_save_with_primary_stepped_down - @client = ReplSetClient.new build_seeds(3) - - primary = Mongo::Client.new(@client.primary_pool.host, @client.primary_pool.port) - assert_raise Mongo::ConnectionFailure do - primary['admin'].command(step_down_command) - end - - rescue_connection_failure do - @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 3}}) - end - end - - def test_save_with_primary_killed - @client = ReplSetClient.new build_seeds(3) - - @rs.kill_primary(Signal.list['KILL']) - - rescue_connection_failure do - @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 2}}) - end - end - - def test_connect_with_first_node_removed - @client = ReplSetClient.new build_seeds(3) - @client[MONGO_TEST_DB]['bar'].save({:a => 1}, {:safe => {:w => 3}}) - - old_primary = [@client.primary_pool.host, @client.primary_pool.port] - old_primary_conn = Mongo::Client.new(*old_primary) - assert_raise Mongo::ConnectionFailure do - old_primary_conn['admin'].command(step_down_command) - end - - # Wait for new primary - rescue_connection_failure do - sleep 1 until @rs.get_node_with_state(1) - end - - new_primary = @rs.get_all_host_pairs_with_state(1).first - new_primary_conn = Mongo::Client.new(*new_primary) - - config = nil - - # Remove old primary from replset - rescue_connection_failure do - config = @client['local']['system.replset'].find_one - end - - old_member = config['members'].select {|m| m['host'] == old_primary.join(':')}.first - config['members'].reject! {|m| m['host'] == old_primary.join(':')} - config['version'] += 1 - - begin - new_primary_conn['admin'].command({'replSetReconfig' => config}) - rescue Mongo::ConnectionFailure - end - - # Wait for the dust to settle - rescue_connection_failure do - assert @client[MONGO_TEST_DB]['bar'].find_one - end - - # Make sure a new connection skips the old primary - @new_conn = ReplSetClient.new build_seeds(3) - @new_conn.connect - new_nodes = [@new_conn.primary] + @new_conn.secondaries - assert !(new_nodes).include?(old_primary) - - # Add the old primary back - config['members'] << old_member - config['version'] += 1 - - begin - new_primary_conn['admin'].command({'replSetReconfig' => config}) - rescue Mongo::ConnectionFailure - end - end - - def test_connect_with_hung_first_node - hung_node = nil - begin - hung_node = IO.popen('nc -lk 127.0.0.1 29999 >/dev/null 2>&1') - - @client = ReplSetClient.new(['localhost:29999'] + build_seeds(3), - :connect_timeout => 2) - @client.connect - assert ['localhost:29999'] != @client.primary - assert !@client.secondaries.include?('localhost:29999') - ensure - Process.kill("KILL", hung_node.pid) if hung_node - end - end - - def test_connect_with_connection_string - @client = Client.from_uri("mongodb://#{@rs.host}:#{@rs.ports[0]},#{@rs.host}:#{@rs.ports[1]}?replicaset=#{@rs.name}") - assert @client.is_a?(ReplSetClient) - assert @client.connected? - end - - def test_connect_with_connection_string_in_env_var - ENV['MONGODB_URI'] = "mongodb://#{@rs.host}:#{@rs.ports[0]},#{@rs.host}:#{@rs.ports[1]}?replicaset=#{@rs.name}" - @client = ReplSetClient.new - assert @client.is_a?(ReplSetClient) - assert_equal 2, @client.seeds.length - assert_equal @rs.host, @client.seeds[0][0] - assert_equal @rs.host, @client.seeds[1][0] - assert_equal @rs.ports[0], @client.seeds[0][1] - assert_equal @rs.ports[1], @client.seeds[1][1] - assert @client.connected? - end - - def test_connect_with_connection_string_in_implicit_mongodb_uri - ENV['MONGODB_URI'] = "mongodb://#{@rs.host}:#{@rs.ports[0]},#{@rs.host}:#{@rs.ports[1]}?replicaset=#{@rs.name}" - @client = Client.from_uri - assert @client.is_a?(ReplSetClient) - assert_equal 2, @client.seeds.length - assert_equal @rs.host, @client.seeds[0][0] - assert_equal @rs.host, @client.seeds[1][0] - assert_equal @rs.ports[0], @client.seeds[0][1] - assert_equal @rs.ports[1], @client.seeds[1][1] - assert_equal @rs.name, @client.replica_set_name - assert @client.connected? - end - - def test_connect_with_new_seed_format - @client = ReplSetClient.new build_seeds(3) - assert @client.connected? - end - - def test_connect_with_old_seed_format - silently do - @client = ReplSetClient.new([@rs.host, @rs.ports[0]], [@rs.host, @rs.ports[1]], [@rs.host, @rs.ports[2]]) - end - assert @client.connected? - end - - def test_connect_with_full_connection_string - @client = Client.from_uri("mongodb://#{@rs.host}:#{@rs.ports[0]},#{@rs.host}:#{@rs.ports[1]}?replicaset=#{@rs.name};safe=true;w=2;fsync=true;slaveok=true") - assert @client.is_a?(ReplSetClient) - assert @client.connected? - assert_equal 2, @client.safe[:w] - assert @client.safe[:fsync] - assert @client.read_pool - end - - def test_connect_with_full_connection_string_in_env_var - ENV['MONGODB_URI'] = "mongodb://#{@rs.host}:#{@rs.ports[0]},#{@rs.host}:#{@rs.ports[1]}?replicaset=#{@rs.name};safe=true;w=2;fsync=true;slaveok=true" - @client = ReplSetClient.new - assert @client.is_a?(ReplSetClient) - assert @client.connected? - assert_equal 2, @client.safe[:w] - assert @client.safe[:fsync] - assert @client.read_pool - end - - def test_connect_options_override_env_var - ENV['MONGODB_URI'] = "mongodb://#{@rs.host}:#{@rs.ports[0]},#{@rs.host}:#{@rs.ports[1]}?replicaset=#{@rs.name};safe=true;w=2;fsync=true;slaveok=true" - @client = ReplSetClient.new({:safe => false}) - assert @client.is_a?(ReplSetClient) - assert @client.connected? - assert_equal @client.safe, false - end -end diff --git a/test/replica_set/old/count_test.rb b/test/replica_set/old/count_test.rb deleted file mode 100644 index d79261bd93..0000000000 --- a/test/replica_set/old/count_test.rb +++ /dev/null @@ -1,43 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' - -class ReplicaSetCountTest < Test::Unit::TestCase - - def setup - ensure_rs - @client = ReplSetClient.new(build_seeds(3), :read => :primary_preferred) - assert @client.primary_pool - @primary = Client.new(@client.primary_pool.host, @client.primary_pool.port) - @db = @client.db(MONGO_TEST_DB) - @db.drop_collection("test-sets") - @coll = @db.collection("test-sets") - end - - def teardown - @rs.restart_killed_nodes - @client.close if @conn - end - - def test_correct_count_after_insertion_reconnect - @coll.insert({:a => 20}, :safe => {:w => 2, :wtimeout => 10000}) - assert_equal 1, @coll.count - - # Kill the current master node - @node = @rs.kill_primary - - rescue_connection_failure do - @coll.insert({:a => 30}, :safe => true) - end - - @coll.insert({:a => 40}, :safe => true) - assert_equal 3, @coll.count, "Second count failed" - end - - def test_count_command_sent_to_primary - @coll.insert({:a => 20}, :safe => {:w => 2, :wtimeout => 10000}) - count_before = @primary['admin'].command({:serverStatus => 1})['opcounters']['command'] - assert_equal 1, @coll.count - count_after = @primary['admin'].command({:serverStatus => 1})['opcounters']['command'] - assert_equal 2, count_after - count_before - end -end diff --git a/test/replica_set/old/cursor_test.rb b/test/replica_set/old/cursor_test.rb deleted file mode 100644 index c1269f0988..0000000000 --- a/test/replica_set/old/cursor_test.rb +++ /dev/null @@ -1,70 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' - -class ReplicaSetCursorTest < Test::Unit::TestCase - def setup - ensure_rs - end - - def test_cursors_get_closed - setup_client - assert_cursor_count - end - - def test_cursors_get_closed_secondary - setup_client(:secondary) - assert_cursor_count - end - - private - - def setup_client(read=:primary) - # Setup ReplicaSet Client - @client = Mongo::ReplSetClient.new( - build_seeds(3), - :read => read - ) - - @db = @client.db(MONGO_TEST_DB) - @db.drop_collection("cursor_tests") - @coll = @db.collection("cursor_tests") - - @coll.insert({:a => 1}, :safe => true, :w => 3) - @coll.insert({:b => 2}, :safe => true, :w => 3) - @coll.insert({:c => 3}, :safe => true, :w => 3) - - # Pin reader - @coll.find_one - - # Setup Direct Connections - @primary = Mongo::Client.new(*@client.manager.primary) - @read = Mongo::Client.new(*@client.manager.read) - end - - def cursor_count(client) - client['cursor_tests'].command({:cursorInfo => 1})['totalOpen'] - end - - def query_count(client) - client['admin'].command({:serverStatus => 1})['opcounters']['query'] - end - - def assert_cursor_count - before_primary = cursor_count(@primary) - before_read = cursor_count(@read) - before_query = query_count(@read) - - @coll.find.limit(2).to_a - sleep(1) - - after_primary = cursor_count(@primary) - after_read = cursor_count(@read) - after_query = query_count(@read) - - assert_equal before_primary, after_primary - assert_equal before_read, after_read - assert_equal 1, after_query - before_query - end - -end - diff --git a/test/replica_set/old/insert_test.rb b/test/replica_set/old/insert_test.rb deleted file mode 100644 index 43cb3742f9..0000000000 --- a/test/replica_set/old/insert_test.rb +++ /dev/null @@ -1,52 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' - -class ReplicaSetInsertTest < Test::Unit::TestCase - - def setup - ensure_rs - @client = ReplSetClient.new build_seeds(3) - @db = @client.db(MONGO_TEST_DB) - @db.drop_collection("test-sets") - @coll = @db.collection("test-sets") - end - - def teardown - @rs.restart_killed_nodes - @client.close if @conn - end - - def test_insert - @coll.save({:a => 20}, :safe => {:w => 2}) - - @rs.kill_primary - - rescue_connection_failure do - @coll.save({:a => 30}, :safe => {:w => 2}) - end - - @coll.save({:a => 40}, :safe => {:w => 2}) - @coll.save({:a => 50}, :safe => {:w => 2}) - @coll.save({:a => 60}, :safe => {:w => 2}) - @coll.save({:a => 70}, :safe => {:w => 2}) - - # Restart the old master and wait for sync - @rs.restart_killed_nodes - sleep(5) - results = [] - - rescue_connection_failure do - @coll.find.each {|r| results << r} - [20, 30, 40, 50, 60, 70].each do |a| - assert results.any? {|r| r['a'] == a}, "Could not find record for a => #{a}" - end - end - - @coll.save({:a => 80}, :safe => {:w => 2}) - @coll.find.each {|r| results << r} - [20, 30, 40, 50, 60, 70, 80].each do |a| - assert results.any? {|r| r['a'] == a}, "Could not find record for a => #{a} on second find" - end - end - -end diff --git a/test/replica_set/old/pooled_insert_test.rb b/test/replica_set/old/pooled_insert_test.rb deleted file mode 100644 index f16c49dcfe..0000000000 --- a/test/replica_set/old/pooled_insert_test.rb +++ /dev/null @@ -1,57 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' - -# NOTE: This test expects a replica set of three nodes to be running -# on the local host. -class ReplicaSetPooledInsertTest < Test::Unit::TestCase - - def setup - ensure_rs - @client = ReplSetClient.new(build_seeds(3), :pool_size => 10, :pool_timeout => 5, :refresh_mode => false) - @db = @client.db(MONGO_TEST_DB) - @db.drop_collection("test-sets") - @coll = @db.collection("test-sets") - end - - def teardown - @rs.restart_killed_nodes - @client.close if @conn - end - - def test_insert - expected_results = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - @coll.save({:a => -1}, :safe => {:w => 2}) - - @rs.kill_primary - - threads = [] - 10.times do |i| - threads[i] = Thread.new do - rescue_connection_failure do - @coll.save({:a => i}, :safe => {:w => 2}) - end - end - end - - threads.each {|t| t.join} - - # Restart the old master and wait for sync - @rs.restart_killed_nodes - sleep(5) - results = [] - - rescue_connection_failure do - @coll.find.each {|r| results << r} - expected_results.each do |a| - assert results.any? {|r| r['a'] == a}, "Could not find record for a => #{a}" - end - end - - @coll.save({:a => 10}, :safe => {:w => 2}) - @coll.find.each {|r| results << r} - (expected_results + [10]).each do |a| - assert results.any? {|r| r['a'] == a}, "Could not find record for a => #{a} on second find" - end - end - -end diff --git a/test/replica_set/old/query_test.rb b/test/replica_set/old/query_test.rb deleted file mode 100644 index 77dbb8b369..0000000000 --- a/test/replica_set/old/query_test.rb +++ /dev/null @@ -1,50 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' - -class ReplicaSetQueryTest < Test::Unit::TestCase - - def setup - ensure_rs - @client = ReplSetClient.new build_seeds(1) - @db = @client.db(MONGO_TEST_DB) - @db.drop_collection("test-sets") - @coll = @db.collection("test-sets") - end - - def teardown - @rs.restart_killed_nodes - @client.close if @conn - end - - def test_query - @coll.save({:a => 20}, :safe => {:w => 3}) - @coll.save({:a => 30}, :safe => {:w => 3}) - @coll.save({:a => 40}, :safe => {:w => 3}) - results = [] - @coll.find.each {|r| results << r} - [20, 30, 40].each do |a| - assert results.any? {|r| r['a'] == a}, "Could not find record for a => #{a}" - end - - #puts "Benchmark before failover: #{benchmark_queries}" - - @rs.kill_primary - - results = [] - rescue_connection_failure do - @coll.find.each {|r| results << r} - [20, 30, 40].each do |a| - assert results.any? {|r| r['a'] == a}, "Could not find record for a => #{a}" - end - - #puts "Benchmark after failover: #{benchmark_queries}" - end - end - - def benchmark_queries - t1 = Time.now - 10000.times { @coll.find_one } - Time.now - t1 - end - -end diff --git a/test/replica_set/old/read_preference_test.rb b/test/replica_set/old/read_preference_test.rb deleted file mode 100644 index f66a8c6b7b..0000000000 --- a/test/replica_set/old/read_preference_test.rb +++ /dev/null @@ -1,240 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' -require 'logger' - -class ReadPreferenceTest < Test::Unit::TestCase - - def setup - ensure_rs(:secondary_count => 1, :arbiter_count => 1) - - # Insert data - conn = Client.new(@rs.host, @rs.primary[1]) - db = conn.db(MONGO_TEST_DB) - coll = db.collection("test-sets") - coll.save({:a => 20}, :safe => {:w => 2}) - end - - def test_read_primary - conn = make_connection - rescue_connection_failure do - assert conn.read_primary? - assert conn.primary? - end - - conn = make_connection(:primary_preferred) - rescue_connection_failure do - assert conn.read_primary? - assert conn.primary? - end - - conn = make_connection(:secondary) - rescue_connection_failure do - assert !conn.read_primary? - assert !conn.primary? - end - - conn = make_connection(:secondary_preferred) - rescue_connection_failure do - assert !conn.read_primary? - assert !conn.primary? - end - end - - def test_read_secondary_then_primary - conn = make_connection - conn[MONGO_TEST_DB]['test-sets'].find_one({}, {:read => :secondary}) - assert conn[MONGO_TEST_DB]['test-sets'].find_one - end - - def test_connection_pools - conn = make_connection - assert conn.primary_pool, "No primary pool!" - assert conn.read_pool, "No read pool!" - assert conn.primary_pool.port == conn.read_pool.port, - "Primary port and read port are not the same!" - - conn = make_connection(:primary_preferred) - assert conn.primary_pool, "No primary pool!" - assert conn.read_pool, "No read pool!" - assert conn.primary_pool.port == conn.read_pool.port, - "Primary port and read port are not the same!" - - conn = make_connection(:secondary) - assert conn.primary_pool, "No primary pool!" - assert conn.read_pool, "No read pool!" - assert conn.primary_pool.port != conn.read_pool.port, - "Primary port and read port are the same!" - - conn = make_connection(:secondary_preferred) - assert conn.primary_pool, "No primary pool!" - assert conn.read_pool, "No read pool!" - assert conn.primary_pool.port != conn.read_pool.port, - "Primary port and read port are the same!" - end - - def test_read_routing - prepare_routing_test - - # Test that reads are going to the right members - assert_query_route(@primary, @primary_direct) - assert_query_route(@primary_preferred, @primary_direct) - assert_query_route(@secondary, @secondary_direct) - assert_query_route(@secondary_preferred, @secondary_direct) - end - - def test_read_routing_with_primary_down - prepare_routing_test - - # Test that reads are going to the right members - assert_query_route(@primary, @primary_direct) - assert_query_route(@primary_preferred, @primary_direct) - assert_query_route(@secondary, @secondary_direct) - assert_query_route(@secondary_preferred, @secondary_direct) - - # Kill the primary so only a single secondary exists - @rs.kill_primary - - # Test that reads are going to the right members - assert_raise_error ConnectionFailure do - @primary[MONGO_TEST_DB]['test-sets'].find_one - end - assert_query_route(@primary_preferred, @secondary_direct) - assert_query_route(@secondary, @secondary_direct) - assert_query_route(@secondary_preferred, @secondary_direct) - - # Restore set - @rs.restart_killed_nodes - sleep(1) - @repl_cons.each { |con| con.refresh } - sleep(1) - @primary_direct = Client.new( - @rs.host, - @primary.read_pool.port - ) - - # Test that reads are going to the right members - assert_query_route(@primary, @primary_direct) - assert_query_route(@primary_preferred, @primary_direct) - assert_query_route(@secondary, @secondary_direct) - assert_query_route(@secondary_preferred, @secondary_direct) - end - - def test_read_routing_with_secondary_down - prepare_routing_test - - # Test that reads are going to the right members - assert_query_route(@primary, @primary_direct) - assert_query_route(@primary_preferred, @primary_direct) - assert_query_route(@secondary, @secondary_direct) - assert_query_route(@secondary_preferred, @secondary_direct) - - # Kill the secondary so that only primary exists - @rs.kill_secondary - - # Test that reads are going to the right members - assert_query_route(@primary, @primary_direct) - assert_query_route(@primary_preferred, @primary_direct) - assert_raise_error ConnectionFailure do - @secondary[MONGO_TEST_DB]['test-sets'].find_one - end - assert_query_route(@secondary_preferred, @primary_direct) - - # Restore set - @rs.restart_killed_nodes - sleep(1) - @repl_cons.each { |con| con.refresh } - sleep(1) - @secondary_direct = Client.new( - @rs.host, - @secondary.read_pool.port, - :slave_ok => true - ) - - # Test that reads are going to the right members - assert_query_route(@primary, @primary_direct) - assert_query_route(@primary_preferred, @primary_direct) - assert_query_route(@secondary, @secondary_direct) - assert_query_route(@secondary_preferred, @secondary_direct) - end - - def test_write_conecern - @client = make_connection(:secondary_preferred) - @db = @client[MONGO_TEST_DB] - @coll = @db.collection("test-sets", :safe => { - :w => 2, :wtimeout => 20000 - }) - @coll.save({:a => 20}) - @coll.save({:a => 30}) - @coll.save({:a => 40}) - - # pin the read pool - @coll.find_one - @secondary = Client.new(@rs.host, @client.read_pool.port, :slave_ok => true) - - results = [] - @coll.find.each {|r| results << r["a"]} - - assert results.include?(20) - assert results.include?(30) - assert results.include?(40) - - @rs.kill_primary - - results = [] - rescue_connection_failure do - @coll.find.each {|r| results << r} - [20, 30, 40].each do |a| - assert results.any? {|r| r['a'] == a}, "Could not find record for a => #{a}" - end - end - @rs.restart_killed_nodes - end - - def test_write_lots_of_data - @client = make_connection(:secondary_preferred) - @db = @client[MONGO_TEST_DB] - @coll = @db.collection("test-sets", {:safe => {:w => 2}}) - - 6000.times do |n| - @coll.save({:a => n}) - end - - cursor = @coll.find() - cursor.next - cursor.close - end - - private - - def prepare_routing_test - # Setup replica set connections - @primary = make_connection(:primary) - @primary_preferred = make_connection(:primary_preferred) - @secondary = make_connection(:secondary) - @secondary_preferred = make_connection(:secondary_preferred) - @repl_cons = [@primary, @primary_preferred, @secondary, @secondary_preferred] - - # Setup direct connections - @primary_direct = Client.new(@rs.host, @primary.read_pool.port) - @secondary_direct = Client.new(@rs.host, @secondary.read_pool.port, :slave_ok => true) - end - - def make_connection(mode = :primary, opts = {}) - opts.merge!({:read => mode}) - ReplSetClient.new(build_seeds(3), opts) - end - - def query_count(connection) - connection['admin'].command({:serverStatus => 1})['opcounters']['query'] - end - - def assert_query_route(test_connection, expected_target) - #puts "#{test_connection.read_pool.port} #{expected_target.read_pool.port}" - queries_before = query_count(expected_target) - assert_nothing_raised do - test_connection['MONGO_TEST_DB']['test-sets'].find_one - end - queries_after = query_count(expected_target) - assert_equal 1, queries_after - queries_before - end -end diff --git a/test/replica_set/old/refresh_test.rb b/test/replica_set/old/refresh_test.rb deleted file mode 100644 index 85a8f908c7..0000000000 --- a/test/replica_set/old/refresh_test.rb +++ /dev/null @@ -1,156 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' -require 'benchmark' - -class ReplicaSetRefreshTest < Test::Unit::TestCase - - def setup - ensure_rs - end - - def teardown - @rs.restart_killed_nodes - @client.close if defined?(@conn) - end - -=begin - def test_connect_speed - Benchmark.bm do |x| - x.report("Connect") do - 10.times do - ReplSetClient.new(build_seeds(3), :refresh_mode => false) - end - end - - @con = ReplSetClient.new(build_seeds(3), :refresh_mode => false) - - x.report("manager") do - man = Mongo::PoolManager.new(@con, @con.seeds) - 10.times do - man.connect - end - end - end - end -=end - - def test_connect_and_manual_refresh_with_secondaries_down - @rs.kill_all_secondaries - sleep(4) - - rescue_connection_failure do - @client = ReplSetClient.new(build_seeds(3), :refresh_mode => false) - end - - assert_equal [], @client.secondaries - assert @client.connected? - assert_equal @client.read_pool, @client.primary_pool - - # Refresh with no change to set - @client.refresh - assert_equal [], @client.secondaries - assert @client.connected? - assert_equal @client.read_pool, @client.primary_pool - - @rs.restart_killed_nodes - assert_equal [], @client.secondaries - assert @client.connected? - assert_equal @client.read_pool, @client.primary_pool - - # Refresh with everything up - @client.refresh - assert @client.read_pool - assert @client.secondaries.length > 0 - end - - def test_automated_refresh_with_secondaries_down - @rs.kill_all_secondaries - sleep(4) - - rescue_connection_failure do - @client = ReplSetClient.new(build_seeds(3), - :refresh_interval => 2, :refresh_mode => :sync, :read => :secondary_preferred) - end - - assert_equal [], @client.secondaries - assert @client.connected? - assert_equal @client.manager.read, @client.manager.primary - old_refresh_version = @client.refresh_version - - @rs.restart_killed_nodes - sleep(4) - @client['foo']['bar'].find_one - @client['foo']['bar'].insert({:a => 1}) - - assert @client.refresh_version > old_refresh_version, - "Refresh version hasn't changed." - assert @client.secondaries.length > 0, - "No secondaries have been added." - assert @client.manager.read != @client.manager.primary, - "Read pool and primary pool are identical." - end - - def test_automated_refresh_when_secondary_goes_down - @client = ReplSetClient.new(build_seeds(3), - :refresh_interval => 2, :refresh_mode => :sync) - - num_secondaries = @client.secondary_pools.length - old_refresh_version = @client.refresh_version - - @rs.kill_secondary - sleep(4) - @client['foo']['bar'].find_one - - assert @client.refresh_version > old_refresh_version, - "Refresh version hasn't changed." - assert_equal num_secondaries - 1, @client.secondaries.length - assert_equal num_secondaries - 1, @client.secondary_pools.length - - @rs.restart_killed_nodes - end - - def test_automated_refresh_with_removed_node - @client = ReplSetClient.new(build_seeds(3), - :refresh_interval => 2, :refresh_mode => :sync) - - num_secondaries = @client.secondary_pools.length - old_refresh_version = @client.refresh_version - - n = @rs.remove_secondary_node - sleep(4) - @client['foo']['bar'].find_one - - assert @client.refresh_version > old_refresh_version, - "Refresh version hasn't changed." - assert_equal num_secondaries - 1, @client.secondaries.length - assert_equal num_secondaries - 1, @client.secondary_pools.length - - @rs.add_node(n) - end - - def test_adding_and_removing_nodes - @client = ReplSetClient.new(build_seeds(3), - :refresh_interval => 2, :refresh_mode => :sync) - - @rs.add_node - sleep(4) - @client['foo']['bar'].find_one - - @conn2 = ReplSetClient.new(build_seeds(3), - :refresh_interval => 2, :refresh_mode => :sync) - - assert @conn2.secondaries.sort == @client.secondaries.sort, - "Second connection secondaries not equal to first." - assert_equal 3, @client.secondary_pools.length - assert_equal 3, @client.secondaries.length - - config = @client['admin'].command({:ismaster => 1}) - - @rs.remove_secondary_node - sleep(4) - config = @client['admin'].command({:ismaster => 1}) - - assert_equal 2, @client.secondary_pools.length - assert_equal 2, @client.secondaries.length - end -end diff --git a/test/replica_set/old/refresh_with_threads_test.rb b/test/replica_set/old/refresh_with_threads_test.rb deleted file mode 100644 index 76b7db6d0d..0000000000 --- a/test/replica_set/old/refresh_with_threads_test.rb +++ /dev/null @@ -1,60 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' -require 'benchmark' - -class ReplicaSetRefreshWithThreadsTest < Test::Unit::TestCase - - def setup - ensure_rs - @client = nil - end - - def teardown - @client.close if @conn - end - - def test_read_write_load_with_added_nodes - # MongoDB < 2.0 will disconnect clients on rs.reconfig() - return true if @rs.version < "2" - - seeds = build_seeds(3) - args = { - :refresh_interval => 5, - :refresh_mode => :sync, - :read => :secondary_preferred - } - @client = ReplSetClient.new(seeds, args) - @duplicate = @client[MONGO_TEST_DB]['duplicate'] - @unique = @client[MONGO_TEST_DB]['unique'] - @duplicate.insert("test" => "insert") - @duplicate.insert("test" => "update") - @unique.insert("test" => "insert") - @unique.insert("test" => "update") - @unique.create_index("test", :unique => true) - - threads = [] - 10.times do - threads << Thread.new do - 1000.times do |i| - if i % 2 == 0 - assert_raise Mongo::OperationFailure do - @unique.insert({"test" => "insert"}, :safe => true) - end - else - @duplicate.insert({"test" => "insert"}, :safe => true) - end - end - end - end - - @rs.add_node - threads.each {|t| t.join } - - sleep(1) - - @client['admin'].command({:ismaster => 1}) - - assert_equal 3, @client.secondary_pools.length - assert_equal 3, @client.secondaries.length - end -end diff --git a/test/replica_set/old/replication_ack_test.rb b/test/replica_set/old/replication_ack_test.rb deleted file mode 100644 index e2ea003b43..0000000000 --- a/test/replica_set/old/replication_ack_test.rb +++ /dev/null @@ -1,68 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require './test/replica_sets/rs_test_helper' - -class ReplicaSetAckTest < Test::Unit::TestCase - - def setup - ensure_rs - @client = ReplSetClient.new(build_seeds(1)) - - @slave1 = Client.new(@client.secondary_pools[0].host, - @client.secondary_pools[0].port, :slave_ok => true) - - assert !@slave1.read_primary? - - @db = @client.db(MONGO_TEST_DB) - @db.drop_collection("test-sets") - @col = @db.collection("test-sets") - end - - def teardown - @rs.restart_killed_nodes - @client.close if @conn - end - - def test_safe_mode_with_w_failure - assert_raise_error OperationFailure, "timeout" do - @col.insert({:foo => 1}, :safe => {:w => 4, :wtimeout => 1, :fsync => true}) - end - assert_raise_error OperationFailure, "timeout" do - @col.update({:foo => 1}, {:foo => 2}, :safe => {:w => 4, :wtimeout => 1, :fsync => true}) - end - assert_raise_error OperationFailure, "timeout" do - @col.remove({:foo => 2}, :safe => {:w => 4, :wtimeout => 1, :fsync => true}) - end - end - - def test_safe_mode_replication_ack - @col.insert({:baz => "bar"}, :safe => {:w => 3, :wtimeout => 5000}) - - assert @col.insert({:foo => "0" * 5000}, :safe => {:w => 3, :wtimeout => 5000}) - assert_equal 2, @slave1[MONGO_TEST_DB]["test-sets"].count - - assert @col.update({:baz => "bar"}, {:baz => "foo"}, :safe => {:w => 3, :wtimeout => 5000}) - assert @slave1[MONGO_TEST_DB]["test-sets"].find_one({:baz => "foo"}) - - assert @col.remove({}, :safe => {:w => 3, :wtimeout => 5000}) - assert_equal 0, @slave1[MONGO_TEST_DB]["test-sets"].count - end - - def test_last_error_responses - 20.times { @col.insert({:baz => "bar"}) } - response = @db.get_last_error(:w => 2, :wtimeout => 5000) - assert response['ok'] == 1 - assert response['lastOp'] - - @col.update({}, {:baz => "foo"}, :multi => true) - response = @db.get_last_error(:w => 2, :wtimeout => 5000) - assert response['ok'] == 1 - assert response['lastOp'] - - @col.remove({}) - response = @db.get_last_error(:w => 2, :wtimeout => 5000) - assert response['ok'] == 1 - assert response['n'] == 20 - assert response['lastOp'] - end - -end diff --git a/test/replica_set/old/rs_test_helper.rb b/test/replica_set/old/rs_test_helper.rb deleted file mode 100644 index bce1ea3112..0000000000 --- a/test/replica_set/old/rs_test_helper.rb +++ /dev/null @@ -1,39 +0,0 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require File.expand_path("../../test_helper", __FILE__) -require './test/tools/repl_set_manager' - -class Test::Unit::TestCase - # Ensure replica set is available as an instance variable and that - # a new set is spun up for each TestCase class - def ensure_rs(opts={}) - unless defined?(@@current_class) and @@current_class == self.class - @@current_class = self.class - @@rs = ReplSetManager.new(opts) - @@rs.start_set - end - @rs = @@rs - end - - # Generic code for rescuing connection failures and retrying operations. - # This could be combined with some timeout functionality. - def rescue_connection_failure(max_retries=30) - retries = 0 - begin - yield - rescue Mongo::ConnectionFailure => ex - #puts "Rescue attempt #{retries}: from #{ex}" - retries += 1 - raise ex if retries > max_retries - sleep(2) - retry - end - end - - def build_seeds(num_hosts) - seeds = [] - num_hosts.times do |n| - seeds << "#{@rs.host}:#{@rs.ports[n]}" - end - seeds - end -end diff --git a/test/replica_set/query_test.rb b/test/replica_set/query_test.rb index be0e4c7085..85bf5240e2 100644 --- a/test/replica_set/query_test.rb +++ b/test/replica_set/query_test.rb @@ -20,9 +20,9 @@ def self.shutdown end def test_query - @coll.save({:a => 20}, :safe => {:w => 3}) - @coll.save({:a => 30}, :safe => {:w => 3}) - @coll.save({:a => 40}, :safe => {:w => 3}) + @coll.save({:a => 20}, :w => 3) + @coll.save({:a => 30}, :w => 3) + @coll.save({:a => 40}, :w => 3) results = [] @coll.find.each {|r| results << r} [20, 30, 40].each do |a| diff --git a/test/replica_set/replication_ack_test.rb b/test/replica_set/replication_ack_test.rb index 0dcc2f3be2..4f8713c130 100644 --- a/test/replica_set/replication_ack_test.rb +++ b/test/replica_set/replication_ack_test.rb @@ -6,7 +6,8 @@ def setup ensure_cluster(:rs) @client = ReplSetClient.new(@rs.repl_set_seeds) - @slave1 = Client.new(@client.secondary_pools[0].host, + @slave1 = Client.new( + @client.secondary_pools[0].host, @client.secondary_pools[0].port, :slave_ok => true) assert !@slave1.read_primary? @@ -27,26 +28,26 @@ def self.shutdown def test_safe_mode_with_w_failure assert_raise_error OperationFailure, "timeout" do - @col.insert({:foo => 1}, :safe => {:w => 4, :wtimeout => 1, :fsync => true}) + @col.insert({:foo => 1}, :w => 4, :wtimeout => 1, :fsync => true) end assert_raise_error OperationFailure, "timeout" do - @col.update({:foo => 1}, {:foo => 2}, :safe => {:w => 4, :wtimeout => 1, :fsync => true}) + @col.update({:foo => 1}, {:foo => 2}, :w => 4, :wtimeout => 1, :fsync => true) end assert_raise_error OperationFailure, "timeout" do - @col.remove({:foo => 2}, :safe => {:w => 4, :wtimeout => 1, :fsync => true}) + @col.remove({:foo => 2}, :w => 4, :wtimeout => 1, :fsync => true) end end def test_safe_mode_replication_ack - @col.insert({:baz => "bar"}, :safe => {:w => 3, :wtimeout => 5000}) + @col.insert({:baz => "bar"}, :w => 3, :wtimeout => 5000) - assert @col.insert({:foo => "0" * 5000}, :safe => {:w => 3, :wtimeout => 5000}) + assert @col.insert({:foo => "0" * 5000}, :w => 3, :wtimeout => 5000) assert_equal 2, @slave1[MONGO_TEST_DB]["test-sets"].count - assert @col.update({:baz => "bar"}, {:baz => "foo"}, :safe => {:w => 3, :wtimeout => 5000}) + assert @col.update({:baz => "bar"}, {:baz => "foo"}, :w => 3, :wtimeout => 5000) assert @slave1[MONGO_TEST_DB]["test-sets"].find_one({:baz => "foo"}) - assert @col.remove({}, :safe => {:w => 3, :wtimeout => 5000}) + assert @col.remove({}, :w => 3, :wtimeout => 5000) assert_equal 0, @slave1[MONGO_TEST_DB]["test-sets"].count end @@ -56,7 +57,7 @@ def test_last_error_responses assert response['ok'] == 1 assert response['lastOp'] - @col.update({}, {:baz => "foo"}, :multi => true) + @col.update({}, {:baz => "foo"}) response = @db.get_last_error(:w => 2, :wtimeout => 5000) assert response['ok'] == 1 assert response['lastOp'] diff --git a/test/test_helper.rb b/test/test_helper.rb index 81509e78c4..391220d3f5 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -92,12 +92,16 @@ class Test::Unit::TestCase include Mongo include BSON - def self.standard_connection(options={}) - Client.new(TEST_HOST, TEST_PORT, options) + def self.standard_connection(options={}, legacy=false) + if legacy + Connection.new(TEST_HOST, TEST_PORT, options) + else + Client.new(TEST_HOST, TEST_PORT, options) + end end - def standard_connection(options={}) - self.class.standard_connection(options) + def standard_connection(options={}, legacy=false) + self.class.standard_connection(options, legacy) end def self.host_port diff --git a/test/unit/client_test.rb b/test/unit/client_test.rb new file mode 100644 index 0000000000..830b9072a6 --- /dev/null +++ b/test/unit/client_test.rb @@ -0,0 +1,230 @@ +require 'test_helper' +include Mongo + +class ClientTest < Test::Unit::TestCase + context "Mongo::Client intialization " do + context "given a single node" do + setup do + @client = Client.new('localhost', 27017, :connect => false) + TCPSocket.stubs(:new).returns(new_mock_socket) + + admin_db = new_mock_db + admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1}) + @client.expects(:[]).with('admin').returns(admin_db) + @client.connect + end + + should "acknowledge writes by default" do + assert_equal 1, @client.write_concern[:w] + end + + should "set localhost and port to master" do + assert_equal 'localhost', @client.primary_pool.host + assert_equal 27017, @client.primary_pool.port + end + + should "set connection pool to 1" do + assert_equal 1, @client.primary_pool.size + end + + should "default slave_ok to false" do + assert !@client.slave_ok? + end + + should "raise exception for invalid host or port" do + assert_raise MongoArgumentError do + Client.new(:w => 1) + end + assert_raise MongoArgumentError do + Client.new('localhost', :w => 1) + end + end + + should "warn if invalid options are specified" do + client = Client.allocate + opts = {:connect => false} + + ReplSetClient::REPL_SET_OPTS.each do |opt| + client.expects(:warn).with("#{opt} is not a valid option for #{client.class}") + opts[opt] = true + end + + args = ['localhost', 27017, opts] + client.send(:initialize, *args) + end + + context "given a replica set" do + + should "warn if invalid options are specified" do + client = ReplSetClient.allocate + opts = {:connect => false} + + Client::CLIENT_ONLY_OPTS.each do |opt| + client.expects(:warn).with("#{opt} is not a valid option for #{client.class}") + opts[opt] = true + end + + args = [['localhost:27017'], opts] + client.send(:initialize, *args) + end + end + end + + context "initializing with a mongodb uri" do + should "parse a simple uri" do + @client = Client.from_uri("mongodb://localhost", :connect => false) + assert_equal ['localhost', 27017], @client.host_to_try + end + + should "allow a complex host names" do + host_name = "foo.bar-12345.org" + @client = Client.from_uri("mongodb://#{host_name}", :connect => false) + assert_equal [host_name, 27017], @client.host_to_try + end + + should "allow db without username and password" do + host_name = "foo.bar-12345.org" + @client = Client.from_uri("mongodb://#{host_name}/foo", :connect => false) + assert_equal [host_name, 27017], @client.host_to_try + end + + should "set write concern options on connection" do + host_name = "localhost" + opts = "w=2&wtimeoutMS=1000&fsync=true&journal=true" + @client = Client.from_uri("mongodb://#{host_name}/foo?#{opts}", :connect => false) + assert_equal({:w => 2, :wtimeout => 1000, :fsync => true, :j => true}, @client.write_concern) + end + + should "set timeout options on connection" do + host_name = "localhost" + opts = "connectTimeoutMS=1000&socketTimeoutMS=5000" + @client = Client.from_uri("mongodb://#{host_name}/foo?#{opts}", :connect => false) + assert_equal 1, @client.connect_timeout + assert_equal 5, @client.op_timeout + end + + should "parse a uri with a hyphen & underscore in the username or password" do + @client = Client.from_uri("mongodb://hyphen-user_name:p-s_s@localhost:27017/db", :connect => false) + assert_equal ['localhost', 27017], @client.host_to_try + auth_hash = { 'db_name' => 'db', 'username' => 'hyphen-user_name', "password" => 'p-s_s' } + assert_equal auth_hash, @client.auths[0] + end + + should "attempt to connect" do + TCPSocket.stubs(:new).returns(new_mock_socket) + @client = Client.from_uri("mongodb://localhost", :connect => false) + + admin_db = new_mock_db + admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1}) + @client.expects(:[]).with('admin').returns(admin_db) + @client.connect + end + + should "raise an error on invalid uris" do + assert_raise MongoArgumentError do + Client.from_uri("mongo://localhost", :connect => false) + end + + assert_raise MongoArgumentError do + Client.from_uri("mongodb://localhost:abc", :connect => false) + end + end + + should "require all of username, if password and db are specified" do + assert Client.from_uri("mongodb://kyle:jones@localhost/db", :connect => false) + + assert_raise MongoArgumentError do + Client.from_uri("mongodb://kyle:password@localhost", :connect => false) + end + end + end + + context "initializing with ENV['MONGODB_URI']" do + setup do + @old_mongodb_uri = ENV['MONGODB_URI'] + end + + teardown do + ENV['MONGODB_URI'] = @old_mongodb_uri + end + + should "parse a simple uri" do + ENV['MONGODB_URI'] = "mongodb://localhost?connect=false" + @client = Client.new + assert_equal ['localhost', 27017], @client.host_to_try + end + + should "allow a complex host names" do + host_name = "foo.bar-12345.org" + ENV['MONGODB_URI'] = "mongodb://#{host_name}?connect=false" + @client = Client.new + assert_equal [host_name, 27017], @client.host_to_try + end + + should "allow db without username and password" do + host_name = "foo.bar-12345.org" + ENV['MONGODB_URI'] = "mongodb://#{host_name}/foo?connect=false" + @client = Client.new + assert_equal [host_name, 27017], @client.host_to_try + end + + should "set write concern options on connection" do + host_name = "localhost" + opts = "w=2&wtimeoutMS=1000&fsync=true&journal=true&connect=false" + ENV['MONGODB_URI'] = "mongodb://#{host_name}/foo?#{opts}" + @client = Client.new + assert_equal({:w => 2, :wtimeout => 1000, :fsync => true, :j => true}, @client.write_concern) + end + + should "set timeout options on connection" do + host_name = "localhost" + opts = "connectTimeoutMS=1000&socketTimeoutMS=5000&connect=false" + ENV['MONGODB_URI'] = "mongodb://#{host_name}/foo?#{opts}" + @client = Client.new + assert_equal 1, @client.connect_timeout + assert_equal 5, @client.op_timeout + end + + should "parse a uri with a hyphen & underscore in the username or password" do + ENV['MONGODB_URI'] = "mongodb://hyphen-user_name:p-s_s@localhost:27017/db?connect=false" + @client = Client.new + assert_equal ['localhost', 27017], @client.host_to_try + auth_hash = { 'db_name' => 'db', 'username' => 'hyphen-user_name', "password" => 'p-s_s' } + assert_equal auth_hash, @client.auths[0] + end + + should "attempt to connect" do + TCPSocket.stubs(:new).returns(new_mock_socket) + ENV['MONGODB_URI'] = "mongodb://localhost?connect=false" # connect=false ?? + @client = Client.new + + admin_db = new_mock_db + admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1}) + @client.expects(:[]).with('admin').returns(admin_db) + @client.connect + end + + should "raise an error on invalid uris" do + ENV['MONGODB_URI'] = "mongo://localhost" + assert_raise MongoArgumentError do + Client.new + end + + ENV['MONGODB_URI'] = "mongodb://localhost:abc" + assert_raise MongoArgumentError do + Client.new + end + end + + should "require all of username, if password and db are specified" do + ENV['MONGODB_URI'] = "mongodb://kyle:jones@localhost/db?connect=false" + assert Client.new + + ENV['MONGODB_URI'] = "mongodb://kyle:password@localhost" + assert_raise MongoArgumentError do + Client.new + end + end + end + end +end diff --git a/test/unit/collection_test.rb b/test/unit/collection_test.rb index 5c9c429689..a62fe002fa 100644 --- a/test/unit/collection_test.rb +++ b/test/unit/collection_test.rb @@ -13,7 +13,7 @@ class CollectionTest < Test::Unit::TestCase @client = Client.new('localhost', 27017, :logger => @logger, :connect => false) @db = @client['testing'] @coll = @db.collection('books') - @client.expects(:send_message).with do |op, msg, log| + @client.expects(:send_message_with_acknowledge).with do |op, msg, log| op == 2001 end @coll.stubs(:log_operation) @@ -24,7 +24,7 @@ class CollectionTest < Test::Unit::TestCase @client = Client.new('localhost', 27017, :logger => @logger, :connect => false) @db = @client['testing'] @coll = @db.collection('books') - @client.expects(:send_message).with do |op, msg, log| + @client.expects(:send_message_with_acknowledge).with do |op, msg, log| op == 2002 end @coll.expects(:log_operation).with do |name, payload| @@ -50,7 +50,7 @@ class CollectionTest < Test::Unit::TestCase @db = @client['testing'] @coll = @db.collection('books') data = BSON::Binary.new(("BINARY " * 1000).unpack("c*")) - @client.expects(:send_message).with do |op, msg, log| + @client.expects(:send_message_with_acknowledge).with do |op, msg, log| op == 2002 end @coll.expects(:log_operation).with do |name, payload| @@ -63,7 +63,20 @@ class CollectionTest < Test::Unit::TestCase @client = Client.new('localhost', 27017, :logger => @logger, :connect => false) @db = @client['testing'] @coll = @db.collection('books') - @client.expects(:send_message_with_safe_check).with do |op, msg, db_name, log| + @client.expects(:send_message_with_acknowledge).with do |op, msg, db_name, log| + op == 2001 + end + @coll.expects(:log_operation).with do |name, payload| + (name == :update) && payload[:document][:title].include?('Moby') + end + @coll.update({}, {:title => 'Moby Dick'}) + end + + should "send safe update message with legacy" do + @client = Connection.new('localhost', 27017, :logger => @logger, :connect => false) + @db = @client['testing'] + @coll = @db.collection('books') + @client.expects(:send_message_with_acknowledge).with do |op, msg, db_name, log| op == 2001 end @coll.expects(:log_operation).with do |name, payload| @@ -76,7 +89,18 @@ class CollectionTest < Test::Unit::TestCase @client = Client.new('localhost', 27017, :logger => @logger, :connect => false) @db = @client['testing'] @coll = @db.collection('books') - @client.expects(:send_message_with_safe_check).with do |op, msg, db_name, log| + @client.expects(:send_message_with_acknowledge).with do |op, msg, db_name, log| + op == 2001 + end + @coll.stubs(:log_operation) + @coll.update({}, {:title => 'Moby Dick'}) + end + + should "send safe insert message with legacy" do + @client = Connection.new('localhost', 27017, :logger => @logger, :connect => false) + @db = @client['testing'] + @coll = @db.collection('books') + @client.expects(:send_message_with_acknowledge).with do |op, msg, db_name, log| op == 2001 end @coll.stubs(:log_operation) diff --git a/test/unit/connection_test.rb b/test/unit/connection_test.rb index 3c2d3785c7..e3f1094311 100644 --- a/test/unit/connection_test.rb +++ b/test/unit/connection_test.rb @@ -1,155 +1,140 @@ require 'test_helper' -include Mongo class ConnectionTest < Test::Unit::TestCase - context "Initialization: " do + context "Mongo::Client intialization " do context "given a single node" do setup do - @client = Client.new('localhost', 27017, :safe => true, :connect => false) + @connection = Mongo::Connection.new('localhost', 27017, :safe => true, :connect => false) TCPSocket.stubs(:new).returns(new_mock_socket) admin_db = new_mock_db admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1}) - @client.expects(:[]).with('admin').returns(admin_db) - @client.connect + @connection.expects(:[]).with('admin').returns(admin_db) + @connection.connect end should "set safe mode true" do - assert_equal true, @client.safe + assert_equal true, @connection.safe end should "set localhost and port to master" do - assert_equal 'localhost', @client.primary_pool.host - assert_equal 27017, @client.primary_pool.port + assert_equal 'localhost', @connection.primary_pool.host + assert_equal 27017, @connection.primary_pool.port end should "set connection pool to 1" do - assert_equal 1, @client.primary_pool.size + assert_equal 1, @connection.primary_pool.size end should "default slave_ok to false" do - assert !@client.slave_ok? + assert !@connection.slave_ok? end should "raise exception for invalid host or port" do assert_raise MongoArgumentError do - Client.new(:safe => true) + Mongo::Connection.new(:safe => true) end assert_raise MongoArgumentError do - Client.new('localhost', :safe => true) + Mongo::Connection.new('localhost', :safe => true) end end should "warn if invalid options are specified" do - client = Client.allocate + connection = Mongo::Connection.allocate opts = {:connect => false} - ReplSetClient::REPL_SET_OPTS.each do |opt| - client.expects(:warn).with("#{opt} is not a valid option for #{client.class}") + Mongo::ReplSetConnection::REPL_SET_OPTS.each do |opt| + connection.expects(:warn).with("#{opt} is not a valid option for #{connection.class}") opts[opt] = true end args = ['localhost', 27017, opts] - client.send(:initialize, *args) - end - - should "warn if deprecated connection class is used" do - conn = Connection.allocate - conn.expects(:warn).with('[DEPRECATED] Mongo::Connection has been replaced with Mongo::Client.') - args = ['localhost', 27017, {:connect => false}] - conn.send(:initialize, *args) + connection.send(:initialize, *args) end context "given a replica set" do - should "warn if invalid options are specified" do - client = ReplSetClient.allocate - opts = {:connect => false} + # should "warn if invalid options are specified" do + # connection = Mongo::ReplSetConnection.allocate + # opts = {:connect => false} - Client::CONNECTION_OPTS.each do |opt| - client.expects(:warn).with("#{opt} is not a valid option for #{client.class}") - opts[opt] = true - end + # # Mongo::Connection::CLIENT_ONLY_OPTS.each do |opt| + # connection.expects(:warn).with("#{:slave_ok} is not a valid option for #{connection.class}") + # opts[:slave_ok] = true + # # end - args = [['localhost:27017'], opts] - client.send(:initialize, *args) - end - - should "warn if deprecated connection class is used" do - conn = ReplSetConnection.allocate - conn.expects(:warn).with('[DEPRECATED] Mongo::ReplSetConnection has been replaced with Mongo::ReplSetClient.') - args = [['localhost:27017'], {:connect => false}] - conn.send(:initialize, *args) - end + # args = [['localhost:27017'], opts] + # connection.send(:initialize, *args) + # end end end context "initializing with a mongodb uri" do should "parse a simple uri" do - @client = Client.from_uri("mongodb://localhost", :connect => false) - assert_equal ['localhost', 27017], @client.host_to_try + @connection = Mongo::Connection.from_uri("mongodb://localhost", :connect => false) + assert_equal ['localhost', 27017], @connection.host_to_try end should "allow a complex host names" do host_name = "foo.bar-12345.org" - @client = Client.from_uri("mongodb://#{host_name}", :connect => false) - assert_equal [host_name, 27017], @client.host_to_try + @connection = Mongo::Connection.from_uri("mongodb://#{host_name}", :connect => false) + assert_equal [host_name, 27017], @connection.host_to_try end should "allow db without username and password" do host_name = "foo.bar-12345.org" - @client = Client.from_uri("mongodb://#{host_name}/foo", :connect => false) - assert_equal [host_name, 27017], @client.host_to_try + @connection = Mongo::Connection.from_uri("mongodb://#{host_name}/foo", :connect => false) + assert_equal [host_name, 27017], @connection.host_to_try end should "set safe options on connection" do host_name = "localhost" opts = "safe=true&w=2&wtimeoutMS=1000&fsync=true&journal=true" - @client = Client.from_uri("mongodb://#{host_name}/foo?#{opts}", :connect => false) - assert_equal({:w => 2, :wtimeout => 1000, :fsync => true, :j => true}, @client.safe) + @connection = Mongo::Connection.from_uri("mongodb://#{host_name}/foo?#{opts}", :connect => false) + assert_equal({:w => 2, :wtimeout => 1000, :fsync => true, :j => true}, @connection.write_concern) end should "set timeout options on connection" do host_name = "localhost" opts = "connectTimeoutMS=1000&socketTimeoutMS=5000" - @client = Client.from_uri("mongodb://#{host_name}/foo?#{opts}", :connect => false) - assert_equal 1, @client.connect_timeout - assert_equal 5, @client.op_timeout + @connection = Mongo::Connection.from_uri("mongodb://#{host_name}/foo?#{opts}", :connect => false) + assert_equal 1, @connection.connect_timeout + assert_equal 5, @connection.op_timeout end should "parse a uri with a hyphen & underscore in the username or password" do - @client = Client.from_uri("mongodb://hyphen-user_name:p-s_s@localhost:27017/db", :connect => false) - assert_equal ['localhost', 27017], @client.host_to_try + @connection = Mongo::Connection.from_uri("mongodb://hyphen-user_name:p-s_s@localhost:27017/db", :connect => false) + assert_equal ['localhost', 27017], @connection.host_to_try auth_hash = { 'db_name' => 'db', 'username' => 'hyphen-user_name', "password" => 'p-s_s' } - assert_equal auth_hash, @client.auths[0] + assert_equal auth_hash, @connection.auths[0] end should "attempt to connect" do TCPSocket.stubs(:new).returns(new_mock_socket) - @client = Client.from_uri("mongodb://localhost", :connect => false) + @connection = Mongo::Connection.from_uri("mongodb://localhost", :connect => false) admin_db = new_mock_db admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1}) - @client.expects(:[]).with('admin').returns(admin_db) - @client.connect + @connection.expects(:[]).with('admin').returns(admin_db) + @connection.connect end should "raise an error on invalid uris" do assert_raise MongoArgumentError do - Client.from_uri("mongo://localhost", :connect => false) + Mongo::Connection.from_uri("mongo://localhost", :connect => false) end assert_raise MongoArgumentError do - Client.from_uri("mongodb://localhost:abc", :connect => false) + Mongo::Connection.from_uri("mongodb://localhost:abc", :connect => false) end end should "require all of username, if password and db are specified" do - assert Client.from_uri("mongodb://kyle:jones@localhost/db", :connect => false) + assert Mongo::Connection.from_uri("mongodb://kyle:jones@localhost/db", :connect => false) assert_raise MongoArgumentError do - Client.from_uri("mongodb://kyle:password@localhost", :connect => false) + Mongo::Connection.from_uri("mongodb://kyle:password@localhost", :connect => false) end end end @@ -165,79 +150,79 @@ class ConnectionTest < Test::Unit::TestCase should "parse a simple uri" do ENV['MONGODB_URI'] = "mongodb://localhost?connect=false" - @client = Client.new - assert_equal ['localhost', 27017], @client.host_to_try + @connection = Mongo::Connection.new + assert_equal ['localhost', 27017], @connection.host_to_try end should "allow a complex host names" do host_name = "foo.bar-12345.org" ENV['MONGODB_URI'] = "mongodb://#{host_name}?connect=false" - @client = Client.new - assert_equal [host_name, 27017], @client.host_to_try + @connection = Mongo::Connection.new + assert_equal [host_name, 27017], @connection.host_to_try end should "allow db without username and password" do host_name = "foo.bar-12345.org" ENV['MONGODB_URI'] = "mongodb://#{host_name}/foo?connect=false" - @client = Client.new - assert_equal [host_name, 27017], @client.host_to_try + @connection = Mongo::Connection.new + assert_equal [host_name, 27017], @connection.host_to_try end should "set safe options on connection" do host_name = "localhost" opts = "safe=true&w=2&wtimeoutMS=1000&fsync=true&journal=true&connect=false" ENV['MONGODB_URI'] = "mongodb://#{host_name}/foo?#{opts}" - @client = Client.new - assert_equal({:w => 2, :wtimeout => 1000, :fsync => true, :j => true}, @client.safe) + @connection = Mongo::Connection.new + assert_equal({:w => 2, :wtimeout => 1000, :fsync => true, :j => true}, @connection.safe) end should "set timeout options on connection" do host_name = "localhost" opts = "connectTimeoutMS=1000&socketTimeoutMS=5000&connect=false" ENV['MONGODB_URI'] = "mongodb://#{host_name}/foo?#{opts}" - @client = Client.new - assert_equal 1, @client.connect_timeout - assert_equal 5, @client.op_timeout + @connection = Mongo::Connection.new + assert_equal 1, @connection.connect_timeout + assert_equal 5, @connection.op_timeout end should "parse a uri with a hyphen & underscore in the username or password" do ENV['MONGODB_URI'] = "mongodb://hyphen-user_name:p-s_s@localhost:27017/db?connect=false" - @client = Client.new - assert_equal ['localhost', 27017], @client.host_to_try + @connection = Mongo::Connection.new + assert_equal ['localhost', 27017], @connection.host_to_try auth_hash = { 'db_name' => 'db', 'username' => 'hyphen-user_name', "password" => 'p-s_s' } - assert_equal auth_hash, @client.auths[0] + assert_equal auth_hash, @connection.auths[0] end should "attempt to connect" do TCPSocket.stubs(:new).returns(new_mock_socket) ENV['MONGODB_URI'] = "mongodb://localhost?connect=false" # connect=false ?? - @client = Client.new + @connection = Mongo::Connection.new admin_db = new_mock_db admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1}) - @client.expects(:[]).with('admin').returns(admin_db) - @client.connect + @connection.expects(:[]).with('admin').returns(admin_db) + @connection.connect end should "raise an error on invalid uris" do ENV['MONGODB_URI'] = "mongo://localhost" assert_raise MongoArgumentError do - Client.new + Mongo::Connection.new end ENV['MONGODB_URI'] = "mongodb://localhost:abc" assert_raise MongoArgumentError do - Client.new + Mongo::Connection.new end end should "require all of username, if password and db are specified" do ENV['MONGODB_URI'] = "mongodb://kyle:jones@localhost/db?connect=false" - assert Client.new + assert Mongo::Connection.new ENV['MONGODB_URI'] = "mongodb://kyle:password@localhost" assert_raise MongoArgumentError do - Client.new + Mongo::Connection.new end end end diff --git a/test/unit/db_test.rb b/test/unit/db_test.rb index b001adc6a7..8b2c54b0f2 100644 --- a/test/unit/db_test.rb +++ b/test/unit/db_test.rb @@ -14,7 +14,7 @@ class DBTest < Test::Unit::TestCase context "DB commands" do setup do @client = stub() - @client.stubs(:safe) + @client.stubs(:write_concern).returns({}) @client.stubs(:read_preference) @client.stubs(:tag_sets) @client.stubs(:acceptable_latency) diff --git a/test/unit/grid_test.rb b/test/unit/grid_test.rb index eb3c6e7f58..cee65a387a 100644 --- a/test/unit/grid_test.rb +++ b/test/unit/grid_test.rb @@ -5,7 +5,7 @@ class GridTest < Test::Unit::TestCase context "GridFS: " do setup do @client = stub() - @client.stubs(:safe) + @client.stubs(:write_concern).returns({}) @client.stubs(:read_preference) @client.stubs(:tag_sets) @client.stubs(:acceptable_latency) diff --git a/test/unit/safe_test.rb b/test/unit/safe_test.rb new file mode 100644 index 0000000000..3a64038b03 --- /dev/null +++ b/test/unit/safe_test.rb @@ -0,0 +1,125 @@ +require 'test_helper' + +class WriteConcernTest < Test::Unit::TestCase + + context "Write-Concern modes on Mongo::Connection " do + setup do + @safe_value = {:w => 7, :j => false, :fsync => false, :wtimeout => false} + @client = Mongo::Connection.new('localhost', 27017, :safe => @safe_value, :connect => false) + end + + should "propogate to DB" do + db = @client['foo'] + assert_equal @safe_value[:w], db.write_concern[:w] + + + db = @client.db('foo') + assert_equal @safe_value[:w], db.write_concern[:w] + + db = DB.new('foo', @client) + assert_equal @safe_value[:w], db.write_concern[:w] + end + + should "allow db override" do + db = DB.new('foo', @client, :safe => false) + assert_equal 0, db.write_concern[:w] + + db = @client.db('foo', :safe => false) + assert_equal 0, db.write_concern[:w] + end + + context "on DB: " do + setup do + @db = @client['foo'] + end + + should "propogate to collection" do + col = @db.collection('bar') + assert_equal @safe_value, col.write_concern + + col = @db['bar'] + assert_equal @safe_value, col.write_concern + + col = Collection.new('bar', @db) + assert_equal @safe_value, col.write_concern + end + + should "allow override on collection" do + col = @db.collection('bar', :safe => false) + assert_equal 0, col.write_concern[:w] + + col = Collection.new('bar', @db, :safe => false) + assert_equal 0, col.write_concern[:w] + end + end + + context "on operations supporting safe mode" do + setup do + @col = @client['foo']['bar'] + end + + should "use default value on insert" do + @client.expects(:send_message_with_acknowledge).with do |op, msg, log, n, safe| + safe == @safe_value + end + + @col.insert({:a => 1}) + end + + should "allow override alternate value on insert" do + @client.expects(:send_message_with_acknowledge).with do |op, msg, log, n, safe| + safe == {:w => 100, :j => false, :fsync => false, :wtimeout => false} + end + + @col.insert({:a => 1}, :safe => {:w => 100}) + end + + should "allow override to disable on insert" do + @client.expects(:send_message) + @col.insert({:a => 1}, :safe => false) + end + + should "use default value on update" do + @client.expects(:send_message_with_acknowledge).with do |op, msg, log, n, safe| + safe == @safe_value + end + + @col.update({:a => 1}, {:a => 2}) + end + + should "allow override alternate value on update" do + @client.expects(:send_message_with_acknowledge).with do |op, msg, log, n, safe| + safe == {:w => 100, :j => false, :fsync => false, :wtimeout => false} + end + + @col.update({:a => 1}, {:a => 2}, :safe => {:w => 100}) + end + + should "allow override to disable on update" do + @client.expects(:send_message) + @col.update({:a => 1}, {:a => 2}, :safe => false) + end + + should "use default value on remove" do + @client.expects(:send_message_with_acknowledge).with do |op, msg, log, n, safe| + safe == @safe_value + end + + @col.remove + end + + should "allow override alternate value on remove" do + @client.expects(:send_message_with_acknowledge).with do |op, msg, log, n, safe| + safe == {:w => 100, :j => false, :fsync => false, :wtimeout => false} + end + + @col.remove({}, :safe => {:w => 100}) + end + + should "allow override to disable on remove" do + @client.expects(:send_message) + @col.remove({}, :safe => false) + end + end + end +end diff --git a/test/unit/write_concern_test.rb b/test/unit/write_concern_test.rb index 55e4f86f90..4a475ad1fe 100644 --- a/test/unit/write_concern_test.rb +++ b/test/unit/write_concern_test.rb @@ -2,30 +2,38 @@ class WriteConcernTest < Test::Unit::TestCase - context "Write-Concern modes on connection: " do + context "Write-Concern modes on Mongo::Client " do setup do - @safe_value = {:w => 7} - @client = Mongo::Client.new('localhost', 27017, :safe => @safe_value, :connect => false) + @write_concern = { + :w => 7, + :j => false, + :fsync => false, + :wtimeout => false + } + + @client = + Mongo::Client.new('localhost', 27017, + @write_concern.merge({:connect => false})) end should "propogate to DB" do db = @client['foo'] - assert_equal @safe_value, db.safe + assert_equal @write_concern, db.write_concern db = @client.db('foo') - assert_equal @safe_value, db.safe + assert_equal @write_concern, db.write_concern db = DB.new('foo', @client) - assert_equal @safe_value, db.safe + assert_equal @write_concern, db.write_concern end should "allow db override" do - db = DB.new('foo', @client, :safe => false) - assert_equal false, db.safe + db = DB.new('foo', @client, :w => 0) + assert_equal 0, db.write_concern[:w] - db = @client.db('foo', :safe => false) - assert_equal false, db.safe + db = @client.db('foo', :w => 0) + assert_equal 0, db.write_concern[:w] end context "on DB: " do @@ -34,91 +42,91 @@ class WriteConcernTest < Test::Unit::TestCase end should "propogate to collection" do - col = @db.collection('bar') - assert_equal @safe_value, col.safe + collection = @db.collection('bar') + assert_equal @write_concern, collection.write_concern - col = @db['bar'] - assert_equal @safe_value, col.safe + collection = @db['bar'] + assert_equal @write_concern, collection.write_concern - col = Collection.new('bar', @db) - assert_equal @safe_value, col.safe + collection = Collection.new('bar', @db) + assert_equal @write_concern, collection.write_concern end should "allow override on collection" do - col = @db.collection('bar', :safe => false) - assert_equal false, col.safe + collection = @db.collection('bar', :w => 0) + assert_equal 0, collection.write_concern[:w] - col = Collection.new('bar', @db, :safe => false) - assert_equal false, col.safe + collection = Collection.new('bar', @db, :w => 0) + assert_equal 0, collection.write_concern[:w] end end - context "on operations supporting safe mode" do + context "on operations supporting 'acknowledged' mode" do setup do - @col = @client['foo']['bar'] + @collection = @client['foo']['bar'] end should "use default value on insert" do - @client.expects(:send_message_with_safe_check).with do |op, msg, log, n, safe| - safe == @safe_value + @client.expects(:send_message_with_acknowledge).with do |op, msg, log, n, wc| + wc == @write_concern end - @col.insert({:a => 1}) + @collection.insert({:a => 1}) end should "allow override alternate value on insert" do - @client.expects(:send_message_with_safe_check).with do |op, msg, log, n, safe| - safe == {:w => 100} + @client.expects(:send_message_with_acknowledge).with do |op, msg, log, n, wc| + wc == {:w => 100, :j => false, :fsync => false, :wtimeout => false} end - @col.insert({:a => 1}, :safe => {:w => 100}) + @collection.insert({:a => 1}, {:w => 100}) end should "allow override to disable on insert" do @client.expects(:send_message) - @col.insert({:a => 1}, :safe => false) + @collection.insert({:a => 1}, :w => 0) end should "use default value on update" do - @client.expects(:send_message_with_safe_check).with do |op, msg, log, n, safe| - safe == @safe_value + @client.expects(:send_message_with_acknowledge).with do |op, msg, log, n, wc| + wc == @write_concern end - @col.update({:a => 1}, {:a => 2}) + @collection.update({:a => 1}, {:a => 2}) end should "allow override alternate value on update" do - @client.expects(:send_message_with_safe_check).with do |op, msg, log, n, safe| - safe == {:w => 100} + @client.expects(:send_message_with_acknowledge).with do |op, msg, log, n, wc| + wc == {:w => 100, :j => false, :fsync => false, :wtimeout => false} end - @col.update({:a => 1}, {:a => 2}, :safe => {:w => 100}) + @collection.update({:a => 1}, {:a => 2}, {:w => 100}) end should "allow override to disable on update" do @client.expects(:send_message) - @col.update({:a => 1}, {:a => 2}, :safe => false) + @collection.update({:a => 1}, {:a => 2}, :w => 0) end should "use default value on remove" do - @client.expects(:send_message_with_safe_check).with do |op, msg, log, n, safe| - safe == @safe_value + @client.expects(:send_message_with_acknowledge).with do |op, msg, log, n, wc| + wc == @write_concern end - @col.remove + @collection.remove end should "allow override alternate value on remove" do - @client.expects(:send_message_with_safe_check).with do |op, msg, log, n, safe| - safe == {:w => 100} + @client.expects(:send_message_with_acknowledge).with do |op, msg, log, n, wc| + wc == {:w => 100, :j => false, :fsync => false, :wtimeout => false} end - @col.remove({}, :safe => {:w => 100}) + @collection.remove({}, {:w => 100}) end should "allow override to disable on remove" do @client.expects(:send_message) - @col.remove({}, :safe => false) + @collection.remove({}, :w => 0) end end end