diff options
author | Randolph Tan <randolph@10gen.com> | 2016-11-03 13:44:34 -0400 |
---|---|---|
committer | Randolph Tan <randolph@10gen.com> | 2016-11-09 16:01:59 -0500 |
commit | a38156adf985f57861f7f00e1dfa24183abcc105 (patch) | |
tree | 512d8405417f0a1dc6fdc27327c168f964f79a56 | |
parent | f985c0ce3fa7efb0e857747f0a72bdef3326ac55 (diff) | |
download | mongo-a38156adf985f57861f7f00e1dfa24183abcc105.tar.gz |
SERVER-26065 Write more generic optime comparator function
-rw-r--r-- | jstests/core/optime_cmp.js | 34 | ||||
-rw-r--r-- | jstests/replsets/rslib.js | 12 | ||||
-rw-r--r-- | jstests/slow1/replsets_priority1.js | 290 | ||||
-rw-r--r-- | src/mongo/shell/replsettest.js | 43 | ||||
-rw-r--r-- | src/mongo/shell/utils.js | 57 |
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"); |