Connection.from_uri and Connection.paired. Connection API enhancement.
This commit is contained in:
parent
fc2ddf3bbd
commit
f176a45a20
@ -30,7 +30,10 @@ module Mongo
|
|||||||
STANDARD_HEADER_SIZE = 16
|
STANDARD_HEADER_SIZE = 16
|
||||||
RESPONSE_HEADER_SIZE = 20
|
RESPONSE_HEADER_SIZE = 20
|
||||||
|
|
||||||
attr_reader :logger, :size, :host, :port, :nodes, :sockets, :checked_out
|
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]"
|
||||||
|
|
||||||
|
attr_reader :logger, :size, :host, :port, :nodes, :auths, :sockets, :checked_out
|
||||||
|
|
||||||
# Counter for generating unique request ids.
|
# Counter for generating unique request ids.
|
||||||
@@current_request_id = 0
|
@@current_request_id = 0
|
||||||
@ -74,21 +77,24 @@ module Mongo
|
|||||||
# @example localhost, 3000, where this node may be a slave
|
# @example localhost, 3000, where this node may be a slave
|
||||||
# Connection.new("localhost", 3000, :slave_ok => true)
|
# Connection.new("localhost", 3000, :slave_ok => true)
|
||||||
#
|
#
|
||||||
# @example A pair of servers. The driver will always talk to master.
|
# @example DEPRECATED. To initialize a paired connection, use Connection.paired instead.
|
||||||
# # On connection errors, Mongo::ConnectionFailure will be raised.
|
|
||||||
# Connection.new({:left => ["db1.example.com", 27017],
|
# Connection.new({:left => ["db1.example.com", 27017],
|
||||||
# :right => ["db2.example.com", 27017]})
|
# :right => ["db2.example.com", 27017]})
|
||||||
#
|
#
|
||||||
# @example A pair of servers with connection pooling enabled. Note the nil param placeholder for port.
|
# @example DEPRECATED. To initialize a paired connection, use Connection.paired instead.
|
||||||
# Connection.new({:left => ["db1.example.com", 27017],
|
# Connection.new({:left => ["db1.example.com", 27017],
|
||||||
# :right => ["db2.example.com", 27017]}, nil,
|
# :right => ["db2.example.com", 27017]}, nil,
|
||||||
# :pool_size => 20, :timeout => 5)
|
# :pool_size => 20, :timeout => 5)
|
||||||
#
|
#
|
||||||
# @see http://www.mongodb.org/display/DOCS/Replica+Pairs+in+Ruby Replica pairs in Ruby
|
# @see http://www.mongodb.org/display/DOCS/Replica+Pairs+in+Ruby Replica pairs in Ruby
|
||||||
#
|
#
|
||||||
# @core connections constructor_details
|
# @core connections
|
||||||
def initialize(pair_or_host=nil, port=nil, options={})
|
def initialize(pair_or_host=nil, port=nil, options={})
|
||||||
@nodes = format_pair(pair_or_host, port)
|
if block_given?
|
||||||
|
@nodes, @auths = yield self
|
||||||
|
else
|
||||||
|
@nodes = format_pair(pair_or_host, port)
|
||||||
|
end
|
||||||
|
|
||||||
# Host and port of current master.
|
# Host and port of current master.
|
||||||
@host = @port = nil
|
@host = @port = nil
|
||||||
@ -120,7 +126,50 @@ module Mongo
|
|||||||
@options = options
|
@options = options
|
||||||
|
|
||||||
should_connect = options[:connect].nil? ? true : options[:connect]
|
should_connect = options[:connect].nil? ? true : options[:connect]
|
||||||
connect_to_master if should_connect
|
if should_connect
|
||||||
|
connect_to_master
|
||||||
|
authenticate_databases if @auths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Initialize a paired connection to MongoDB.
|
||||||
|
#
|
||||||
|
# @param nodes [Array] An array of arrays, each of which specified a host and port.
|
||||||
|
# @param opts Takes the same options as Connection.new
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# Connection.new([["db1.example.com", 27017],
|
||||||
|
# ["db2.example.com", 27017]])
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# Connection.new([["db1.example.com", 27017],
|
||||||
|
# ["db2.example.com", 27017]],
|
||||||
|
# :pool_size => 20, :timeout => 5)
|
||||||
|
#
|
||||||
|
# @return [Mongo::Connection]
|
||||||
|
def self.paired(nodes, opts={})
|
||||||
|
unless nodes.length == 2 && nodes.all? {|n| n.is_a? Array}
|
||||||
|
raise MongoArgumentError, "Connection.paired requires that exactly two nodes be specified."
|
||||||
|
end
|
||||||
|
# Block returns an array, the first element being an array of nodes and the second an array
|
||||||
|
# of authorizations for the database.
|
||||||
|
new(nil, nil, opts) do |con|
|
||||||
|
[[con.pair_val_to_connection(nodes[0]), con.pair_val_to_connection(nodes[1])], []]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Initialize a connection to MongoDB using the MongoDB URI spec:
|
||||||
|
#
|
||||||
|
# @param uri [String]
|
||||||
|
# A string of the format mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]
|
||||||
|
#
|
||||||
|
# @param opts Any of the options available for Connection.new
|
||||||
|
#
|
||||||
|
# @return [Mongo::Connection]
|
||||||
|
def self.from_uri(uri, opts={})
|
||||||
|
new(nil, nil, opts) do |con|
|
||||||
|
con.parse_uri(uri)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return a hash with all database names
|
# Return a hash with all database names
|
||||||
@ -350,6 +399,86 @@ module Mongo
|
|||||||
@checked_out.clear
|
@checked_out.clear
|
||||||
end
|
end
|
||||||
|
|
||||||
|
## Configuration helper methods
|
||||||
|
|
||||||
|
# Returns an array of host-port pairs.
|
||||||
|
#
|
||||||
|
# @private
|
||||||
|
def format_pair(pair_or_host, port)
|
||||||
|
case pair_or_host
|
||||||
|
when String
|
||||||
|
[[pair_or_host, port ? port.to_i : DEFAULT_PORT]]
|
||||||
|
when Hash
|
||||||
|
warn "Initializing a paired connection with Connection.new is deprecated. Use Connection.pair instead."
|
||||||
|
connections = []
|
||||||
|
connections << pair_val_to_connection(pair_or_host[:left])
|
||||||
|
connections << pair_val_to_connection(pair_or_host[:right])
|
||||||
|
connections
|
||||||
|
when nil
|
||||||
|
[['localhost', DEFAULT_PORT]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Convert an argument containing a host name string and a
|
||||||
|
# port number integer into a [host, port] pair array.
|
||||||
|
#
|
||||||
|
# @private
|
||||||
|
def pair_val_to_connection(a)
|
||||||
|
case a
|
||||||
|
when nil
|
||||||
|
['localhost', DEFAULT_PORT]
|
||||||
|
when String
|
||||||
|
[a, DEFAULT_PORT]
|
||||||
|
when Integer
|
||||||
|
['localhost', a]
|
||||||
|
when Array
|
||||||
|
a
|
||||||
|
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) && !(uname && pwd && db)
|
||||||
|
raise MongoArgumentError, "MongoDB URI must include all three of username, password, " +
|
||||||
|
"and db if any one of these is specified."
|
||||||
|
else
|
||||||
|
auths << [uname, pwd, db]
|
||||||
|
end
|
||||||
|
|
||||||
|
nodes << [host, port]
|
||||||
|
end
|
||||||
|
|
||||||
|
[nodes, auths]
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Return a socket to the pool.
|
# Return a socket to the pool.
|
||||||
@ -526,38 +655,18 @@ module Mongo
|
|||||||
message
|
message
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Authenticate for any auth info provided on instantiating the connection.
|
||||||
## Private helper methods
|
# Only called when a MongoDB URI has been used to instantiate the connection, and
|
||||||
|
# when that connection specifies databases and authentication credentials.
|
||||||
# Returns an array of host-port pairs.
|
#
|
||||||
def format_pair(pair_or_host, port)
|
# @raise [MongoDBError]
|
||||||
case pair_or_host
|
def authenticate_databases
|
||||||
when String
|
@auths.each do |auth|
|
||||||
[[pair_or_host, port ? port.to_i : DEFAULT_PORT]]
|
user = auth[0]
|
||||||
when Hash
|
pwd = auth[1]
|
||||||
connections = []
|
db_name = auth[2]
|
||||||
connections << pair_val_to_connection(pair_or_host[:left])
|
self.db(db_name).authenticate(user, pwd) || raise(MongoDBError, "Failed to authenticate db #{db_name}.")
|
||||||
connections << pair_val_to_connection(pair_or_host[:right])
|
|
||||||
connections
|
|
||||||
when nil
|
|
||||||
[['localhost', DEFAULT_PORT]]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Turns an array containing a host name string and a
|
|
||||||
# port number integer into a [host, port] pair array.
|
|
||||||
def pair_val_to_connection(a)
|
|
||||||
case a
|
|
||||||
when nil
|
|
||||||
['localhost', DEFAULT_PORT]
|
|
||||||
when String
|
|
||||||
[a, DEFAULT_PORT]
|
|
||||||
when Integer
|
|
||||||
['localhost', a]
|
|
||||||
when Array
|
|
||||||
a
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -135,6 +135,15 @@ class DBTest < Test::Unit::TestCase
|
|||||||
@@db.remove_user('spongebob')
|
@@db.remove_user('spongebob')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_authenticate_with_connection_uri
|
||||||
|
@@db.add_user('spongebob', 'squarepants')
|
||||||
|
assert Mongo::Connection.from_uri("mongodb://spongebob:squarepants@localhost/#{@@db.name}")
|
||||||
|
|
||||||
|
assert_raise MongoDBError do
|
||||||
|
Mongo::Connection.from_uri("mongodb://wrong:info@localhost/#{@@db.name}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_logout
|
def test_logout
|
||||||
assert @@db.logout
|
assert @@db.logout
|
||||||
end
|
end
|
||||||
|
@ -38,6 +38,80 @@ class ConnectionTest < Test::Unit::TestCase
|
|||||||
assert !@conn.slave_ok?
|
assert !@conn.slave_ok?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "initializing a paired connection" do
|
||||||
|
should "require left and right nodes" do
|
||||||
|
assert_raise MongoArgumentError do
|
||||||
|
Connection.paired(['localhost', 27018], :connect => false)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_raise MongoArgumentError do
|
||||||
|
Connection.paired(['localhost', 27018], :connect => false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
should "store both nodes" do
|
||||||
|
@conn = Connection.paired([['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]
|
||||||
|
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]
|
||||||
|
assert_equal ['kyle', 's3cr3t', 'app'], @conn.auths[0]
|
||||||
|
assert_equal ['mickey', 'm0u5e', 'dsny'], @conn.auths[1]
|
||||||
|
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.connect_to_master
|
||||||
|
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
|
||||||
|
|
||||||
context "with a nonstandard port" do
|
context "with a nonstandard port" do
|
||||||
|
Loading…
Reference in New Issue
Block a user