Initial ReplSetConnection checking. Refactoring.

This commit is contained in:
Kyle Banker 2010-12-10 16:00:35 -05:00
parent a17455da27
commit 08b7cddc81
7 changed files with 484 additions and 246 deletions

View File

@ -42,9 +42,11 @@ require 'mongo/util/support'
require 'mongo/util/core_ext' require 'mongo/util/core_ext'
require 'mongo/util/pool' require 'mongo/util/pool'
require 'mongo/util/server_version' require 'mongo/util/server_version'
require 'mongo/util/uri_parser'
require 'mongo/collection' require 'mongo/collection'
require 'mongo/connection' require 'mongo/connection'
require 'mongo/repl_set_connection'
require 'mongo/cursor' require 'mongo/cursor'
require 'mongo/db' require 'mongo/db'
require 'mongo/exceptions' require 'mongo/exceptions'

View File

@ -39,7 +39,7 @@ module Mongo
MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]" MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]"
attr_reader :logger, :size, :nodes, :auths, :primary, :secondaries, :arbiters, 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. # Counter for generating unique request ids.
@@current_request_id = 0 @@current_request_id = 0
@ -92,20 +92,13 @@ module Mongo
# #
# @core connections # @core connections
def initialize(host=nil, port=nil, options={}) def initialize(host=nil, port=nil, options={})
@auths = [] @auths = options.fetch(:auths, [])
if block_given? @host_to_try = format_pair(host, port)
@nodes = yield self
else
@nodes = format_pair(host, port)
end
# Host and port of current master. # Host and port of current master.
@host = @port = nil @host = @port = nil
# Replica set name
@replica_set_name = options[:rs_name]
# Lock for request ids. # Lock for request ids.
@id_lock = Mutex.new @id_lock = Mutex.new
@ -129,25 +122,19 @@ module Mongo
# slave_ok can be true only if one node is specified # slave_ok can be true only if one node is specified
@slave_ok = options[:slave_ok] @slave_ok = options[:slave_ok]
# Cache the various node types
# when connecting to a replica set.
@primary = nil @primary = nil
@secondaries = []
@arbiters = []
# Connection pool for primay node # Connection pool for primay node
@primary_pool = nil @primary_pool = nil
# Connection pools for each secondary node
@secondary_pools = []
@read_pool = nil
@logger = options[:logger] || nil @logger = options[:logger] || nil
should_connect = options.fetch(:connect, true) should_connect = options.fetch(:connect, true)
connect if should_connect connect if should_connect
end end
# DEPRECATED
#
# Initialize a connection to a MongoDB replica set using an array of seed nodes. # 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 # 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) # :read_secondary => true)
# #
# @return [Mongo::Connection] # @return [Mongo::Connection]
#
# @deprecated
def self.multi(nodes, opts={}) def self.multi(nodes, opts={})
unless nodes.length > 0 && nodes.all? {|n| n.is_a? Array} unless nodes.length > 0 && nodes.all? {|n| n.is_a? Array}
raise MongoArgumentError, "Connection.multi requires at least one node to be specified." raise MongoArgumentError, "Connection.multi requires at least one node to be specified."
@ -195,8 +184,13 @@ module Mongo
# #
# @return [Mongo::Connection] # @return [Mongo::Connection]
def self.from_uri(uri, opts={}) def self.from_uri(uri, opts={})
new(nil, nil, opts) do |con| nodes, auths = Mongo::URIParser.parse(uri)
con.parse_uri(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
end end
@ -480,21 +474,12 @@ module Mongo
# @raise [ConnectionFailure] if unable to connect to any host or port. # @raise [ConnectionFailure] if unable to connect to any host or port.
def connect def connect
reset_connection reset_connection
@nodes_to_try = @nodes.clone
while connecting? config = check_is_master(@host_to_try)
node = @nodes_to_try.shift if is_primary?(config)
config = check_is_master(node) set_primary(@host_to_try)
if is_primary?(config)
set_primary(node)
else
set_auxillary(node, config)
end
end end
pick_secondary_for_read if @read_secondary
raise ConnectionFailure, "failed to connect to any given host:port" unless connected? raise ConnectionFailure, "failed to connect to any given host:port" unless connected?
end end
@ -513,23 +498,21 @@ module Mongo
def close def close
@primary_pool.close if @primary_pool @primary_pool.close if @primary_pool
@primary_pool = nil @primary_pool = nil
@read_pool = nil
@secondary_pools.each do |pool|
pool.close
end
end end
## Configuration helper methods ## Configuration helper methods
# Returns an array of host-port pairs. # Returns a host-port pair.
#
# @return [Array]
# #
# @private # @private
def format_pair(pair_or_host, port) def format_pair(host, port)
case pair_or_host case host
when String when String
[[pair_or_host, port ? port.to_i : DEFAULT_PORT]] [host, port ? port.to_i : DEFAULT_PORT]
when nil when nil
[['localhost', DEFAULT_PORT]] ['localhost', DEFAULT_PORT]
end end
end end
@ -550,50 +533,7 @@ module Mongo
end end
end end
# Parse a MongoDB URI. This method is used by Connection.from_uri. # Checkout a socket for reading (i.e., a secondary node).
# 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).
def checkout_reader def checkout_reader
connect unless connected? connect unless connected?
@ -629,23 +569,12 @@ module Mongo
private 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 # If a ConnectionFailure is raised, this method will be called
# to close the connection and reset connection values. # to close the connection and reset connection values.
# TODO: evaluate whether this method is actually necessary
def reset_connection def reset_connection
close close
@primary = nil @primary = nil
@secondaries = []
@secondary_pools = []
@arbiters = []
@nodes_tried = []
@nodes_to_try = []
end end
# Primary is defined as either a master node or a slave if # 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 # If a primary node is discovered, we set the the @host and @port and
# apply any saved authentication. # apply any saved authentication.
# TODO: simplify
def is_primary?(config) def is_primary?(config)
config && (config['ismaster'] == 1 || config['ismaster'] == true) || !@replica_set && @slave_ok config && (config['ismaster'] == 1 || config['ismaster'] == true) || !@replica_set && @slave_ok
end end
@ -665,18 +595,17 @@ module Mongo
config = self['admin'].command({:ismaster => 1}, :sock => socket) config = self['admin'].command({:ismaster => 1}, :sock => socket)
check_set_name(config, socket)
rescue OperationFailure, SocketError, SystemCallError, IOError => ex rescue OperationFailure, SocketError, SystemCallError, IOError => ex
close unless connected? close# unless connected?
ensure ensure
@nodes_tried << node # @nodes_tried << node
if config # if config
update_node_list(config['hosts']) if config['hosts'] # update_node_list(config['hosts']) if config['hosts']
if config['msg'] && @logger # if config['msg'] && @logger
@logger.warn("MONGODB #{config['msg']}") # @logger.warn("MONGODB #{config['msg']}")
end # end
end # end
socket.close if socket socket.close if socket
end end
@ -684,21 +613,6 @@ module Mongo
config config
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
# Set the specified node as primary, and # Set the specified node as primary, and
# apply any saved authentication credentials. # apply any saved authentication credentials.
def set_primary(node) def set_primary(node)
@ -708,45 +622,6 @@ module Mongo
apply_saved_authentication apply_saved_authentication
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
def receive(sock) def receive(sock)
receive_and_discard_header(sock) receive_and_discard_header(sock)
number_received, cursor_id = receive_response_header(sock) number_received, cursor_id = receive_response_header(sock)

View File

@ -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

View File

@ -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

View File

@ -26,7 +26,7 @@ unless defined? MONGO_TEST_DB
end end
unless defined? TEST_PORT 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 end
unless defined? TEST_HOST unless defined? TEST_HOST

View File

@ -41,103 +41,21 @@ class ConnectionTest < Test::Unit::TestCase
end end
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 context "initializing with a mongodb uri" do
should "parse a simple uri" do should "parse a simple uri" do
@conn = Connection.from_uri("mongodb://localhost", :connect => false) @conn = Connection.from_uri("mongodb://localhost", :connect => false)
assert_equal ['localhost', 27017], @conn.nodes[0] assert_equal ['localhost', 27017], @conn.host_to_try
end end
should "allow a complex host names" do should "allow a complex host names" do
host_name = "foo.bar-12345.org" host_name = "foo.bar-12345.org"
@conn = Connection.from_uri("mongodb://#{host_name}", :connect => false) @conn = Connection.from_uri("mongodb://#{host_name}", :connect => false)
assert_equal [host_name, 27017], @conn.nodes[0] assert_equal [host_name, 27017], @conn.host_to_try
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 end
should "parse a uri with a hyphen & underscore in the username or password" do 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) @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' } auth_hash = { 'db_name' => 'db', 'username' => 'hyphen-user_name', "password" => 'p-s_s' }
assert_equal auth_hash, @conn.auths[0] assert_equal auth_hash, @conn.auths[0]
end end

View File

@ -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