mongo/jstests/sharding/move_primary_clone.js

246 lines
9.8 KiB
JavaScript

import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";
import {ShardingTest} from "jstests/libs/shardingtest.js";
function sortByName(a, b) {
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
}
function getCollections(shard) {
let res = shard.getDB("test1").runCommand({listCollections: 1});
assert.commandWorked(res);
let collections = res.cursor.firstBatch;
// Sort collections by name.
collections.sort(sortByName);
assert.eq(collections.length, 2);
return collections;
}
function checkOptions(c, expectedOptions) {
assert.hasFields(c, ["options"], "Missing options field for collection " + c.name);
assert.hasFields(c.options, expectedOptions, "Missing expected option(s) for collection " + c.name);
}
function checkCollectionsCopiedCorrectly(fromShard, toShard, tracked, barUUID, fooUUID) {
let c1, c2;
[c1, c2] = getCollections(toShard);
function checkName(c, expectedName) {
assert.eq(c.name, expectedName, "Expected collection to be " + expectedName + ", got " + c.name);
}
function checkUUIDsEqual(c, expectedUUID) {
assert.hasFields(c, ["info"], "Missing info field for collection " + c.name);
assert.hasFields(c.info, ["uuid"], "Missing uuid field for collection " + c.name);
assert.eq(c.info.uuid, expectedUUID, "Incorrect uuid for collection " + c.name);
}
function checkUUIDsNotEqual(c, originalUUID) {
assert.hasFields(c, ["info"], "Missing info field for collection " + c.name);
assert.hasFields(c.info, ["uuid"], "Missing uuid field for collection " + c.name);
assert.neq(
c.info.uuid,
originalUUID,
"UUID for " + c.name + " should be different than the original collection but is the same",
);
}
function checkIndexes(collName, collTracked, expectedIndexes) {
let res = toShard.getDB("test1").runCommand({listIndexes: collName});
assert.commandWorked(res, "Failed to get indexes for collection " + collName);
let indexes = res.cursor.firstBatch;
indexes.sort(sortByName);
// For each unsharded collection, there should be a total of 2 indexes - one for the _id
// field and the other we have created. However, in the case of sharded collections, only
// the _id index is present. When running movePrimary, indexes of sharded collections are
// not copied.
if (collTracked) assert(indexes.length == 1);
else assert(indexes.length == 2);
indexes.forEach((index, i) => {
let expected;
if (i == 0) {
expected = {name: "_id_", key: {_id: 1}};
} else {
expected = expectedIndexes[i - 1];
}
Object.keys(expected).forEach((k) => {
assert.eq(index[k], expected[k]);
});
});
}
function checkCount(shard, collName, count) {
let res = shard.getDB("test1").runCommand({count: collName});
assert.commandWorked(res);
assert.eq(res.n, count);
}
checkName(c1, "bar");
checkName(c2, "foo");
checkOptions(c1, Object.keys(barOptions));
checkIndexes("bar", tracked[1], barIndexes);
checkOptions(c2, Object.keys(fooOptions));
checkIndexes("foo", tracked[0], fooIndexes);
if (tracked[0]) {
checkCount(fromShard, "foo", 3);
checkCount(toShard, "foo", 0);
checkUUIDsEqual(c2, fooUUID);
} else {
checkCount(toShard, "foo", 3);
checkCount(fromShard, "foo", 0);
checkUUIDsNotEqual(c2, fooUUID);
}
if (tracked[1]) {
checkCount(fromShard, "bar", 3);
checkCount(toShard, "bar", 0);
checkUUIDsEqual(c1, barUUID);
} else {
checkCount(toShard, "bar", 3);
checkCount(fromShard, "bar", 0);
checkUUIDsNotEqual(c1, barUUID);
}
}
function createCollections(sharded) {
assert.commandWorked(st.getDB("test1").runCommand({dropDatabase: 1}));
let db = st.getDB("test1");
assert.commandWorked(db.createCollection("foo", fooOptions));
assert.commandWorked(db.createCollection("bar", barOptions));
for (let i = 0; i < 3; i++) {
assert.commandWorked(db.foo.insert({a: i}));
assert.commandWorked(db.bar.insert({a: i}));
}
assert.eq(3, db.foo.count());
assert.eq(3, db.bar.count());
assert.commandWorked(db.runCommand({createIndexes: "foo", indexes: fooIndexes}));
assert.commandWorked(db.runCommand({createIndexes: "bar", indexes: barIndexes}));
if (sharded) {
assert.commandWorked(db.adminCommand({enableSharding: "test1"}));
assert.commandWorked(db.adminCommand({shardCollection: "test1.foo", key: {_id: 1}}));
assert.commandWorked(db.adminCommand({shardCollection: "test1.bar", key: {_id: 1}}));
}
}
function movePrimaryWithFailpoint(sharded) {
let db = st.getDB("test1");
createCollections(sharded);
let tracked = [
FixtureHelpers.isTracked(st.s.getCollection("test1.foo")),
FixtureHelpers.isTracked(st.s.getCollection("test1.bar")),
];
let fromShard = st.getPrimaryShard("test1");
let toShard = st.getOther(fromShard);
assert.eq(3, fromShard.getDB("test1").foo.count(), "from shard doesn't have data before move");
assert.eq(0, toShard.getDB("test1").foo.count(), "to shard has data before move");
assert.eq(3, fromShard.getDB("test1").bar.count(), "from shard doesn't have data before move");
assert.eq(0, toShard.getDB("test1").bar.count(), "to shard has data before move");
let listCollsFrom = fromShard.getDB("test1").runCommand({listCollections: 1});
let fromColls = listCollsFrom.cursor.firstBatch;
fromColls.sort(sortByName);
let baruuid = fromColls[0].info.uuid;
let foouuid = fromColls[1].info.uuid;
assert.commandWorked(
toShard.getDB("admin").runCommand({configureFailPoint: "movePrimaryFailPoint", mode: "alwaysOn"}),
);
// Failpoint will cause movePrimary to fail after the first collection has been copied over
assert.commandFailed(st.s0.adminCommand({movePrimary: "test1", to: toShard.name}));
assert.commandWorked(toShard.getDB("admin").runCommand({configureFailPoint: "movePrimaryFailPoint", mode: "off"}));
if (sharded || tracked.includes(true)) {
// If the collections are sharded or tracked, the UUID of the collection on the donor should
// be copied over and the options should be the same so retrying the move should succeed.
assert.commandWorked(st.s0.adminCommand({movePrimary: "test1", to: toShard.name}));
checkCollectionsCopiedCorrectly(fromShard, toShard, tracked, baruuid, foouuid);
// Now change an option on the toShard, and verify that calling clone again succeeds
// when the options don't match.
assert.commandWorked(toShard.getDB("test1").runCommand({collMod: "bar", validationLevel: "moderate"}));
assert.commandWorked(st.s0.adminCommand({movePrimary: "test1", to: fromShard.name}));
// Assert that the fromShard does not have the new options, but the toShard does
let barOnToShard = getCollections(toShard)[0];
checkOptions(
barOnToShard,
Object.keys({validator: {$jsonSchema: {required: ["a"]}}, validationLevel: "moderate"}),
);
let barOnFromShard = getCollections(fromShard)[0];
checkOptions(barOnFromShard, Object.keys(barOptions));
// The docs should still be on the original primary shard (fromShard).
checkCollectionsCopiedCorrectly(fromShard, toShard, tracked, baruuid, foouuid);
// Now drop and recreate the collection on the toShard. The collection should now have
// a different UUID, so we should fail on movePrimary.
assert.commandWorked(toShard.getDB("test1").runCommand({drop: "bar"}));
assert.commandWorked(toShard.getDB("test1").bar.insert({"x": 1}));
assert.commandFailedWithCode(
st.s0.adminCommand({movePrimary: "test1", to: toShard.name}),
ErrorCodes.InvalidOptions,
);
} else {
// The failure of the previous attempt caused the dirty data on the recipient to be dropped,
// so the data cloning shouldn't find any impediments.
assert.commandWorked(st.s0.adminCommand({movePrimary: "test1", to: toShard.name}));
}
}
function movePrimaryNoFailpoint(sharded) {
let db = st.getDB("test1");
createCollections(sharded);
let tracked = [
FixtureHelpers.isTracked(st.s.getCollection("test1.foo")),
FixtureHelpers.isTracked(st.s.getCollection("test1.bar")),
];
let fromShard = st.getPrimaryShard("test1");
let toShard = st.getOther(fromShard);
assert.eq(3, fromShard.getDB("test1").foo.count(), "from shard doesn't have data before move");
assert.eq(0, toShard.getDB("test1").foo.count(), "to shard has data before move");
assert.eq(3, fromShard.getDB("test1").bar.count(), "from shard doesn't have data before move");
assert.eq(0, toShard.getDB("test1").bar.count(), "to shard has data before move");
let listCollsFrom = fromShard.getDB("test1").runCommand({listCollections: 1});
let fromColls = listCollsFrom.cursor.firstBatch;
fromColls.sort(sortByName);
let baruuid = fromColls[0].info.uuid;
let foouuid = fromColls[1].info.uuid;
assert.commandWorked(st.s0.adminCommand({movePrimary: "test1", to: toShard.name}));
checkCollectionsCopiedCorrectly(fromShard, toShard, tracked, baruuid, foouuid);
}
var st = new ShardingTest({shards: 2});
var fooOptions = {validationLevel: "off"};
var barOptions = {validator: {$jsonSchema: {required: ["a"]}}};
var fooIndexes = [{key: {a: 1}, name: "index1", expireAfterSeconds: 5000}];
var barIndexes = [{key: {a: -1}, name: "index2"}];
movePrimaryWithFailpoint(true);
movePrimaryWithFailpoint(false);
movePrimaryNoFailpoint(true);
movePrimaryNoFailpoint(false);
st.stop();