RUBY-205 RUBY-150 Support new connection URI options
This commit is contained in:
parent
9da68bb3db
commit
4e5b1a7d23
|
@ -138,15 +138,19 @@ module Mongo
|
||||||
#
|
#
|
||||||
# @param opts Any of the options available for Connection.new
|
# @param opts Any of the options available for Connection.new
|
||||||
#
|
#
|
||||||
# @return [Mongo::Connection]
|
# @return [Mongo::Connection, Mongo::ReplSetConnection]
|
||||||
def self.from_uri(uri, opts={})
|
def self.from_uri(string, extra_opts={})
|
||||||
nodes, auths = Mongo::URIParser.parse(uri)
|
uri = URIParser.new(string)
|
||||||
opts.merge!({:auths => auths})
|
opts = uri.connection_options
|
||||||
if nodes.length == 1
|
opts.merge!(extra_opts)
|
||||||
Connection.new(nodes[0][0], nodes[0][1], opts)
|
|
||||||
elsif nodes.length > 1
|
if uri.nodes.length == 1
|
||||||
nodes << opts
|
opts.merge!({:auths => uri.auths})
|
||||||
ReplSetConnection.new(*nodes)
|
Connection.new(uri.nodes[0][0], uri.nodes[0][1], opts)
|
||||||
|
elsif uri.nodes.length > 1
|
||||||
|
nodes = uri.nodes.clone
|
||||||
|
nodes_with_opts = nodes << opts
|
||||||
|
ReplSetConnection.new(*nodes_with_opts)
|
||||||
else
|
else
|
||||||
raise MongoArgumentError, "No nodes specified. Please ensure that you've provided at least one node."
|
raise MongoArgumentError, "No nodes specified. Please ensure that you've provided at least one node."
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,6 +32,8 @@ module Mongo
|
||||||
# @param [Array] args A list of host-port pairs ending with a hash containing any options. See
|
# @param [Array] args A list of host-port pairs ending with a hash containing any options. See
|
||||||
# the examples below for exactly how to use the constructor.
|
# the examples below for exactly how to use the constructor.
|
||||||
#
|
#
|
||||||
|
# @option options [String] :rs_name (nil) The name of the replica set to connect to. You
|
||||||
|
# can use this option to verify that you're connecting to the right replica set.
|
||||||
# @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
|
# @option options [Boolean, Hash] :safe (false) Set the default safe-mode options
|
||||||
# propogated to DB objects instantiated off of this Connection. This
|
# propogated to DB objects instantiated off of this Connection. This
|
||||||
# default can be overridden upon instantiation of any DB by explicity setting a :safe value
|
# default can be overridden upon instantiation of any DB by explicity setting a :safe value
|
||||||
|
|
|
@ -17,28 +17,99 @@
|
||||||
# ++
|
# ++
|
||||||
|
|
||||||
module Mongo
|
module Mongo
|
||||||
module URIParser
|
class URIParser
|
||||||
|
|
||||||
DEFAULT_PORT = 27017
|
DEFAULT_PORT = 27017
|
||||||
MONGODB_URI_MATCHER = /(([-_.\w\d]+):([-_\w\d]+)@)?([-.\w\d]+)(:([\w\d]+))?(\/([-\d\w]+))?/
|
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]"
|
MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]"
|
||||||
|
SPEC_ATTRS = [:nodes, :auths]
|
||||||
|
OPT_ATTRS = [:connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync]
|
||||||
|
|
||||||
extend self
|
OPT_VALID = {:connect => lambda {|arg| ['direct', 'replicaset'].include?(arg)},
|
||||||
|
:replicaset => lambda {|arg| arg.length > 0},
|
||||||
|
:slaveok => lambda {|arg| ['true', 'false'].include?(arg)},
|
||||||
|
:safe => lambda {|arg| ['true', 'false'].include?(arg)},
|
||||||
|
:w => lambda {|arg| arg =~ /^\d+$/ },
|
||||||
|
:wtimeout => lambda {|arg| arg =~ /^\d+$/ },
|
||||||
|
:fsync => lambda {|arg| ['true', 'false'].include?(arg)}
|
||||||
|
}
|
||||||
|
|
||||||
|
OPT_ERR = {:connect => "must be 'direct' or 'replicaset'",
|
||||||
|
:replicaset => "must be a string containing the name of the replica set to connect to",
|
||||||
|
:slaveok => "must be 'true' or 'false'",
|
||||||
|
:safe => "must be 'true' or 'false'",
|
||||||
|
:w => "must be an integer specifying number of nodes to replica to",
|
||||||
|
:wtimeout => "must be an integer specifying milliseconds",
|
||||||
|
:fsync => "must be 'true' or 'false'"
|
||||||
|
}
|
||||||
|
|
||||||
|
OPT_CONV = {:connect => lambda {|arg| arg},
|
||||||
|
:replicaset => lambda {|arg| arg},
|
||||||
|
:slaveok => lambda {|arg| arg == 'true' ? true : false},
|
||||||
|
:safe => lambda {|arg| arg == 'true' ? true : false},
|
||||||
|
:w => lambda {|arg| arg.to_i},
|
||||||
|
:wtimeout => lambda {|arg| arg.to_i},
|
||||||
|
:fsync => lambda {|arg| arg == 'true' ? true : false}
|
||||||
|
}
|
||||||
|
|
||||||
|
ATTRS = SPEC_ATTRS + OPT_ATTRS
|
||||||
|
|
||||||
|
attr_reader *ATTRS
|
||||||
|
|
||||||
# Parse a MongoDB URI. This method is used by Connection.from_uri.
|
# 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.
|
# Returns an array of nodes and an array of db authorizations, if applicable.
|
||||||
#
|
def initialize(string)
|
||||||
# @private
|
|
||||||
def parse(string)
|
|
||||||
if string =~ /^mongodb:\/\//
|
if string =~ /^mongodb:\/\//
|
||||||
string = string[10..-1]
|
string = string[10..-1]
|
||||||
else
|
else
|
||||||
raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
|
raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
|
||||||
end
|
end
|
||||||
|
|
||||||
nodes = []
|
hosts, opts = string.split('?')
|
||||||
auths = []
|
parse_hosts(hosts)
|
||||||
specs = string.split(',')
|
parse_options(opts)
|
||||||
|
configure_connect
|
||||||
|
end
|
||||||
|
|
||||||
|
def connection_options
|
||||||
|
opts = {}
|
||||||
|
|
||||||
|
if (@w || @wtimeout || @fsync) && !@safe
|
||||||
|
raise MongoArgumentError, "Safe must be true if w, wtimeout, or fsync is specified"
|
||||||
|
end
|
||||||
|
|
||||||
|
if @safe
|
||||||
|
if @w || @wtimeout || @fsync
|
||||||
|
safe_opts = {}
|
||||||
|
safe_opts[:w] = @w if @w
|
||||||
|
safe_opts[:wtimeout] = @wtimeout if @wtimeout
|
||||||
|
safe_opts[:fsync] = @fsync if @fsync
|
||||||
|
else
|
||||||
|
safe_opts = true
|
||||||
|
end
|
||||||
|
|
||||||
|
opts[:safe] = safe_opts
|
||||||
|
end
|
||||||
|
|
||||||
|
if @slaveok
|
||||||
|
if @connect == 'direct'
|
||||||
|
opts[:slave_ok] = true
|
||||||
|
else
|
||||||
|
opts[:read_secondary] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
opts[:rs_name] = @replicaset if @replicaset
|
||||||
|
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def parse_hosts(hosts)
|
||||||
|
@nodes = []
|
||||||
|
@auths = []
|
||||||
|
specs = hosts.split(',')
|
||||||
specs.each do |spec|
|
specs.each do |spec|
|
||||||
matches = MONGODB_URI_MATCHER.match(spec)
|
matches = MONGODB_URI_MATCHER.match(spec)
|
||||||
if !matches
|
if !matches
|
||||||
|
@ -62,10 +133,47 @@ module Mongo
|
||||||
"and db if any one of these is specified."
|
"and db if any one of these is specified."
|
||||||
end
|
end
|
||||||
|
|
||||||
nodes << [host, port]
|
@nodes << [host, port]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
[nodes, auths]
|
# This method uses the lambdas defined in OPT_VALID and OPT_CONV to validate
|
||||||
|
# and convert the given options.
|
||||||
|
def parse_options(opts)
|
||||||
|
return unless opts
|
||||||
|
separator = opts.include?('&') ? '&' : ';'
|
||||||
|
opts.split(separator).each do |attr|
|
||||||
|
key, value = attr.split('=')
|
||||||
|
key = key.to_sym
|
||||||
|
value = value.strip.downcase
|
||||||
|
if !OPT_ATTRS.include?(key)
|
||||||
|
raise MongoArgumentError, "Invalid Mongo URI option #{key}"
|
||||||
|
end
|
||||||
|
|
||||||
|
if OPT_VALID[key].call(value)
|
||||||
|
instance_variable_set("@#{key}", OPT_CONV[key].call(value))
|
||||||
|
else
|
||||||
|
raise MongoArgumentError, "Invalid value for #{key}: #{OPT_ERR[key]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def configure_connect
|
||||||
|
if @nodes.length > 1 && !@connect
|
||||||
|
@connect = 'replicaset'
|
||||||
|
end
|
||||||
|
|
||||||
|
if !@connect
|
||||||
|
if @nodes.length > 1
|
||||||
|
@connect = 'replicaset'
|
||||||
|
else
|
||||||
|
@connect = 'direct'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if @connect == 'direct' && @replicaset
|
||||||
|
raise MongoArgumentError, "If specifying a replica set name, please also specify that connect=replicaset"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,8 +16,8 @@ class ConnectTest < Test::Unit::TestCase
|
||||||
|
|
||||||
def test_connect_with_deprecated_multi
|
def test_connect_with_deprecated_multi
|
||||||
@conn = Connection.multi([[RS.host, RS.ports[0]], [RS.host, RS.ports[1]]], :name => RS.name)
|
@conn = Connection.multi([[RS.host, RS.ports[0]], [RS.host, RS.ports[1]]], :name => RS.name)
|
||||||
assert @conn.connected?
|
|
||||||
assert @conn.is_a?(ReplSetConnection)
|
assert @conn.is_a?(ReplSetConnection)
|
||||||
|
assert @conn.connected?
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_connect_bad_name
|
def test_connect_bad_name
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
||||||
|
require './test/replica_sets/rs_test_helper'
|
||||||
|
|
||||||
|
# NOTE: This test expects a replica set of three nodes to be running on RS.host,
|
||||||
|
# on ports TEST_PORT, RS.ports[1], and TEST + 2.
|
||||||
|
class ConnectionStringTest < Test::Unit::TestCase
|
||||||
|
include Mongo
|
||||||
|
|
||||||
|
def setup
|
||||||
|
RS.restart_killed_nodes
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
RS.restart_killed_nodes
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_connect_with_connection_string
|
||||||
|
@conn = Connection.from_uri("mongodb://#{RS.host}:#{RS.ports[0]},#{RS.host}:#{RS.ports[1]}?replicaset=#{RS.name}")
|
||||||
|
assert @conn.is_a?(ReplSetConnection)
|
||||||
|
assert @conn.connected?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_connect_with_full_connection_string
|
||||||
|
@conn = Connection.from_uri("mongodb://#{RS.host}:#{RS.ports[0]},#{RS.host}:#{RS.ports[1]}?replicaset=#{RS.name};safe=true;w=2;fsync=true;slaveok=true")
|
||||||
|
assert @conn.is_a?(ReplSetConnection)
|
||||||
|
assert @conn.connected?
|
||||||
|
assert_equal 2, @conn.safe[:w]
|
||||||
|
assert @conn.safe[:fsync]
|
||||||
|
assert @conn.read_pool
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -22,8 +22,8 @@ class ConnectionTest < Test::Unit::TestCase
|
||||||
TCPSocket.stubs(:new).returns(new_mock_socket)
|
TCPSocket.stubs(:new).returns(new_mock_socket)
|
||||||
|
|
||||||
admin_db = new_mock_db
|
admin_db = new_mock_db
|
||||||
admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1})
|
admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1}).twice
|
||||||
@conn.expects(:[]).with('admin').returns(admin_db)
|
@conn.expects(:[]).with('admin').returns(admin_db).twice
|
||||||
@conn.connect
|
@conn.connect
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -65,8 +65,8 @@ class ConnectionTest < Test::Unit::TestCase
|
||||||
@conn = Connection.from_uri("mongodb://localhost", :connect => false)
|
@conn = Connection.from_uri("mongodb://localhost", :connect => false)
|
||||||
|
|
||||||
admin_db = new_mock_db
|
admin_db = new_mock_db
|
||||||
admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1})
|
admin_db.expects(:command).returns({'ok' => 1, 'ismaster' => 1}).twice
|
||||||
@conn.expects(:[]).with('admin').returns(admin_db)
|
@conn.expects(:[]).with('admin').returns(admin_db).twice
|
||||||
@conn.expects(:apply_saved_authentication)
|
@conn.expects(:apply_saved_authentication)
|
||||||
@conn.connect
|
@conn.connect
|
||||||
end
|
end
|
||||||
|
|
|
@ -67,16 +67,6 @@ class ReplSetConnectionTest < Test::Unit::TestCase
|
||||||
assert_equal ['localhost', 27017], @conn.nodes[0]
|
assert_equal ['localhost', 27017], @conn.nodes[0]
|
||||||
assert_equal ['mydb.com', 27018], @conn.nodes[1]
|
assert_equal ['mydb.com', 27018], @conn.nodes[1]
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
require './test/test_helper'
|
||||||
|
|
||||||
|
class TestThreading < Test::Unit::TestCase
|
||||||
|
include Mongo
|
||||||
|
|
||||||
|
def test_uri_without_port
|
||||||
|
parser = Mongo::URIParser.new('mongodb://localhost')
|
||||||
|
assert_equal 1, parser.nodes.length
|
||||||
|
assert_equal 'localhost', parser.nodes[0][0]
|
||||||
|
assert_equal 27017, parser.nodes[0][1]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_basic_uri
|
||||||
|
parser = Mongo::URIParser.new('mongodb://localhost:27018')
|
||||||
|
assert_equal 1, parser.nodes.length
|
||||||
|
assert_equal 'localhost', parser.nodes[0][0]
|
||||||
|
assert_equal 27018, parser.nodes[0][1]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_multiple_uris
|
||||||
|
parser = Mongo::URIParser.new('mongodb://a.example.com:27018,b.example.com')
|
||||||
|
assert_equal 2, parser.nodes.length
|
||||||
|
assert_equal 'a.example.com', parser.nodes[0][0]
|
||||||
|
assert_equal 27018, parser.nodes[0][1]
|
||||||
|
assert_equal 'b.example.com', parser.nodes[1][0]
|
||||||
|
assert_equal 27017, parser.nodes[1][1]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_multiple_uris_with_auths
|
||||||
|
parser = Mongo::URIParser.new('mongodb://bob:secret@a.example.com:27018/test,joe:secret2@b.example.com/test2')
|
||||||
|
assert_equal 2, parser.nodes.length
|
||||||
|
assert_equal 'a.example.com', parser.nodes[0][0]
|
||||||
|
assert_equal 27018, parser.nodes[0][1]
|
||||||
|
assert_equal 'b.example.com', parser.nodes[1][0]
|
||||||
|
assert_equal 27017, parser.nodes[1][1]
|
||||||
|
assert_equal 2, parser.auths.length
|
||||||
|
assert_equal "bob", parser.auths[0]["username"]
|
||||||
|
assert_equal "secret", parser.auths[0]["password"]
|
||||||
|
assert_equal "test", parser.auths[0]["db_name"]
|
||||||
|
assert_equal "joe", parser.auths[1]["username"]
|
||||||
|
assert_equal "secret2", parser.auths[1]["password"]
|
||||||
|
assert_equal "test2", parser.auths[1]["db_name"]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_opts_basic
|
||||||
|
parser = Mongo::URIParser.new('mongodb://localhost:27018?connect=direct;slaveok=true;safe=true')
|
||||||
|
assert_equal 'direct', parser.connect
|
||||||
|
assert parser.slaveok
|
||||||
|
assert parser.safe
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_opts_with_amp_separator
|
||||||
|
parser = Mongo::URIParser.new('mongodb://localhost:27018?connect=direct&slaveok=true&safe=true')
|
||||||
|
assert_equal 'direct', parser.connect
|
||||||
|
assert parser.slaveok
|
||||||
|
assert parser.safe
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_opts_safe
|
||||||
|
parser = Mongo::URIParser.new('mongodb://localhost:27018?safe=true;w=2;wtimeout=200;fsync=true')
|
||||||
|
assert parser.safe
|
||||||
|
assert_equal 2, parser.w
|
||||||
|
assert_equal 200, parser.wtimeout
|
||||||
|
assert parser.fsync
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_opts_replica_set
|
||||||
|
assert_raise_error MongoArgumentError, "specify that connect=replicaset" do
|
||||||
|
Mongo::URIParser.new('mongodb://localhost:27018?replicaset=foo')
|
||||||
|
end
|
||||||
|
parser = Mongo::URIParser.new('mongodb://localhost:27018?connect=replicaset;replicaset=foo')
|
||||||
|
assert_equal 'foo', parser.replicaset
|
||||||
|
assert_equal 'replicaset', parser.connect
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue