diff options
author | Varun Ravichandran <varun.ravichandran@mongodb.com> | 2022-03-17 23:50:53 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-04-01 19:24:56 +0000 |
commit | fe488bc9f33d81fbc77f179b4e77effc4f0844fc (patch) | |
tree | 8c81733111eb1d6266be62a734238462717255e8 | |
parent | 9fe5c85db8a6aa1432807d8c473360a81a9a2eaf (diff) | |
download | mongo-fe488bc9f33d81fbc77f179b4e77effc4f0844fc.tar.gz |
SERVER-62261: Implement getClusterParameter command on mongod and mongos
29 files changed, 774 insertions, 18 deletions
diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js index d42234eef7f..dbc73db0e47 100644 --- a/jstests/auth/lib/commands_lib.js +++ b/jstests/auth/lib/commands_lib.js @@ -4176,6 +4176,18 @@ var authCommandsLib = { ] }, { + testname: "getClusterParameter", + command: {getClusterParameter: "testIntClusterParameter"}, + skipTest: (conn) => !TestData.setParameters.featureFlagClusterWideConfig, + testcases: [ + { + runOnDb: adminDbName, + roles: {clusterManager: 1, clusterAdmin: 1, root: 1, __system: 1}, + privileges: [{resource: {cluster: true}, actions: ["getClusterParameter"]}] + } + ] + }, + { testname: "getCmdLineOpts", command: {getCmdLineOpts: 1}, testcases: [ @@ -5652,10 +5664,14 @@ var authCommandsLib = { } ] }, - { // TODO: Temporarily disabled until an appropriate parameter is configured for testing (SERVER-62261) + { testname: "setClusterParameter", - command: {setClusterParameter: {param: true}}, - skipTest: (conn) => true || !TestData.setParameters.featureFlagClusterWideConfig, + command: {setClusterParameter: {testIntClusterParameterParam: {intData: 17}}}, + skipTest: (conn) => { + const hello = assert.commandWorked(conn.getDB("admin").runCommand({hello: 1})); + const isStandalone = hello.msg !== "isdbgrid" && !hello.hasOwnProperty('setName'); + return !TestData.setParameters.featureFlagClusterWideConfig || isStandalone; + }, testcases: [ { runOnDb: adminDbName, diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js index d18e14b19e3..ec722dffa4d 100644 --- a/jstests/core/views/views_all_commands.js +++ b/jstests/core/views/views_all_commands.js @@ -359,6 +359,7 @@ let viewsCommandTests = { getAuditConfig: {skip: isUnrelated}, getDatabaseVersion: {skip: isUnrelated}, getChangeStreamOptions: {skip: isUnrelated}, + getClusterParameter: {skip: isUnrelated}, getCmdLineOpts: {skip: isUnrelated}, getDefaultRWConcern: {skip: isUnrelated}, getDiagnosticData: {skip: isUnrelated}, diff --git a/jstests/libs/cluster_server_parameter_utils.js b/jstests/libs/cluster_server_parameter_utils.js new file mode 100644 index 00000000000..8800ee45af7 --- /dev/null +++ b/jstests/libs/cluster_server_parameter_utils.js @@ -0,0 +1,249 @@ +/** + * Util functions used by cluster server parameter tests. + * + * When adding new cluster server parameters, do the following: + * 1. Add its name to clusterParameterNames. + * 2. Add the clusterParameter document that's expected as default to clusterParametersDefault. + * 3. Add the clusterParameter document that setClusterParameter is expected to insert after its + * first invocation to clusterParametersInsert. + * 4. Add the clusterParameter document that setClusterParameter is expected to update to after its + * second invocation to clusterParametersUpdate. + * + */ + +const clusterParameterNames = ["testStrClusterParameter", "testIntClusterParameter"]; +const clusterParametersDefault = [ + { + _id: "testStrClusterParameter", + clusterParameterTime: Timestamp(0, 0), + strData: "off", + }, + { + _id: "testIntClusterParameter", + clusterParameterTime: Timestamp(0, 0), + intData: 16, + } +]; + +const clusterParametersInsert = [ + { + _id: "testStrClusterParameter", + clusterParameterTime: Timestamp(10, 2), + strData: "on", + }, + { + _id: "testIntClusterParameter", + clusterParameterTime: Timestamp(10, 2), + intData: 17, + } +]; + +const clusterParametersUpdate = [ + { + _id: "testStrClusterParameter", + clusterParameterTime: Timestamp(20, 4), + strData: "sleep", + }, + { + _id: "testIntClusterParameter", + clusterParameterTime: Timestamp(20, 4), + intData: 18, + } +]; + +// 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); + }); +} + +// TO-DO SERVER-65128: replace this function with a call to setClusterParameter. +// Upserts config.clusterParameters document with w:majority. +function simulateSetClusterParameterReplicaSet(rst, query, update) { + const clusterParametersNS = rst.getPrimary().getDB('config').clusterParameters; + assert.commandWorked( + clusterParametersNS.update(query, update, {upsert: true, writeConcern: {w: "majority"}})); +} + +// TO-DO SERVER-65128: replace this function with a call to setClusterParameter. +// Upserts config.clusterParameters document with w:majority into configsvr and all shards. +function simulateSetClusterParameterSharded(st, query, update) { + simulateSetClusterParameterReplicaSet(st.configRS, query, update); + + const shards = [st.rs0, st.rs1, st.rs2]; + shards.forEach(function(shard) { + simulateSetClusterParameterReplicaSet(shard, query, update); + }); +} + +// Runs getClusterParameter on a specific mongod or mongos node and returns true/false depending +// on whether . +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) { + sorted[key] = actualClusterParameter[key]; + return sorted; + }, {}); + if (bsonWoCompare(sortedExpectedClusterParameter, sortedActualClusterParameter) !== 0) { + 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 and each mongod in each shard replica set. +function runGetClusterParameterSharded(st, getClusterParameterArgs, expectedClusterParameters) { + runGetClusterParameterNode(st.s0, 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 testValidParameters(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, simulate setClusterParameter and verify that getClusterParameter + // returns the updated value on all nodes in the replica set. + for (let i = 0; i < clusterParameterNames.length; i++) { + query = {_id: clusterParameterNames[i]}; + simulateSetClusterParameterReplicaSet(conn, query, 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++) { + query = {_id: clusterParameterNames[i]}; + simulateSetClusterParameterReplicaSet(conn, query, 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++) { + query = {_id: clusterParameterNames[i]}; + simulateSetClusterParameterSharded(conn, query, 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++) { + query = {_id: clusterParameterNames[i]}; + simulateSetClusterParameterSharded(conn, query, 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); + } +} + +// Tests that invalid uses of getClusterParameter fails +function testInvalidParametersNode(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); +} + +// Tests that invalid uses of getClusterParameter fail with the appropriate errors. +function testInvalidParameters(conn) { + if (conn instanceof ReplSetTest) { + testInvalidParametersNode(conn.getPrimary()); + conn.getSecondaries().forEach(function(secondary) { + testInvalidParametersNode(secondary); + }); + } else { + testInvalidParametersNode(conn.s0); + const shards = [conn.rs0, conn.rs1, conn.rs2]; + shards.forEach(function(shard) { + shard.getSecondaries().forEach(function(secondary) { + testInvalidParametersNode(secondary); + }); + }); + } +} diff --git a/jstests/replsets/cluster_server_parameter_commands_replset.js b/jstests/replsets/cluster_server_parameter_commands_replset.js new file mode 100644 index 00000000000..5e296108349 --- /dev/null +++ b/jstests/replsets/cluster_server_parameter_commands_replset.js @@ -0,0 +1,39 @@ +/** + * Checks that getClusterParameter runs as expected on replica set nodes. + * + * @tags: [ + * # Requires all nodes to be running the latest binary. + * requires_fcv_60, + * featureFlagClusterWideConfig, + * does_not_support_stepdowns + * ] + */ +(function() { +'use strict'; + +load('jstests/libs/cluster_server_parameter_utils.js'); + +// Tests that getClusterParameter works on a non-sharded replica set. +function runReplSetTest() { + const rst = new ReplSetTest({ + nodes: 3, + }); + rst.startSet(); + rst.initiate(); + + // Setup the necessary logging level for the test. + setupReplicaSet(rst); + + // First, ensure that nonexistent parameters and unauthorized users are rejected with the + // appropriate error codes. + testInvalidParameters(rst); + + // Then, ensure that getClusterParameter returns the expected values for all valid invocations + // of getClusterParameter. + testValidParameters(rst); + + rst.stopSet(); +} + +runReplSetTest(); +})(); diff --git a/jstests/replsets/db_reads_while_recovering_all_commands.js b/jstests/replsets/db_reads_while_recovering_all_commands.js index 279d167edba..1422d902e2d 100644 --- a/jstests/replsets/db_reads_while_recovering_all_commands.js +++ b/jstests/replsets/db_reads_while_recovering_all_commands.js @@ -214,6 +214,7 @@ const allCommands = { fsyncUnlock: {skip: isNotAUserDataRead}, getAuditConfig: {skip: isNotAUserDataRead}, getChangeStreamOptions: {skip: isNotAUserDataRead}, + getClusterParameter: {skip: isNotAUserDataRead}, getCmdLineOpts: {skip: isNotAUserDataRead}, getDatabaseVersion: {skip: isNotAUserDataRead}, getDefaultRWConcern: {skip: isNotAUserDataRead}, diff --git a/jstests/sharding/cluster_server_parameter_commands_sharded.js b/jstests/sharding/cluster_server_parameter_commands_sharded.js new file mode 100644 index 00000000000..9fecae646eb --- /dev/null +++ b/jstests/sharding/cluster_server_parameter_commands_sharded.js @@ -0,0 +1,43 @@ +/** + * Checks that getClusterParameter runs as expected on sharded clusters. + * + * @tags: [ + * # Requires all nodes to be running the latest binary. + * requires_fcv_60, + * featureFlagClusterWideConfig, + * does_not_support_stepdowns + * ] + */ +(function() { +'use strict'; + +load('jstests/libs/cluster_server_parameter_utils.js'); + +// Tests that getClusterParameter works on all nodes of a sharded cluster. +function runShardedTest() { + const options = { + mongos: 1, + config: 1, + shards: 3, + rs: { + nodes: 3, + }, + }; + const st = new ShardingTest(options); + + // Setup the necessary logging on mongos and the shards. + setupSharded(st); + + // First, ensure that nonexistent parameters are rejected with the + // appropriate error codes on mongos and all shards. + testInvalidParameters(st); + + // Then, ensure that getClusterParameter returns the expected values for all valid invocations + // of getClusterParameter. + testValidParameters(st); + + st.stop(); +} + +runShardedTest(); +})(); diff --git a/jstests/sharding/database_versioning_all_commands.js b/jstests/sharding/database_versioning_all_commands.js index edd95248a8a..5127c7836ad 100644 --- a/jstests/sharding/database_versioning_all_commands.js +++ b/jstests/sharding/database_versioning_all_commands.js @@ -458,6 +458,7 @@ let testCases = { getAuditConfig: {skip: "not on a user database", conditional: true}, getChangeStreamOptions: {skip: "executes locally on mongos (not sent to any remote node)", conditional: true}, + getClusterParameter: {skip: "always targets the config server"}, getCmdLineOpts: {skip: "executes locally on mongos (not sent to any remote node)"}, getDefaultRWConcern: {skip: "executes locally on mongos (not sent to any remote node)"}, getDiagnosticData: {skip: "executes locally on mongos (not sent to any remote node)"}, diff --git a/jstests/sharding/libs/last_lts_mongod_commands.js b/jstests/sharding/libs/last_lts_mongod_commands.js index f289ba2720d..c25af3a8275 100644 --- a/jstests/sharding/libs/last_lts_mongod_commands.js +++ b/jstests/sharding/libs/last_lts_mongod_commands.js @@ -20,6 +20,7 @@ const commandsAddedToMongodSinceLastLTS = [ "clusterGetMore", "clusterInsert", "clusterUpdate", + "getClusterParameter", "rotateCertificates", "setClusterParameter", "setUserWriteBlockMode", diff --git a/jstests/sharding/libs/last_lts_mongos_commands.js b/jstests/sharding/libs/last_lts_mongos_commands.js index 90eb308fa4e..f7a72743e6b 100644 --- a/jstests/sharding/libs/last_lts_mongos_commands.js +++ b/jstests/sharding/libs/last_lts_mongos_commands.js @@ -18,6 +18,7 @@ const commandsAddedToMongosSinceLastLTS = [ "commitReshardCollection", "compactStructuredEncryptionData", "configureCollectionBalancing", + "getClusterParameter", "moveRange", "reshardCollection", "rotateCertificates", diff --git a/jstests/sharding/read_write_concern_defaults_application.js b/jstests/sharding/read_write_concern_defaults_application.js index b7804ac5e58..1330131c764 100644 --- a/jstests/sharding/read_write_concern_defaults_application.js +++ b/jstests/sharding/read_write_concern_defaults_application.js @@ -476,6 +476,7 @@ let testCases = { fsyncUnlock: {skip: "does not accept read or write concern"}, getAuditConfig: {skip: "does not accept read or write concern"}, getChangeStreamOptions: {skip: "TODO PM-2502"}, + getClusterParameter: {skip: "does not accept read or write concern"}, getCmdLineOpts: {skip: "does not accept read or write concern"}, getDatabaseVersion: {skip: "does not accept read or write concern"}, getDefaultRWConcern: {skip: "does not accept read or write concern"}, diff --git a/jstests/sharding/safe_secondary_reads_drop_recreate.js b/jstests/sharding/safe_secondary_reads_drop_recreate.js index e7fd5f85963..21e887dc4bc 100644 --- a/jstests/sharding/safe_secondary_reads_drop_recreate.js +++ b/jstests/sharding/safe_secondary_reads_drop_recreate.js @@ -214,6 +214,7 @@ let testCases = { fsyncUnlock: {skip: "does not return user data"}, getAuditConfig: {skip: "does not return user data"}, getChangeStreamOptions: {skip: "does not return user data"}, + getClusterParameter: {skip: "does not return user data"}, getCmdLineOpts: {skip: "does not return user data"}, getDefaultRWConcern: {skip: "does not return user data"}, getDiagnosticData: {skip: "does not return user data"}, diff --git a/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js b/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js index da5b4cb9d0d..b7a35ef9d49 100644 --- a/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js +++ b/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js @@ -247,6 +247,7 @@ let testCases = { fsyncUnlock: {skip: "does not return user data"}, getAuditConfig: {skip: "does not return user data"}, getChangeStreamOptions: {skip: "does not return user data"}, + getClusterParameter: {skip: "does not return user data"}, getCmdLineOpts: {skip: "does not return user data"}, getDefaultRWConcern: {skip: "does not return user data"}, getDiagnosticData: {skip: "does not return user data"}, diff --git a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js index 24991db2b9e..4fef3a7cbb8 100644 --- a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js +++ b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js @@ -219,6 +219,7 @@ let testCases = { fsyncUnlock: {skip: "does not return user data"}, getAuditConfig: {skip: "does not return user data"}, getChangeStreamOptions: {skip: "does not return user data"}, + getClusterParameter: {skip: "does not return user data"}, getCmdLineOpts: {skip: "does not return user data"}, getDefaultRWConcern: {skip: "does not return user data"}, getDiagnosticData: {skip: "does not return user data"}, diff --git a/src/mongo/db/auth/action_type.idl b/src/mongo/db/auth/action_type.idl index dc11b26fb4a..6cfbd32d0a9 100644 --- a/src/mongo/db/auth/action_type.idl +++ b/src/mongo/db/auth/action_type.idl @@ -97,6 +97,7 @@ enums: forceUUID : "forceUUID" fsync : "fsync" getChangeStreamOptions: "getChangeStreamOptions" + getClusterParameter: "getClusterParameter" getDatabaseVersion : "getDatabaseVersion" getDefaultRWConcern : "getDefaultRWConcern" getCmdLineOpts : "getCmdLineOpts" diff --git a/src/mongo/db/auth/builtin_roles.cpp b/src/mongo/db/auth/builtin_roles.cpp index cbad7142669..70331b72391 100644 --- a/src/mongo/db/auth/builtin_roles.cpp +++ b/src/mongo/db/auth/builtin_roles.cpp @@ -255,7 +255,8 @@ MONGO_INITIALIZER(AuthorizationBuiltinRoles)(InitializerContext* context) { << ActionType::setFreeMonitoring << ActionType::setChangeStreamOptions << ActionType::getChangeStreamOptions - << ActionType::setClusterParameter; + << ActionType::setClusterParameter + << ActionType::getClusterParameter; clusterManagerRoleDatabaseActions << ActionType::clearJumboFlag diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index d28bd5d286b..cd2f7b33a87 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -322,9 +322,9 @@ env.Library( ) env.Library( - target='set_cluster_parameter_idl', + target='cluster_server_parameter_cmds_idl', source=[ - 'set_cluster_parameter.idl', + 'cluster_server_parameter_cmds.idl', ], LIBDEPS=[ '$BUILD_DIR/mongo/base', @@ -535,6 +535,7 @@ env.Library( "dbcommands_d.cpp", "dbhash.cpp", "driverHelpers.cpp", + 'get_cluster_parameter_command.cpp', "internal_rename_if_options_and_indexes_match_cmd.cpp", "fle2_compact_cmd.cpp", "map_reduce_command.cpp", @@ -638,7 +639,7 @@ env.Library( LIBDEPS=[ '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/s/write_ops/cluster_write_ops', - 'set_cluster_parameter_idl' + 'cluster_server_parameter_cmds_idl' ], ) @@ -849,4 +850,4 @@ env.CppUnitTest( "set_cluster_parameter_invocation", "standalone", ], -)
\ No newline at end of file +) diff --git a/src/mongo/db/commands/set_cluster_parameter.idl b/src/mongo/db/commands/cluster_server_parameter_cmds.idl index b20af08dc8e..9c768f43f23 100644 --- a/src/mongo/db/commands/set_cluster_parameter.idl +++ b/src/mongo/db/commands/cluster_server_parameter_cmds.idl @@ -27,17 +27,34 @@ # global: - cpp_namespace: "mongo" + cpp_namespace: "mongo" imports: - "mongo/idl/basic_types.idl" +structs: + GetClusterParameterReply: + description: "Reply to getClusterParameter command" + fields: + clusterParameters: array<object> + commands: setClusterParameter: - description: Command to set a ClusterServerParameter + description: "Command to set a ClusterServerParameter" command_name: setClusterParameter cpp_name: SetClusterParameter api_version: "" namespace: type type: object strict: true + + getClusterParameter: + description: "Retrieves the in-memory value of the specified cluster server parameter(s)" + command_name: getClusterParameter + cpp_name: GetClusterParameter + api_version: "" + namespace: type + type: + variant: [string, array<string>] + reply_type: GetClusterParameterReply + strict: false diff --git a/src/mongo/db/commands/get_cluster_parameter_command.cpp b/src/mongo/db/commands/get_cluster_parameter_command.cpp new file mode 100644 index 00000000000..76f6f91c590 --- /dev/null +++ b/src/mongo/db/commands/get_cluster_parameter_command.cpp @@ -0,0 +1,142 @@ +/** + * Copyright (C) 2022-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand + +#include "mongo/platform/basic.h" + +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/commands.h" +#include "mongo/db/commands/cluster_server_parameter_cmds_gen.h" +#include "mongo/idl/cluster_server_parameter_gen.h" +#include "mongo/logv2/log.h" + +namespace mongo { + +namespace { + +class GetClusterParameterCommand final : public TypedCommand<GetClusterParameterCommand> { +public: + using Request = GetClusterParameter; + using Reply = GetClusterParameter::Reply; + using Map = ServerParameterSet::Map; + + AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { + return AllowedOnSecondary::kAlways; + } + + bool adminOnly() const override { + return true; + } + + std::string help() const override { + return "Get in-memory cluster parameter value(s) from this node"; + } + + class Invocation final : public InvocationBase { + public: + using InvocationBase::InvocationBase; + + Reply typedRun(OperationContext* opCtx) { + uassert( + ErrorCodes::IllegalOperation, + "featureFlagClusterWideConfig not enabled", + gFeatureFlagClusterWideConfig.isEnabled(serverGlobalParams.featureCompatibility)); + + const stdx::variant<std::string, std::vector<std::string>>& cmdBody = + request().getCommandParameter(); + ServerParameterSet* clusterParameters = ServerParameterSet::getClusterParameterSet(); + std::vector<BSONObj> parameterValues; + std::vector<std::string> parameterNames; + + // For each parameter, generate a BSON representation of it and retrieve its name. + auto makeBSON = [&](ServerParameter* requestedParameter) { + BSONObjBuilder bob; + bob.append("_id"_sd, requestedParameter->name()); + requestedParameter->append(opCtx, bob, requestedParameter->name()); + parameterValues.push_back(bob.obj()); + parameterNames.push_back(requestedParameter->name()); + }; + + stdx::visit( + visit_helper::Overloaded{ + [&](const std::string& strParameterName) { + if (strParameterName == "*"_sd) { + // Retrieve all cluster parameter values. + Map clusterParameterMap = clusterParameters->getMap(); + parameterValues.reserve(clusterParameterMap.size()); + parameterNames.reserve(clusterParameterMap.size()); + for (const auto& param : clusterParameterMap) { + makeBSON(param.second); + } + } else { + // Any other string must correspond to a single parameter name. + ServerParameter* sp = clusterParameters->get(strParameterName); + makeBSON(sp); + } + }, + [&](const std::vector<std::string>& listParameterNames) { + parameterValues.reserve(listParameterNames.size()); + parameterNames.reserve(listParameterNames.size()); + for (const auto& requestedParameterName : listParameterNames) { + ServerParameter* sp = clusterParameters->get(requestedParameterName); + makeBSON(sp); + } + }}, + cmdBody); + + LOGV2_DEBUG(6226100, + 2, + "Retrieved parameter values for cluster server parameters", + "parameterNames"_attr = parameterNames); + + return Reply(parameterValues); + } + + private: + bool supportsWriteConcern() const override { + return false; + } + + void doCheckAuthorization(OperationContext* opCtx) const override { + auto* authzSession = AuthorizationSession::get(opCtx->getClient()); + uassert(ErrorCodes::Unauthorized, + "Not authorized to retrieve cluster server parameters", + authzSession->isAuthorizedForPrivilege(Privilege{ + ResourcePattern::forClusterResource(), ActionType::getClusterParameter})); + } + + NamespaceString ns() const override { + return NamespaceString(request().getDbName(), ""); + } + }; +} getClusterParameterCommand; + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/commands/set_cluster_parameter_command.cpp b/src/mongo/db/commands/set_cluster_parameter_command.cpp index b5285dbeae3..9d4074ac0bc 100644 --- a/src/mongo/db/commands/set_cluster_parameter_command.cpp +++ b/src/mongo/db/commands/set_cluster_parameter_command.cpp @@ -27,13 +27,16 @@ * it in the license file. */ +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kCommand + #include "mongo/platform/basic.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" -#include "mongo/db/commands/set_cluster_parameter_gen.h" +#include "mongo/db/commands/cluster_server_parameter_cmds_gen.h" #include "mongo/db/commands/set_cluster_parameter_invocation.h" #include "mongo/idl/cluster_server_parameter_gen.h" +#include "mongo/logv2/log.h" namespace mongo { @@ -94,5 +97,6 @@ public: } }; } setClusterParameterCommand; + } // namespace } // namespace mongo diff --git a/src/mongo/db/commands/set_cluster_parameter_invocation.cpp b/src/mongo/db/commands/set_cluster_parameter_invocation.cpp index 4cb1087996a..513ae376564 100644 --- a/src/mongo/db/commands/set_cluster_parameter_invocation.cpp +++ b/src/mongo/db/commands/set_cluster_parameter_invocation.cpp @@ -31,10 +31,10 @@ #include "mongo/platform/basic.h" +#include "mongo/db/commands/set_cluster_parameter_invocation.h" + #include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" -#include "mongo/db/commands/set_cluster_parameter_gen.h" -#include "mongo/db/commands/set_cluster_parameter_invocation.h" #include "mongo/db/vector_clock.h" #include "mongo/idl/cluster_server_parameter_gen.h" #include "mongo/logv2/log.h" diff --git a/src/mongo/db/commands/set_cluster_parameter_invocation.h b/src/mongo/db/commands/set_cluster_parameter_invocation.h index 6077ccac5d9..35b74530a95 100644 --- a/src/mongo/db/commands/set_cluster_parameter_invocation.h +++ b/src/mongo/db/commands/set_cluster_parameter_invocation.h @@ -27,7 +27,9 @@ * it in the license file. */ -#include "mongo/db/commands/set_cluster_parameter_gen.h" +#pragma once + +#include "mongo/db/commands/cluster_server_parameter_cmds_gen.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/dbhelpers.h" diff --git a/src/mongo/db/namespace_string.cpp b/src/mongo/db/namespace_string.cpp index 2eab32c841f..649423f106b 100644 --- a/src/mongo/db/namespace_string.cpp +++ b/src/mongo/db/namespace_string.cpp @@ -162,6 +162,9 @@ const NamespaceString NamespaceString::kUserWritesCriticalSectionsNamespace( const NamespaceString NamespaceString::kCompactStructuredEncryptionCoordinatorNamespace( NamespaceString::kConfigDb, "compact_structured_encryption_coordinator"); +const NamespaceString NamespaceString::kClusterParametersNamespace(NamespaceString::kConfigDb, + "clusterParameters"); + bool NamespaceString::isListCollectionsCursorNS() const { return coll() == listCollectionsCursorCol; } diff --git a/src/mongo/db/namespace_string.h b/src/mongo/db/namespace_string.h index 599f5aac11f..4d1dba82f83 100644 --- a/src/mongo/db/namespace_string.h +++ b/src/mongo/db/namespace_string.h @@ -216,6 +216,9 @@ public: // Namespace used for CompactParticipantCoordinator service. static const NamespaceString kCompactStructuredEncryptionCoordinatorNamespace; + // Namespace used for storing cluster wide parameters. + static const NamespaceString kClusterParametersNamespace; + /** * Constructs an empty NamespaceString. */ diff --git a/src/mongo/idl/SConscript b/src/mongo/idl/SConscript index 17520eafd06..a63af90f83a 100644 --- a/src/mongo/idl/SConscript +++ b/src/mongo/idl/SConscript @@ -61,7 +61,6 @@ env.Library( LIBDEPS_PRIVATE=[ 'feature_flag', 'idl_parser', - 'server_parameter', ], ) diff --git a/src/mongo/idl/cluster_server_parameter.idl b/src/mongo/idl/cluster_server_parameter.idl index 762f33006ee..dc1b53ee0e6 100644 --- a/src/mongo/idl/cluster_server_parameter.idl +++ b/src/mongo/idl/cluster_server_parameter.idl @@ -51,9 +51,47 @@ structs: In normal operation, the clusterParameterTime will increase as the CSP is updated via setClusterParameter, but may decrease to a previously-seen value on rollback. type: logicalTime + default: LogicalTime::kUninitialized + + TestIntClusterParameterStorage: + description: "Storage used for testIntClusterParameter" + inline_chained_structs: true + chained_structs: + ClusterServerParameter: ClusterServerParameter + fields: + intData: + description: "Some int parameter" + type: safeInt64 + default: 16 + + TestStrClusterParameterStorage: + description: "Storage used for testStrClusterParameter" + inline_chained_structs: true + chained_structs: + ClusterServerParameter: ClusterServerParameter + fields: + strData: + description: "Some string parameter" + type: string + default: "\"off\"" feature_flags: featureFlagClusterWideConfig: description: Mechanism for cluster-wide configuration options cpp_varname: gFeatureFlagClusterWideConfig default: false + +server_parameters: + testIntClusterParameter: + set_at: cluster + description: "Test cluster parameter that is only usable if enableTestCommands=true" + cpp_vartype: TestIntClusterParameterStorage + cpp_varname: intStorage + test_only: true + + testStrClusterParameter: + set_at: cluster + description: "Test cluster parameter that is only usable if enableTestCommands=true" + cpp_vartype: TestStrClusterParameterStorage + cpp_varname: strStorage + test_only: true diff --git a/src/mongo/s/SConscript b/src/mongo/s/SConscript index 187e802d613..bfc36af2a0f 100644 --- a/src/mongo/s/SConscript +++ b/src/mongo/s/SConscript @@ -234,7 +234,7 @@ env.Library( LIBDEPS=[ '$BUILD_DIR/mongo/client/connection_string', '$BUILD_DIR/mongo/db/coll_mod_command_idl', - '$BUILD_DIR/mongo/db/commands/set_cluster_parameter_idl', + '$BUILD_DIR/mongo/db/commands/cluster_server_parameter_cmds_idl', '$BUILD_DIR/mongo/db/commands/set_user_write_block_mode_idl', '$BUILD_DIR/mongo/db/common', '$BUILD_DIR/mongo/db/namespace_string', diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript index 385824d19f6..d80e09c82d3 100644 --- a/src/mongo/s/commands/SConscript +++ b/src/mongo/s/commands/SConscript @@ -60,6 +60,7 @@ env.Library( 'cluster_fle2_compact_cmd.cpp', 'cluster_fsync_cmd.cpp', 'cluster_ftdc_commands.cpp', + 'cluster_get_cluster_parameter_cmd.cpp', 'cluster_get_last_error_cmd.cpp', 'cluster_get_shard_version_cmd.cpp', 'cluster_getmore_cmd_s.cpp', @@ -106,6 +107,7 @@ env.Library( '$BUILD_DIR/mongo/db/api_parameters', '$BUILD_DIR/mongo/db/auth/auth_checks', '$BUILD_DIR/mongo/db/commands/change_stream_options', + '$BUILD_DIR/mongo/db/commands/cluster_server_parameter_cmds_idl', '$BUILD_DIR/mongo/db/commands/core', '$BUILD_DIR/mongo/db/commands/create_command', '$BUILD_DIR/mongo/db/commands/current_op_common', @@ -118,7 +120,6 @@ env.Library( '$BUILD_DIR/mongo/db/commands/rwc_defaults_commands', '$BUILD_DIR/mongo/db/commands/server_status', '$BUILD_DIR/mongo/db/commands/servers', - '$BUILD_DIR/mongo/db/commands/set_cluster_parameter_idl', '$BUILD_DIR/mongo/db/commands/set_feature_compatibility_version_idl', '$BUILD_DIR/mongo/db/commands/set_index_commit_quorum_idl', '$BUILD_DIR/mongo/db/commands/set_user_write_block_mode_idl', diff --git a/src/mongo/s/commands/cluster_get_cluster_parameter_cmd.cpp b/src/mongo/s/commands/cluster_get_cluster_parameter_cmd.cpp new file mode 100644 index 00000000000..7f5022ab989 --- /dev/null +++ b/src/mongo/s/commands/cluster_get_cluster_parameter_cmd.cpp @@ -0,0 +1,188 @@ +/** + * Copyright (C) 2022-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kSharding + +#include "mongo/platform/basic.h" + +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/commands.h" +#include "mongo/db/commands/cluster_server_parameter_cmds_gen.h" +#include "mongo/idl/cluster_server_parameter_gen.h" +#include "mongo/logv2/log.h" +#include "mongo/s/cluster_commands_helpers.h" +#include "mongo/s/grid.h" +#include "mongo/s/request_types/sharded_ddl_commands_gen.h" + +namespace mongo { +namespace { + +class GetClusterParameterCmd final : public TypedCommand<GetClusterParameterCmd> { +public: + using Request = GetClusterParameter; + using Reply = GetClusterParameter::Reply; + using Map = ServerParameterSet::Map; + + AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { + return AllowedOnSecondary::kAlways; + } + + bool adminOnly() const override { + return true; + } + + std::string help() const override { + return "Get majority-written cluster parameter value(s) from the config servers"; + } + + class Invocation final : public InvocationBase { + public: + using InvocationBase::InvocationBase; + + Reply typedRun(OperationContext* opCtx) { + uassert( + ErrorCodes::IllegalOperation, + "featureFlagClusterWideConfig not enabled", + gFeatureFlagClusterWideConfig.isEnabled(serverGlobalParams.featureCompatibility)); + + // For now, the mongos implementation retrieves the names of the requested cluster + // server parameters and queries them from the config.clusterParameters namespace on + // the config servers. This may change after SERVER-62264, when cluster server + // parameters will be cached on mongoses as well. + auto configServers = Grid::get(opCtx)->shardRegistry()->getConfigShard(); + + // Create the query document such that all documents in config.clusterParmeters with _id + // in the requested list of ServerParameters are returned. + const stdx::variant<std::string, std::vector<std::string>>& cmdBody = + request().getCommandParameter(); + ServerParameterSet* clusterParameters = ServerParameterSet::getClusterParameterSet(); + + std::vector<std::string> requestedParameterNames; + BSONObjBuilder queryDocBuilder; + BSONObjBuilder inObjBuilder = queryDocBuilder.subobjStart("_id"_sd); + BSONArrayBuilder parameterNameBuilder = inObjBuilder.subarrayStart("$in"_sd); + + stdx::visit( + visit_helper::Overloaded{ + [&](const std::string& strParameterName) { + if (strParameterName == "*"_sd) { + // Append all cluster parameter names. + Map clusterParameterMap = clusterParameters->getMap(); + requestedParameterNames.reserve(clusterParameterMap.size()); + for (const auto& param : clusterParameterMap) { + parameterNameBuilder.append(param.first); + requestedParameterNames.push_back(param.first); + } + } else { + parameterNameBuilder.append(strParameterName); + requestedParameterNames.push_back(strParameterName); + } + }, + [&](const std::vector<std::string>& listParameterNames) { + requestedParameterNames.reserve(listParameterNames.size()); + for (const auto& requestedParameterName : listParameterNames) { + parameterNameBuilder.append(requestedParameterName); + requestedParameterNames.push_back(requestedParameterName); + } + }}, + cmdBody); + + parameterNameBuilder.doneFast(); + inObjBuilder.doneFast(); + + // Perform the majority read on the config server primary. + BSONObj query = queryDocBuilder.obj(); + LOGV2(6226101, "Querying config servers for cluster parameters", "query"_attr = query); + auto findResponse = uassertStatusOK(configServers->exhaustiveFindOnConfig( + opCtx, + ReadPreferenceSetting{ReadPreference::PrimaryOnly}, + repl::ReadConcernLevel::kMajorityReadConcern, + NamespaceString::kClusterParametersNamespace, + query, + BSONObj(), + boost::none)); + + // Any parameters that are not included in the response don't have a cluster parameter + // document yet, which means they still are using the default value. + std::vector<BSONObj> retrievedParameters = std::move(findResponse.docs); + if (retrievedParameters.size() < requestedParameterNames.size()) { + std::vector<std::string> onDiskParameterNames; + onDiskParameterNames.reserve(retrievedParameters.size()); + std::transform(retrievedParameters.begin(), + retrievedParameters.end(), + std::back_inserter(onDiskParameterNames), + [&](const auto& onDiskParameter) { + return onDiskParameter["_id"_sd].String(); + }); + + // Sort and find the set difference of the requested parameters and the parameters + // returned. + std::vector<std::string> defaultParameterNames; + defaultParameterNames.reserve(requestedParameterNames.size() - + onDiskParameterNames.size()); + std::sort(onDiskParameterNames.begin(), onDiskParameterNames.end()); + std::set_difference(requestedParameterNames.begin(), + requestedParameterNames.end(), + onDiskParameterNames.begin(), + onDiskParameterNames.end(), + std::back_inserter(defaultParameterNames)); + + for (const auto& defaultParameterName : defaultParameterNames) { + auto defaultParameter = clusterParameters->get(defaultParameterName); + BSONObjBuilder bob; + bob.append("_id"_sd, defaultParameterName); + defaultParameter->append(opCtx, bob, defaultParameterName); + retrievedParameters.push_back(bob.obj()); + } + } + + return Reply(retrievedParameters); + } + + private: + bool supportsWriteConcern() const override { + return false; + } + + void doCheckAuthorization(OperationContext* opCtx) const final { + auto* authzSession = AuthorizationSession::get(opCtx->getClient()); + uassert(ErrorCodes::Unauthorized, + "Not authorized to retrieve cluster parameters", + authzSession->isAuthorizedForPrivilege(Privilege{ + ResourcePattern::forClusterResource(), ActionType::getClusterParameter})); + } + + NamespaceString ns() const override { + return NamespaceString(request().getDbName(), ""); + } + }; +} getClusterParameterCmd; + +} // namespace +} // namespace mongo diff --git a/src/mongo/s/commands/cluster_set_cluster_parameter_cmd.cpp b/src/mongo/s/commands/cluster_set_cluster_parameter_cmd.cpp index 6b5d4ce6e9e..32b304b903a 100644 --- a/src/mongo/s/commands/cluster_set_cluster_parameter_cmd.cpp +++ b/src/mongo/s/commands/cluster_set_cluster_parameter_cmd.cpp @@ -33,7 +33,7 @@ #include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" -#include "mongo/db/commands/set_cluster_parameter_gen.h" +#include "mongo/db/commands/cluster_server_parameter_cmds_gen.h" #include "mongo/s/cluster_commands_helpers.h" #include "mongo/s/grid.h" #include "mongo/s/request_types/sharded_ddl_commands_gen.h" |