diff --git a/lib/mongo.rb b/lib/mongo.rb index 754c424..b76ee23 100644 --- a/lib/mongo.rb +++ b/lib/mongo.rb @@ -42,9 +42,11 @@ require 'mongo/util/support' require 'mongo/util/core_ext' require 'mongo/util/pool' require 'mongo/util/server_version' +require 'mongo/util/uri_parser' require 'mongo/collection' require 'mongo/connection' +require 'mongo/repl_set_connection' require 'mongo/cursor' require 'mongo/db' require 'mongo/exceptions' diff --git a/lib/mongo/connection.rb b/lib/mongo/connection.rb index e71ce86..bb5cacb 100644 --- a/lib/mongo/connection.rb +++ b/lib/mongo/connection.rb @@ -39,7 +39,7 @@ module Mongo MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]" attr_reader :logger, :size, :nodes, :auths, :primary, :secondaries, :arbiters, - :safe, :primary_pool, :read_pool, :secondary_pools + :safe, :primary_pool, :read_pool, :secondary_pools, :host_to_try # Counter for generating unique request ids. @@current_request_id = 0 @@ -92,20 +92,13 @@ module Mongo # # @core connections def initialize(host=nil, port=nil, options={}) - @auths = [] + @auths = options.fetch(:auths, []) - if block_given? - @nodes = yield self - else - @nodes = format_pair(host, port) - end + @host_to_try = format_pair(host, port) # Host and port of current master. @host = @port = nil - # Replica set name - @replica_set_name = options[:rs_name] - # Lock for request ids. @id_lock = Mutex.new @@ -129,25 +122,19 @@ module Mongo # slave_ok can be true only if one node is specified @slave_ok = options[:slave_ok] - # Cache the various node types - # when connecting to a replica set. @primary = nil - @secondaries = [] - @arbiters = [] # Connection pool for primay node @primary_pool = nil - # Connection pools for each secondary node - @secondary_pools = [] - @read_pool = nil - @logger = options[:logger] || nil should_connect = options.fetch(:connect, true) connect if should_connect end + # DEPRECATED + # # Initialize a connection to a MongoDB replica set using an array of seed nodes. # # The seed nodes specified will be used on the initial connection to the replica set, but note @@ -170,6 +157,8 @@ module Mongo # :read_secondary => true) # # @return [Mongo::Connection] + # + # @deprecated def self.multi(nodes, opts={}) unless nodes.length > 0 && nodes.all? {|n| n.is_a? Array} raise MongoArgumentError, "Connection.multi requires at least one node to be specified." @@ -195,8 +184,13 @@ module Mongo # # @return [Mongo::Connection] def self.from_uri(uri, opts={}) - new(nil, nil, opts) do |con| - con.parse_uri(uri) + nodes, auths = Mongo::URIParser.parse(uri) + opts.merge!({:auths => auths}) + if nodes.length == 1 + Connection.new(nodes[0][0], nodes[0][1], opts) + elsif nodes.length > 1 + nodes << opts + ReplSetConnection.new(*nodes) end end @@ -480,21 +474,12 @@ module Mongo # @raise [ConnectionFailure] if unable to connect to any host or port. def connect reset_connection - @nodes_to_try = @nodes.clone - while connecting? - node = @nodes_to_try.shift - config = check_is_master(node) - - if is_primary?(config) - set_primary(node) - else - set_auxillary(node, config) - end + config = check_is_master(@host_to_try) + if is_primary?(config) + set_primary(@host_to_try) end - pick_secondary_for_read if @read_secondary - raise ConnectionFailure, "failed to connect to any given host:port" unless connected? end @@ -513,23 +498,21 @@ module Mongo def close @primary_pool.close if @primary_pool @primary_pool = nil - @read_pool = nil - @secondary_pools.each do |pool| - pool.close - end end ## Configuration helper methods - # Returns an array of host-port pairs. + # Returns a host-port pair. + # + # @return [Array] # # @private - def format_pair(pair_or_host, port) - case pair_or_host + def format_pair(host, port) + case host when String - [[pair_or_host, port ? port.to_i : DEFAULT_PORT]] + [host, port ? port.to_i : DEFAULT_PORT] when nil - [['localhost', DEFAULT_PORT]] + ['localhost', DEFAULT_PORT] end end @@ -550,50 +533,7 @@ module Mongo end end - # Parse a MongoDB URI. This method is used by Connection.from_uri. - # Returns an array of nodes and an array of db authorizations, if applicable. - # - # @private - def parse_uri(string) - if string =~ /^mongodb:\/\// - string = string[10..-1] - else - raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}" - end - - nodes = [] - auths = [] - specs = string.split(',') - specs.each do |spec| - matches = MONGODB_URI_MATCHER.match(spec) - if !matches - raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}" - end - - uname = matches[2] - pwd = matches[3] - host = matches[4] - port = matches[6] || DEFAULT_PORT - if !(port.to_s =~ /^\d+$/) - raise MongoArgumentError, "Invalid port #{port}; port must be specified as digits." - end - port = port.to_i - db = matches[8] - - if uname && pwd && db - add_auth(db, uname, pwd) - elsif uname || pwd || db - raise MongoArgumentError, "MongoDB URI must include all three of username, password, " + - "and db if any one of these is specified." - end - - nodes << [host, port] - end - - nodes - end - - # Checkout a socket for reading (i.e., a secondary node). + # Checkout a socket for reading (i.e., a secondary node). def checkout_reader connect unless connected? @@ -629,23 +569,12 @@ module Mongo private - # Pick a node randomly from the set of possible secondaries. - def pick_secondary_for_read - if (size = @secondary_pools.size) > 0 - @read_pool = @secondary_pools[rand(size)] - end - end - # If a ConnectionFailure is raised, this method will be called # to close the connection and reset connection values. + # TODO: evaluate whether this method is actually necessary def reset_connection close @primary = nil - @secondaries = [] - @secondary_pools = [] - @arbiters = [] - @nodes_tried = [] - @nodes_to_try = [] end # Primary is defined as either a master node or a slave if @@ -653,6 +582,7 @@ module Mongo # # If a primary node is discovered, we set the the @host and @port and # apply any saved authentication. + # TODO: simplify def is_primary?(config) config && (config['ismaster'] == 1 || config['ismaster'] == true) || !@replica_set && @slave_ok end @@ -665,18 +595,17 @@ module Mongo config = self['admin'].command({:ismaster => 1}, :sock => socket) - check_set_name(config, socket) rescue OperationFailure, SocketError, SystemCallError, IOError => ex - close unless connected? + close# unless connected? ensure - @nodes_tried << node - if config - update_node_list(config['hosts']) if config['hosts'] + # @nodes_tried << node + # if config + # update_node_list(config['hosts']) if config['hosts'] - if config['msg'] && @logger - @logger.warn("MONGODB #{config['msg']}") - end - end + # if config['msg'] && @logger + # @logger.warn("MONGODB #{config['msg']}") + # end + # end socket.close if socket end @@ -684,21 +613,6 @@ module Mongo config end - # Make sure that we're connected to the expected replica set. - def check_set_name(config, socket) - if @replica_set_name - config = self['admin'].command({:replSetGetStatus => 1}, - :sock => socket, :check_response => false) - - if !Mongo::Support.ok?(config) - raise ReplicaSetConnectionError, config['errmsg'] - elsif config['set'] != @replica_set_name - raise ReplicaSetConnectionError, - "Attempting to connect to replica set '#{config['set']}' but expected '#{@replica_set_name}'" - end - end - end - # Set the specified node as primary, and # apply any saved authentication credentials. def set_primary(node) @@ -708,45 +622,6 @@ module Mongo apply_saved_authentication end - # Determines what kind of node we have and caches its host - # and port so that users can easily connect manually. - def set_auxillary(node, config) - if config - if config['secondary'] - host, port = *node - @secondaries << node unless @secondaries.include?(node) - @secondary_pools << Pool.new(self, host, port, :size => @pool_size, :timeout => @timeout) - elsif config['arbiterOnly'] - @arbiters << node unless @arbiters.include?(node) - end - end - end - - # Update the list of known nodes. Only applies to replica sets, - # where the response to the ismaster command will return a list - # of known hosts. - # - # @param hosts [Array] a list of hosts, specified as string-encoded - # host-port values. Example: ["myserver-1.org:27017", "myserver-1.org:27017"] - # - # @return [Array] the updated list of nodes - def update_node_list(hosts) - new_nodes = hosts.map do |host| - if !host.respond_to?(:split) - warn "Could not parse host #{host.inspect}." - next - end - - host, port = host.split(':') - [host, port.to_i] - end - - # Replace the list of seed nodes with the canonical list. - @nodes = new_nodes.clone - - @nodes_to_try = new_nodes - @nodes_tried - end - def receive(sock) receive_and_discard_header(sock) number_received, cursor_id = receive_response_header(sock) diff --git a/lib/mongo/repl_set_connection.rb b/lib/mongo/repl_set_connection.rb new file mode 100644 index 0000000..d07558d --- /dev/null +++ b/lib/mongo/repl_set_connection.rb @@ -0,0 +1,191 @@ +# encoding: UTF-8 + +# -- +# Copyright (C) 2008-2010 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 + + # Instantiates and manages connections to MongoDB. + class ReplSetConnection + attr_reader :nodes, :secondaries, :arbiters, :read_pool, :secondary_pools + + def initialize(*args) + + if args.last.is_a?(Hash) + options = args.pop + end + + @nodes = args + + # Replica set name + @replica_set_name = options[:rs_name] + + # Cache the various node types when connecting to a replica set. + @secondaries = [] + @arbiters = [] + + # Connection pools for each secondary node + @secondary_pools = [] + @read_pool = nil + + super + end + + # Create a new socket and attempt to connect to master. + # If successful, sets host and port to master and returns the socket. + # + # If connecting to a replica set, this method will replace the + # initially-provided seed list with any nodes known to the set. + # + # @raise [ConnectionFailure] if unable to connect to any host or port. + def connect + reset_connection + @nodes_to_try = @nodes.clone + + while connecting? + node = @nodes_to_try.shift + config = check_is_master(node) + + if is_primary?(config) + set_primary(node) + else + set_auxillary(node, config) + end + end + + pick_secondary_for_read if @read_secondary + + raise ConnectionFailure, "failed to connect to any given host:port" unless connected? + end + + def connecting? + @nodes_to_try.length > 0 + end + + # Close the connection to the database. + def close + super + @read_pool = nil + @secondary_pools.each do |pool| + pool.close + end + end + + # If a ConnectionFailure is raised, this method will be called + # to close the connection and reset connection values. + def reset_connection + super + @secondaries = [] + @secondary_pools = [] + @arbiters = [] + @nodes_tried = [] + @nodes_to_try = [] + end + + private + + def check_is_master(node) + begin + host, port = *node + socket = TCPSocket.new(host, port) + socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) + + config = self['admin'].command({:ismaster => 1}, :sock => socket) + + check_set_name(config, socket) + rescue OperationFailure, SocketError, SystemCallError, IOError => ex + close unless connected? + ensure + @nodes_tried << node + if config + update_node_list(config['hosts']) if config['hosts'] + + if config['msg'] && @logger + @logger.warn("MONGODB #{config['msg']}") + end + end + + socket.close if socket + end + + config + end + + + + # Pick a node randomly from the set of possible secondaries. + def pick_secondary_for_read + if (size = @secondary_pools.size) > 0 + @read_pool = @secondary_pools[rand(size)] + end + end + + # Make sure that we're connected to the expected replica set. + def check_set_name(config, socket) + if @replica_set_name + config = self['admin'].command({:replSetGetStatus => 1}, + :sock => socket, :check_response => false) + + if !Mongo::Support.ok?(config) + raise ReplicaSetConnectionError, config['errmsg'] + elsif config['set'] != @replica_set_name + raise ReplicaSetConnectionError, + "Attempting to connect to replica set '#{config['set']}' but expected '#{@replica_set_name}'" + end + end + end + + # Determines what kind of node we have and caches its host + # and port so that users can easily connect manually. + def set_auxillary(node, config) + if config + if config['secondary'] + host, port = *node + @secondaries << node unless @secondaries.include?(node) + @secondary_pools << Pool.new(self, host, port, :size => @pool_size, :timeout => @timeout) + elsif config['arbiterOnly'] + @arbiters << node unless @arbiters.include?(node) + end + end + end + + # Update the list of known nodes. Only applies to replica sets, + # where the response to the ismaster command will return a list + # of known hosts. + # + # @param hosts [Array] a list of hosts, specified as string-encoded + # host-port values. Example: ["myserver-1.org:27017", "myserver-1.org:27017"] + # + # @return [Array] the updated list of nodes + def update_node_list(hosts) + new_nodes = hosts.map do |host| + if !host.respond_to?(:split) + warn "Could not parse host #{host.inspect}." + next + end + + host, port = host.split(':') + [host, port.to_i] + end + + # Replace the list of seed nodes with the canonical list. + @nodes = new_nodes.clone + + @nodes_to_try = new_nodes - @nodes_tried + end + + end +end diff --git a/lib/mongo/util/uri_parser.rb b/lib/mongo/util/uri_parser.rb new file mode 100644 index 0000000..e413beb --- /dev/null +++ b/lib/mongo/util/uri_parser.rb @@ -0,0 +1,71 @@ +# encoding: UTF-8 + +# -- +# Copyright (C) 2008-2010 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 URIParser + + DEFAULT_PORT = 27017 + MONGODB_URI_MATCHER = /(([-_.\w\d]+):([-_\w\d]+)@)?([-.\w\d]+)(:([\w\d]+))?(\/([-\d\w]+))?/ + MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]" + + extend self + + # Parse a MongoDB URI. This method is used by Connection.from_uri. + # Returns an array of nodes and an array of db authorizations, if applicable. + # + # @private + def parse(string) + if string =~ /^mongodb:\/\// + string = string[10..-1] + else + raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}" + end + + nodes = [] + auths = [] + specs = string.split(',') + specs.each do |spec| + matches = MONGODB_URI_MATCHER.match(spec) + if !matches + raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}" + end + + uname = matches[2] + pwd = matches[3] + host = matches[4] + port = matches[6] || DEFAULT_PORT + if !(port.to_s =~ /^\d+$/) + raise MongoArgumentError, "Invalid port #{port}; port must be specified as digits." + end + port = port.to_i + db = matches[8] + + if uname && pwd && db + auths << {'db_name' => db, 'username' => uname, 'password' => pwd} + elsif uname || pwd || db + raise MongoArgumentError, "MongoDB URI must include all three of username, password, " + + "and db if any one of these is specified." + end + + nodes << [host, port] + end + + [nodes, auths] + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 0e13370..56d2888 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -26,7 +26,7 @@ unless defined? MONGO_TEST_DB end unless defined? TEST_PORT - TEST_PORT = ENV['MONGO_RUBY_DRIVER_PORT'].to_i || Connection::DEFAULT_PORT + TEST_PORT = ENV['MONGO_RUBY_DRIVER_PORT'] ? ENV['MONGO_RUBY_DRIVER_PORT'].to_i : Mongo::Connection::DEFAULT_PORT end unless defined? TEST_HOST diff --git a/test/unit/connection_test.rb b/test/unit/connection_test.rb index 4edc2b0..d0d4627 100644 --- a/test/unit/connection_test.rb +++ b/test/unit/connection_test.rb @@ -41,103 +41,21 @@ class ConnectionTest < Test::Unit::TestCase end end - context "connecting to a replica set" do - setup do - TCPSocket.stubs(:new).returns(new_mock_socket('localhost', 27017)) - @conn = Connection.multi([['localhost', 27017]], :connect => false, :read_secondary => true) - - admin_db = new_mock_db - @hosts = ['localhost:27018', 'localhost:27019', 'localhost:27020'] - - admin_db.stubs(:command).returns({'ok' => 1, 'ismaster' => 1, 'hosts' => @hosts}). - then.returns({'ok' => 1, 'ismaster' => 0, 'hosts' => @hosts, 'secondary' => 1}). - then.returns({'ok' => 1, 'ismaster' => 0, 'hosts' => @hosts, 'secondary' => 1}). - then.returns({'ok' => 1, 'ismaster' => 0, 'arbiterOnly' => 1}) - - @conn.stubs(:[]).with('admin').returns(admin_db) - @conn.connect - end - - should "store the hosts returned from the ismaster command" do - assert_equal 'localhost', @conn.primary_pool.host - assert_equal 27017, @conn.primary_pool.port - - assert_equal 'localhost', @conn.secondary_pools[0].host - assert_equal 27018, @conn.secondary_pools[0].port - - assert_equal 'localhost', @conn.secondary_pools[1].host - assert_equal 27019, @conn.secondary_pools[1].port - - assert_equal 2, @conn.secondary_pools.length - end - end - - context "connecting to a replica set and providing seed nodes" do - setup do - TCPSocket.stubs(:new).returns(new_mock_socket) - @conn = Connection.multi([['localhost', 27017], ['localhost', 27019]], :connect => false) - - admin_db = new_mock_db - @hosts = ['localhost:27017', 'localhost:27018', 'localhost:27019'] - admin_db.stubs(:command).returns({'ok' => 1, 'ismaster' => 1, 'hosts' => @hosts}) - @conn.stubs(:[]).with('admin').returns(admin_db) - @conn.connect - end - - should "not store any hosts redundantly" do - end - end - - context "initializing a paired connection" do - should "require left and right nodes" do - assert_raise MongoArgumentError do - Connection.multi(['localhost', 27018], :connect => false) - end - - assert_raise MongoArgumentError do - Connection.multi(['localhost', 27018], :connect => false) - end - end - - should "store both nodes" do - @conn = Connection.multi([['localhost', 27017], ['localhost', 27018]], :connect => false) - - assert_equal ['localhost', 27017], @conn.nodes[0] - assert_equal ['localhost', 27018], @conn.nodes[1] - end - end - context "initializing with a mongodb uri" do should "parse a simple uri" do @conn = Connection.from_uri("mongodb://localhost", :connect => false) - assert_equal ['localhost', 27017], @conn.nodes[0] + assert_equal ['localhost', 27017], @conn.host_to_try end should "allow a complex host names" do host_name = "foo.bar-12345.org" @conn = Connection.from_uri("mongodb://#{host_name}", :connect => false) - assert_equal [host_name, 27017], @conn.nodes[0] - end - - should "parse a uri specifying multiple nodes" do - @conn = Connection.from_uri("mongodb://localhost:27017,mydb.com:27018", :connect => false) - assert_equal ['localhost', 27017], @conn.nodes[0] - assert_equal ['mydb.com', 27018], @conn.nodes[1] - end - - should "parse a uri specifying multiple nodes with auth" do - @conn = Connection.from_uri("mongodb://kyle:s3cr3t@localhost:27017/app,mickey:m0u5e@mydb.com:27018/dsny", :connect => false) - assert_equal ['localhost', 27017], @conn.nodes[0] - assert_equal ['mydb.com', 27018], @conn.nodes[1] - auth_hash = {'username' => 'kyle', 'password' => 's3cr3t', 'db_name' => 'app'} - assert_equal auth_hash, @conn.auths[0] - auth_hash = {'username' => 'mickey', 'password' => 'm0u5e', 'db_name' => 'dsny'} - assert_equal auth_hash, @conn.auths[1] + assert_equal [host_name, 27017], @conn.host_to_try end should "parse a uri with a hyphen & underscore in the username or password" do @conn = Connection.from_uri("mongodb://hyphen-user_name:p-s_s@localhost:27017/db", :connect => false) - assert_equal ['localhost', 27017], @conn.nodes[0] + assert_equal ['localhost', 27017], @conn.host_to_try auth_hash = { 'db_name' => 'db', 'username' => 'hyphen-user_name', "password" => 'p-s_s' } assert_equal auth_hash, @conn.auths[0] end diff --git a/test/unit/repl_set_connection_test.rb b/test/unit/repl_set_connection_test.rb new file mode 100644 index 0000000..1fe14ce --- /dev/null +++ b/test/unit/repl_set_connection_test.rb @@ -0,0 +1,181 @@ +require './test/test_helper' +include Mongo + +#class ConnectionTest < Test::Unit::TestCase +# context "Initialization: " do +# setup do +# def new_mock_socket(host='localhost', port=27017) +# socket = Object.new +# socket.stubs(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) +# socket.stubs(:close) +# socket +# end +# +# def new_mock_db +# db = Object.new +# end +# end +# +# context "given a single node" do +# setup do +# @conn = Connection.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}) +# @conn.expects(:[]).with('admin').returns(admin_db) +# @conn.connect +# end +# +# should "set localhost and port to master" do +# assert_equal 'localhost', @conn.primary_pool.host +# assert_equal 27017, @conn.primary_pool.port +# end +# +# should "set connection pool to 1" do +# assert_equal 1, @conn.primary_pool.size +# end +# +# should "default slave_ok to false" do +# assert !@conn.slave_ok? +# end +# end +# +# context "connecting to a replica set" do +# setup do +# TCPSocket.stubs(:new).returns(new_mock_socket('localhost', 27017)) +# @conn = Connection.multi([['localhost', 27017]], :connect => false, :read_secondary => true) +# +# admin_db = new_mock_db +# @hosts = ['localhost:27018', 'localhost:27019', 'localhost:27020'] +# +# admin_db.stubs(:command).returns({'ok' => 1, 'ismaster' => 1, 'hosts' => @hosts}). +# then.returns({'ok' => 1, 'ismaster' => 0, 'hosts' => @hosts, 'secondary' => 1}). +# then.returns({'ok' => 1, 'ismaster' => 0, 'hosts' => @hosts, 'secondary' => 1}). +# then.returns({'ok' => 1, 'ismaster' => 0, 'arbiterOnly' => 1}) +# +# @conn.stubs(:[]).with('admin').returns(admin_db) +# @conn.connect +# end +# +# should "store the hosts returned from the ismaster command" do +# assert_equal 'localhost', @conn.primary_pool.host +# assert_equal 27017, @conn.primary_pool.port +# +# assert_equal 'localhost', @conn.secondary_pools[0].host +# assert_equal 27018, @conn.secondary_pools[0].port +# +# assert_equal 'localhost', @conn.secondary_pools[1].host +# assert_equal 27019, @conn.secondary_pools[1].port +# +# assert_equal 2, @conn.secondary_pools.length +# end +# end +# +# context "connecting to a replica set and providing seed nodes" do +# setup do +# TCPSocket.stubs(:new).returns(new_mock_socket) +# @conn = Connection.multi([['localhost', 27017], ['localhost', 27019]], :connect => false) +# +# admin_db = new_mock_db +# @hosts = ['localhost:27017', 'localhost:27018', 'localhost:27019'] +# admin_db.stubs(:command).returns({'ok' => 1, 'ismaster' => 1, 'hosts' => @hosts}) +# @conn.stubs(:[]).with('admin').returns(admin_db) +# @conn.connect +# end +# +# should "not store any hosts redundantly" do +# end +# end +# +# context "initializing a paired connection" do +# should "require left and right nodes" do +# assert_raise MongoArgumentError do +# Connection.multi(['localhost', 27018], :connect => false) +# end +# +# assert_raise MongoArgumentError do +# Connection.multi(['localhost', 27018], :connect => false) +# end +# end +# +# should "store both nodes" do +# @conn = Connection.multi([['localhost', 27017], ['localhost', 27018]], :connect => false) +# +# assert_equal ['localhost', 27017], @conn.nodes[0] +# assert_equal ['localhost', 27018], @conn.nodes[1] +# end +# end +# +# context "initializing with a mongodb uri" do +# should "parse a simple uri" do +# @conn = Connection.from_uri("mongodb://localhost", :connect => false) +# assert_equal ['localhost', 27017], @conn.primary +# end +# +# should "allow a complex host names" do +# host_name = "foo.bar-12345.org" +# @conn = Connection.from_uri("mongodb://#{host_name}", :connect => false) +# assert_equal [host_name, 27017], @conn.primary +# end +# +# should "parse a uri specifying multiple nodes" do +# #@conn = Connection.from_uri("mongodb://localhost:27017,mydb.com:27018", :connect => false) +# #assert_equal ['localhost', 27017], @conn.nodes[0] +# #assert_equal ['mydb.com', 27018], @conn.nodes[1] +# end +# +# should "parse a uri specifying multiple nodes with auth" do +# #@conn = Connection.from_uri("mongodb://kyle:s3cr3t@localhost:27017/app,mickey:m0u5e@mydb.com:27018/dsny", :connect => false) +# #assert_equal ['localhost', 27017], @conn.nodes[0] +# #assert_equal ['mydb.com', 27018], @conn.nodes[1] +# #auth_hash = {'username' => 'kyle', 'password' => 's3cr3t', 'db_name' => 'app'} +# #assert_equal auth_hash, @conn.auths[0] +# #auth_hash = {'username' => 'mickey', 'password' => 'm0u5e', 'db_name' => 'dsny'} +# #assert_equal auth_hash, @conn.auths[1] +# end +# +# should "parse a uri with a hyphen & underscore in the username or password" do +# @conn = Connection.from_uri("mongodb://hyphen-user_name:p-s_s@localhost:27017/db", :connect => false) +# assert_equal ['localhost', 27017], @conn.nodes[0] +# auth_hash = { 'db_name' => 'db', 'username' => 'hyphen-user_name', "password" => 'p-s_s' } +# assert_equal auth_hash, @conn.auths[0] +# end +# +# should "attempt to connect" do +# TCPSocket.stubs(:new).returns(new_mock_socket) +# @conn = Connection.from_uri("mongodb://localhost", :connect => false) +# +# admin_db = new_mock_db +# admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1}) +# @conn.expects(:[]).with('admin').returns(admin_db) +# @conn.expects(:apply_saved_authentication) +# @conn.connect +# end +# +# should "raise an error on invalid uris" do +# assert_raise MongoArgumentError do +# Connection.from_uri("mongo://localhost", :connect => false) +# end +# +# assert_raise MongoArgumentError do +# Connection.from_uri("mongodb://localhost:abc", :connect => false) +# end +# +# assert_raise MongoArgumentError do +# Connection.from_uri("mongodb://localhost:27017, my.db.com:27018, ", :connect => false) +# end +# end +# +# should "require all of username, password, and database if any one is specified" do +# assert_raise MongoArgumentError do +# Connection.from_uri("mongodb://localhost/db", :connect => false) +# end +# +# assert_raise MongoArgumentError do +# Connection.from_uri("mongodb://kyle:password@localhost", :connect => false) +# end +# end +# end +# end +#end