Initial ReplSetConnection checking. Refactoring.
This commit is contained in:
parent
a17455da27
commit
08b7cddc81
|
@ -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'
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue