Skip to content

Commit 99f6eca

Browse files
committed
CXX-606 Backport server r2.6.10..r2.6.11 changes
Server SHAs picked into this commit: 872f3ff b8c1c49 17fa52e ed5dbf3 bd585fa 04a99c4 6e50440 d00c173
1 parent 32f9dbd commit 99f6eca

10 files changed

+272
-16
lines changed

SConstruct

+35-3
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,6 @@ add_option( "no-glibc-check" , "don't check for new versions of glibc" , 0 , Fal
223223
add_option( "mm", "use main memory instead of memory mapped files" , 0 , True )
224224
add_option( "asio" , "Use Asynchronous IO (NOT READY YET)" , 0 , True )
225225
add_option( "ssl" , "Enable SSL" , 0 , True )
226-
add_option( "ssl-fips-capability", "Enable the ability to activate FIPS 140-2 mode", 0, True );
227226

228227
# library choices
229228
add_option( "usev8" , "use v8 for javascript" , 0 , True )
@@ -910,8 +909,6 @@ if has_option( "ssl" ):
910909
else:
911910
env.Append( LIBS=["ssl"] )
912911
env.Append( LIBS=["crypto"] )
913-
if has_option("ssl-fips-capability"):
914-
env.Append( CPPDEFINES=["MONGO_SSL_FIPS"] )
915912

916913
try:
917914
umask = os.umask(022)
@@ -1444,6 +1441,41 @@ def doConfigure(myenv):
14441441
# ask each module to configure itself and the build environment.
14451442
moduleconfig.configure_modules(mongo_modules, conf)
14461443

1444+
def CheckLinkSSL(context):
1445+
test_body = """
1446+
#include <openssl/err.h>
1447+
#include <openssl/ssl.h>
1448+
#include <stdlib.h>
1449+
1450+
int main() {
1451+
SSL_library_init();
1452+
SSL_load_error_strings();
1453+
ERR_load_crypto_strings();
1454+
1455+
OpenSSL_add_all_algorithms();
1456+
ERR_free_strings();
1457+
return EXIT_SUCCESS;
1458+
}
1459+
"""
1460+
context.Message("Checking if OpenSSL is available...")
1461+
ret = context.TryLink(textwrap.dedent(test_body), ".c")
1462+
context.Result(ret)
1463+
return ret
1464+
conf.AddTest("CheckLinkSSL", CheckLinkSSL)
1465+
1466+
if has_option("ssl"):
1467+
if not conf.CheckLinkSSL():
1468+
print "SSL is enabled, but is unavailable"
1469+
Exit(1)
1470+
1471+
if conf.CheckDeclaration(
1472+
"FIPS_mode_set",
1473+
includes="""
1474+
#include <openssl/crypto.h>
1475+
#include <openssl/evp.h>
1476+
"""):
1477+
conf.env.Append(CPPDEFINES=['MONGO_HAVE_FIPS_MODE_SET'])
1478+
14471479
return conf.Finish()
14481480

14491481
env = doConfigure( env )

src/mongo/client/dbclientcursor.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,10 @@ namespace mongo {
325325
}
326326

327327
DBClientCursor::~DBClientCursor() {
328+
kill();
329+
}
330+
331+
void DBClientCursor::kill() {
328332
DESTRUCTOR_GUARD (
329333

330334
if ( cursorId && _ownCursor && ! inShutdown() ) {
@@ -359,6 +363,9 @@ namespace mongo {
359363
}
360364

361365
);
366+
367+
// Mark this cursor as dead since we can't do any getMores.
368+
cursorId = 0;
362369
}
363370

364371

src/mongo/client/dbclientcursor.h

+12
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,18 @@ namespace mongo {
202202
void initLazy( bool isRetry = false );
203203
bool initLazyFinish( bool& retry );
204204

205+
/**
206+
* Marks this object as dead and sends the KillCursors message to the server.
207+
*
208+
* Any errors that result from this are swallowed since this is typically performed as part
209+
* of cleanup and a failure to kill the cursor should not result in a failure of the
210+
* operation using the cursor.
211+
*
212+
* Killing an already killed or exhausted cursor does nothing, so it is safe to always call
213+
* this if you want to ensure that a cursor is killed.
214+
*/
215+
void kill();
216+
205217
class Batch : boost::noncopyable {
206218
friend class DBClientCursor;
207219
auto_ptr<Message> m;

src/mongo/client/replica_set_monitor.cpp

+24-3
Original file line numberDiff line numberDiff line change
@@ -537,8 +537,16 @@ namespace {
537537
return;
538538
}
539539

540-
if (reply.isMaster)
541-
receivedIsMasterFromMaster(reply);
540+
if (reply.isMaster) {
541+
const bool stalePrimary = !receivedIsMasterFromMaster(reply);
542+
if (stalePrimary) {
543+
log() << "node " << from << " believes it is primary, but its election id of "
544+
<< reply.electionId << " is older than the most recent election id"
545+
<< " for this set, " << _set->maxElectionId;
546+
failedHost(from);
547+
return;
548+
}
549+
}
542550

543551
if (_scan->foundUpMaster) {
544552
// We only update a Node if a master has confirmed it is in the set.
@@ -614,9 +622,16 @@ namespace {
614622
return scan;
615623
}
616624

617-
void Refresher::receivedIsMasterFromMaster(const IsMasterReply& reply) {
625+
bool Refresher::receivedIsMasterFromMaster(const IsMasterReply& reply) {
618626
invariant(reply.isMaster);
619627

628+
if (reply.electionId.isSet()) {
629+
if (_set->maxElectionId.isSet() && _set->maxElectionId.compare(reply.electionId) > 0) {
630+
return false;
631+
}
632+
_set->maxElectionId = reply.electionId;
633+
}
634+
620635
// Mark all nodes as not master. We will mark ourself as master before releasing the lock.
621636
// NOTE: we use a "last-wins" policy if multiple hosts claim to be master.
622637
for (size_t i = 0; i < _set->nodes.size(); i++) {
@@ -686,6 +701,8 @@ namespace {
686701

687702
_scan->foundUpMaster = true;
688703
_set->lastSeenMaster = reply.host;
704+
705+
return true;
689706
}
690707

691708
void Refresher::receivedIsMasterBeforeFoundMaster(const IsMasterReply& reply) {
@@ -779,6 +796,10 @@ namespace {
779796
// hidden nodes can't be master, even if they claim to be.
780797
isMaster = !hidden && raw["ismaster"].trueValue();
781798

799+
if (isMaster && raw.hasField("electionId")) {
800+
electionId = raw["electionId"].OID();
801+
}
802+
782803
const string primaryString = raw["primary"].str();
783804
primary = primaryString.empty() ? HostAndPort() : HostAndPort(primaryString);
784805

src/mongo/client/replica_set_monitor.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -292,12 +292,17 @@ namespace mongo {
292292
static ScanStatePtr startNewScan(const SetState* set);
293293

294294
private:
295+
295296
/**
297+
* First, checks that the "reply" is not from a stale primary by
298+
* comparing the electionId of "reply" to the maxElectionId recorded by the SetState.
299+
* Returns true if "reply" belongs to a non-stale primary.
300+
*
296301
* Updates _set and _scan based on set-membership information from a master.
297302
* Applies _scan->unconfirmedReplies to confirmed nodes.
298303
* Does not update this host's node in _set->nodes.
299304
*/
300-
void receivedIsMasterFromMaster(const IsMasterReply& reply);
305+
bool receivedIsMasterFromMaster(const IsMasterReply& reply);
301306

302307
/**
303308
* Adjusts the _scan work queue based on information from this host.

src/mongo/client/replica_set_monitor_internal.h

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ namespace mongo {
5555
bool isMaster;
5656
bool secondary;
5757
bool hidden;
58+
OID electionId; // Set if this isMaster reply is from the primary
5859
HostAndPort primary; // empty if not present
5960
std::set<HostAndPort> normalHosts; // both "hosts" and "passives"
6061
BSONObj tags;
@@ -162,6 +163,7 @@ namespace mongo {
162163
const std::string name; // safe to read outside lock since it is const
163164
int consecutiveFailedScans;
164165
std::set<HostAndPort> seedNodes; // updated whenever a master reports set membership changes
166+
OID maxElectionId; // largest election id observed by this ReplicaSetMonitor
165167
HostAndPort lastSeenMaster; // empty if we have never seen a master. can be same as current
166168
Nodes nodes; // maintained sorted and unique by host
167169
ScanStatePtr currentScan; // NULL if no scan in progress

src/mongo/client/replica_set_monitor_test.cpp

+182
Original file line numberDiff line numberDiff line change
@@ -769,3 +769,185 @@ TEST(ReplicaSetMonitorTests, OutOfBandFailedHost) {
769769
}
770770
}
771771
}
772+
773+
// Newly elected primary with electionId >= maximum electionId seen by the Refresher
774+
TEST(ReplicaSetMonitorTests, NewPrimaryWithMaxElectionId) {
775+
SetStatePtr state = boost::make_shared<SetState>("name", basicSeedsSet);
776+
Refresher refresher(state);
777+
778+
set<HostAndPort> seen;
779+
780+
// get all hosts to contact first
781+
for (size_t i = 0; i != basicSeeds.size(); ++i) {
782+
NextStep ns = refresher.getNextStep();
783+
ASSERT_EQUALS(ns.step, NextStep::CONTACT_HOST);
784+
ASSERT(basicSeedsSet.count(ns.host));
785+
ASSERT(!seen.count(ns.host));
786+
seen.insert(ns.host);
787+
}
788+
789+
const ReadPreferenceSetting primaryOnly(ReadPreference_PrimaryOnly, TagSet());
790+
791+
// mock all replies
792+
for (size_t i = 0; i != basicSeeds.size(); ++i) {
793+
// All hosts to talk to are already dispatched, but no reply has been received
794+
NextStep ns = refresher.getNextStep();
795+
ASSERT_EQUALS(ns.step, NextStep::WAIT);
796+
ASSERT(ns.host.empty());
797+
798+
refresher.receivedIsMaster(basicSeeds[i],
799+
-1,
800+
BSON("setName" << "name"
801+
<< "ismaster" << true
802+
<< "secondary" << false
803+
<< "hosts" << BSON_ARRAY("a" << "b" << "c")
804+
<< "electionId" << OID::gen()
805+
<< "ok" << true));
806+
807+
// Ensure the set primary is the host we just got a reply from
808+
HostAndPort currentPrimary = state->getMatchingHost(primaryOnly);
809+
ASSERT_EQUALS(currentPrimary.host(), basicSeeds[i].host());
810+
ASSERT_EQUALS(state->nodes.size(), basicSeeds.size());
811+
812+
// Check the state of each individual node
813+
for (size_t j = 0; j != basicSeeds.size(); ++j) {
814+
Node* node = state->findNode(basicSeeds[j]);
815+
ASSERT(node);
816+
ASSERT_EQUALS(node->host.toString(), basicSeeds[j].toString());
817+
ASSERT_EQUALS(node->isUp, j <= i);
818+
ASSERT_EQUALS(node->isMaster, j == i);
819+
ASSERT(node->tags.isEmpty());
820+
}
821+
}
822+
823+
// Now all hosts have returned data
824+
NextStep ns = refresher.getNextStep();
825+
ASSERT_EQUALS(ns.step, NextStep::DONE);
826+
ASSERT(ns.host.empty());
827+
}
828+
829+
// Ignore electionId of secondaries
830+
TEST(ReplicaSetMonitorTests, IgnoreElectionIdFromSecondaries) {
831+
SetStatePtr state = boost::make_shared<SetState>("name", basicSeedsSet);
832+
Refresher refresher(state);
833+
834+
set<HostAndPort> seen;
835+
836+
const OID primaryElectionId = OID::gen();
837+
838+
// mock all replies
839+
for (size_t i = 0; i != basicSeeds.size(); ++i) {
840+
NextStep ns = refresher.getNextStep();
841+
ASSERT_EQUALS(ns.step, NextStep::CONTACT_HOST);
842+
ASSERT(basicSeedsSet.count(ns.host));
843+
ASSERT(!seen.count(ns.host));
844+
seen.insert(ns.host);
845+
846+
// mock a reply
847+
const bool primary = ns.host.host() == "a";
848+
refresher.receivedIsMaster(ns.host,
849+
-1,
850+
BSON("setName" << "name"
851+
<< "ismaster" << primary
852+
<< "secondary" << !primary
853+
<< "electionId" << (primary ?
854+
primaryElectionId : OID::gen())
855+
<< "hosts" << BSON_ARRAY("a" << "b" << "c")
856+
<< "ok" << true));
857+
}
858+
859+
// check that the SetState's maxElectionId == primary's electionId
860+
ASSERT_EQUALS(state->maxElectionId, primaryElectionId);
861+
862+
// Now all hosts have returned data
863+
NextStep ns = refresher.getNextStep();
864+
ASSERT_EQUALS(ns.step, NextStep::DONE);
865+
ASSERT(ns.host.empty());
866+
}
867+
868+
// Stale Primary with obsolete electionId
869+
TEST(ReplicaSetMonitorTests, StalePrimaryWithObsoleteElectionId) {
870+
SetStatePtr state = boost::make_shared<SetState>("name", basicSeedsSet);
871+
Refresher refresher(state);
872+
873+
const OID firstElectionId = OID::gen();
874+
const OID secondElectionId = OID::gen();
875+
876+
set<HostAndPort> seen;
877+
878+
// contact first host claiming to be primary with greater electionId
879+
{
880+
NextStep ns = refresher.getNextStep();
881+
ASSERT_EQUALS(ns.step, NextStep::CONTACT_HOST);
882+
ASSERT(basicSeedsSet.count(ns.host));
883+
ASSERT(!seen.count(ns.host));
884+
seen.insert(ns.host);
885+
886+
refresher.receivedIsMaster(ns.host,
887+
-1,
888+
BSON("setName" << "name"
889+
<< "ismaster" << true
890+
<< "secondary" << false
891+
<< "electionId" << secondElectionId
892+
<< "hosts" << BSON_ARRAY("a" << "b" << "c")
893+
<< "ok" << true));
894+
895+
Node* node = state->findNode(ns.host);
896+
ASSERT(node);
897+
ASSERT_TRUE(node->isMaster);
898+
ASSERT_EQUALS(state->maxElectionId, secondElectionId);
899+
}
900+
901+
// contact second host claiming to be primary with smaller electionId
902+
{
903+
NextStep ns = refresher.getNextStep();
904+
ASSERT_EQUALS(ns.step, NextStep::CONTACT_HOST);
905+
ASSERT(basicSeedsSet.count(ns.host));
906+
ASSERT(!seen.count(ns.host));
907+
seen.insert(ns.host);
908+
909+
refresher.receivedIsMaster(ns.host,
910+
-1,
911+
BSON("setName" << "name"
912+
<< "ismaster" << true
913+
<< "secondary" << false
914+
<< "electionId" << firstElectionId
915+
<< "hosts" << BSON_ARRAY("a" << "b" << "c")
916+
<< "ok" << true));
917+
918+
Node* node = state->findNode(ns.host);
919+
ASSERT(node);
920+
// The SetState shouldn't see this host as master
921+
ASSERT_FALSE(node->isMaster);
922+
// the max electionId should remain the same
923+
ASSERT_EQUALS(state->maxElectionId, secondElectionId);
924+
}
925+
926+
// third host is a secondary
927+
{
928+
NextStep ns = refresher.getNextStep();
929+
ASSERT_EQUALS(ns.step, NextStep::CONTACT_HOST);
930+
ASSERT(basicSeedsSet.count(ns.host));
931+
ASSERT(!seen.count(ns.host));
932+
seen.insert(ns.host);
933+
934+
refresher.receivedIsMaster(ns.host,
935+
-1,
936+
BSON("setName" << "name"
937+
<< "ismaster" << false
938+
<< "secondary" << true
939+
<< "hosts" << BSON_ARRAY("a" << "b" << "c")
940+
<< "ok" << true));
941+
942+
Node* node = state->findNode(ns.host);
943+
ASSERT(node);
944+
ASSERT_FALSE(node->isMaster);
945+
// the max electionId should remain the same
946+
ASSERT_EQUALS(state->maxElectionId, secondElectionId);
947+
}
948+
949+
// Now all hosts have returned data
950+
NextStep ns = refresher.getNextStep();
951+
ASSERT_EQUALS(ns.step, NextStep::DONE);
952+
ASSERT(ns.host.empty());
953+
}

0 commit comments

Comments
 (0)