Files
mongo/jstests/multiVersion/change_streams_resume_token_version.js

139 lines
6.9 KiB
JavaScript

// Tests that a resume token from FCV 4.0 cannot be used with the new 'startAfter' option, because
// the old version of the resume token doesn't contain enough information to distinguish an
// invalidate event from the event which generated the invalidate.
// @tags: [exclude_from_large_txns_due_to_change_streams]
(function() {
"use strict";
load("jstests/libs/collection_drop_recreate.js"); // For assertDropAndRecreateCollection.
load("jstests/multiVersion/libs/change_stream_hwm_helpers.js"); // For ChangeStreamHWMHelpers.
load("jstests/multiVersion/libs/multi_rs.js"); // For upgradeSet.
load("jstests/replsets/rslib.js"); // For startSetIfSupportsReadMajority.
const preBackport40Version = ChangeStreamHWMHelpers.preBackport40Version;
const latest42Version = ChangeStreamHWMHelpers.latest42Version;
const rst = new ReplSetTest({nodes: 2, nodeOptions: {binVersion: preBackport40Version}});
if (!startSetIfSupportsReadMajority(rst)) {
jsTestLog("Skipping test since storage engine doesn't support majority read concern.");
rst.stopSet();
return;
}
rst.initiate();
let testDB = rst.getPrimary().getDB(jsTestName());
let coll = testDB.change_stream_upgrade;
// Up- or downgrades the replset and then refreshes our references to the test collection.
function refreshReplSet(version) {
// Upgrade the set and wait for it to become available again.
rst.upgradeSet({binVersion: version});
rst.awaitReplication();
// Having upgraded the cluster, reacquire references to the db and collection.
testDB = rst.getPrimary().getDB(jsTestName());
coll = testDB.change_stream_upgrade;
}
// Creates a collection, drops it, and returns the resulting 'drop' and 'invalidate' tokens.
function generateDropAndInvalidateTokens() {
assertDropAndRecreateCollection(testDB, coll.getName());
const streamStartedOnOldFCV = coll.watch();
coll.drop();
assert.soon(() => streamStartedOnOldFCV.hasNext());
let change = streamStartedOnOldFCV.next();
assert.eq(change.operationType, "drop", tojson(change));
const resumeTokenFromDrop = change._id;
assert.soon(() => streamStartedOnOldFCV.hasNext());
change = streamStartedOnOldFCV.next();
assert.eq(change.operationType, "invalidate", tojson(change));
const resumeTokenFromInvalidate = change._id;
return [resumeTokenFromDrop, resumeTokenFromInvalidate];
}
function testInvalidateV0(resumeTokenFromDrop, resumeTokenFromInvalidate) {
// These two resume tokens should be the same. Because they cannot be distinguished, any
// attempt to resume or start a new stream should immediately return invalidate.
assert.eq(resumeTokenFromDrop, resumeTokenFromInvalidate);
for (let token of[resumeTokenFromDrop, resumeTokenFromInvalidate]) {
let newStream = coll.watch([], {startAfter: token, collation: {locale: "simple"}});
assert.soon(() => newStream.hasNext());
assert.eq(newStream.next().operationType, "invalidate");
// Test the same thing but with 'resumeAfter' instead of 'startAfter'.
newStream = coll.watch([], {resumeAfter: token, collation: {locale: "simple"}});
assert.soon(() => newStream.hasNext());
assert.eq(newStream.next().operationType, "invalidate");
}
}
function testInvalidateV1(resumeTokenFromDrop, resumeTokenFromInvalidate) {
// This stream should be using the new version of resume tokens which *can* distinguish a
// drop from the invalidate that follows it. Recreate the collection with the same name and
// insert a document.
assert.commandWorked(testDB.runCommand({create: coll.getName()}));
assert.commandWorked(coll.insert({_id: "insert after drop"}));
assert.neq(resumeTokenFromDrop,
resumeTokenFromInvalidate,
() => tojson(resumeTokenFromDrop) + " should not equal " +
tojson(resumeTokenFromInvalidate));
let newStream =
coll.watch([], {startAfter: resumeTokenFromDrop, collation: {locale: "simple"}});
assert.soon(() => newStream.hasNext());
assert.eq(newStream.next().operationType, "invalidate");
newStream =
coll.watch([], {startAfter: resumeTokenFromInvalidate, collation: {locale: "simple"}});
assert.soon(() => newStream.hasNext());
const change = newStream.next();
assert.eq(change.operationType, "insert");
assert.eq(change.documentKey._id, "insert after drop");
// Test the same thing but with 'resumeAfter' instead of 'startAfter'. This should see an
// invalidate on the first, and reject the second.
newStream =
coll.watch([], {resumeAfter: resumeTokenFromDrop, collation: {locale: "simple"}});
assert.soon(() => newStream.hasNext());
assert.eq(newStream.next().operationType, "invalidate");
const error = assert.throws(
() => coll.watch(
[], {resumeAfter: resumeTokenFromInvalidate, collation: {locale: "simple"}}));
assert.eq(error.code, ErrorCodes.InvalidResumeToken);
}
// We will test 'drop' and 'invalidate' tokens for resume token formats v0 and v1.
let resumeTokenFromDropV0, resumeTokenFromInvalidateV0;
let resumeTokenFromDropV1, resumeTokenFromInvalidateV1;
// We start on 'preBackport40Version'. Generate v0 'drop' and 'invalidate' resume tokens.
assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "4.0"}));
[resumeTokenFromDropV0, resumeTokenFromInvalidateV0] = generateDropAndInvalidateTokens();
// Now upgrade the set to 'latest42Version' with FCV 4.0.
refreshReplSet(latest42Version);
assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "4.0"}));
// Confirm that the v0 tokens behave as expected for binary 4.2 FCV 4.0.
testInvalidateV0(resumeTokenFromDropV0, resumeTokenFromInvalidateV0);
// Confirm that new tokens generated by binary 4.2 in FCV 4.0 are v1 rather than v0.
[resumeTokenFromDropV1, resumeTokenFromInvalidateV1] = generateDropAndInvalidateTokens();
testInvalidateV1(resumeTokenFromDropV1, resumeTokenFromInvalidateV1);
// Now upgrade the set to 'latest42Version' with FCV 4.2.
assert.commandWorked(testDB.adminCommand({setFeatureCompatibilityVersion: "4.2"}));
// Confirm that the v0 tokens behave as expected for binary 4.2 FCV 4.2.
testInvalidateV0(resumeTokenFromDropV0, resumeTokenFromInvalidateV0);
// Confirm that new tokens generated by binary 4.2 in FCV 4.2 are v1 rather than v0.
[resumeTokenFromDropV1, resumeTokenFromInvalidateV1] = generateDropAndInvalidateTokens();
testInvalidateV1(resumeTokenFromDropV1, resumeTokenFromInvalidateV1);
rst.stopSet();
}());