summaryrefslogtreecommitdiff
path: root/jstests
diff options
context:
space:
mode:
authorMathias Stearn <mathias@10gen.com>2016-09-20 13:39:18 -0400
committerMathias Stearn <mathias@10gen.com>2016-09-22 13:01:52 -0400
commitfec839b99f4b9e08016112fe8b9492e327af91b8 (patch)
tree3fc7f54a7c655117c0abf0f4ad25c68ec426d7fc /jstests
parentdcabea22e02f2a4bf39095986df00ab4ccff75fe (diff)
downloadmongo-fec839b99f4b9e08016112fe8b9492e327af91b8.tar.gz
SERVER-26016 Add more injected-state tests of oplog replay at startup
Diffstat (limited to 'jstests')
-rw-r--r--jstests/replsets/oplog_replay_on_startup.js419
1 files changed, 382 insertions, 37 deletions
diff --git a/jstests/replsets/oplog_replay_on_startup.js b/jstests/replsets/oplog_replay_on_startup.js
index 8f6291be9cd..4b1affa61dd 100644
--- a/jstests/replsets/oplog_replay_on_startup.js
+++ b/jstests/replsets/oplog_replay_on_startup.js
@@ -5,8 +5,9 @@
(function() {
"use strict";
+ var ns = "test.coll";
+
var rst = new ReplSetTest({
- name: "server_7200",
nodes: 1,
});
@@ -14,56 +15,400 @@
rst.initiate();
var conn = rst.getPrimary(); // Waits for PRIMARY state.
- var ns = "test.coll";
+ var term = conn.getCollection('local.oplog.rs').find().sort({$natural: -1}).limit(1).next().t;
+ if (typeof(term) == 'undefined') {
+ term = -1; // Use a dummy term for PV0.
+ }
+
+ function runTest({
+ oplogEntries,
+ collectionContents,
+ deletePoint,
+ begin,
+ minValid,
+ expectedState,
+ expectedApplied,
+ }) {
+ if (term != -1) {
+ term++; // Each test gets a new term on PV1 to ensure OpTimes always move forward.
+ }
+
+ conn = rst.restart(0, {noReplSet: true}); // Restart as a standalone node.
+ assert.neq(null, conn, "failed to restart");
+ var oplog = conn.getCollection('local.oplog.rs');
+ var minValidColl = conn.getCollection('local.replset.minvalid');
+ var coll = conn.getCollection(ns);
+
+ // Reset state to empty.
+ assert.commandWorked(oplog.runCommand('emptycapped'));
+ coll.drop();
+ assert.commandWorked(coll.runCommand('create'));
+
+ var ts = (num) => num === null ? Timestamp() : Timestamp(1000, num);
+
+ oplogEntries.forEach((num) => {
+ assert.writeOK(oplog.insert({
+ ts: ts(num),
+ t: term,
+ h: 1,
+ op: 'i',
+ ns: ns,
+ o: {_id: num},
+ }));
+ });
+
+ collectionContents.forEach((num) => {
+ assert.writeOK(coll.insert({_id: num}));
+ });
+
+ var injectedMinValidDoc = {
+ _id: ObjectId(),
+
+ // minvalid:
+ ts: ts(minValid),
+ t: term,
+
+ // appliedThrough
+ begin: {
+ ts: ts(begin),
+ t: term,
+ },
+
+ oplogDeleteFromPoint: ts(deletePoint),
+ };
+
+ // This weird mechanism is the only way to bypass mongod's attempt to fill in null
+ // Timestamps.
+ assert.writeOK(minValidColl.remove({}));
+ assert.writeOK(minValidColl.update({}, {$set: injectedMinValidDoc}, {upsert: true}));
+ assert.eq(minValidColl.findOne(),
+ injectedMinValidDoc,
+ "If the Timestamps differ, the server may be filling in the null timestamps");
+
+ try {
+ conn = rst.restart(0); // Restart in replSet mode again.
+ } catch (e) {
+ assert.eq(expectedState, 'FATAL', 'node failed to restart: ' + e);
+ return;
+ }
+
+ // Wait for the node to go to SECONDARY if it is able.
+ assert.soon(
+ () =>
+ conn.adminCommand('serverStatus').metrics.repl.apply.attemptsToBecomeSecondary > 0,
+ () => conn.adminCommand('serverStatus').metrics.repl.apply.attemptsToBecomeSecondary);
- conn.getCollection(ns).insert({_id: 1});
+ var isMaster = conn.adminCommand('ismaster');
+ switch (expectedState) {
+ case 'SECONDARY':
+ // Primary is also acceptable since once a node becomes secondary, it will try to
+ // become primary if it is eligible and has enough votes (which this node does).
+ // This is supposed to test that we reach secondary, not that we stay there.
+ assert(isMaster.ismaster || isMaster.secondary,
+ 'not PRIMARY or SECONDARY: ' + tojson(isMaster));
+ break;
- conn = rst.restart(0, {noReplSet: true}); // Restart as a standalone node.
- assert.neq(null, conn, "failed to restart");
+ case 'RECOVERING':
+ assert(!isMaster.ismaster && !isMaster.secondary,
+ 'not in RECOVERING: ' + tojson(isMaster));
- var originalOp = conn.getCollection('local.oplog.rs').find().sort({$natural: -1}).limit(1)[0];
- if (!('t' in originalOp)) {
- originalOp.t = -1; // Add the dummy term for PV0.
+ // Restart as a standalone node again so we can read from the collection.
+ conn = rst.restart(0, {noReplSet: true});
+ break;
+
+ case 'FATAL':
+ doassert("server startup didn't fail when it should have");
+ break;
+
+ default:
+ doassert(`expectedState ${expectedState} is not supported`);
+ }
+
+ // Ensure the oplog has the entries it should have and none that it shouldn't.
+ assert.eq(conn.getCollection('local.oplog.rs')
+ .find({ns: ns, op: 'i'})
+ .sort({$natural: 1})
+ .map((op) => op.o._id),
+ expectedApplied);
+
+ // Ensure that all ops that should have been applied were.
+ conn.setSlaveOk(true);
+ assert.eq(conn.getCollection(ns).find().sort({_id: 1}).map((obj) => obj._id),
+ expectedApplied);
}
- var tsPlus = (inc) => Timestamp(originalOp.ts.t, originalOp.ts.i + inc);
- var injectedOps = [
- // These will be applied.
- {ts: tsPlus(1), t: originalOp.t, h: 1, op: 'i', ns: ns, o: {_id: 2}},
- {ts: tsPlus(2), t: originalOp.t, h: 1, op: 'i', ns: ns, o: {_id: 3}},
+ //
+ // Normal 3.4 cases
+ //
- // These will be deleted.
- {ts: tsPlus(3), t: originalOp.t, h: 1, op: 'i', ns: ns, o: {_id: 4}},
- {ts: tsPlus(4), t: originalOp.t, h: 1, op: 'i', ns: ns, o: {_id: 5}},
- ];
+ runTest({
+ oplogEntries: [1, 2, 3],
+ collectionContents: [1, 2, 3],
+ deletePoint: null,
+ begin: null,
+ minValid: null,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3],
+ });
- var injectedMinValidDoc = {
- // minvalid:
- ts: injectedOps[1].ts,
- t: injectedOps[1].t,
+ runTest({
+ oplogEntries: [1, 2, 3],
+ collectionContents: [1, 2, 3],
+ deletePoint: null,
+ begin: null,
+ minValid: 2,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3],
+ });
- // appliedThrough
- begin: {
- ts: originalOp.ts,
- t: originalOp.t,
- },
+ runTest({
+ oplogEntries: [1, 2, 3],
+ collectionContents: [1, 2, 3],
+ deletePoint: null,
+ begin: null,
+ minValid: 3,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3],
+ });
- oplogDeleteFromPoint: injectedOps[2].ts,
- };
+ runTest({
+ oplogEntries: [1, 2, 3],
+ collectionContents: [1, 2, 3],
+ deletePoint: null,
+ begin: 3,
+ minValid: 3,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3],
+ });
- conn.getCollection("local.oplog.rs").insert(injectedOps);
- conn.getCollection("local.replset.minvalid").update({}, injectedMinValidDoc, {upsert: true});
+ runTest({
+ oplogEntries: [1, 2, 3],
+ collectionContents: [1, 2, 3],
+ deletePoint: 4,
+ begin: 3,
+ minValid: 3,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3],
+ });
- rst.restart(0); // Restart in replSet mode again.
+ runTest({
+ oplogEntries: [1, 2, 3, 4, 5, 6],
+ collectionContents: [1, 2, 3],
+ deletePoint: 4,
+ begin: 3,
+ minValid: 3,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3],
+ });
- var conn = rst.getPrimary(); // Waits for PRIMARY state.
- assert.neq(null, conn, "failed to restart");
+ runTest({
+ oplogEntries: [1, 2, 3, /*4,*/ 5, 6],
+ collectionContents: [1, 2, 3],
+ deletePoint: 4,
+ begin: 3,
+ minValid: 3,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3, 4, 5, 6],
+ collectionContents: [1, 2, 3],
+ deletePoint: null,
+ begin: 3,
+ minValid: 3,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3, 4, 5, 6],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3, 4, 5, 6],
+ collectionContents: [1, 2, 3],
+ deletePoint: null,
+ begin: 3,
+ minValid: 6,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3, 4, 5, 6],
+ });
+
+ //
+ // 3.2 -> 3.4 upgrade cases
+ //
- // Ensure the oplog has the entries it should have and none that it shouldn't.
- assert.eq(conn.getCollection('local.oplog.rs').distinct('o._id', {ns: ns, op: 'i'}), [1, 2, 3]);
+ runTest({
+ oplogEntries: [1, 2, 3],
+ collectionContents: [1, 2, 3],
+ deletePoint: null,
+ begin: 3,
+ minValid: 6,
+ expectedState: 'RECOVERING',
+ expectedApplied: [1, 2, 3],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3, 4, 5],
+ collectionContents: [1, 2, 3],
+ deletePoint: null,
+ begin: 3,
+ minValid: 6,
+ expectedState: 'RECOVERING',
+ expectedApplied: [1, 2, 3, 4, 5],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3, 4, 5],
+ collectionContents: [1, 2, 3, 4, 5],
+ deletePoint: null,
+ begin: null,
+ minValid: 6,
+ expectedState: 'RECOVERING',
+ expectedApplied: [1, 2, 3, 4, 5],
+ });
+
+ //
+ // 3.4 -> 3.2 -> 3.4 downgrade/reupgrade cases
+ //
+
+ runTest({
+ oplogEntries: [1, 2, 3],
+ collectionContents: [1, 2, 3],
+ deletePoint: 4,
+ begin: 3,
+ minValid: 6,
+ expectedState: 'RECOVERING',
+ expectedApplied: [1, 2, 3],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3, 4, 5],
+ collectionContents: [1, 2, 3],
+ deletePoint: 4,
+ begin: 3,
+ minValid: 6,
+ expectedState: 'RECOVERING',
+ expectedApplied: [1, 2, 3],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3, /*4,*/ 5, 6],
+ collectionContents: [1, 2, 3],
+ deletePoint: 4,
+ begin: 3,
+ minValid: 6,
+ expectedState: 'RECOVERING',
+ expectedApplied: [1, 2, 3],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3],
+ collectionContents: [1, 2, 3],
+ deletePoint: 2,
+ begin: null,
+ minValid: 3,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3],
+ collectionContents: [1, 2, 3],
+ deletePoint: 2,
+ begin: 3,
+ minValid: 6,
+ expectedState: 'RECOVERING',
+ expectedApplied: [1, 2, 3],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3, 4, 5],
+ collectionContents: [1, 2, 3],
+ deletePoint: 2,
+ begin: 3,
+ minValid: 6,
+ expectedState: 'RECOVERING',
+ expectedApplied: [1, 2, 3, 4, 5],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3, 4, 5, 6],
+ collectionContents: [1, 2, 3],
+ deletePoint: 2,
+ begin: 3,
+ minValid: 6,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3, 4, 5, 6],
+ });
- // Ensure that all ops that should have been applied were.
- assert.eq(conn.getCollection(ns).distinct('_id'), [1, 2, 3]);
+ //
+ // These states should be impossible to get into.
+ //
+
+ runTest({
+ oplogEntries: [1, 2, 3],
+ collectionContents: [1, 2, 3, 4],
+ deletePoint: null,
+ begin: 4,
+ minValid: null, // doesn't matter.
+ expectedState: 'FATAL',
+ });
+
+ runTest({
+ oplogEntries: [4, 5, 6],
+ collectionContents: [1, 2],
+ deletePoint: 2,
+ begin: 3,
+ minValid: null, // doesn't matter.
+ expectedState: 'FATAL',
+ });
+
+ runTest({
+ oplogEntries: [4, 5, 6],
+ collectionContents: [1, 2],
+ deletePoint: null,
+ begin: 3,
+ minValid: null, // doesn't matter.
+ expectedState: 'FATAL',
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3, 4, 5, 6],
+ collectionContents: [1, 2, 3],
+ deletePoint: 2,
+ begin: 3,
+ minValid: 3,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3, 4, 5, 6],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3, 4, 5, 6],
+ collectionContents: [1, 2, 3, 4, 5],
+ deletePoint: null,
+ begin: 5,
+ minValid: 3,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3, 4, 5, 6],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3, 4, 5, 6],
+ collectionContents: [1, 2, 3, 4, 5],
+ deletePoint: null,
+ begin: 5,
+ minValid: null,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3, 4, 5, 6],
+ });
+
+ runTest({
+ oplogEntries: [1, 2, 3, 4, 5],
+ collectionContents: [1],
+ deletePoint: 4,
+ begin: 1,
+ minValid: 3,
+ expectedState: 'SECONDARY',
+ expectedApplied: [1, 2, 3],
+ });
rst.stopSet();
})();