From c3d73e41511e5ab97336738a2e6d577dfb7ce53f Mon Sep 17 00:00:00 2001 From: Jim Menard Date: Wed, 14 Jan 2009 18:37:28 -0500 Subject: [PATCH] Mongo and DB ctors can now take array of nodes. Tests now close @db in teardown. --- lib/mongo/db.rb | 45 +++++++++++++++++++++++++++++++++++--------- lib/mongo/mongo.rb | 30 +++++++++++++++++++++++++---- tests/test_admin.rb | 5 +++-- tests/test_cursor.rb | 5 ++++- tests/test_db.rb | 35 +++++++++++++++++++--------------- tests/test_db_api.rb | 5 ++++- 6 files changed, 93 insertions(+), 32 deletions(-) diff --git a/lib/mongo/db.rb b/lib/mongo/db.rb index e5610f5..16661b6 100644 --- a/lib/mongo/db.rb +++ b/lib/mongo/db.rb @@ -49,25 +49,47 @@ module XGen # The name of the database. attr_reader :name - attr_reader :host, :port + # Host to which we are currently connected. + attr_reader :host + # Port to which we are currently connected. + attr_reader :port + + # An array of [host, port] pairs. + attr_reader :nodes # The database's socket. For internal (and Cursor) use only. attr_reader :socket # db_name :: The database name # - # host :: The database host name or IP address. Defaults to 'localhost'. + # nodes :: An array of [host, port] pairs. # - # port :: The database port number. Defaults to - # XGen::Mongo::Driver::Mongo::DEFAULT_PORT. - # - def initialize(db_name, host='localhost', port=XGen::Mongo::Driver::Mongo::DEFAULT_PORT) + # When a DB object first connects, it tries the first node. If that + # fails, it keeps trying to connect to the remaining nodes until it + # sucessfully connects. + def initialize(db_name, nodes) raise "Invalid DB name" if !db_name || (db_name && db_name.length > 0 && db_name.include?(".")) - @name, @host, @port = db_name, host, port - @socket = TCPSocket.new(@host, @port) + @name, @nodes = db_name, nodes @strict = false @semaphore = Object.new @semaphore.extend Mutex_m + connect_to_first_available_host + end + + def connect_to_first_available_host + close if @socket + @host = @port = nil + @nodes.detect { |hp| + @host, @port = *hp + begin + @socket = TCPSocket.new(@host, @port) + break if ok?(db_command(:ismaster => 1)) # success + rescue => ex + close if @socket + end + @socket + } + raise "error: failed to connect to any given host:port" unless @socket end # Returns an array of collection names. Each name is of the form @@ -183,7 +205,12 @@ module XGen # Close the connection to the database. def close - @socket.close + @socket.close if @socket + @socket = nil + end + + def connected? + @socket != nil end # Send a MsgMessage to the database. diff --git a/lib/mongo/mongo.rb b/lib/mongo/mongo.rb index caa7545..8ef3a16 100644 --- a/lib/mongo/mongo.rb +++ b/lib/mongo/mongo.rb @@ -25,14 +25,36 @@ module XGen DEFAULT_PORT = 27017 - # Host default is 'localhost', port default is DEFAULT_PORT. - def initialize(host='localhost', port=DEFAULT_PORT) - @host, @port = host, port + # Either nodes_or_host is a host name string and port is an optional + # port number that defaults to DEFAULT_PORT, or nodes_or_host is an + # array of arrays, where each is a host/port pair (or a host with no + # port). Finally, if both args are nil then host is 'localhost' and + # port is DEFAULT_PORT. Since that's so confusing, here are a few + # examples: + # + # Mongo.new # localhost, DEFAULT_PORT + # Mongo.new("localhost") # localhost, DEFAULT_PORT + # Mongo.new("localhost", 3000) # localhost, 3000 + # Mongo.new([["localhost"]]) # localhost, DEFAULT_PORT + # Mongo.new([["localhost", 3000]]) # localhost, 3000 + # Mongo.new([["db1.example.com", 3000], ["db2.example.com", 3000]]]) + # + # When a DB object first connects, it tries nodes and stops at the + # first one it connects to. + def initialize(nodes_or_host, port=nil) + @nodes = case nodes_or_host + when String + [[nodes_or_host, port || DEFAULT_PORT]] + when Array + nodes_or_host.collect { |nh| [nh[0], nh[1] || DEFAULT_PORT] } + when nil + [['localhost', DEFAULT_PORT]] + end end # Return the XGen::Mongo::Driver::DB named +db_name+. def db(db_name) - XGen::Mongo::Driver::DB.new(db_name, @host, @port) + XGen::Mongo::Driver::DB.new(db_name, @nodes) end # Not implemented. diff --git a/tests/test_admin.rb b/tests/test_admin.rb index 8e54fd7..6a12186 100644 --- a/tests/test_admin.rb +++ b/tests/test_admin.rb @@ -20,9 +20,10 @@ class AdminTest < Test::Unit::TestCase end def teardown - unless @db.socket.closed? + if @db.connected? @admin.profiling_level = :off - @coll.clear unless @coll == nil + @coll.clear if @coll + @db.close end end diff --git a/tests/test_cursor.rb b/tests/test_cursor.rb index a0b717f..0a156f5 100644 --- a/tests/test_cursor.rb +++ b/tests/test_cursor.rb @@ -18,7 +18,10 @@ class CursorTest < Test::Unit::TestCase end def teardown - @coll.clear unless @coll == nil || @db.socket.closed? + if @db.connected? + @coll.clear if @coll + @db.close + end end def test_explain diff --git a/tests/test_db.rb b/tests/test_db.rb index 9474b14..208041a 100644 --- a/tests/test_db.rb +++ b/tests/test_db.rb @@ -8,39 +8,44 @@ class DBAPITest < Test::Unit::TestCase include XGen::Mongo::Driver def setup - host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' - port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT - @db = Mongo.new(host, port).db('ruby-mongo-test') - @coll = @db.collection('test') - @coll.clear - @r1 = @coll.insert('a' => 1) # collection not created until it's used - @coll_full_name = 'ruby-mongo-test.test' + @host = ENV['MONGO_RUBY_DRIVER_HOST'] || 'localhost' + @port = ENV['MONGO_RUBY_DRIVER_PORT'] || Mongo::DEFAULT_PORT + @db = Mongo.new(@host, @port).db('ruby-mongo-test') end def teardown - @coll.clear unless @coll == nil || @db.socket.closed? + if @db.connected? + @db.close + end end def test_close @db.close - assert @db.socket.closed? + assert !@db.connected? begin - @coll.insert('a' => 1) - fail "expected IOError exception" - rescue IOError => ex - assert_match /closed stream/, ex.to_s + @db.collection('test').insert('a' => 1) + fail "expected 'NilClass' exception" + rescue => ex + assert_match /NilClass/, ex.to_s end end def test_full_coll_name - assert_equal @coll_full_name, @db.full_coll_name(@coll.name) + coll = @db.collection('test') + assert_equal 'ruby-mongo-test.test', @db.full_coll_name(coll.name) end def test_master # Doesn't really test anything since we probably only have one database # during this test. @db.switch_to_master - assert_not_nil @db.socket + assert @db.connected? + end + + def test_array + @db.close + @db = Mongo.new([["nosuch.example.com"], [@host, @port]]).db('ruby-mongo-test') + assert @db.connected? end end diff --git a/tests/test_db_api.rb b/tests/test_db_api.rb index 33f3c5a..9f42775 100644 --- a/tests/test_db_api.rb +++ b/tests/test_db_api.rb @@ -18,7 +18,10 @@ class DBAPITest < Test::Unit::TestCase end def teardown - @coll.clear unless @coll == nil || @db.socket.closed? + if @db.connected? + @coll.clear unless @coll == nil + @db.close + end end def test_clear