From 10b2c6f7076dcb643fce302f3ccf2aea520ee6a0 Mon Sep 17 00:00:00 2001 From: Wandenberg Date: Tue, 16 Sep 2014 23:23:37 -0400 Subject: [PATCH 1/9] fix bad hint test check message --- spec/moped/query_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/moped/query_spec.rb b/spec/moped/query_spec.rb index 11ea0aa..a51252a 100644 --- a/spec/moped/query_spec.rb +++ b/spec/moped/query_spec.rb @@ -334,7 +334,7 @@ it "raises an error when hinting an invalid index" do expect { users.find(scope: scope).hint(scope: 1).to_a - }.to raise_error(Moped::Errors::QueryFailure, %r{failed with error 10113: "bad hint"}) + }.to raise_error(Moped::Errors::QueryFailure, %r{bad hint}) end end From 9b6addf89f3a4ded2f9daa9b517d68a8f527833b Mon Sep 17 00:00:00 2001 From: Wandenberg Date: Tue, 16 Sep 2014 23:45:33 -0400 Subject: [PATCH 2/9] add support to operation timeout, stopping long queries --- Gemfile | 1 + lib/moped/connection.rb | 21 ++++++++++++++++----- lib/moped/errors.rb | 3 +++ spec/moped/query_spec.rb | 36 ++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 29 +++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index c318dae..b91fc32 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source "http://rubygems.org" group :test do + gem "popen4" gem "rspec", "~> 2.13" unless ENV["CI"] gem "guard-rspec" diff --git a/lib/moped/connection.rb b/lib/moped/connection.rb index f1de386..9b5d8f8 100644 --- a/lib/moped/connection.rb +++ b/lib/moped/connection.rb @@ -174,11 +174,22 @@ def write(operations) # # @since 1.2.9 def read_data(socket, length) - data = socket.read(length) - unless data - raise Errors::ConnectionFailure.new( - "Attempted to read #{length} bytes from the socket but nothing was returned." - ) + # Block on data to read for op_timeout seconds + begin + op_timeout = @options[:op_timeout] || timeout + ready = IO.select([socket], nil, [socket], op_timeout) + unless ready + raise Errors::OperationTimeout.new("Took more than #{op_timeout} seconds to receive data.") + end + rescue IOError => e + raise Errors::ConnectionFailure + end + + # Read data from socket + begin + data = socket.read(length) + rescue SystemCallError, IOError => e + raise Errors::ConnectionFailure.new("Attempted to read #{length} bytes from the socket but an error happend #{e.message}.") end if data.length < length data << read_data(socket, length - data.length) diff --git a/lib/moped/errors.rb b/lib/moped/errors.rb index 1c32607..59a63ab 100644 --- a/lib/moped/errors.rb +++ b/lib/moped/errors.rb @@ -8,6 +8,9 @@ module Errors # Generic error class for exceptions related to connection failures. class ConnectionFailure < StandardError; end + # Generic error class for exceptions related to read timeout failures. + class OperationTimeout < StandardError; end + # Raised when a database name is invalid. class InvalidDatabaseName < StandardError; end diff --git a/spec/moped/query_spec.rb b/spec/moped/query_spec.rb index a51252a..9666430 100644 --- a/spec/moped/query_spec.rb +++ b/spec/moped/query_spec.rb @@ -919,6 +919,42 @@ end end + context "with test commands enabled" do + + let(:session) do + Moped::Session.new([ "127.0.0.1:#{port}" ], database: "moped_test") + end + + let(:users) do + session.with(safe: true)[:users] + end + + describe "when a query take too long" do + let(:port) { 31100 } + + before do + start_mongo_server(port, "--setParameter enableTestCommands=1") + Process.detach(spawn("echo 'db.adminCommand({sleep: 1, w: true, secs: 10})' | mongo localhost:#{port}", :out => "/dev/null", :err => "/dev/null")) + sleep(1) # to sleep command on mongodb begins work + end + + after do + stop_mongo_server(port) + end + + it "raises a operation timeout exception" do + time = Benchmark.realtime do + expect { + Timeout::timeout(7) do + users.find("age" => { "$gte" => 65 }).first + end + }.to raise_exception("Took more than 5 seconds to receive data.") + end + expect(time).to be < 5.5 + end + end + end + context "with a remote connection", mongohq: :auth do before(:all) do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6209e43..86ece32 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -15,6 +15,10 @@ $:.unshift((Pathname(__FILE__).dirname.parent + "lib").to_s) +require "benchmark" +require "fileutils" +require "tmpdir" +require "popen4" require "moped" require "support/examples" require "support/mongohq" @@ -35,7 +39,32 @@ return true if value == :auth && !Support::MongoHQ.auth_node_configured? end + config.after(:suite) do + stop_mongo_server(31100) + end + unless Support::MongoHQ.replica_set_configured? || Support::MongoHQ.auth_node_configured? $stderr.puts Support::MongoHQ.message end end + +def start_mongo_server(port, extra_options=nil) + stop_mongo_server(port) + dbpath = File.join(Dir.tmpdir, port.to_s) + FileUtils.mkdir_p(dbpath) + POpen4::popen4("mongod --oplogSize 40 --noprealloc --smallfiles --port #{port} --dbpath #{dbpath} --logpath #{dbpath}/log --pidfilepath #{dbpath}/pid --fork #{extra_options}") do |stdout, stderr, stdin, pid| + error_message = stderr.read.strip unless stderr.eof + raise StandardError.new error_message unless error_message.nil? + end + + while `echo 'db.runCommand({ping:1}).ok' | mongo --quiet --port #{port}`.chomp != "1" + sleep 0.1 + end +end + +def stop_mongo_server(port) + dbpath = File.join(Dir.tmpdir, port.to_s) + pidfile = File.join(dbpath, "pid") + `kill #{File.read(pidfile).chomp}` if File.exists?(pidfile) + FileUtils.rm_rf(dbpath) +end From 2b447cc35c830492242503e4a4ddfe55688ef726 Mon Sep 17 00:00:00 2001 From: Wandenberg Date: Thu, 18 Sep 2014 00:40:42 -0400 Subject: [PATCH 3/9] fix retry of write operations, only execute the lastError command again is not enough --- lib/moped/node.rb | 10 +- spec/moped/cluster_spec.rb | 200 +++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 3 + 3 files changed, 205 insertions(+), 8 deletions(-) diff --git a/lib/moped/node.rb b/lib/moped/node.rb index 21fdb80..6e0af12 100644 --- a/lib/moped/node.rb +++ b/lib/moped/node.rb @@ -94,7 +94,7 @@ def command(database, cmd, options = {}) if reply.command_failure? if reply.unauthorized? && auth.has_key?(database) login(database, *auth[database]) - result = command(database, cmd, options) + raise Errors::ReplicaSetReconfigured.new(operation, result) else raise Errors::OperationFailure.new(operation, result) end @@ -372,14 +372,8 @@ def query(database, collection, selector, options = {}) process(operation) do |reply| if reply.query_failed? if reply.unauthorized? && auth.has_key?(database) - # If we got here, most likely this is the case of Moped - # authenticating successfully against the node originally, but the - # node has been reset or gone down and come back up. The most - # common case here is a rs.stepDown() which will reinitialize the - # connection. In this case we need to requthenticate and try again, - # otherwise we'll just raise the error to the user. login(database, *auth[database]) - reply = query(database, collection, selector, options) + raise Errors::ReplicaSetReconfigured.new(operation, reply.documents.first) else raise Errors::QueryFailure.new(operation, reply.documents.first) end diff --git a/spec/moped/cluster_spec.rb b/spec/moped/cluster_spec.rb index cdae2b0..1bd7418 100644 --- a/spec/moped/cluster_spec.rb +++ b/spec/moped/cluster_spec.rb @@ -459,3 +459,203 @@ end end end + +describe Moped::Cluster, "after a reconfiguration" do + let(:options) do + { + max_retries: 30, + retry_interval: 1, + timeout: 5, + database: 'test_db', + consistency: :strong, + safe: {w: 'majority'} + } + end + + let(:replica_set_name) { 'dev' } + + let(:session) do + Moped::Session.new([ "127.0.0.1:31100", "127.0.0.1:31101", "127.0.0.1:31102" ], options) + end + + def step_down_servers + file = Tempfile.new('step_down') + begin + user_data = with_authentication? ? ", 'admin', 'admin_pwd'" : "" + file.puts %{ + function stepDown(dbs) { + for (i in dbs) { + dbs[i].adminCommand({replSetFreeze:5}); + try { dbs[i].adminCommand({replSetStepDown:5}); } catch(e) { print(e) }; + } + }; + + var db1 = connect('localhost:31100/admin'#{user_data}); + var db2 = connect('localhost:31101/admin'#{user_data}); + var db3 = connect('localhost:31102/admin'#{user_data}); + + var dbs = [db1, db2, db3]; + stepDown(dbs); + + while (db1.adminCommand({ismaster:1}).ismaster || db2.adminCommand({ismaster:1}).ismaster || db2.adminCommand({ismaster:1}).ismaster) { + stepDown(dbs); + } + } + file.close + + system "mongo --nodb #{file.path} 2>&1 > /dev/null" + + ensure + file.close + file.unlink # deletes the temp file + end + end + + shared_examples_for "recover the session" do + it "should execute commands normally before the stepDown" do + time = Benchmark.realtime do + session[:foo].find().remove_all() + session[:foo].find().to_a.count.should eql(0) + session[:foo].insert({ name: "bar 1" }) + session[:foo].find().to_a.count.should eql(1) + expect { + session[:foo].insert({ name: "bar 1" }) + }.to raise_exception + end + time.should be < 2 + end + + it "should recover and execute a find" do + session[:foo].find().remove_all() + session[:foo].insert({ name: "bar 1" }) + step_down_servers + time = Benchmark.realtime do + session[:foo].find().to_a.count.should eql(1) + end + time.should be > 5 + time.should be < 29 + end + + it "should recover and execute an insert" do + session[:foo].find().remove_all() + session[:foo].insert({ name: "bar 1" }) + step_down_servers + time = Benchmark.realtime do + session[:foo].insert({ name: "bar 2" }) + session[:foo].find().to_a.count.should eql(2) + end + time.should be > 5 + time.should be < 29 + + session[:foo].insert({ name: "bar 3" }) + session[:foo].find().to_a.count.should eql(3) + end + + it "should recover and try an insert which hit a constraint" do + session[:foo].find().remove_all() + session[:foo].insert({ name: "bar 1" }) + step_down_servers + time = Benchmark.realtime do + expect { + session[:foo].insert({ name: "bar 1" }) + }.to raise_exception + end + time.should be > 5 + time.should be < 29 + + session[:foo].find().to_a.count.should eql(1) + + session[:foo].insert({ name: "bar 2" }) + session[:foo].find().to_a.count.should eql(2) + end + end + + describe "with authentication off" do + before do + rs_initiated = `echo 'db.isMaster().me' | mongo --quiet --port 31100 2>/dev/null`.chomp.end_with?("31100") + unless rs_initiated + start_mongo_server(31100, "--replSet #{replica_set_name}") + start_mongo_server(31101, "--replSet #{replica_set_name}") + start_mongo_server(31102, "--replSet #{replica_set_name}") + + `echo "rs.initiate({_id : '#{replica_set_name}', 'members' : [{_id:0, host:'localhost:31100'},{_id:1, host:'localhost:31101'},{_id:2, host:'localhost:31102'}]})" | mongo --port 31100` + rs_up = false + while !rs_up + status = `echo 'rs.status().members[0].stateStr + "|" + rs.status().members[1].stateStr + "|" + rs.status().members[2].stateStr' | mongo --quiet --port 31100`.chomp.split("|") + rs_up = status.all?{|st| st == "PRIMARY" || st == "SECONDARY"} + end + + master = `echo 'db.isMaster().primary' | mongo --quiet --port 31100`.chomp + + `echo " + use test_db; + db.foo.ensureIndex({name:1}, {unique:1}); + " | mongo #{master}` + end + end + + let(:with_authentication?) { false } + + it_should_behave_like "recover the session" + end + + describe "with authentication on" do + before do + rs_initiated = `echo 'db.isMaster().me' | mongo --quiet --port 31100 -u admin -p admin_pwd --authenticationDatabase admin 2>/dev/null`.chomp.end_with?("31100") + unless rs_initiated + keyfile = File.join(Dir.tmpdir, "31000", "keyfile") + FileUtils.mkdir_p(File.dirname(keyfile)) + File.open(keyfile, "w") do |f| f.puts "SyrfEmAevWPEbgRZoZx9qZcZtJAAfd269da+kzi0H/7OuowGLxM3yGGUHhD379qP +nw4X8TT2T6ecx6aqJgxG+biJYVOpNK3HHU9Dp5q6Jd0bWGHGGbgFHV32/z2FFiti +EFLimW/vfn2DcJwTW29nQWhz2wN+xfMuwA6hVxFczlQlz5hIY0+a+bQChKw8wDZk +rW1OjTQ//csqPbVA8fwB49ghLGp+o84VujhRxLJ+0sbs8dKoIgmVlX2kLeHGQSf0 +KmF9b8kAWRLwLneOR3ESovXpEoK0qpQb2ym6BNqP32JKyPA6Svb/smVONhjUI71f +/zQ2ETX7ylpxIzw2SMv/zOWcVHBqIbdP9Llrxb3X0EsB6J8PeI8qLjpS94FyEddw +ACMcAxbP+6BaLjXyJ2WsrEeqThAyUC3uF5YN/oQ9XiATqP7pDOTrmfn8LvryyzcB +ByrLRTPOicBaG7y13ATcCbBdrYH3BE4EeLkTUZOg7VzvRnATvDpt0wOkSnbqXow8 +GQ6iMUgd2XvUCuknQLD6gWyoUyHiPADKrLsgnd3Qo9BPxYJ9VWSKB4phK3N7Bic+ +BwxlcpDFzGI285GR4IjcJbRRjjywHq5XHOxrJfN+QrZ/6wy6yu2+4NTPj+BPC5iX +/dNllTEyn7V+pr6FiRv8rv8RcxJgf3nfn/Xz0t2zW2olcalEFxwKKmR20pZxPnSv +Kr6sVHEzh0mtA21LoK5G8bztXsgFgWU7hh9z8UUo7KQQnDfyPb6k4xroeeQtWBNo +TZF1pI5joLytNSEtT+BYA5wQSYm4WCbhG+j7ipcPIJw6Un4ZtAZs0aixDfVE0zo0 +w2FWrYH2dmmCMbz7cEXeqvQiHh9IU/hkTrKGY95STszGGFFjhtS2TbHAn2rRoFI0 +VwNxMJCC+9ZijTWBeGyQOuEupuI4C9IzA5Gz72048tpZ0qMJ9mOiH3lZFtNTg/5P +28Td2xzaujtXjRnP3aZ9z2lKytlr +" + end + + File.chmod(0600, keyfile) + + start_mongo_server(31100, "--replSet #{replica_set_name} --keyFile #{keyfile} --auth") + start_mongo_server(31101, "--replSet #{replica_set_name} --keyFile #{keyfile} --auth") + start_mongo_server(31102, "--replSet #{replica_set_name} --keyFile #{keyfile} --auth") + + `echo "rs.initiate({_id : '#{replica_set_name}', 'members' : [{_id:0, host:'localhost:31100'},{_id:1, host:'localhost:31101'},{_id:2, host:'localhost:31102'}]})" | mongo --port 31100` + rs_up = false + while !rs_up + status = `echo 'rs.status().members[0].stateStr + "|" + rs.status().members[1].stateStr + "|" + rs.status().members[2].stateStr' | mongo --quiet --port 31100`.chomp.split("|") + rs_up = status.all?{|st| st == "PRIMARY" || st == "SECONDARY"} + end + + master = `echo 'db.isMaster().primary' | mongo --quiet --port 31100`.chomp + + `echo " + use admin; + db.addUser('admin', 'admin_pwd'); + " | mongo #{master}` + + `echo " + use test_db; + db.addUser('common', 'common_pwd'); + db.foo.ensureIndex({name:1}, {unique:1}); + " | mongo #{master} -u admin -p admin_pwd --authenticationDatabase admin` + end + + session.login('common', 'common_pwd') + end + + let(:with_authentication?) { true } + + it_should_behave_like "recover the session" + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 86ece32..358fccf 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -18,6 +18,7 @@ require "benchmark" require "fileutils" require "tmpdir" +require "tempfile" require "popen4" require "moped" require "support/examples" @@ -41,6 +42,8 @@ config.after(:suite) do stop_mongo_server(31100) + stop_mongo_server(31101) + stop_mongo_server(31102) end unless Support::MongoHQ.replica_set_configured? || Support::MongoHQ.auth_node_configured? From 248e97b1b65142ee5746be26bc120e981dff92bb Mon Sep 17 00:00:00 2001 From: Wandenberg Date: Sat, 20 Sep 2014 17:12:32 -0300 Subject: [PATCH 4/9] remove popen4 gem --- Gemfile | 1 - spec/spec_helper.rb | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index b91fc32..c318dae 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,6 @@ source "http://rubygems.org" group :test do - gem "popen4" gem "rspec", "~> 2.13" unless ENV["CI"] gem "guard-rspec" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 358fccf..16a0eab 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -19,7 +19,6 @@ require "fileutils" require "tmpdir" require "tempfile" -require "popen4" require "moped" require "support/examples" require "support/mongohq" @@ -55,14 +54,9 @@ def start_mongo_server(port, extra_options=nil) stop_mongo_server(port) dbpath = File.join(Dir.tmpdir, port.to_s) FileUtils.mkdir_p(dbpath) - POpen4::popen4("mongod --oplogSize 40 --noprealloc --smallfiles --port #{port} --dbpath #{dbpath} --logpath #{dbpath}/log --pidfilepath #{dbpath}/pid --fork #{extra_options}") do |stdout, stderr, stdin, pid| - error_message = stderr.read.strip unless stderr.eof - raise StandardError.new error_message unless error_message.nil? - end + `mongod --oplogSize 40 --noprealloc --smallfiles --port #{port} --dbpath #{dbpath} --logpath #{dbpath}/log --pidfilepath #{dbpath}/pid --fork #{extra_options}` - while `echo 'db.runCommand({ping:1}).ok' | mongo --quiet --port #{port}`.chomp != "1" - sleep 0.1 - end + sleep 0.1 while `echo 'db.runCommand({ping:1}).ok' | mongo --quiet --port #{port}`.chomp != "1" end def stop_mongo_server(port) From d43a8957eaa6bb873b7af44c7404cc6f2aefabe4 Mon Sep 17 00:00:00 2001 From: Wandenberg Date: Sat, 20 Sep 2014 17:13:32 -0300 Subject: [PATCH 5/9] improve replicaset environment checks --- spec/moped/cluster_spec.rb | 76 ++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/spec/moped/cluster_spec.rb b/spec/moped/cluster_spec.rb index 1bd7418..8b1b961 100644 --- a/spec/moped/cluster_spec.rb +++ b/spec/moped/cluster_spec.rb @@ -478,37 +478,43 @@ Moped::Session.new([ "127.0.0.1:31100", "127.0.0.1:31101", "127.0.0.1:31102" ], options) end - def step_down_servers - file = Tempfile.new('step_down') - begin - user_data = with_authentication? ? ", 'admin', 'admin_pwd'" : "" - file.puts %{ - function stepDown(dbs) { - for (i in dbs) { - dbs[i].adminCommand({replSetFreeze:5}); - try { dbs[i].adminCommand({replSetStepDown:5}); } catch(e) { print(e) }; - } - }; - - var db1 = connect('localhost:31100/admin'#{user_data}); - var db2 = connect('localhost:31101/admin'#{user_data}); - var db3 = connect('localhost:31102/admin'#{user_data}); + def servers_status + auth = has_user_admin? ? "-u admin -p admin_pwd --authenticationDatabase admin" : "" + `echo 'rs.status().members[0].stateStr + "|" + rs.status().members[1].stateStr + "|" + rs.status().members[2].stateStr' | mongo --quiet --port 31100 #{auth} 2>/dev/null`.chomp.split("|") + end - var dbs = [db1, db2, db3]; - stepDown(dbs); + def has_user_admin? + auth = with_authentication? ? "-u admin -p admin_pwd --authenticationDatabase admin" : "" + `echo 'db.getSisterDB("admin").getUser("admin").user' | mongo --quiet --port 31100 #{auth} 2>/dev/null`.chomp == "admin" + end - while (db1.adminCommand({ismaster:1}).ismaster || db2.adminCommand({ismaster:1}).ismaster || db2.adminCommand({ismaster:1}).ismaster) { + def step_down_servers + step_down_file = File.join(Dir.tmpdir, with_authentication? ? "step_down_with_authentication.js" : "step_down_without_authentication.js") + unless File.exists?(step_down_file) + File.open(step_down_file, "w") do |file| + user_data = with_authentication? ? ", 'admin', 'admin_pwd'" : "" + file.puts %{ + function stepDown(dbs) { + for (i in dbs) { + dbs[i].adminCommand({replSetFreeze:5}); + try { dbs[i].adminCommand({replSetStepDown:5}); } catch(e) { print(e) }; + } + }; + + var db1 = connect('localhost:31100/admin'#{user_data}); + var db2 = connect('localhost:31101/admin'#{user_data}); + var db3 = connect('localhost:31102/admin'#{user_data}); + + var dbs = [db1, db2, db3]; stepDown(dbs); - } - } - file.close - system "mongo --nodb #{file.path} 2>&1 > /dev/null" - - ensure - file.close - file.unlink # deletes the temp file + while (db1.adminCommand({ismaster:1}).ismaster || db2.adminCommand({ismaster:1}).ismaster || db2.adminCommand({ismaster:1}).ismaster) { + stepDown(dbs); + } + } + end end + system "mongo --nodb #{step_down_file} 2>&1 > /dev/null" end shared_examples_for "recover the session" do @@ -572,18 +578,13 @@ def step_down_servers describe "with authentication off" do before do - rs_initiated = `echo 'db.isMaster().me' | mongo --quiet --port 31100 2>/dev/null`.chomp.end_with?("31100") - unless rs_initiated + unless servers_status.all?{|st| st == "PRIMARY" || st == "SECONDARY"} && !has_user_admin? start_mongo_server(31100, "--replSet #{replica_set_name}") start_mongo_server(31101, "--replSet #{replica_set_name}") start_mongo_server(31102, "--replSet #{replica_set_name}") `echo "rs.initiate({_id : '#{replica_set_name}', 'members' : [{_id:0, host:'localhost:31100'},{_id:1, host:'localhost:31101'},{_id:2, host:'localhost:31102'}]})" | mongo --port 31100` - rs_up = false - while !rs_up - status = `echo 'rs.status().members[0].stateStr + "|" + rs.status().members[1].stateStr + "|" + rs.status().members[2].stateStr' | mongo --quiet --port 31100`.chomp.split("|") - rs_up = status.all?{|st| st == "PRIMARY" || st == "SECONDARY"} - end + sleep 0.1 while !servers_status.all?{|st| st == "PRIMARY" || st == "SECONDARY"} master = `echo 'db.isMaster().primary' | mongo --quiet --port 31100`.chomp @@ -601,8 +602,7 @@ def step_down_servers describe "with authentication on" do before do - rs_initiated = `echo 'db.isMaster().me' | mongo --quiet --port 31100 -u admin -p admin_pwd --authenticationDatabase admin 2>/dev/null`.chomp.end_with?("31100") - unless rs_initiated + unless servers_status.all?{|st| st == "PRIMARY" || st == "SECONDARY"} && has_user_admin? keyfile = File.join(Dir.tmpdir, "31000", "keyfile") FileUtils.mkdir_p(File.dirname(keyfile)) File.open(keyfile, "w") do |f| f.puts "SyrfEmAevWPEbgRZoZx9qZcZtJAAfd269da+kzi0H/7OuowGLxM3yGGUHhD379qP @@ -631,11 +631,7 @@ def step_down_servers start_mongo_server(31102, "--replSet #{replica_set_name} --keyFile #{keyfile} --auth") `echo "rs.initiate({_id : '#{replica_set_name}', 'members' : [{_id:0, host:'localhost:31100'},{_id:1, host:'localhost:31101'},{_id:2, host:'localhost:31102'}]})" | mongo --port 31100` - rs_up = false - while !rs_up - status = `echo 'rs.status().members[0].stateStr + "|" + rs.status().members[1].stateStr + "|" + rs.status().members[2].stateStr' | mongo --quiet --port 31100`.chomp.split("|") - rs_up = status.all?{|st| st == "PRIMARY" || st == "SECONDARY"} - end + sleep 0.1 while !servers_status.all?{|st| st == "PRIMARY" || st == "SECONDARY"} master = `echo 'db.isMaster().primary' | mongo --quiet --port 31100`.chomp From 95fa0b9d6d44cc4bececdb8b0739489abd5339da Mon Sep 17 00:00:00 2001 From: Wandenberg Date: Sat, 20 Sep 2014 20:51:21 -0300 Subject: [PATCH 6/9] remove rspec warning about stub --- spec/moped/cluster_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/moped/cluster_spec.rb b/spec/moped/cluster_spec.rb index 8b1b961..3721ff6 100644 --- a/spec/moped/cluster_spec.rb +++ b/spec/moped/cluster_spec.rb @@ -398,7 +398,7 @@ end let(:node) do - stub + double end context "when a node has no peers" do From e43bce965c7681c9a6990249220bc80e0bc85964 Mon Sep 17 00:00:00 2001 From: Wandenberg Date: Sat, 20 Sep 2014 20:53:00 -0300 Subject: [PATCH 7/9] try to do tests more stable --- spec/support/replica_set_simulator.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/spec/support/replica_set_simulator.rb b/spec/support/replica_set_simulator.rb index bdbf0ea..3d482cd 100644 --- a/spec/support/replica_set_simulator.rb +++ b/spec/support/replica_set_simulator.rb @@ -308,7 +308,13 @@ def next_client servers.each do |server| Moped.logger.debug "replica_set: accepting new client for #{server.port}" - @clients << server.accept + begin + @clients << server.accept + rescue IOError, Errno::EBADF, TypeError + # Looks like we hit a bad file descriptor or closed connection. + Moped.logger.debug "replica_set: io error, retrying" + retry + end end Moped.logger.debug "replica_set: closing dead clients" From 8042a81b4bb5a533f9686e1a3659b964e54c2a66 Mon Sep 17 00:00:00 2001 From: Wandenberg Date: Sat, 20 Sep 2014 22:34:38 -0300 Subject: [PATCH 8/9] change the command to make mongod sleep --- spec/moped/query_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/moped/query_spec.rb b/spec/moped/query_spec.rb index 9666430..3a8f252 100644 --- a/spec/moped/query_spec.rb +++ b/spec/moped/query_spec.rb @@ -934,7 +934,7 @@ before do start_mongo_server(port, "--setParameter enableTestCommands=1") - Process.detach(spawn("echo 'db.adminCommand({sleep: 1, w: true, secs: 10})' | mongo localhost:#{port}", :out => "/dev/null", :err => "/dev/null")) + Process.detach(spawn("echo 'db.adminCommand({sleep: 1, w: true, secs: 10})' | mongo localhost:#{port} 2>&1 > /dev/null")) sleep(1) # to sleep command on mongodb begins work end From f66732585a2c5e6ffa8d8730557a0f8ea66f2cbf Mon Sep 17 00:00:00 2001 From: Wandenberg Date: Wed, 24 Sep 2014 09:38:05 -0300 Subject: [PATCH 9/9] refactor to operation timeout work with SSL connections --- lib/moped/connection.rb | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/moped/connection.rb b/lib/moped/connection.rb index 9b5d8f8..fcd753a 100644 --- a/lib/moped/connection.rb +++ b/lib/moped/connection.rb @@ -175,19 +175,18 @@ def write(operations) # @since 1.2.9 def read_data(socket, length) # Block on data to read for op_timeout seconds + # using the suggested implementation of http://www.ruby-doc.org/core-2.1.3/Kernel.html#method-i-select + # to work with SSL connections + time_left = op_timeout = @options[:op_timeout] || timeout begin - op_timeout = @options[:op_timeout] || timeout - ready = IO.select([socket], nil, [socket], op_timeout) - unless ready - raise Errors::OperationTimeout.new("Took more than #{op_timeout} seconds to receive data.") - end - rescue IOError => e - raise Errors::ConnectionFailure - end - - # Read data from socket - begin - data = socket.read(length) + raise Errors::OperationTimeout.new("Took more than #{op_timeout} seconds to receive data.") if (time_left -= 0.1) <= 0 + data = socket.read_nonblock(length) + rescue IO::WaitReadable + Kernel::select([socket], nil, [socket], 0.1) + retry + rescue IO::WaitWritable + Kernel::select(nil, [socket], [socket], 0.1) + retry rescue SystemCallError, IOError => e raise Errors::ConnectionFailure.new("Attempted to read #{length} bytes from the socket but an error happend #{e.message}.") end