summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRandolph Tan <randolph@10gen.com>2016-11-03 13:44:34 -0400
committerRandolph Tan <randolph@10gen.com>2016-11-09 16:01:59 -0500
commita38156adf985f57861f7f00e1dfa24183abcc105 (patch)
tree512d8405417f0a1dc6fdc27327c168f964f79a56
parentf985c0ce3fa7efb0e857747f0a72bdef3326ac55 (diff)
downloadmongo-a38156adf985f57861f7f00e1dfa24183abcc105.tar.gz
SERVER-26065 Write more generic optime comparator function
-rw-r--r--jstests/core/optime_cmp.js34
-rw-r--r--jstests/replsets/rslib.js12
-rw-r--r--jstests/slow1/replsets_priority1.js290
-rw-r--r--src/mongo/shell/replsettest.js43
-rw-r--r--src/mongo/shell/utils.js57
5 files changed, 251 insertions, 185 deletions
diff --git a/jstests/core/optime_cmp.js b/jstests/core/optime_cmp.js
new file mode 100644
index 00000000000..7ddd83bfc8e
--- /dev/null
+++ b/jstests/core/optime_cmp.js
@@ -0,0 +1,34 @@
+(function() {
+ 'use strict';
+
+ // PV0
+ assert.eq(-1, rs.compareOpTimes(Timestamp(1, 0), Timestamp(2, 0)));
+ assert.eq(-1, rs.compareOpTimes(Timestamp(1, 0), Timestamp(1, 1)));
+
+ assert.eq(0, rs.compareOpTimes(Timestamp(2, 0), Timestamp(2, 0)));
+
+ assert.eq(1, rs.compareOpTimes(Timestamp(2, 0), Timestamp(1, 1)));
+ assert.eq(1, rs.compareOpTimes(Timestamp(1, 2), Timestamp(1, 1)));
+
+ // lhs PV0 rhs PV1
+ assert.throws(function() {
+ rs.compareOpTimes(Timestamp(1, 0), {ts: Timestamp(2, 0), t: 2});
+ });
+
+ // lhs PV1 rhs PV0
+ assert.throws(function() {
+ rs.compareOpTimes({ts: Timestamp(1, 0), t: 2}, Timestamp(2, 0));
+ });
+
+ // PV1
+ assert.eq(-1, rs.compareOpTimes({ts: Timestamp(2, 2), t: 2}, {ts: Timestamp(3, 1), t: 2}));
+ assert.eq(-1, rs.compareOpTimes({ts: Timestamp(2, 2), t: 2}, {ts: Timestamp(2, 4), t: 2}));
+ assert.eq(-1, rs.compareOpTimes({ts: Timestamp(3, 0), t: 2}, {ts: Timestamp(2, 0), t: 3}));
+
+ assert.eq(0, rs.compareOpTimes({ts: Timestamp(3, 0), t: 2}, {ts: Timestamp(3, 0), t: 2}));
+
+ assert.eq(1, rs.compareOpTimes({ts: Timestamp(3, 1), t: 2}, {ts: Timestamp(2, 2), t: 2}));
+ assert.eq(1, rs.compareOpTimes({ts: Timestamp(2, 4), t: 2}, {ts: Timestamp(2, 2), t: 2}));
+ assert.eq(1, rs.compareOpTimes({ts: Timestamp(2, 0), t: 3}, {ts: Timestamp(3, 0), t: 2}));
+
+})();
diff --git a/jstests/replsets/rslib.js b/jstests/replsets/rslib.js
index 6206a4a8168..d60f86462bf 100644
--- a/jstests/replsets/rslib.js
+++ b/jstests/replsets/rslib.js
@@ -10,6 +10,7 @@ var waitUntilAllNodesCaughtUp;
var updateConfigIfNotDurable;
var reInitiateWithoutThrowingOnAbortedMember;
var awaitRSClientHosts;
+var getLastOpTime;
(function() {
"use strict";
@@ -356,4 +357,15 @@ var awaitRSClientHosts;
return false;
}, 'timed out waiting for replica set client to recognize hosts', timeout);
};
+
+ /**
+ * Returns the last opTime of the connection based from replSetGetStatus. Can only
+ * be used on replica set nodes.
+ */
+ getLastOpTime = function(conn) {
+ var replSetStatus =
+ assert.commandWorked(conn.getDB("admin").runCommand({replSetGetStatus: 1}));
+ var connStatus = replSetStatus.members.filter(m => m.self)[0];
+ return connStatus.optime;
+ };
}());
diff --git a/jstests/slow1/replsets_priority1.js b/jstests/slow1/replsets_priority1.js
index 4bbb856a4b6..dfd31173b6b 100644
--- a/jstests/slow1/replsets_priority1.js
+++ b/jstests/slow1/replsets_priority1.js
@@ -1,203 +1,205 @@
// come up with random priorities and make sure that the right member gets
// elected. then kill that member and make sure the next one gets elected.
-print("\n\n\nreplsets_priority1.js BEGIN\n");
+(function() {
-load("jstests/replsets/rslib.js");
+ "use strict";
-var rs = new ReplSetTest({name: 'testSet', nodes: 3, nodeOptions: {verbose: 2}});
-var nodes = rs.startSet();
-rs.initiate();
+ load("jstests/replsets/rslib.js");
-var master = rs.getPrimary();
+ var rs = new ReplSetTest({name: 'testSet', nodes: 3, nodeOptions: {verbose: 2}});
+ var nodes = rs.startSet();
+ rs.initiate();
-var everyoneOkSoon = function() {
- var status;
- assert.soon(function() {
- var ok = true;
- status = master.adminCommand({replSetGetStatus: 1});
+ var master = rs.getPrimary();
- if (!status.members) {
- return false;
- }
+ var everyoneOkSoon = function() {
+ var status;
+ assert.soon(function() {
+ var ok = true;
+ status = master.adminCommand({replSetGetStatus: 1});
- for (var i in status.members) {
- if (status.members[i].health == 0) {
- continue;
+ if (!status.members) {
+ return false;
}
- ok &= status.members[i].state == 1 || status.members[i].state == 2;
- }
- return ok;
- }, tojson(status));
-};
-var checkPrimaryIs = function(node) {
+ for (var i in status.members) {
+ if (status.members[i].health == 0) {
+ continue;
+ }
+ ok &= status.members[i].state == 1 || status.members[i].state == 2;
+ }
+ return ok;
+ }, tojson(status));
+ };
- print("nreplsets_priority1.js checkPrimaryIs(" + node.host + ")");
+ var checkPrimaryIs = function(node) {
- var status;
+ print("nreplsets_priority1.js checkPrimaryIs(" + node.host + ")");
- assert.soon(function() {
- var ok = true;
+ var status;
- try {
- status = master.adminCommand({replSetGetStatus: 1});
- } catch (e) {
- print(e);
- print("nreplsets_priority1.js checkPrimaryIs reconnecting");
- reconnect(master);
- status = master.adminCommand({replSetGetStatus: 1});
- }
+ assert.soon(function() {
+ var ok = true;
- var str = "goal: " + node.host + "==1 states: ";
- if (!status || !status.members) {
- return false;
- }
- status.members.forEach(function(m) {
- str += m.name + ": " + m.state + " ";
-
- if (m.name == node.host) {
- ok &= m.state == 1;
- } else {
- ok &= m.state != 1 || (m.state == 1 && m.health == 0);
+ try {
+ status = master.adminCommand({replSetGetStatus: 1});
+ } catch (e) {
+ print(e);
+ print("nreplsets_priority1.js checkPrimaryIs reconnecting");
+ reconnect(master);
+ status = master.adminCommand({replSetGetStatus: 1});
}
- });
- print();
- print(str);
- print();
- occasionally(function() {
- print("\nstatus:");
- printjson(status);
+ var str = "goal: " + node.host + "==1 states: ";
+ if (!status || !status.members) {
+ return false;
+ }
+ status.members.forEach(function(m) {
+ str += m.name + ": " + m.state + " ";
+
+ if (m.name == node.host) {
+ ok &= m.state == 1;
+ } else {
+ ok &= m.state != 1 || (m.state == 1 && m.health == 0);
+ }
+ });
+ print();
+ print(str);
print();
- }, 15);
- return ok;
- }, node.host + '==1', 240000, 1000);
+ occasionally(function() {
+ print("\nstatus:");
+ printjson(status);
+ print();
+ }, 15);
- everyoneOkSoon();
-};
+ return ok;
+ }, node.host + '==1', 240000, 1000);
-everyoneOkSoon();
+ everyoneOkSoon();
+ };
-print("\n\nreplsets_priority1.js initial sync");
+ everyoneOkSoon();
-// intial sync
-master.getDB("foo").bar.insert({x: 1});
-rs.awaitReplication();
+ print("\n\nreplsets_priority1.js initial sync");
-print("\n\nreplsets_priority1.js starting loop");
+ // intial sync
+ master.getDB("foo").bar.insert({x: 1});
+ rs.awaitReplication();
-var n = 5;
-for (i = 0; i < n; i++) {
- print("Round " + i + ": FIGHT!");
+ print("\n\nreplsets_priority1.js starting loop");
- var max = null;
- var second = null;
- reconnect(master);
- var config = master.getDB("local").system.replset.findOne();
+ var n = 5;
+ for (var i = 0; i < n; i++) {
+ print("Round " + i + ": FIGHT!");
- var version = config.version;
- config.version++;
+ var max = null;
+ var second = null;
+ reconnect(master);
+ var config = master.getDB("local").system.replset.findOne();
- for (var j = 0; j < config.members.length; j++) {
- var priority = Math.random() * 100;
- print("random priority : " + priority);
- config.members[j].priority = priority;
+ var version = config.version;
+ config.version++;
- if (!max || priority > max.priority) {
- max = config.members[j];
- }
- }
+ for (var j = 0; j < config.members.length; j++) {
+ var priority = Math.random() * 100;
+ print("random priority : " + priority);
+ config.members[j].priority = priority;
- for (var j = 0; j < config.members.length; j++) {
- if (config.members[j] == max) {
- continue;
+ if (!max || priority > max.priority) {
+ max = config.members[j];
+ }
}
- if (!second || config.members[j].priority > second.priority) {
- second = config.members[j];
+
+ for (var j = 0; j < config.members.length; j++) {
+ if (config.members[j] == max) {
+ continue;
+ }
+ if (!second || config.members[j].priority > second.priority) {
+ second = config.members[j];
+ }
}
- }
- print("\n\nreplsets_priority1.js max is " + max.host + " with priority " + max.priority +
- ", reconfiguring...");
+ print("\n\nreplsets_priority1.js max is " + max.host + " with priority " + max.priority +
+ ", reconfiguring...");
- var count = 0;
- while (config.version != version && count < 100) {
- reconnect(master);
+ var count = 0;
+ while (config.version != version && count < 100) {
+ reconnect(master);
- occasionally(function() {
- print("version is " + version + ", trying to update to " + config.version);
- });
+ occasionally(function() {
+ print("version is " + version + ", trying to update to " + config.version);
+ });
- try {
- master.adminCommand({replSetReconfig: config});
- master = rs.getPrimary();
- reconnect(master);
+ try {
+ master.adminCommand({replSetReconfig: config});
+ master = rs.getPrimary();
+ reconnect(master);
- version = master.getDB("local").system.replset.findOne().version;
- } catch (e) {
- print("nreplsets_priority1.js Caught exception: " + e);
- }
+ version = master.getDB("local").system.replset.findOne().version;
+ } catch (e) {
+ print("nreplsets_priority1.js Caught exception: " + e);
+ }
- count++;
- }
+ count++;
+ }
- print("\nreplsets_priority1.js wait for 2 slaves");
+ print("\nreplsets_priority1.js wait for 2 slaves");
- assert.soon(function() {
- rs.getPrimary();
- return rs.liveNodes.slaves.length == 2;
- }, "2 slaves");
+ assert.soon(function() {
+ rs.getPrimary();
+ return rs.liveNodes.slaves.length == 2;
+ }, "2 slaves");
- print("\nreplsets_priority1.js wait for new config version " + config.version);
+ print("\nreplsets_priority1.js wait for new config version " + config.version);
- assert.soon(function() {
- versions = [0, 0];
- rs.liveNodes.slaves[0].setSlaveOk();
- versions[0] = rs.liveNodes.slaves[0].getDB("local").system.replset.findOne().version;
- rs.liveNodes.slaves[1].setSlaveOk();
- versions[1] = rs.liveNodes.slaves[1].getDB("local").system.replset.findOne().version;
- return versions[0] == config.version && versions[1] == config.version;
- });
+ assert.soon(function() {
+ var versions = [0, 0];
+ rs.liveNodes.slaves[0].setSlaveOk();
+ versions[0] = rs.liveNodes.slaves[0].getDB("local").system.replset.findOne().version;
+ rs.liveNodes.slaves[1].setSlaveOk();
+ versions[1] = rs.liveNodes.slaves[1].getDB("local").system.replset.findOne().version;
+ return versions[0] == config.version && versions[1] == config.version;
+ });
- print("replsets_priority1.js awaitReplication");
+ print("replsets_priority1.js awaitReplication");
- // the reconfiguration needs to be replicated! the hb sends it out
- // separately from the repl
- rs.awaitReplication();
+ // the reconfiguration needs to be replicated! the hb sends it out
+ // separately from the repl
+ rs.awaitReplication();
- print("reconfigured. Checking statuses.");
+ print("reconfigured. Checking statuses.");
- checkPrimaryIs(max);
+ checkPrimaryIs(max);
- // Wait for election oplog entry to be replicated, to avoid rollbacks later on.
- rs.awaitReplication();
+ // Wait for election oplog entry to be replicated, to avoid rollbacks later on.
+ rs.awaitReplication();
- print("rs.stop");
+ print("rs.stop");
- rs.stop(max._id);
+ rs.stop(max._id);
- var master = rs.getPrimary();
+ master = rs.getPrimary();
- print("\nkilled max primary. Checking statuses.");
+ print("\nkilled max primary. Checking statuses.");
- print("second is " + second.host + " with priority " + second.priority);
- checkPrimaryIs(second);
+ print("second is " + second.host + " with priority " + second.priority);
+ checkPrimaryIs(second);
- // Wait for election oplog entry to be replicated, to avoid rollbacks later on.
- rs.awaitReplication();
+ // Wait for election oplog entry to be replicated, to avoid rollbacks later on.
+ rs.awaitReplication();
- print("restart max " + max._id);
+ print("restart max " + max._id);
- rs.restart(max._id);
- master = rs.getPrimary();
+ rs.restart(max._id);
+ master = rs.getPrimary();
- print("max restarted. Checking statuses.");
- checkPrimaryIs(max);
+ print("max restarted. Checking statuses.");
+ checkPrimaryIs(max);
- // Wait for election oplog entry to be replicated, to avoid rollbacks later on.
- rs.awaitReplication();
-}
+ // Wait for election oplog entry to be replicated, to avoid rollbacks later on.
+ rs.awaitReplication();
+ }
-print("\n\n\n\n\nreplsets_priority1.js SUCCESS!\n\n");
+})();
diff --git a/src/mongo/shell/replsettest.js b/src/mongo/shell/replsettest.js
index bdb0980304f..2a5ec4b2227 100644
--- a/src/mongo/shell/replsettest.js
+++ b/src/mongo/shell/replsettest.js
@@ -302,17 +302,6 @@ var ReplSetTest = function(opts) {
}
/*
- * Checks if given optime object conforms to Protocol Version 1 optime format.
- *
- * PV1 Format:
- * {ts:Timestamp, t:NumberLong}
- *
- */
- function _isOptimeV1(optime) {
- return (optime.hasOwnProperty("ts") && optime.hasOwnProperty("t"));
- }
-
- /*
* Compares Timestamp objects. Returns true if ts1 is 'earlier' than ts2, else false.
*/
function _isEarlierTimestamp(ts1, ts2) {
@@ -322,34 +311,6 @@ var ReplSetTest = function(opts) {
return ts1.getTime() < ts2.getTime();
}
- /*
- * Compares optimes. Returns true if ot1 is 'earlier' than ot2, else false.
- *
- * Note: Since Protocol Version 1 was introduced for replication, 'optimes'
- * can come in two different formats. This function checks for this and changes
- * any timestamps of PV0 format to PV1 format, by adding a default term number of -1,
- * to make comparison logic generic for both formats.
- *
- * Optime Formats:
- * PV0: Timestamp
- * PV1: {ts:Timestamp, t:NumberLong}
- */
- function _isEarlierOpTime(ot1, ot2) {
- // Make sure both optimes have a timestamp and a term.
- var ot1 = _isOptimeV1(ot1) ? ot1 : {ts: ot1, t: NumberLong(-1)};
- var ot2 = _isOptimeV1(ot2) ? ot2 : {ts: ot2, t: NumberLong(-1)};
-
- // If both optimes have a term that's not -1 and one has a lower term, return that optime.
- if (!friendlyEqual(ot1.t, NumberLong(-1)) && !friendlyEqual(ot2.t, NumberLong(-1))) {
- if (!friendlyEqual(ot1.t, ot2.t)) {
- return ot1.t < ot2.t;
- }
- }
-
- // Otherwise, choose the optime with the lower timestamp.
- return _isEarlierTimestamp(ot1.ts, ot2.ts);
- }
-
/**
* Returns list of nodes as host:port strings.
*/
@@ -724,7 +685,7 @@ var ReplSetTest = function(opts) {
if (friendlyEqual(rcmOpTime, {ts: Timestamp(0, 0), t: NumberLong(0)})) {
return false;
}
- if (_isEarlierOpTime(rcmOpTime, masterOpTime)) {
+ if (rs.compareOpTimes(rcmOpTime, masterOpTime) < 0) {
return false;
}
}
@@ -824,7 +785,7 @@ var ReplSetTest = function(opts) {
slaveOpTime = _getLastOpTime(slave);
}
- if (_isEarlierOpTime(masterLatestOpTime, slaveOpTime)) {
+ if (rs.compareOpTimes(masterLatestOpTime, slaveOpTime) < 0) {
masterLatestOpTime = _getLastOpTime(master);
print("ReplSetTest awaitReplication: optime for " + slaveName +
" is newer, resetting latest primary optime to " +
diff --git a/src/mongo/shell/utils.js b/src/mongo/shell/utils.js
index a7c9fe4c9a8..dc6db851124 100644
--- a/src/mongo/shell/utils.js
+++ b/src/mongo/shell/utils.js
@@ -981,6 +981,26 @@ var Random = (function() {
})();
+/**
+ * Compares Timestamp objects. Returns -1 if ts1 is 'earlier' than ts2, 1 if 'later'
+ * and 0 if equal.
+ */
+function timestampCmp(ts1, ts2) {
+ if (ts1.getTime() == ts2.getTime()) {
+ if (ts1.getInc() < ts2.getInc()) {
+ return -1;
+ } else if (ts1.getInc() > ts2.getInc()) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else if (ts1.getTime() < ts2.getTime()) {
+ return -1;
+ } else {
+ return 1;
+ }
+}
+
Geo = {};
Geo.distance = function(a, b) {
var ax = null;
@@ -1296,6 +1316,43 @@ rs.debug.getLastOpWritten = function(server) {
return s.oplog.rs.find().sort({$natural: -1}).limit(1).next();
};
+/**
+ * Compares optimes. Returns -1 if ot1 is 'earlier' than ot2, 1 if 'later' and 0 if equal.
+ *
+ * Note: Since Protocol Version 1 was introduced for replication, 'optimes'
+ * can come in two different formats. This function will throw an error when the opTime
+ * passed do not have the same protocol version.
+ *
+ * Optime Formats:
+ * PV0: Timestamp
+ * PV1: {ts:Timestamp, t:NumberLong}
+ */
+rs.compareOpTimes = function(ot1, ot2) {
+ function _isOptimeV1(optime) {
+ return (optime.hasOwnProperty("ts") && optime.hasOwnProperty("t"));
+ }
+
+ // Make sure both optimes have a timestamp and a term.
+ var ot1 = _isOptimeV1(ot1) ? ot1 : {ts: ot1, t: NumberLong(-1)};
+ var ot2 = _isOptimeV1(ot2) ? ot2 : {ts: ot2, t: NumberLong(-1)};
+
+ if ((ot1.t == -1 && ot2.t != -1) || (ot1.t != -1 && ot2.t == -1)) {
+ throw Error('cannot compare optimes between different protocol versions');
+ }
+
+ if (!friendlyEqual(ot1.t, ot2.t)) {
+ if (ot1.t < ot2.t) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+ // else equal terms, so proceed to compare timestamp component.
+
+ // Otherwise, choose the optime with the lower timestamp.
+ return timestampCmp(ot1.ts, ot2.ts);
+};
+
help = shellHelper.help = function(x) {
if (x == "mr") {
print("\nSee also http://dochub.mongodb.org/core/mapreduce");