2011-08-24 22:34:00 +00:00
|
|
|
module Mongo
|
|
|
|
class PoolManager
|
|
|
|
|
|
|
|
attr_reader :connection, :seeds, :arbiters, :primary, :secondaries,
|
2011-08-30 19:59:04 +00:00
|
|
|
:primary_pool, :read_pool, :secondary_pools, :hosts, :nodes, :max_bson_size,
|
|
|
|
:tags_to_pools
|
2011-08-24 22:34:00 +00:00
|
|
|
|
|
|
|
def initialize(connection, seeds)
|
|
|
|
@connection = connection
|
|
|
|
@seeds = seeds
|
2011-08-25 15:27:58 +00:00
|
|
|
@refresh_node = nil
|
2011-08-24 22:34:00 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def connect
|
|
|
|
initialize_data
|
2011-08-25 18:57:24 +00:00
|
|
|
members = connect_to_members
|
|
|
|
initialize_pools(members)
|
|
|
|
update_seed_list(members)
|
|
|
|
@members = members
|
2011-08-24 22:34:00 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Ensure that the view of the replica set is current by
|
|
|
|
# running the ismaster command and checking to see whether
|
|
|
|
# we've connected to all known nodes. If not, automatically
|
|
|
|
# connect to these unconnected nodes. This is handy when we've
|
|
|
|
# connected to a replica set with no primary or when a secondary
|
|
|
|
# node comes up after we've connected.
|
|
|
|
#
|
|
|
|
# If we're connected to nodes that are no longer part of the set,
|
|
|
|
# remove these from our set of secondary pools.
|
|
|
|
def update_required?(hosts)
|
2011-08-25 15:27:58 +00:00
|
|
|
if !@refresh_node || !@refresh_node.active?
|
2011-08-24 22:34:00 +00:00
|
|
|
begin
|
2011-08-25 15:27:58 +00:00
|
|
|
@refresh_node = get_valid_seed_node
|
2011-08-24 22:34:00 +00:00
|
|
|
rescue ConnectionFailure
|
2011-08-25 15:27:58 +00:00
|
|
|
warn "Could not refresh config because no valid seed node was available."
|
|
|
|
return
|
2011-08-24 22:34:00 +00:00
|
|
|
end
|
|
|
|
end
|
2011-08-25 15:27:58 +00:00
|
|
|
node = @refresh_node
|
2011-08-24 22:34:00 +00:00
|
|
|
|
|
|
|
node_list = node.node_list
|
|
|
|
|
|
|
|
unconnected_nodes = node_list - hosts
|
|
|
|
removed_nodes = hosts - node_list
|
|
|
|
|
|
|
|
if unconnected_nodes.empty? && removed_nodes.empty?
|
|
|
|
return false
|
|
|
|
else
|
|
|
|
{:unconnected => unconnected_nodes, :removed => removed_nodes}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def update(manager, node_struct)
|
|
|
|
reference_manager_data(manager)
|
|
|
|
unconnected_nodes = node_struct[:unconnected]
|
|
|
|
removed_nodes = node_struct[:removed]
|
|
|
|
|
|
|
|
if !removed_nodes.empty?
|
|
|
|
removed_nodes.each do |node|
|
|
|
|
if @primary_pool && @primary_pool.host_string == node
|
|
|
|
@primary = nil
|
|
|
|
@primary_pool.close
|
|
|
|
@primary_pool = nil
|
2011-08-25 22:52:20 +00:00
|
|
|
elsif rejected_pool = @secondary_pools.detect {|pool| pool.host_string == node}
|
|
|
|
@secondary_pools.delete(rejected_pool)
|
|
|
|
@secondaries.delete(rejected_pool.host_port)
|
2011-08-24 22:34:00 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if !unconnected_nodes.empty?
|
|
|
|
nodes = []
|
|
|
|
unconnected_nodes.each do |host_port|
|
|
|
|
node = Mongo::Node.new(self.connection, host_port)
|
|
|
|
if node.connect && node.set_config
|
|
|
|
nodes << node
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if !nodes.empty?
|
|
|
|
initialize_pools(nodes)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def reference_manager_data(manager)
|
|
|
|
@primary = manager.primary
|
|
|
|
@primary_pool = manager.primary_pool
|
|
|
|
@secondaries = manager.secondaries
|
|
|
|
@secondary_pools = manager.secondary_pools
|
|
|
|
@read_pool = manager.read_pool
|
|
|
|
@arbiters = manager.arbiters
|
|
|
|
@hosts = manager.hosts
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize_data
|
|
|
|
@primary = nil
|
|
|
|
@primary_pool = nil
|
|
|
|
@read_pool = nil
|
|
|
|
@arbiters = []
|
|
|
|
@secondaries = []
|
|
|
|
@secondary_pools = []
|
|
|
|
@hosts = []
|
2011-08-25 18:57:24 +00:00
|
|
|
@members = []
|
2011-08-30 19:59:04 +00:00
|
|
|
@tags_to_pools = {}
|
2011-08-24 22:34:00 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Connect to each member of the replica set
|
|
|
|
# as reported by the given seed node, and return
|
|
|
|
# as a list of Mongo::Node objects.
|
|
|
|
def connect_to_members
|
2011-08-25 18:57:24 +00:00
|
|
|
members = []
|
2011-08-24 22:34:00 +00:00
|
|
|
|
|
|
|
seed = get_valid_seed_node
|
|
|
|
|
|
|
|
seed.node_list.each do |host|
|
|
|
|
node = Mongo::Node.new(self.connection, host)
|
|
|
|
if node.connect && node.set_config
|
2011-08-25 18:57:24 +00:00
|
|
|
members << node
|
2011-08-24 22:34:00 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-08-25 18:57:24 +00:00
|
|
|
if members.empty?
|
2011-08-24 22:34:00 +00:00
|
|
|
raise ConnectionFailure, "Failed to connect to any given member."
|
|
|
|
end
|
|
|
|
|
2011-08-25 18:57:24 +00:00
|
|
|
members
|
2011-08-24 22:34:00 +00:00
|
|
|
end
|
|
|
|
|
2011-08-30 19:59:04 +00:00
|
|
|
def associate_tags_with_pool(tags, pool)
|
|
|
|
tags.each_key do |key|
|
|
|
|
@tags_to_pools[{key => tags[key]}] ||= []
|
|
|
|
@tags_to_pools[{key => tags[key]}] << pool
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Sort each tag pool entry in descending order
|
|
|
|
# according to ping time.
|
|
|
|
def sort_tag_pools!
|
|
|
|
@tags_to_pools.each_value do |pool_list|
|
|
|
|
pool_list.sort! do |a, b|
|
|
|
|
a.ping_time <=> b.ping_time
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-08-24 22:34:00 +00:00
|
|
|
# Initialize the connection pools for the primary and secondary nodes.
|
2011-08-25 18:57:24 +00:00
|
|
|
def initialize_pools(members)
|
|
|
|
members.each do |member|
|
2011-08-24 22:34:00 +00:00
|
|
|
@hosts << member.host_string
|
|
|
|
|
|
|
|
if member.primary?
|
|
|
|
@primary = member.host_port
|
|
|
|
@primary_pool = Pool.new(self.connection, member.host, member.port,
|
|
|
|
:size => self.connection.pool_size,
|
|
|
|
:timeout => self.connection.connect_timeout,
|
|
|
|
:node => member)
|
2011-08-30 19:59:04 +00:00
|
|
|
associate_tags_with_pool(member.tags, @primary_pool)
|
2011-08-24 22:34:00 +00:00
|
|
|
elsif member.secondary? && !@secondaries.include?(member.host_port)
|
|
|
|
@secondaries << member.host_port
|
2011-08-30 19:59:04 +00:00
|
|
|
pool = Pool.new(self.connection, member.host, member.port,
|
2011-08-24 22:34:00 +00:00
|
|
|
:size => self.connection.pool_size,
|
|
|
|
:timeout => self.connection.connect_timeout,
|
|
|
|
:node => member)
|
2011-08-30 19:59:04 +00:00
|
|
|
@secondary_pools << pool
|
|
|
|
associate_tags_with_pool(member.tags, pool)
|
2011-08-24 22:34:00 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2011-08-30 19:59:04 +00:00
|
|
|
sort_tag_pools!
|
2011-08-25 18:57:24 +00:00
|
|
|
@max_bson_size = members.first.config['maxBsonObjectSize'] ||
|
|
|
|
Mongo::DEFAULT_MAX_BSON_SIZE
|
|
|
|
@arbiters = members.first.arbiters
|
2011-08-24 22:34:00 +00:00
|
|
|
choose_read_pool
|
|
|
|
end
|
|
|
|
|
|
|
|
# Pick a node from the set of possible secondaries.
|
|
|
|
# If more than one node is available, use the ping
|
|
|
|
# time to figure out which nodes to choose from.
|
|
|
|
def choose_read_pool
|
|
|
|
if @secondary_pools.empty?
|
|
|
|
@read_pool = @primary_pool
|
|
|
|
elsif @secondary_pools.size == 1
|
|
|
|
@read_pool = @secondary_pools[0]
|
|
|
|
else
|
|
|
|
ping_ranges = Array.new(3) { |i| Array.new }
|
|
|
|
@secondary_pools.each do |pool|
|
|
|
|
case pool.ping_time
|
|
|
|
when 0..150
|
|
|
|
ping_ranges[0] << pool
|
|
|
|
when 150..1000
|
|
|
|
ping_ranges[1] << pool
|
|
|
|
else
|
|
|
|
ping_ranges[2] << pool
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for list in ping_ranges do
|
|
|
|
break if !list.empty?
|
|
|
|
end
|
|
|
|
|
|
|
|
@read_pool = list[rand(list.length)]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Iterate through the list of provided seed
|
|
|
|
# nodes until we've gotten a response from the
|
|
|
|
# replica set we're trying to connect to.
|
|
|
|
#
|
|
|
|
# If we don't get a response, raise an exception.
|
|
|
|
def get_valid_seed_node
|
|
|
|
@seeds.each do |seed|
|
|
|
|
node = Mongo::Node.new(self.connection, seed)
|
|
|
|
if node.connect && node.set_config
|
|
|
|
return node
|
2011-08-25 15:27:58 +00:00
|
|
|
else
|
|
|
|
node.disconnect
|
2011-08-24 22:34:00 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
raise ConnectionFailure, "Cannot connect to a replica set using seeds " +
|
|
|
|
"#{@seeds.map {|s| "#{s[0]}:#{s[1]}" }.join(', ')}"
|
|
|
|
end
|
|
|
|
|
2011-08-25 18:57:24 +00:00
|
|
|
def update_seed_list(members)
|
|
|
|
@seeds = members.map { |n| n.host_port }
|
2011-08-24 22:34:00 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|