/** * Checks that setClusterParameter command only run once * * We have a restart in the test with some stored values that must be preserved so it cannot run in * inMemory variants * * @tags: [ * does_not_support_stepdowns, * requires_persistence, * ] */ import {configureFailPoint} from "jstests/libs/fail_point_util.js"; import {Thread} from "jstests/libs/parallelTester.js"; import {ReplSetTest} from "jstests/libs/replsettest.js"; import {ShardingTest} from "jstests/libs/shardingtest.js"; import {reconfig} from "jstests/replsets/rslib.js"; import {removeShard} from "jstests/sharding/libs/remove_shard_util.js"; // This test requires running commands directly against the shard. TestData.replicaSetEndpointIncompatible = true; const clusterParameter1Value = { intData: 42, }; const clusterParameter1Name = "testIntClusterParameter"; const clusterParameter1 = { [clusterParameter1Name]: clusterParameter1Value, }; const clusterParameter2Value = { strData: "on", }; const clusterParameter2Name = "testStrClusterParameter"; const clusterParameter2 = { [clusterParameter2Name]: clusterParameter2Value, }; const clusterParameter3Value = { boolData: true, }; const clusterParameter3Name = "testBoolClusterParameter"; const clusterParameter3 = { [clusterParameter3Name]: clusterParameter3Value, }; const checkClusterParameters = (clusterParameterName, clusterParameterValue, configRS, shard, checkTime = true) => { const shardParametersConfigColl = shard.getCollection("config.clusterParameters"); const clusterParametersConfigColl = configRS.getCollection("config.clusterParameters"); assert.eq(1, clusterParametersConfigColl.countDocuments({_id: clusterParameterName})); const configClusterParameter = clusterParametersConfigColl.findOne( {_id: clusterParameterName}, {_id: 0, clusterParameterTime: 0}, ); const shardClusterParameter = shardParametersConfigColl.findOne( {_id: clusterParameterName}, {_id: 0, clusterParameterTime: 0}, ); assert.docEq(configClusterParameter, clusterParameterValue); assert.docEq(shardClusterParameter, clusterParameterValue); if (checkTime) { // Check the full cluster has the same clusterParameterTime as the config server. const configParameterTime = clusterParametersConfigColl.findOne( {_id: clusterParameterName}, {clusterParameterTime: 1}, ).clusterParameterTime; assert.eq( configParameterTime, shardParametersConfigColl.findOne({_id: clusterParameterName}, {clusterParameterTime: 1}) .clusterParameterTime, ); } }; { const st = new ShardingTest({shards: 1, rs: {nodes: 3}}); { jsTestLog( "Check that 2 requests for the same cluster parameter and same value generates only one coordinator.", ); let fp = configureFailPoint(st.configRS.getPrimary(), "hangBeforeRunningConfigsvrCoordinatorInstance"); let setClusterParameterSuccessThread = new Thread( (mongosConnString, clusterParameter) => { let mongos = new Mongo(mongosConnString); assert.commandWorked(mongos.adminCommand({setClusterParameter: clusterParameter})); }, st.s.host, clusterParameter1, ); setClusterParameterSuccessThread.start(); fp.wait(); let setClusterParameterJoinSuccessThread = new Thread( (mongosConnString, clusterParameter) => { let mongos = new Mongo(mongosConnString); assert.commandWorked(mongos.adminCommand({setClusterParameter: clusterParameter})); }, st.s.host, clusterParameter1, ); setClusterParameterJoinSuccessThread.start(); fp.wait(); let currOp = st.configRS .getPrimary() .getDB("admin") .aggregate([{$currentOp: {allUsers: true}}, {$match: {desc: "SetClusterParameterCoordinator"}}]) .toArray(); assert.eq(1, currOp.length); assert(currOp[0].hasOwnProperty("command")); assert.docEq(currOp[0].command, clusterParameter1); jsTestLog("Check that a second request will fail with ConflictingOperationInProgress."); assert.commandFailedWithCode( st.s.adminCommand({setClusterParameter: clusterParameter2}), ErrorCodes.ConflictingOperationInProgress, ); fp.off(); setClusterParameterSuccessThread.join(); setClusterParameterJoinSuccessThread.join(); } { jsTestLog("Check forward progress until completion in the presence of a config server stepdown."); let fp = configureFailPoint(st.configRS.getPrimary(), "hangBeforeRunningConfigsvrCoordinatorInstance"); let setClusterParameterThread = new Thread( (mongosConnString, clusterParameter) => { let mongos = new Mongo(mongosConnString); assert.commandWorked(mongos.adminCommand({setClusterParameter: clusterParameter})); }, st.s.host, clusterParameter2, ); setClusterParameterThread.start(); fp.wait(); let newPrimary = st.configRS.getSecondary(); st.configRS.stepUp(newPrimary); // After the stepdown the command should be retried and finish successfully. setClusterParameterThread.join(); checkClusterParameters( clusterParameter2Name, clusterParameter2Value, st.configRS.getPrimary(), st.rs0.getPrimary(), ); fp.off(); } st.stop(); } if (!TestData.configShard) { { const st2 = new ShardingTest({mongos: 1, shards: 0, name: "second_cluster"}); const newShard2Name = "newShard2"; const newShard2 = new ReplSetTest({name: newShard2Name, nodes: 1}); newShard2.startSet(); newShard2.initiate(); { jsTestLog( "Check that on replica set to sharded cluster transition parameters added in RS are synced in the cluster.", ); assert.commandWorked(newShard2.getPrimary().adminCommand({setClusterParameter: clusterParameter1})); newShard2.restart(0, {shardsvr: ""}); newShard2.awaitNodesAgreeOnPrimary(); // After restarting a node with --shardsvr it should not accept setClusterParameter // commands. assert.commandFailedWithCode( newShard2.getPrimary().adminCommand({setClusterParameter: clusterParameter3}), ErrorCodes.NotImplemented, ); checkClusterParameters( clusterParameter1Name, clusterParameter1Value, newShard2.getPrimary(), newShard2.getPrimary(), false, ); assert.commandWorked(st2.s.adminCommand({addShard: newShard2.getURL(), name: newShard2Name})); // After adding the shard there the clusterParameter set on the rs clusterParameter1 // must have been adopted by the whole cluster. checkClusterParameters( clusterParameter1Name, clusterParameter1Value, st2.configRS.getPrimary(), newShard2.getPrimary(), ); assert.eq( st2.configRS.getPrimary().getCollection("config.clusterParameters").countDocuments({}), newShard2.getPrimary().getCollection("config.clusterParameters").countDocuments({}), ); } { jsTestLog("Check that parameters added in cluster overwrite custom RS parameters."); const newShard3Name = "newShard3"; const newShard3 = new ReplSetTest({name: newShard3Name, nodes: 1}); newShard3.startSet(); newShard3.initiate(); assert.commandWorked(newShard3.getPrimary().adminCommand({setClusterParameter: clusterParameter2})); newShard3.restart(0, {shardsvr: ""}); newShard3.awaitNodesAgreeOnPrimary(); checkClusterParameters( clusterParameter2Name, clusterParameter2Value, newShard3.getPrimary(), newShard3.getPrimary(), ); assert.commandWorked(st2.s.adminCommand({addShard: newShard3.getURL(), name: newShard3Name})); // After adding the shard there must be only one cluster parameter: the one set on the // cluster clusterParameter1. checkClusterParameters( clusterParameter1Name, clusterParameter1Value, st2.configRS.getPrimary(), newShard3.getPrimary(), ); assert.eq( 0, newShard3.getPrimary().getCollection("config.clusterParameters").countDocuments({ _id: clusterParameter2Name, }), ); // Well behaved test, remove shard and stop the set. removeShard(st2, newShard3Name); newShard3.stopSet(); } newShard2.stopSet(); st2.stop(); } { jsTestLog("Check that parameters added in an empty cluster overwrite custom RS parameters."); const st3 = new ShardingTest({mongos: 1, shards: 0, name: "third_cluster"}); st3.s.adminCommand({setClusterParameter: clusterParameter1}); const newShard4Name = "newShard2"; const newShard4 = new ReplSetTest({name: newShard4Name, nodes: 1}); newShard4.startSet(); newShard4.initiate(); newShard4.getPrimary().adminCommand({setClusterParameter: clusterParameter2}); newShard4.restart(0, {shardsvr: ""}); newShard4.awaitNodesAgreeOnPrimary(); checkClusterParameters( clusterParameter2Name, clusterParameter2Value, newShard4.getPrimary(), newShard4.getPrimary(), ); assert.commandWorked(st3.s.adminCommand({addShard: newShard4.getURL(), name: newShard4Name})); checkClusterParameters( clusterParameter1Name, clusterParameter1Value, st3.configRS.getPrimary(), newShard4.getPrimary(), ); newShard4.stopSet(); st3.stop(); } { jsTestLog("Check that addShard serializes with setClusterParameter."); const st4 = new ShardingTest({shards: 1, rs: {nodes: 3}}); const newShardName = "newShard"; const newShard = new ReplSetTest({name: newShardName, nodes: 1}); newShard.startSet({shardsvr: ""}); newShard.initiate(); let shardsvrSetClusterParameterFailPoint = configureFailPoint( st4.rs0.getPrimary(), "hangInShardsvrSetClusterParameter", ); let setClusterParameterThread = new Thread( (mongosConnString, clusterParameter) => { let mongos = new Mongo(mongosConnString); assert.commandWorked(mongos.adminCommand({setClusterParameter: clusterParameter})); }, st4.s.host, clusterParameter3, ); setClusterParameterThread.start(); shardsvrSetClusterParameterFailPoint.wait(); assert.commandFailedWithCode( st4.s.adminCommand({addShard: newShard.getURL(), name: newShardName, maxTimeMS: 1000}), ErrorCodes.MaxTimeMSExpired, ); shardsvrSetClusterParameterFailPoint.off(); setClusterParameterThread.join(); jsTestLog("Check that the config server will push all parameters when adding a new shard."); assert.commandWorked(st4.s.adminCommand({addShard: newShard.getURL(), name: newShardName})); checkClusterParameters( clusterParameter3Name, clusterParameter3Value, st4.configRS.getPrimary(), newShard.getPrimary(), ); removeShard(st4, newShardName); newShard.stopSet(); // Final check, the initial shard has all the cluster parameters checkClusterParameters( clusterParameter3Name, clusterParameter3Value, st4.configRS.getPrimary(), st4.rs0.getPrimary(), ); st4.stop(); } } else { // In config shard mode { jsTestLog("Check that RS which transitions to a config shard keeps cluster params."); const configShardName = "configShard"; const configShard = new ReplSetTest({ name: configShardName, nodes: 1, nodeOptions: {setParameter: {skipShardingConfigurationChecks: true}}, }); configShard.startSet(); configShard.initiate(); configShard.getPrimary().adminCommand({setClusterParameter: clusterParameter2}); let cfg = configShard.getReplSetConfigFromNode(); cfg.configsvr = true; reconfig(configShard, cfg); configShard.restart(0, { configsvr: "", setParameter: {skipShardingConfigurationChecks: false, featureFlagTransitionToCatalogShard: true}, }); configShard.awaitNodesAgreeOnPrimary(); // Cluster params should still exist. checkClusterParameters( clusterParameter2Name, clusterParameter2Value, configShard.getPrimary(), configShard.getPrimary(), ); if (TestData.mongosBinVersion) { // Lower the config shard's FCV so an earlier binary mongos can connect. const targetFCV = binVersionToFCV(TestData.mongosBinVersion); assert.commandWorked( configShard.getPrimary().adminCommand({setFeatureCompatibilityVersion: targetFCV, confirm: true}), ); } let mongos = MongoRunner.runMongos({ configdb: configShard.getURL(), setParameter: "featureFlagTransitionToCatalogShard=true", }); assert.commandWorked(mongos.adminCommand({transitionFromDedicatedConfigServer: 1})); checkClusterParameters( clusterParameter2Name, clusterParameter2Value, configShard.getPrimary(), configShard.getPrimary(), ); // Config shard should not accept cluster parameters set directly on it. assert.commandFailedWithCode( configShard.getPrimary().adminCommand({setClusterParameter: clusterParameter3}), ErrorCodes.NotImplemented, ); jsTestLog("Check that parameters added in a config shard cluster overwrite custom RS parameters."); const newShard5Name = "newShard5"; const newShard5 = new ReplSetTest({name: newShard5Name, nodes: 1}); newShard5.startSet(); newShard5.initiate(); newShard5.getPrimary().adminCommand({setClusterParameter: clusterParameter1}); newShard5.restart(0, {shardsvr: ""}); newShard5.awaitNodesAgreeOnPrimary(); checkClusterParameters( clusterParameter1Name, clusterParameter1Value, newShard5.getPrimary(), newShard5.getPrimary(), ); assert.commandWorked(mongos.adminCommand({addShard: newShard5.getURL(), name: newShard5Name})); checkClusterParameters( clusterParameter2Name, clusterParameter2Value, configShard.getPrimary(), newShard5.getPrimary(), ); assert.eq( 0, newShard5.getPrimary().getCollection("config.clusterParameters").countDocuments({ _id: clusterParameter1Name, }), ); MongoRunner.stopMongos(mongos); newShard5.stopSet(); configShard.stopSet(); } }