/** * Util functions used by cluster server parameter tests. * * When adding new cluster server parameter, do the following: * 1. If it's test-only, add its name to the end of testOnlyClusterParameterNames. Otherwise, add it * it to the end of nonTestClusterParameterNames. * 2. Add the clusterParameter document that's expected as default to the end of * testOnlyClusterParametersDefault if it's test-only. Otherwise, add it to the end of * nonTestOnlyClusterParametersDefault. * 3. Add the clusterParameter document that setClusterParameter is expected to insert after its * first invocation to the end of testOnlyClusterParametersInsert if it's test-only. Otherwise, * add it to the end of nonTestOnlyClusterParametersInsert. * 4. Add the clusterParameter document that setClusterParameter is expected to update to after its * second invocation to the end of testOnlyClusterParametersUpdate if it's test-only. Otherwise, * add it to the end of nonTestOnlyClusterParametersUpdate. * */ const testOnlyClusterParameterNames = [ "testStrClusterParameter", "testIntClusterParameter", "testBoolClusterParameter", ]; const nonTestClusterParameterNames = [ "changeStreamOptions", ]; const clusterParameterNames = testOnlyClusterParameterNames.concat(nonTestClusterParameterNames); const testOnlyClusterParametersDefault = [ { _id: "testStrClusterParameter", strData: "off", }, { _id: "testIntClusterParameter", intData: 16, }, { _id: "testBoolClusterParameter", boolData: false, }, ]; const nonTestClusterParametersDefault = [{ _id: "changeStreamOptions", preAndPostImages: { expireAfterSeconds: "off", }, }]; const clusterParametersDefault = testOnlyClusterParametersDefault.concat(nonTestClusterParametersDefault); const testOnlyClusterParametersInsert = [ { _id: "testStrClusterParameter", strData: "on", }, { _id: "testIntClusterParameter", intData: 17, }, { _id: "testBoolClusterParameter", boolData: true, }, ]; const nonTestClusterParametersInsert = [{ _id: "changeStreamOptions", preAndPostImages: { expireAfterSeconds: 30, }, }]; const clusterParametersInsert = testOnlyClusterParametersInsert.concat(nonTestClusterParametersInsert); const testOnlyClusterParametersUpdate = [ { _id: "testStrClusterParameter", strData: "sleep", }, { _id: "testIntClusterParameter", intData: 18, }, { _id: "testBoolClusterParameter", boolData: false, }, ]; const nonTestClusterParametersUpdate = [{ _id: "changeStreamOptions", preAndPostImages: { expireAfterSeconds: "off", }, }]; const clusterParametersUpdate = testOnlyClusterParametersUpdate.concat(nonTestClusterParametersUpdate); // 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; 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'); const actualClusterParameters = assert.commandWorked(adminDB.runCommand({getClusterParameter: getClusterParameterArgs})) .clusterParameters; // Sort the returned clusterParameters and the expected clusterParameters by _id. actualClusterParameters.sort((a, b) => a._id.localeCompare(b._id)); expectedClusterParameters.sort((a, b) => a._id.localeCompare(b._id)); for (let i = 0; i < expectedClusterParameters.length; i++) { const expectedClusterParameter = expectedClusterParameters[i]; const actualClusterParameter = actualClusterParameters[i]; // Sort both expectedClusterParameter and actualClusterParameter into alphabetical order // by key. const sortedExpectedClusterParameter = Object.keys(expectedClusterParameter).sort().reduce(function(sorted, key) { sorted[key] = expectedClusterParameter[key]; return sorted; }, {}); const sortedActualClusterParameter = Object.keys(actualClusterParameter).sort().reduce(function(sorted, key) { if (key !== 'clusterParameterTime') { sorted[key] = actualClusterParameter[key]; } return sorted; }, {}); if (bsonWoCompare(sortedExpectedClusterParameter, sortedActualClusterParameter) !== 0) { print('expected: ' + tojson(sortedExpectedClusterParameter) + '\nactual: ' + tojson(sortedActualClusterParameter)); 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, clusterParameterNames, clusterParametersDefault); runGetClusterParameterReplicaSet(conn, '*', clusterParametersDefault); // 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 < clusterParameterNames.length; i++) { runSetClusterParameter(conn.getPrimary(), clusterParametersInsert[i]); runGetClusterParameterReplicaSet( conn, clusterParameterNames[i], [clusterParametersInsert[i]]); } // Do the above again to verify that document updates are also handled properly. for (let i = 0; i < clusterParameterNames.length; i++) { runSetClusterParameter(conn.getPrimary(), clusterParametersUpdate[i]); runGetClusterParameterReplicaSet( conn, clusterParameterNames[i], [clusterParametersUpdate[i]]); } // Finally, run getClusterParameter in list format and '*' and ensure that they now all // return updated values. runGetClusterParameterReplicaSet(conn, clusterParameterNames, clusterParametersUpdate); runGetClusterParameterReplicaSet(conn, '*', clusterParametersUpdate); } else { // Run getClusterParameter in list format and '*' and ensure it returns all default values // on all nodes in the sharded cluster. runGetClusterParameterSharded(conn, clusterParameterNames, clusterParametersDefault); runGetClusterParameterSharded(conn, '*', clusterParametersDefault); // 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 < clusterParameterNames.length; i++) { runSetClusterParameter(conn.s0, clusterParametersInsert[i]); runGetClusterParameterSharded( conn, clusterParameterNames[i], [clusterParametersInsert[i]]); } // Do the above again to verify that document updates are also handled properly. for (let i = 0; i < clusterParameterNames.length; i++) { runSetClusterParameter(conn.s0, clusterParametersUpdate[i]); runGetClusterParameterSharded( conn, clusterParameterNames[i], [clusterParametersUpdate[i]]); } // Finally, run getClusterParameter in list format and '*' and ensure that they now all // return updated values. runGetClusterParameterSharded(conn, clusterParameterNames, clusterParametersUpdate); runGetClusterParameterSharded(conn, '*', clusterParametersUpdate); } } // 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, '*', nonTestClusterParametersDefault); } 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.BadValue); // 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, '*', nonTestClusterParametersDefault); } } // 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.commandFailedWithCode( adminDB.runCommand({setClusterParameter: {nonexistentParam: {intData: 5}}}), ErrorCodes.IllegalOperation); // Assert that running setClusterParameter with a scalar value fails. assert.commandFailedWithCode( adminDB.runCommand({setClusterParameter: {testIntClusterParameter: 5}}), ErrorCodes.IllegalOperation); 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.commandFailedWithCode( adminDB.runCommand({setClusterParameter: {nonexistentParam: {intData: 5}}}), ErrorCodes.IllegalOperation); // Assert that running setClusterParameter with a scalar value fails. assert.commandFailedWithCode( adminDB.runCommand({setClusterParameter: {testIntClusterParameter: 5}}), ErrorCodes.IllegalOperation); 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); }); } }