mirror of
https://github.com/mongodb/mongo.git
synced 2024-11-27 23:27:11 +01:00
771dabd098
GitOrigin-RevId: 744aa110a53786b23c62ff53f87a1418b5991e8d
216 lines
8.6 KiB
JavaScript
216 lines
8.6 KiB
JavaScript
/**
|
|
* Tests that a user projection which fakes an internal topology-change event is handled gracefully
|
|
* in a sharded cluster.
|
|
* TODO SERVER-65778: rework this test when we can handle faked internal events more robustly.
|
|
*
|
|
* Tests that if a user fakes an internal event with a projection nothing crashes, so not valuable
|
|
* to test with a config shard.
|
|
* @tags: [assumes_read_preference_unchanged, config_shard_incompatible]
|
|
*/
|
|
import {ShardingTest} from "jstests/libs/shardingtest.js";
|
|
|
|
const numShards = 2;
|
|
|
|
const st = new ShardingTest({
|
|
shards: numShards,
|
|
rs: {nodes: 1, setParameter: {writePeriodicNoops: true, periodicNoopIntervalSecs: 1}}
|
|
});
|
|
|
|
const mongosConn = st.s;
|
|
|
|
const testDB = mongosConn.getDB(jsTestName());
|
|
const adminDB = mongosConn.getDB("admin");
|
|
const testColl = testDB.test;
|
|
|
|
// Insert one test document that points to a valid shard, and one that points to an invalid shard.
|
|
// These will generate change events that look identical to a config.shards entry, except for 'ns'.
|
|
// It also means that the documentKey field in the resume token will look like a potentially valid
|
|
// new-shard document.
|
|
const existingShardDoc = testDB.getSiblingDB("config").shards.find({_id: st.rs0.name}).next();
|
|
const existingShardWrongNameDoc = {
|
|
_id: "nonExistentName",
|
|
host: existingShardDoc.host
|
|
};
|
|
const existingShardWrongHostDoc = {
|
|
_id: st.rs1.name,
|
|
host: `${st.rs1.name}/${st.rs1.host}-wrong:${st.rs1.ports[0]}`
|
|
};
|
|
const fakeShardDoc = {
|
|
_id: "shardX",
|
|
host: "shardX/nonExistentHost:27017"
|
|
};
|
|
const invalidShardDoc = {
|
|
_id: "shardY",
|
|
host: null
|
|
};
|
|
const configDotShardsNs = {
|
|
db: "config",
|
|
coll: "shards"
|
|
};
|
|
assert.commandWorked(testColl.insert(existingShardWrongNameDoc));
|
|
assert.commandWorked(testColl.insert(existingShardWrongHostDoc));
|
|
assert.commandWorked(testColl.insert(existingShardDoc));
|
|
assert.commandWorked(testColl.insert(invalidShardDoc));
|
|
assert.commandWorked(testColl.insert(fakeShardDoc));
|
|
|
|
// Log the shard description documents that we just inserted into the collection.
|
|
jsTestLog("Shard docs: " + tojson(testColl.find().toArray()));
|
|
|
|
// Helper function which opens a stream with the given projection and asserts that its behaviour
|
|
// conforms to the specified arguments; it will either throw the given error code, or return the
|
|
// expected events. Passing an empty array will confirm that we see no events in the stream. We
|
|
// further confirm that the faked events do not cause additional cursors to be opened.
|
|
function assertChangeStreamBehaviour(projection, expectedEvents, expectedErrorCode = null) {
|
|
// Can't expect both to see events and to throw an exception.
|
|
assert(!(expectedEvents && expectedErrorCode));
|
|
|
|
// Generate a random ID for this stream.
|
|
const commentID = `${Math.random()}`;
|
|
|
|
// Create a change stream cursor with the specified projection.
|
|
var csCursor = testColl.watch([{$addFields: projection}],
|
|
{startAtOperationTime: Timestamp(1, 1), comment: commentID});
|
|
|
|
// Confirm that the observed events match the expected events, if specified.
|
|
if (expectedEvents && expectedEvents.length > 0) {
|
|
for (let expectedEvent of expectedEvents) {
|
|
assert.soon(() => csCursor.hasNext());
|
|
const nextEvent = csCursor.next();
|
|
for (let fieldName in expectedEvent) {
|
|
assert.eq(
|
|
expectedEvent[fieldName], nextEvent[fieldName], {expectedEvent, nextEvent});
|
|
}
|
|
}
|
|
}
|
|
// If there are no expected events, confirm that the token advances without seeing anything.
|
|
if (expectedEvents && expectedEvents.length == 0) {
|
|
const startPoint = csCursor.getResumeToken();
|
|
assert.soon(() => {
|
|
assert(!csCursor.hasNext(), () => tojson(csCursor.next()));
|
|
return bsonWoCompare(csCursor.getResumeToken(), startPoint) > 0;
|
|
});
|
|
}
|
|
|
|
// If we expect an error code, assert that we throw it soon.
|
|
if (expectedErrorCode) {
|
|
assert.soon(() => {
|
|
try {
|
|
assert.throwsWithCode(() => csCursor.hasNext(), expectedErrorCode);
|
|
} catch (err) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
} else {
|
|
// Otherwise, confirm that we still only have a single cursor on each shard. It's possible
|
|
// that the same cursor will be listed as both active and inactive, so group by cursorId.
|
|
const openCursors = adminDB
|
|
.aggregate([
|
|
{$currentOp: {idleCursors: true}},
|
|
{$match: {"cursor.originatingCommand.comment": commentID}},
|
|
{
|
|
$group: {
|
|
_id: {shard: "$shard", cursorId: "$cursor.cursorId"},
|
|
currentOps: {$push: "$$ROOT"}
|
|
}
|
|
}
|
|
])
|
|
.toArray();
|
|
assert.eq(openCursors.length,
|
|
numShards,
|
|
// Dump all the running operations for better debuggability.
|
|
() => tojson(adminDB.aggregate([{$currentOp: {idleCursors: true}}]).toArray()));
|
|
}
|
|
|
|
// Close the change stream when we are done.
|
|
csCursor.close();
|
|
}
|
|
|
|
// Test that a projection which fakes a 'migrateChunkToNewShard' event is swallowed but has no
|
|
// effect.
|
|
let testProjection = {operationType: "migrateChunkToNewShard"};
|
|
assertChangeStreamBehaviour(testProjection, []);
|
|
|
|
// Test that a projection which fakes an event on config.shards with a non-string operationType is
|
|
// allowed to pass through.
|
|
testProjection = {
|
|
ns: configDotShardsNs,
|
|
operationType: null
|
|
};
|
|
assertChangeStreamBehaviour(testProjection, [
|
|
{operationType: null, fullDocument: existingShardWrongNameDoc},
|
|
{operationType: null, fullDocument: existingShardWrongHostDoc},
|
|
{operationType: null, fullDocument: existingShardDoc},
|
|
{operationType: null, fullDocument: invalidShardDoc},
|
|
{operationType: null, fullDocument: fakeShardDoc}
|
|
]);
|
|
|
|
// Test that a projection which fakes an event on config.shards with a non-timestamp clusterTime
|
|
// is allowed to pass through.
|
|
testProjection = {
|
|
ns: configDotShardsNs,
|
|
clusterTime: null
|
|
};
|
|
assertChangeStreamBehaviour(testProjection, [
|
|
{clusterTime: null, fullDocument: existingShardWrongNameDoc},
|
|
{clusterTime: null, fullDocument: existingShardWrongHostDoc},
|
|
{clusterTime: null, fullDocument: existingShardDoc},
|
|
{clusterTime: null, fullDocument: invalidShardDoc},
|
|
{clusterTime: null, fullDocument: fakeShardDoc}
|
|
]);
|
|
|
|
// Test that a projection which fakes an event on config.shards with a non-object fullDocument
|
|
// is allowed to pass through.
|
|
testProjection = {
|
|
ns: configDotShardsNs,
|
|
fullDocument: null
|
|
};
|
|
assertChangeStreamBehaviour(testProjection, [
|
|
{fullDocument: null},
|
|
{fullDocument: null},
|
|
{fullDocument: null},
|
|
{fullDocument: null},
|
|
{fullDocument: null}
|
|
]);
|
|
|
|
// Test that a projection which fakes a new-shard event on config.shards with a valid fullDocument
|
|
// pointing to an existing shard is swallowed but has no effect.
|
|
testProjection = {
|
|
ns: configDotShardsNs,
|
|
fullDocument: existingShardDoc
|
|
};
|
|
assertChangeStreamBehaviour(testProjection, []);
|
|
|
|
// Test that a projection which fakes a new-shard event on config.shards with a valid fullDocument
|
|
// pointing to an existing shard's host, but the wrong shard name, throws as it attempts to connect.
|
|
testProjection = {
|
|
ns: configDotShardsNs,
|
|
fullDocument: existingShardWrongNameDoc
|
|
};
|
|
assertChangeStreamBehaviour(testProjection, null, ErrorCodes.ShardNotFound);
|
|
|
|
// Test that a projection which fakes a new-shard event on config.shards with a valid fullDocument
|
|
// pointing to an existing shard's name, but the wrong host, is swallowed and has no effect.
|
|
testProjection = {
|
|
ns: configDotShardsNs,
|
|
fullDocument: existingShardWrongHostDoc
|
|
};
|
|
assertChangeStreamBehaviour(testProjection, []);
|
|
|
|
// Test that a projection which fakes a new-shard event on config.shards with a valid fullDocument
|
|
// pointing to a non-existent shard throws as it attempts to connect.
|
|
testProjection = {
|
|
ns: configDotShardsNs,
|
|
fullDocument: fakeShardDoc
|
|
};
|
|
assertChangeStreamBehaviour(testProjection, null, ErrorCodes.ShardNotFound);
|
|
|
|
// Test that a projection which fakes a new-shard event on config.shards with an invalid
|
|
// fullDocument throws a validation exception.
|
|
testProjection = {
|
|
ns: configDotShardsNs,
|
|
fullDocument: invalidShardDoc
|
|
};
|
|
assertChangeStreamBehaviour(testProjection, null, ErrorCodes.TypeMismatch);
|
|
|
|
st.stop(); |