diff --git a/lib/moped/cluster.rb b/lib/moped/cluster.rb index 2bb17cb..22b5db8 100644 --- a/lib/moped/cluster.rb +++ b/lib/moped/cluster.rb @@ -238,11 +238,8 @@ def retry_interval # @since 1.0.0 def with_primary(&block) if node = nodes.find(&:primary?) - begin - node.ensure_primary do - return yield(node) - end - rescue Errors::ConnectionFailure, Errors::ReplicaSetReconfigured + node.ensure_primary do + return yield(node) end end raise Errors::ConnectionFailure, "Could not connect to a primary node for replica set #{inspect}" @@ -275,6 +272,37 @@ def with_secondary(&block) raise Errors::ConnectionFailure, "Could not connect to a secondary node for replica set #{inspect}" end + # Execute the provided block on the cluster and retry if the execution + # fails. + # + # @example Execute with retry. + # cluster.with_retry do + # cluster.with_primary do |node| + # node.refresh + # end + # end + # + # @param [ Integer ] retries The number of times to retry. + # + # @return [ Object ] The result of the block. + # + # @since 2.0.0 + def with_retry(retries = max_retries, &block) + begin + block.call + rescue StandardError => e + raise e unless Failover::STRATEGIES[e.class] == Failover::Retry + if retries > 0 + Loggable.warn(" MOPED:", "Retrying connection attempt #{retries} more time(s). Exception: #{ e.class.name }, #{ e.message }", "n/a") + sleep(retry_interval) + refresh + with_retry(retries - 1, &block) + else + raise e + end + end + end + private # Apply the credentials on all nodes diff --git a/lib/moped/collection.rb b/lib/moped/collection.rb index d68f8da..9776df0 100644 --- a/lib/moped/collection.rb +++ b/lib/moped/collection.rb @@ -121,8 +121,10 @@ def initialize(database, name) # @since 1.0.0 def insert(documents, flags = nil) docs = documents.is_a?(Array) ? documents : [ documents ] - cluster.with_primary do |node| - node.insert(database.name, name, docs, write_concern, flags: flags || []) + cluster.with_retry do + cluster.with_primary do |node| + node.insert(database.name, name, docs, write_concern, flags: flags || []) + end end end diff --git a/lib/moped/errors.rb b/lib/moped/errors.rb index c4c27b7..7354e80 100644 --- a/lib/moped/errors.rb +++ b/lib/moped/errors.rb @@ -28,7 +28,6 @@ class InvalidMongoURI < StandardError; end # Raised when providing an invalid string from an object id. class InvalidObjectId < StandardError - # Create the new error. # # @example Create the new error. @@ -105,29 +104,22 @@ def error_message end end - # Classes of errors that should not disconnect connections. - class DoNotDisconnect < MongoError; end - - # Classes of errors that could be caused by a replica set reconfiguration. - class PotentialReconfiguration < MongoError + # @api private + # + # Exception indicating that replica set was most likely reconfigured + class ReplicaSetReconfigured < MongoError; end - # Not master error codes. - NOT_MASTER = [ 13435, 13436, 10009 ] + # Exception raised when database responds with 'not master' error + class NotMaster < ReplicaSetReconfigured; end - # Error codes received around reconfiguration - CONNECTION_ERRORS_RECONFIGURATION = [ 15988, 10276, 11600, 9001, 13639, 10009 ] + # Exception raised when authentication fails. + class AuthenticationFailure < MongoError; end - # Replica set reconfigurations can be either in the form of an operation - # error with code 13435, or with an error message stating the server is - # not a master. (This encapsulates codes 10054, 10056, 10058) - def reconfiguring_replica_set? - err = details["err"] || details["errmsg"] || details["$err"] || "" - NOT_MASTER.include?(details["code"]) || err.include?("not master") - end + # Exception raised when authorization fails. + class AuthorizationFailure < MongoError; end - def connection_failure? - CONNECTION_ERRORS_RECONFIGURATION.include?(details["code"]) - end + # Exception raised when operation fails + class OperationFailure < MongoError # Is the error due to a namespace not being found? # @@ -154,29 +146,16 @@ def ns_not_exists? end end - # Exception raised when authentication fails. - class AuthenticationFailure < DoNotDisconnect; end - - # Exception class for exceptions generated as a direct result of an - # operation, such as a failed insert or an invalid command. - class OperationFailure < PotentialReconfiguration; end - # Exception raised on invalid queries. - class QueryFailure < PotentialReconfiguration; end + class QueryFailure < MongoError; end # Exception raised if the cursor could not be found. - class CursorNotFound < DoNotDisconnect + class CursorNotFound < MongoError def initialize(operation, cursor_id) super(operation, {"errmsg" => "cursor #{cursor_id} not found"}) end end - # @api private - # - # Internal exception raised by Node#ensure_primary and captured by - # Cluster#with_primary. - class ReplicaSetReconfigured < DoNotDisconnect; end - # Tag applied to unhandled exceptions on a node. module SocketError; end end diff --git a/lib/moped/failover.rb b/lib/moped/failover.rb index d893adc..087812c 100644 --- a/lib/moped/failover.rb +++ b/lib/moped/failover.rb @@ -1,7 +1,6 @@ # encoding: utf-8 require "moped/failover/disconnect" require "moped/failover/ignore" -require "moped/failover/reconfigure" require "moped/failover/retry" module Moped @@ -18,10 +17,13 @@ module Failover # @since 2.0.0 STRATEGIES = { Errors::AuthenticationFailure => Ignore, + Errors::AuthorizationFailure => Retry, Errors::ConnectionFailure => Retry, Errors::CursorNotFound => Ignore, - Errors::OperationFailure => Reconfigure, - Errors::QueryFailure => Reconfigure + Errors::OperationFailure => Ignore, + Errors::QueryFailure => Ignore, + Errors::NotMaster => Retry, + Errors::ReplicaSetReconfigured => Retry, }.freeze # Get the appropriate failover handler given the provided exception. diff --git a/lib/moped/failover/reconfigure.rb b/lib/moped/failover/reconfigure.rb deleted file mode 100644 index babe071..0000000 --- a/lib/moped/failover/reconfigure.rb +++ /dev/null @@ -1,36 +0,0 @@ -# encoding: utf-8 -module Moped - module Failover - - # Reconfigure is for exceptions that indicate that a replica set was - # potentially reconfigured in the middle of an operation. - # - # @since 2.0.0 - module Reconfigure - extend self - - # Executes the failover strategy. In the case of reconfigure, we check if - # the failure was due to a replica set reconfiguration mid operation and - # raise a new error if appropriate. - # - # @example Execute the reconfigure strategy. - # Moped::Failover::Reconfigure.execute(exception, node) - # - # @param [ Exception ] exception The raised exception. - # @param [ Node ] node The node the exception got raised on. - # - # @raise [ Exception, Errors::ReplicaSetReconfigure ] The exception that - # was previously thrown or a reconfiguration error. - # - # @since 2.0.0 - def execute(exception, node) - if exception.reconfiguring_replica_set? - raise(Errors::ReplicaSetReconfigured.new(exception.command, exception.details)) - elsif exception.connection_failure? - raise Errors::ConnectionFailure.new(exception.inspect) - end - raise(exception) - end - end - end -end diff --git a/lib/moped/failover/retry.rb b/lib/moped/failover/retry.rb index bb8b091..45c3448 100644 --- a/lib/moped/failover/retry.rb +++ b/lib/moped/failover/retry.rb @@ -9,7 +9,7 @@ module Failover module Retry extend self - # Executes the failover strategy. In the case of retyr, we disconnect and + # Executes the failover strategy. In the case of retry, we disconnect and # reconnect, then try the operation one more time. # # @example Execute the retry strategy. diff --git a/lib/moped/node.rb b/lib/moped/node.rb index 0ede953..62930e2 100644 --- a/lib/moped/node.rb +++ b/lib/moped/node.rb @@ -169,16 +169,16 @@ def down! # # @since 1.0.0 def ensure_connected(&block) - unless (conn = stack(:connection)).empty? - return yield(conn.first) - end - begin - connection do |conn| - stack(:connection) << conn - connect(conn) unless conn.connected? - conn.apply_credentials(@credentials) - yield(conn) + if (conn = stack(:connection)).length > 0 + yield(conn.first) + else + connection do |conn| + stack(:connection) << conn + connect(conn) unless conn.connected? + conn.apply_credentials(@credentials) + yield(conn) + end end rescue Exception => e Failover.get(e).execute(e, self, &block) @@ -548,6 +548,7 @@ def configure(settings) @arbiter = settings["arbiterOnly"] @passive = settings["passive"] @primary = settings["ismaster"] + @down_at = nil @secondary = settings["secondary"] discover(settings["hosts"]) if auto_discovering? end @@ -585,14 +586,13 @@ def discover(*nodes) def flush(ops = queue) operations, callbacks = ops.transpose logging(operations) do - replies = nil ensure_connected do |conn| conn.write(operations) replies = conn.receive_replies(operations) + replies.zip(callbacks).map do |reply, callback| + callback ? callback.call(reply) : reply + end.last end - replies.zip(callbacks).map do |reply, callback| - callback ? callback[reply] : reply - end.last end ensure ops.clear diff --git a/lib/moped/protocol/command.rb b/lib/moped/protocol/command.rb index 8c0461c..9925cf2 100644 --- a/lib/moped/protocol/command.rb +++ b/lib/moped/protocol/command.rb @@ -37,7 +37,7 @@ def failure?(reply) # # @since 2.0.0 def failure_exception(reply) - Errors::OperationFailure.new(self, reply.documents.first) + reply.failure_exception || Errors::OperationFailure.new(self, reply.documents.first) end # Instantiate the new command. diff --git a/lib/moped/protocol/get_more.rb b/lib/moped/protocol/get_more.rb index 78710b4..7ec68e2 100644 --- a/lib/moped/protocol/get_more.rb +++ b/lib/moped/protocol/get_more.rb @@ -69,11 +69,8 @@ def failure?(reply) # # @since 2.0.0 def failure_exception(reply) - if reply.cursor_not_found? - Errors::CursorNotFound.new(self, cursor_id) - else - Errors::QueryFailure.new(self, reply.documents.first) - end + return Errors::CursorNotFound.new(self, cursor_id) if reply.cursor_not_found? + reply.failure_exception || Errors::QueryFailure.new(self, reply.documents.first) end # Create a new GetMore command. The database and collection arguments diff --git a/lib/moped/protocol/query.rb b/lib/moped/protocol/query.rb index ac455fc..e4788d2 100644 --- a/lib/moped/protocol/query.rb +++ b/lib/moped/protocol/query.rb @@ -91,7 +91,7 @@ def basic_selector # # @since 2.0.0 def failure_exception(reply) - Errors::QueryFailure.new(self, reply.documents.first) + reply.failure_exception || Errors::QueryFailure.new(self, reply.documents.first) end # Determine if the provided reply message is a failure with respect to a diff --git a/lib/moped/protocol/reply.rb b/lib/moped/protocol/reply.rb index e3797b7..5378cc7 100644 --- a/lib/moped/protocol/reply.rb +++ b/lib/moped/protocol/reply.rb @@ -13,8 +13,24 @@ module Protocol class Reply include Message - # Unauthorized assertion errors. - UNAUTHORIZED = [ 10057, 16550 ] + # Error codes + UNAUTHORIZED = [ + 13, # not authorized for query on ... + 10057, + 16550, # not authorized for query on ... + 16544, # not authorized for insert on ... + ] + NOT_MASTER = [ 13435, 13436, 10009, 10054, 10056, 10058, 10107] + + CONNECTION_ERRORS_RECONFIGURATION = [ + 9001, + 10009, + 10276, # DBClientBase::findN: transport error + 11600, # interrupted at shutdown + 13639, # can't connect to new replica set master + 15988, # error querying server + ] + # @attribute # @return [Number] the length of the message @@ -71,6 +87,31 @@ def command_failure? (result["ok"] != 1.0 && result["ok"] != true) || error? end + # Returns specific exception if it can be determined from error code or message returned by DB + def failure_exception + return Errors::AuthorizationFailure.new(self, documents.first) if unauthorized? + return Errors::NotMaster.new(self, documents.first) if not_master? + return Errors::ReplicaSetReconfigured.new(self, documents.first) if connection_failure? + end + + # Error codes received around reconfiguration + def connection_failure? + result = documents[0] + return false if result.nil? + CONNECTION_ERRORS_RECONFIGURATION.include?(result["code"]) + end + + # Not master error codes. + # Replica set reconfigurations can be either in the form of an operation + # error falling into ReplicaSetReconfigured error or with an error message stating the server is + # not a master. + def not_master? + result = documents[0] + return false if result.nil? + err = error_message(result) + NOT_MASTER.include?(result["code"]) || err && err.include?("not master") + end + # Was the provided cursor id not found on the server? # # @example Is the cursor not on the server? diff --git a/lib/moped/query.rb b/lib/moped/query.rb index f308010..d89c6cf 100644 --- a/lib/moped/query.rb +++ b/lib/moped/query.rb @@ -321,14 +321,16 @@ def modify(change, options = {}) # # @since 1.0.0 def remove - cluster.with_primary do |node| - node.remove( - operation.database, - operation.collection, - operation.basic_selector, - write_concern, - flags: [ :remove_first ] - ) + cluster.with_retry do + cluster.with_primary do |node| + node.remove( + operation.database, + operation.collection, + operation.basic_selector, + write_concern, + flags: [ :remove_first ] + ) + end end end @@ -341,13 +343,15 @@ def remove # # @since 1.0.0 def remove_all - cluster.with_primary do |node| - node.remove( - operation.database, - operation.collection, - operation.basic_selector, - write_concern - ) + cluster.with_retry do + cluster.with_primary do |node| + node.remove( + operation.database, + operation.collection, + operation.basic_selector, + write_concern + ) + end end end @@ -423,15 +427,17 @@ def tailable # # @since 1.0.0 def update(change, flags = nil) - cluster.with_primary do |node| - node.update( - operation.database, - operation.collection, - operation.selector["$query"] || operation.selector, - change, - write_concern, - flags: flags - ) + cluster.with_retry do + cluster.with_primary do |node| + node.update( + operation.database, + operation.collection, + operation.selector["$query"] || operation.selector, + change, + write_concern, + flags: flags + ) + end end end diff --git a/lib/moped/read_preference/nearest.rb b/lib/moped/read_preference/nearest.rb index 663c550..25a354a 100644 --- a/lib/moped/read_preference/nearest.rb +++ b/lib/moped/read_preference/nearest.rb @@ -41,7 +41,7 @@ def name # # @since 2.0.0 def with_node(cluster, &block) - with_retry(cluster) do + cluster.with_retry do nearest = cluster.nodes.sort_by(&:latency).first if nearest block.call(nearest) diff --git a/lib/moped/read_preference/primary.rb b/lib/moped/read_preference/primary.rb index 68124aa..2675484 100644 --- a/lib/moped/read_preference/primary.rb +++ b/lib/moped/read_preference/primary.rb @@ -51,7 +51,7 @@ def query_options(options) # # @since 2.0.0 def with_node(cluster, &block) - with_retry(cluster) do + cluster.with_retry do cluster.with_primary(&block) end end diff --git a/lib/moped/read_preference/primary_preferred.rb b/lib/moped/read_preference/primary_preferred.rb index 3c35001..cf780ad 100644 --- a/lib/moped/read_preference/primary_preferred.rb +++ b/lib/moped/read_preference/primary_preferred.rb @@ -42,7 +42,7 @@ def name # # @since 2.0.0 def with_node(cluster, &block) - with_retry(cluster) do + cluster.with_retry do begin cluster.with_primary(&block) rescue Errors::ConnectionFailure diff --git a/lib/moped/read_preference/secondary.rb b/lib/moped/read_preference/secondary.rb index 44dfc1c..8cc338e 100644 --- a/lib/moped/read_preference/secondary.rb +++ b/lib/moped/read_preference/secondary.rb @@ -41,7 +41,7 @@ def name # # @since 2.0.0 def with_node(cluster, &block) - with_retry(cluster) do + cluster.with_retry do cluster.with_secondary(&block) end end diff --git a/lib/moped/read_preference/secondary_preferred.rb b/lib/moped/read_preference/secondary_preferred.rb index fd502a9..25de5b3 100644 --- a/lib/moped/read_preference/secondary_preferred.rb +++ b/lib/moped/read_preference/secondary_preferred.rb @@ -40,7 +40,7 @@ def name # # @since 2.0.0 def with_node(cluster, &block) - with_retry(cluster) do + cluster.with_retry do begin cluster.with_secondary(&block) rescue Errors::ConnectionFailure diff --git a/lib/moped/read_preference/selectable.rb b/lib/moped/read_preference/selectable.rb index 73686de..03be491 100644 --- a/lib/moped/read_preference/selectable.rb +++ b/lib/moped/read_preference/selectable.rb @@ -39,41 +39,6 @@ def query_options(options) options[:flags] |= [ :slave_ok ] options end - - private - - # Execute the provided block on the cluster and retry if the execution - # fails. - # - # @api private - # - # @example Execute with retry. - # preference.with_retry(cluster) do - # cluster.with_primary do |node| - # node.refresh - # end - # end - # - # @param [ Cluster ] cluster The cluster. - # @param [ Integer ] retries The number of times to retry. - # - # @return [ Object ] The result of the block. - # - # @since 2.0.0 - def with_retry(cluster, retries = cluster.max_retries, &block) - begin - block.call - rescue Errors::ConnectionFailure => e - if retries > 0 - Loggable.warn(" MOPED:", "Retrying connection attempt #{retries} more time(s).", "n/a") - sleep(cluster.retry_interval) - cluster.refresh - with_retry(cluster, retries - 1, &block) - else - raise e - end - end - end end end end diff --git a/spec/moped/cluster_spec.rb b/spec/moped/cluster_spec.rb index f51111c..18bf54e 100644 --- a/spec/moped/cluster_spec.rb +++ b/spec/moped/cluster_spec.rb @@ -54,6 +54,29 @@ end.should raise_exception(Moped::Errors::ConnectionFailure) end end + + describe '#with_retry' do + it 'retries' do + cluster.should_receive(:with_retry).twice.and_call_original + lambda do + cluster.with_retry do + cluster.with_secondary do |node| + node.command("admin", ping: 1) + end + end + end.should raise_exception(Moped::Errors::ConnectionFailure) + end + + it 'raises connection error' do + lambda do + cluster.with_retry do + cluster.with_secondary do |node| + node.command("admin", ping: 1) + end + end + end.should raise_exception(Moped::Errors::ConnectionFailure) + end + end end context "when the replica set hasn't connected yet" do @@ -95,6 +118,19 @@ end end.should raise_exception(Moped::Errors::ConnectionFailure) end + + context 'with_retry' do + it 'raises connection error after retrying' do + cluster.should_receive(:with_retry).twice.and_call_original + lambda do + cluster.with_retry do + cluster.with_primary do |node| + node.command("admin", ping: 1) + end + end + end.should raise_exception(Moped::Errors::ConnectionFailure) + end + end end describe "#with_secondary" do @@ -104,6 +140,17 @@ @secondaries.map(&:address).should include node.address.original end end + + context 'with_retry' do + it 'does not retry' do + cluster.should_receive(:with_retry).once.and_call_original + cluster.with_retry do + cluster.with_secondary do |node| + node.command("admin", ping: 1) + end + end + end + end end end diff --git a/spec/moped/errors_spec.rb b/spec/moped/errors_spec.rb index 6558490..ea9a64f 100644 --- a/spec/moped/errors_spec.rb +++ b/spec/moped/errors_spec.rb @@ -31,112 +31,4 @@ end end end - - describe "#reconfiguring_replica_set?" do - - context "when error code 13435" do - - let(:details) do - { "code" => 13435 } - end - - let(:error) do - Moped::Errors::PotentialReconfiguration.new({}, details) - end - - it "returns true" do - error.should be_reconfiguring_replica_set - end - end - - context "when error code 10009" do - - let(:details) do - { "code" => 10009 } - end - - let(:error) do - Moped::Errors::PotentialReconfiguration.new({}, details) - end - - it "returns true" do - error.should be_reconfiguring_replica_set - end - end - - context "when error code 13436" do - - let(:details) do - { "code" => 13436 } - end - - let(:error) do - Moped::Errors::PotentialReconfiguration.new({}, details) - end - - it "returns true" do - error.should be_reconfiguring_replica_set - end - end - - context "when 'err' is not master" do - - let(:details) do - { "err" => "not master" } - end - - let(:error) do - Moped::Errors::PotentialReconfiguration.new({}, details) - end - - it "returns true" do - error.should be_reconfiguring_replica_set - end - end - - context "when 'errmsg' is not master" do - - let(:details) do - { "errmsg" => "not master" } - end - - let(:error) do - Moped::Errors::PotentialReconfiguration.new({}, details) - end - - it "returns true" do - error.should be_reconfiguring_replica_set - end - end - - context "when 'err' contains not master" do - - let(:details) do - { "errmsg" => "not master or secondary; cannot currently read from this replSet member" } - end - - let(:error) do - Moped::Errors::PotentialReconfiguration.new({}, details) - end - - it "returns true" do - error.should be_reconfiguring_replica_set - end - end - - context "when errors are not matching not master" do - - let(:details) do - { "errmsg" => "unauthorized" } - end - - let(:error) do - Moped::Errors::PotentialReconfiguration.new({}, details) - end - - it "returns false" do - error.should_not be_reconfiguring_replica_set - end - end - end end diff --git a/spec/moped/failover/reconfigure_spec.rb b/spec/moped/failover/reconfigure_spec.rb deleted file mode 100644 index 64bc3d7..0000000 --- a/spec/moped/failover/reconfigure_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -require "spec_helper" - -describe Moped::Failover::Reconfigure do - - describe "#execute" do - - let(:node) do - Moped::Node.new("127.0.0.1:27017") - end - - context "when the exception is reconfiguring a replica set" do - - let(:exception) do - Moped::Errors::QueryFailure.new({}, { "code" => 13435 }) - end - - it "raises a replica set reconfigured exception" do - expect { - described_class.execute(exception, node) - }.to raise_error(Moped::Errors::ReplicaSetReconfigured) - end - end - - context "when the exception is a conncetion failure with the server" do - - let(:exception) do - Moped::Errors::QueryFailure.new({}, { "code" => 15988 }) - end - - it "raises a connection failure exception" do - expect { - described_class.execute(exception, node) - }.to raise_error(Moped::Errors::ConnectionFailure) - end - end - - context "when no replica set reconfiguration is happening" do - - let(:exception) do - Moped::Errors::QueryFailure.new({}, {}) - end - - it "raises the exception" do - expect { - described_class.execute(exception, node) - }.to raise_error(exception.class) - end - end - end -end diff --git a/spec/moped/failover_spec.rb b/spec/moped/failover_spec.rb index ff0833e..41c4508 100644 --- a/spec/moped/failover_spec.rb +++ b/spec/moped/failover_spec.rb @@ -23,8 +23,8 @@ described_class.get(Moped::Errors::OperationFailure.new({}, {})) end - it "returns a reconfigure" do - expect(failover).to be_a(Moped::Failover::Reconfigure) + it "returns an ignore" do + expect(failover).to be_a(Moped::Failover::Ignore) end end @@ -34,8 +34,8 @@ described_class.get(Moped::Errors::QueryFailure.new({}, {})) end - it "returns a reconfigure" do - expect(failover).to be_a(Moped::Failover::Reconfigure) + it "returns an ignore" do + expect(failover).to be_a(Moped::Failover::Ignore) end end @@ -71,6 +71,26 @@ expect(failover).to be_a(Moped::Failover::Retry) end end + + context "when providing an authorization failure" do + let(:failover) do + described_class.get(Moped::Errors::AuthorizationFailure.new({}, {})) + end + + it "returns a retry" do + expect(failover).to be_a(Moped::Failover::Retry) + end + end + + context "when providing a not-master failure" do + let(:failover) do + described_class.get(Moped::Errors::NotMaster.new({}, {})) + end + + it "returns a retry" do + expect(failover).to be_a(Moped::Failover::Retry) + end + end end end end diff --git a/spec/moped/node_spec.rb b/spec/moped/node_spec.rb index ee641cb..c1b4217 100644 --- a/spec/moped/node_spec.rb +++ b/spec/moped/node_spec.rb @@ -230,40 +230,50 @@ context "when there is a reconfiguration" do - let(:potential_reconfiguration_error) do - Moped::Errors::QueryFailure.new("", {}) - end + let(:error) { Moped::Errors::QueryFailure.new("", {}) } before do node.connection do |conn| conn.stub(:connected?).and_return(false) - conn.stub(:connect).and_raise(potential_reconfiguration_error) + conn.stub(:connect).and_raise(error) end end - context "and the reconfigation is of a replica set" do - before do - potential_reconfiguration_error.stub(:reconfiguring_replica_set?).and_return(true) + + context "and the cluster returns 'not master' error" do + + let(:error) { Moped::Errors::NotMaster.new("", {}) } + + it "it retries and reraises same error" do + expect { + node.ensure_connected { raise error } + }.to raise_error(error.class) end - it "raises a ReplicaSetReconfigured error" do + it "it disconnects" do + expect(node).to receive(:disconnect).at_least(:once) expect { - node.ensure_connected {} - }.to raise_error(Moped::Errors::ReplicaSetReconfigured) + node.ensure_connected { raise error } + }.to raise_error end end - context "and the reconfigation is not of a replica set" do + context "and the cluster returns authorization error" do - before do - potential_reconfiguration_error.stub(:reconfiguring_replica_set?).and_return(false) + let(:error) { Moped::Errors::AuthorizationFailure.new("", {}) } + + it "it retries and reraises same error" do + expect { + node.ensure_connected { raise error } + }.to raise_error(error.class) end - it "raises a PotentialReconfiguration error" do + it "it disconnects" do + expect(node).to receive(:disconnect).at_least(:once) expect { - node.ensure_connected {} - }.to raise_error(Moped::Errors::PotentialReconfiguration) + node.ensure_connected { raise error } + }.to raise_error end end end @@ -359,12 +369,16 @@ describe "#refresh" do - context "when the ismaster command fails" do + let(:node) do + described_class.new("127.0.0.1:27017") + end - let(:node) do - described_class.new("127.0.0.1:27017") - end + it 'marks node as not down any more when it succeeds to refresh' do + node.down! + expect{ node.refresh }.to change{ node.down? }.to(nil) + end + context "when the ismaster command fails" do before do node.should_receive(:command).with("admin", ismaster: 1).and_raise(Timeout::Error) node.refresh @@ -373,14 +387,14 @@ it "still sets the refresh time" do expect(node.refreshed_at).to_not be_nil end + + it 'keeps node down' do + expect(node).to be_down + end end context "when the node has authentication details" do - let(:node) do - described_class.new("127.0.0.1:27017") - end - before do node.credentials["moped_test"] = [ "user", "pass" ] end diff --git a/spec/moped/operation/read_spec.rb b/spec/moped/operation/read_spec.rb index 1d7e4b2..44cf274 100644 --- a/spec/moped/operation/read_spec.rb +++ b/spec/moped/operation/read_spec.rb @@ -39,10 +39,10 @@ replica_set_node.unauthorized_on_next_message! end - it "raises a failure error" do + it "does not raise a failure error (as it retries and succeeds)" do expect { read.execute(node) - }.to raise_error(exception) + }.to_not raise_error end end end