459 lines
20 KiB
JavaScript
459 lines
20 KiB
JavaScript
/**
|
|
* Util functions used by cluster server parameter tests.
|
|
*
|
|
* When adding new cluster server parameter, do the following:
|
|
* If it's test-only, add its definition to kTestOnlyClusterParameters.
|
|
* Otherwise, add to kNonTestOnlyClusterParameters.
|
|
* The keyname will be the name of the cluster-wide server parameter,
|
|
* it's value will be an object with at least three keys named:
|
|
* * 'default': Properties to expect on an unset CWSP
|
|
* * 'insert': Values to set on the CWSP on first write
|
|
* * 'update': Values to set on the CWSP on second write
|
|
* A fourth property 'featureFlag' may also be set if the
|
|
* parameter depends on a featureFlag.
|
|
* Use the name of the featureFlag if it is required in
|
|
* order to consider the parameter.
|
|
* Prefix the name with a bang '!' if it is only considered
|
|
* when the featureFlag is disabled.
|
|
*/
|
|
|
|
load("jstests/libs/feature_flag_util.js");
|
|
|
|
const kNonTestOnlyClusterParameters = {
|
|
changeStreamOptions: {
|
|
default: {preAndPostImages: {expireAfterSeconds: 'off'}},
|
|
insert: {preAndPostImages: {expireAfterSeconds: 30}},
|
|
update: {preAndPostImages: {expireAfterSeconds: 'off'}},
|
|
featureFlag: '!ServerlessChangeStreams',
|
|
},
|
|
changeStreams: {
|
|
default: {expireAfterSeconds: NumberLong(3600)},
|
|
insert: {expireAfterSeconds: 30},
|
|
update: {expireAfterSeconds: 10},
|
|
featureFlag: 'ServerlessChangeStreams',
|
|
setParameters: {'multitenancySupport': true},
|
|
serverless: true,
|
|
},
|
|
};
|
|
|
|
const kTestOnlyClusterParameters = {
|
|
cwspTestNeedsFeatureFlagClusterWideToaster: {
|
|
default: {intData: 16},
|
|
insert: {intData: 17},
|
|
update: {intData: 18},
|
|
featureFlag: 'ClusterWideToaster',
|
|
},
|
|
testStrClusterParameter: {
|
|
default: {strData: 'off'},
|
|
insert: {strData: 'on'},
|
|
update: {strData: 'sleep'},
|
|
},
|
|
testIntClusterParameter: {
|
|
default: {intData: 16},
|
|
insert: {intData: 17},
|
|
update: {intData: 18},
|
|
},
|
|
testBoolClusterParameter: {
|
|
default: {boolData: false},
|
|
insert: {boolData: true},
|
|
update: {boolData: false},
|
|
},
|
|
};
|
|
|
|
const kAllClusterParameters =
|
|
Object.assign({}, kNonTestOnlyClusterParameters, kTestOnlyClusterParameters);
|
|
const kAllClusterParameterNames = Object.keys(kAllClusterParameters);
|
|
const kAllClusterParameterDefaults = kAllClusterParameterNames.map(
|
|
(name) => Object.assign({_id: name}, kAllClusterParameters[name].default));
|
|
const kAllClusterParameterInserts = kAllClusterParameterNames.map(
|
|
(name) => Object.assign({_id: name}, kAllClusterParameters[name].insert));
|
|
const kAllClusterParameterUpdates = kAllClusterParameterNames.map(
|
|
(name) => Object.assign({_id: name}, kAllClusterParameters[name].update));
|
|
|
|
const kNonTestOnlyClusterParameterDefaults =
|
|
Object.keys(kNonTestOnlyClusterParameters)
|
|
.map((name) => Object.assign({_id: name}, kAllClusterParameters[name].default));
|
|
|
|
function considerParameter(paramName, conn) {
|
|
// { featureFlag: 'name' } indicates that the CWSP should only be considered with the FF
|
|
// enabled. { featureFlag: '!name' } indicates that the CWSP should only be considered with the
|
|
// FF disabled.
|
|
function validateFeatureFlag(cp) {
|
|
if (cp.featureFlag) {
|
|
const considerWhenFFEnabled = cp.featureFlag[0] !== '!';
|
|
const ff = cp.featureFlag.substr(considerWhenFFEnabled ? 0 : 1);
|
|
return FeatureFlagUtil.isEnabled(conn, ff) === considerWhenFFEnabled;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// A dictionary of 'setParameters' that should be validated while considering the current CWSP.
|
|
function validateSetParameter(cp) {
|
|
if (cp.setParameters) {
|
|
for ([param, value] of Object.entries(cp.setParameters)) {
|
|
const resp = conn.getDB("admin").runCommand({getParameter: 1, param: 1});
|
|
const hasParam = resp.hasOwnProperty(param) && resp[param] === value;
|
|
if (!hasParam) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if the current CWSP should be run in the serverless.
|
|
function validateServerless(cp) {
|
|
if (cp.hasOwnProperty("serverless")) {
|
|
const resp =
|
|
assert.commandWorked(conn.getDB("admin").adminCommand({getCmdLineOpts: 1}));
|
|
return cp.serverless === resp.parsed && resp.parsed.replication &&
|
|
resp.parsed.replication.serverless;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const cp = kAllClusterParameters[paramName] || {};
|
|
return validateFeatureFlag(cp) && validateSetParameter(cp) && validateServerless(cp);
|
|
}
|
|
|
|
// Set the log level for get/setClusterParameter logging to appear.
|
|
function setupNode(conn) {
|
|
const adminDB = conn.getDB('admin');
|
|
adminDB.setLogLevel(2);
|
|
}
|
|
|
|
function setupReplicaSet(rst) {
|
|
setupNode(rst.getPrimary());
|
|
|
|
rst.getSecondaries().forEach(function(secondary) {
|
|
setupNode(secondary);
|
|
});
|
|
}
|
|
|
|
function setupSharded(st) {
|
|
setupNode(st.s0);
|
|
|
|
const shards = [st.rs0, st.rs1, st.rs2];
|
|
shards.forEach(function(shard) {
|
|
setupReplicaSet(shard);
|
|
});
|
|
}
|
|
|
|
// Upserts config.clusterParameters document with w:majority via setClusterParameter.
|
|
function runSetClusterParameter(conn, update) {
|
|
const paramName = update._id;
|
|
if (!considerParameter(paramName, conn)) {
|
|
return;
|
|
}
|
|
let updateCopy = Object.assign({}, update);
|
|
delete updateCopy._id;
|
|
delete updateCopy.clusterParameterTime;
|
|
const setClusterParameterDoc = {
|
|
[paramName]: updateCopy,
|
|
};
|
|
|
|
const adminDB = conn.getDB('admin');
|
|
assert.commandWorked(adminDB.runCommand({setClusterParameter: setClusterParameterDoc}));
|
|
}
|
|
|
|
// Runs getClusterParameter on a specific mongod or mongos node and returns true/false depending
|
|
// on whether the expected values were returned.
|
|
function runGetClusterParameterNode(conn, getClusterParameterArgs, expectedClusterParameters) {
|
|
const adminDB = conn.getDB('admin');
|
|
|
|
// Filter out parameters that we don't care about.
|
|
if (Array.isArray(getClusterParameterArgs)) {
|
|
getClusterParameterArgs =
|
|
getClusterParameterArgs.filter((name) => considerParameter(name, conn));
|
|
} else if ((typeof getClusterParameterArgs === 'string') &&
|
|
!considerParameter(getClusterParameterArgs, conn)) {
|
|
return true;
|
|
}
|
|
|
|
const actualClusterParameters =
|
|
assert.commandWorked(adminDB.runCommand({getClusterParameter: getClusterParameterArgs}))
|
|
.clusterParameters;
|
|
|
|
// Reindex actual based on name, and remove irrelevant field.
|
|
let actual = {};
|
|
actualClusterParameters.forEach(function(acp) {
|
|
actual[acp._id] = acp;
|
|
delete actual[acp._id].clusterParameterTime;
|
|
});
|
|
|
|
for (let i = 0; i < expectedClusterParameters.length; i++) {
|
|
if (!considerParameter(expectedClusterParameters[i]._id, conn)) {
|
|
continue;
|
|
}
|
|
|
|
const id = expectedClusterParameters[i]._id;
|
|
if (actual[id] === undefined) {
|
|
jsTest.log('Expected to retreive ' + id + ' but it was not returned');
|
|
return false;
|
|
}
|
|
|
|
if (bsonWoCompare(expectedClusterParameters[i], actual[id]) !== 0) {
|
|
jsTest.log('Server parameter mismatch on node: ' + conn.host + '\n' +
|
|
'Expected: ' + tojson(expectedClusterParameters[i]) + '\n' +
|
|
'Actual: ' + tojson(actual[id]));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Runs getClusterParameter on each replica set node and asserts that the response matches the
|
|
// expected parameter objects on at least a majority of nodes.
|
|
function runGetClusterParameterReplicaSet(rst, getClusterParameterArgs, expectedClusterParameters) {
|
|
let numMatches = 0;
|
|
const numTotalNodes = rst.getSecondaries().length + 1;
|
|
if (runGetClusterParameterNode(
|
|
rst.getPrimary(), getClusterParameterArgs, expectedClusterParameters)) {
|
|
numMatches++;
|
|
}
|
|
|
|
rst.getSecondaries().forEach(function(secondary) {
|
|
if (runGetClusterParameterNode(
|
|
secondary, getClusterParameterArgs, expectedClusterParameters)) {
|
|
numMatches++;
|
|
}
|
|
});
|
|
|
|
assert((numMatches / numTotalNodes) > 0.5);
|
|
}
|
|
|
|
// Runs getClusterParameter on mongos, each mongod in each shard replica set, and each mongod in
|
|
// the config server replica set.
|
|
function runGetClusterParameterSharded(st, getClusterParameterArgs, expectedClusterParameters) {
|
|
runGetClusterParameterNode(st.s0, getClusterParameterArgs, expectedClusterParameters);
|
|
|
|
runGetClusterParameterReplicaSet(
|
|
st.configRS, getClusterParameterArgs, expectedClusterParameters);
|
|
const shards = [st.rs0, st.rs1, st.rs2];
|
|
shards.forEach(function(shard) {
|
|
runGetClusterParameterReplicaSet(shard, getClusterParameterArgs, expectedClusterParameters);
|
|
});
|
|
}
|
|
|
|
// Tests valid usages of getClusterParameter and verifies that the expected values are returned.
|
|
function testValidClusterParameterCommands(conn) {
|
|
if (conn instanceof ReplSetTest) {
|
|
// Run getClusterParameter in list format and '*' and ensure it returns all default values
|
|
// on all nodes in the replica set.
|
|
runGetClusterParameterReplicaSet(
|
|
conn, kAllClusterParameterNames, kAllClusterParameterDefaults);
|
|
runGetClusterParameterReplicaSet(conn, '*', kAllClusterParameterDefaults);
|
|
|
|
// For each parameter, run setClusterParameter and verify that getClusterParameter
|
|
// returns the updated value on all nodes in the replica set.
|
|
for (let i = 0; i < kAllClusterParameterNames.length; i++) {
|
|
runSetClusterParameter(conn.getPrimary(), kAllClusterParameterInserts[i]);
|
|
runGetClusterParameterReplicaSet(
|
|
conn, kAllClusterParameterNames[i], [kAllClusterParameterInserts[i]]);
|
|
}
|
|
|
|
// Do the above again to verify that document updates are also handled properly.
|
|
for (let i = 0; i < kAllClusterParameterNames.length; i++) {
|
|
runSetClusterParameter(conn.getPrimary(), kAllClusterParameterUpdates[i]);
|
|
runGetClusterParameterReplicaSet(
|
|
conn, kAllClusterParameterNames[i], [kAllClusterParameterUpdates[i]]);
|
|
}
|
|
|
|
// Finally, run getClusterParameter in list format and '*' and ensure that they now all
|
|
// return updated values.
|
|
runGetClusterParameterReplicaSet(
|
|
conn, kAllClusterParameterNames, kAllClusterParameterUpdates);
|
|
runGetClusterParameterReplicaSet(conn, '*', kAllClusterParameterUpdates);
|
|
} else {
|
|
// Run getClusterParameter in list format and '*' and ensure it returns all default values
|
|
// on all nodes in the sharded cluster.
|
|
runGetClusterParameterSharded(
|
|
conn, kAllClusterParameterNames, kAllClusterParameterDefaults);
|
|
runGetClusterParameterSharded(conn, '*', kAllClusterParameterDefaults);
|
|
|
|
// For each parameter, simulate setClusterParameter and verify that getClusterParameter
|
|
// returns the updated value on all nodes in the sharded cluster.
|
|
for (let i = 0; i < kAllClusterParameterNames.length; i++) {
|
|
runSetClusterParameter(conn.s0, kAllClusterParameterInserts[i]);
|
|
runGetClusterParameterSharded(
|
|
conn, kAllClusterParameterNames[i], [kAllClusterParameterInserts[i]]);
|
|
}
|
|
|
|
// Do the above again to verify that document updates are also handled properly.
|
|
for (let i = 0; i < kAllClusterParameterNames.length; i++) {
|
|
runSetClusterParameter(conn.s0, kAllClusterParameterUpdates[i]);
|
|
runGetClusterParameterSharded(
|
|
conn, kAllClusterParameterNames[i], [kAllClusterParameterUpdates[i]]);
|
|
}
|
|
|
|
// Finally, run getClusterParameter in list format and '*' and ensure that they now all
|
|
// return updated values.
|
|
runGetClusterParameterSharded(conn, kAllClusterParameterNames, kAllClusterParameterUpdates);
|
|
runGetClusterParameterSharded(conn, '*', kAllClusterParameterUpdates);
|
|
}
|
|
}
|
|
|
|
// Assert that explicitly getting a disabled cluster server parameter fails on a node.
|
|
function testExplicitDisabledGetClusterParameter(conn) {
|
|
const adminDB = conn.getDB('admin');
|
|
assert.commandFailedWithCode(
|
|
adminDB.runCommand({getClusterParameter: "testIntClusterParameter"}), ErrorCodes.BadValue);
|
|
assert.commandFailedWithCode(
|
|
adminDB.runCommand(
|
|
{getClusterParameter: ["changeStreamOptions", "testIntClusterParameter"]}),
|
|
ErrorCodes.BadValue);
|
|
}
|
|
|
|
// Tests that disabled cluster server parameters return errors or are filtered out as appropriate
|
|
// by get/setClusterParameter.
|
|
function testDisabledClusterParameters(conn) {
|
|
if (conn instanceof ReplSetTest) {
|
|
// Assert that explicitly setting a disabled cluster server parameter fails.
|
|
const adminDB = conn.getPrimary().getDB('admin');
|
|
assert.commandFailedWithCode(
|
|
adminDB.runCommand({setClusterParameter: {testIntClusterParameter: {intData: 5}}}),
|
|
ErrorCodes.BadValue);
|
|
|
|
// Assert that explicitly getting a disabled cluster server parameter fails on the primary.
|
|
testExplicitDisabledGetClusterParameter(conn.getPrimary());
|
|
|
|
// Assert that explicitly getting a disabled cluster server parameter fails on secondaries.
|
|
conn.getSecondaries().forEach(function(secondary) {
|
|
testExplicitDisabledGetClusterParameter(secondary);
|
|
});
|
|
|
|
// Assert that getClusterParameter: '*' succeeds but only returns enabled cluster
|
|
// parameters.
|
|
runGetClusterParameterReplicaSet(conn, '*', kNonTestOnlyClusterParameterDefaults);
|
|
} else {
|
|
// Assert that explicitly setting a disabled cluster server parameter fails.
|
|
const adminDB = conn.s0.getDB('admin');
|
|
assert.commandFailedWithCode(
|
|
adminDB.runCommand({setClusterParameter: {testIntClusterParameter: {intData: 5}}}),
|
|
ErrorCodes.IllegalOperation);
|
|
|
|
// Assert that explicitly getting a disabled cluster server parameter fails on mongos.
|
|
testExplicitDisabledGetClusterParameter(conn.s0);
|
|
|
|
// Assert that explicitly getting a disabled cluster server parameter on each shard replica
|
|
// set and the config replica set fails.
|
|
const shards = [conn.rs0, conn.rs1, conn.rs2];
|
|
const configRS = conn.configRS;
|
|
shards.forEach(function(shard) {
|
|
testExplicitDisabledGetClusterParameter(shard.getPrimary());
|
|
shard.getSecondaries().forEach(function(secondary) {
|
|
testExplicitDisabledGetClusterParameter(secondary);
|
|
});
|
|
});
|
|
|
|
testExplicitDisabledGetClusterParameter(configRS.getPrimary());
|
|
configRS.getSecondaries().forEach(function(secondary) {
|
|
testExplicitDisabledGetClusterParameter(secondary);
|
|
});
|
|
|
|
// Assert that getClusterParameter: '*' succeeds but only returns enabled cluster
|
|
// parameters.
|
|
runGetClusterParameterSharded(conn, '*', kNonTestOnlyClusterParameterDefaults);
|
|
}
|
|
}
|
|
|
|
// Tests that invalid uses of getClusterParameter fails on a given node.
|
|
function testInvalidGetClusterParameter(conn) {
|
|
const adminDB = conn.getDB('admin');
|
|
// Assert that specifying a nonexistent parameter returns an error.
|
|
assert.commandFailedWithCode(adminDB.runCommand({getClusterParameter: "nonexistentParam"}),
|
|
ErrorCodes.NoSuchKey);
|
|
assert.commandFailedWithCode(adminDB.runCommand({getClusterParameter: ["nonexistentParam"]}),
|
|
ErrorCodes.NoSuchKey);
|
|
assert.commandFailedWithCode(
|
|
adminDB.runCommand({getClusterParameter: ["testIntClusterParameter", "nonexistentParam"]}),
|
|
ErrorCodes.NoSuchKey);
|
|
assert.commandFailedWithCode(adminDB.runCommand({getClusterParameter: []}),
|
|
ErrorCodes.BadValue);
|
|
}
|
|
|
|
// Tests that invalid uses of set/getClusterParameter fail with the appropriate errors.
|
|
function testInvalidClusterParameterCommands(conn) {
|
|
if (conn instanceof ReplSetTest) {
|
|
const adminDB = conn.getPrimary().getDB('admin');
|
|
|
|
// Assert that invalid uses of getClusterParameter fail on the primary.
|
|
testInvalidGetClusterParameter(conn.getPrimary());
|
|
|
|
// Assert that setting a nonexistent parameter on the primary returns an error.
|
|
assert.commandFailed(
|
|
adminDB.runCommand({setClusterParameter: {nonexistentParam: {intData: 5}}}));
|
|
|
|
// Assert that running setClusterParameter with a scalar value fails.
|
|
assert.commandFailed(
|
|
adminDB.runCommand({setClusterParameter: {testIntClusterParameter: 5}}));
|
|
|
|
conn.getSecondaries().forEach(function(secondary) {
|
|
// Assert that setClusterParameter cannot be run on a secondary.
|
|
const secondaryAdminDB = secondary.getDB('admin');
|
|
assert.commandFailedWithCode(
|
|
secondaryAdminDB.runCommand(
|
|
{setClusterParameter: {testIntClusterParameter: {intData: 5}}}),
|
|
ErrorCodes.NotWritablePrimary);
|
|
// Assert that invalid uses of getClusterParameter fail on secondaries.
|
|
testInvalidGetClusterParameter(secondary);
|
|
});
|
|
} else {
|
|
const adminDB = conn.s0.getDB('admin');
|
|
|
|
// Assert that invalid uses of getClusterParameter fail on mongos.
|
|
testInvalidGetClusterParameter(conn.s0);
|
|
|
|
// Assert that setting a nonexistent parameter on the mongos returns an error.
|
|
assert.commandFailed(
|
|
adminDB.runCommand({setClusterParameter: {nonexistentParam: {intData: 5}}}));
|
|
|
|
// Assert that running setClusterParameter with a scalar value fails.
|
|
assert.commandFailed(
|
|
adminDB.runCommand({setClusterParameter: {testIntClusterParameter: 5}}));
|
|
|
|
const shards = [conn.rs0, conn.rs1, conn.rs2];
|
|
shards.forEach(function(shard) {
|
|
// Assert that setClusterParameter cannot be run directly on a shard primary.
|
|
const shardPrimaryAdmin = shard.getPrimary().getDB('admin');
|
|
assert.commandFailedWithCode(
|
|
shardPrimaryAdmin.runCommand(
|
|
{setClusterParameter: {testIntClusterParameter: {intData: 5}}}),
|
|
ErrorCodes.NotImplemented);
|
|
// Assert that invalid forms of getClusterParameter fail on the shard primary.
|
|
testInvalidGetClusterParameter(shard.getPrimary());
|
|
shard.getSecondaries().forEach(function(secondary) {
|
|
// Assert that setClusterParameter cannot be run on a shard secondary.
|
|
const shardSecondaryAdmin = secondary.getDB('admin');
|
|
assert.commandFailedWithCode(
|
|
shardSecondaryAdmin.runCommand(
|
|
{setClusterParameter: {testIntClusterParameter: {intData: 5}}}),
|
|
ErrorCodes.NotWritablePrimary);
|
|
// Assert that invalid forms of getClusterParameter fail on shard secondaries.
|
|
testInvalidGetClusterParameter(secondary);
|
|
});
|
|
});
|
|
|
|
// Assert that setClusterParameter cannot be run directly on the configsvr primary.
|
|
const configRS = conn.configRS;
|
|
const configPrimaryAdmin = configRS.getPrimary().getDB('admin');
|
|
assert.commandFailedWithCode(
|
|
configPrimaryAdmin.runCommand(
|
|
{setClusterParameter: {testIntClusterParameter: {intData: 5}}}),
|
|
ErrorCodes.NotImplemented);
|
|
// Assert that invalid forms of getClusterParameter fail on the configsvr primary.
|
|
testInvalidGetClusterParameter(configRS.getPrimary());
|
|
configRS.getSecondaries().forEach(function(secondary) {
|
|
// Assert that setClusterParameter cannot be run on a configsvr secondary.
|
|
const configSecondaryAdmin = secondary.getDB('admin');
|
|
assert.commandFailedWithCode(
|
|
configSecondaryAdmin.runCommand(
|
|
{setClusterParameter: {testIntClusterParameter: {intData: 5}}}),
|
|
ErrorCodes.NotWritablePrimary);
|
|
// Assert that invalid forms of getClusterParameter fail on configsvr secondaries.
|
|
testInvalidGetClusterParameter(secondary);
|
|
});
|
|
}
|
|
}
|