diff options
author | Cheahuychou Mao <mao.cheahuychou@gmail.com> | 2023-03-20 22:17:55 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2023-03-21 00:02:01 +0000 |
commit | fed4630c7dacaa6b6c66c3c9efca5b0e5f0775a4 (patch) | |
tree | 1fa7181554886c971626644d58277798149a1ef9 | |
parent | 07c5ea19d962f1c53ec88183a4ae14cfe53cdc44 (diff) | |
download | mongo-fed4630c7dacaa6b6c66c3c9efca5b0e5f0775a4.tar.gz |
SERVER-75030 Disallow configureQueryAnalyzer command on multi-tenant replica set
4 files changed, 208 insertions, 123 deletions
diff --git a/jstests/sharding/analyze_shard_key/analyze_shard_key_basic.js b/jstests/sharding/analyze_shard_key/analyze_shard_key_basic.js index 72dc63cde94..d15a9053411 100644 --- a/jstests/sharding/analyze_shard_key/analyze_shard_key_basic.js +++ b/jstests/sharding/analyze_shard_key/analyze_shard_key_basic.js @@ -8,9 +8,10 @@ load("jstests/libs/catalog_shard_util.js"); -const dbName = "testDb"; +const dbNameBase = "testDb"; -function testNonExistingCollection(testCases) { +function testNonExistingCollection(testCases, tenantId) { + const dbName = tenantId ? (tenantId + "-" + dbNameBase) : dbNameBase; const collName = "testCollNonExisting"; const ns = dbName + "." + collName; const candidateKey = {candidateKey: 1}; @@ -18,7 +19,14 @@ function testNonExistingCollection(testCases) { testCases.forEach(testCase => { jsTest.log(`Running analyzeShardKey command against a non-existing collection: ${ tojson(testCase)}`); - const res = testCase.conn.adminCommand({analyzeShardKey: ns, key: candidateKey}); + const cmdObj = {analyzeShardKey: ns, key: candidateKey}; + if (tenantId) { + cmdObj.$tenant = tenantId; + } + const res = testCase.conn.adminCommand(cmdObj); + // If the command is not supported, it should fail even before the collection validation + // step. That is, it should fail with an IllegalOperation error instead of a + // NamespaceNotFound error. const expectedErrorCode = testCase.isSupported ? ErrorCodes.NamespaceNotFound : ErrorCodes.IllegalOperation; assert.commandFailedWithCode(res, expectedErrorCode); @@ -26,6 +34,7 @@ function testNonExistingCollection(testCases) { } function testExistingUnshardedCollection(writeConn, testCases) { + const dbName = dbNameBase; const collName = "testCollUnsharded"; const ns = dbName + "." + collName; const coll = writeConn.getCollection(ns); @@ -45,7 +54,7 @@ function testExistingUnshardedCollection(writeConn, testCases) { // This is an unsharded collection so in a sharded cluster it only exists on the // primary shard. let expectedErrCode = (() => { - if (testCase.isMongos || testCase.isNonShardsvrMongod) { + if (testCase.isMongos || testCase.isReplSetMongod) { return ErrorCodes.IllegalOperation; } else if (testCase.isPrimaryShardMongod) { return ErrorCodes.CollectionIsEmptyLocally; @@ -71,8 +80,7 @@ function testExistingUnshardedCollection(writeConn, testCases) { if (testCase.isSupported) { // This is an unsharded collection so in a sharded cluster it only exists on the // primary shard. - if (testCase.isNonShardsvrMongod || testCase.isPrimaryShardMongod || - testCase.isMongos) { + if (testCase.isReplSetMongod || testCase.isPrimaryShardMongod || testCase.isMongos) { assert.commandWorked(res0); assert.commandWorked(res1); } else { @@ -87,6 +95,7 @@ function testExistingUnshardedCollection(writeConn, testCases) { } function testExistingShardedCollection(st, testCases) { + const dbName = dbNameBase; const collName = "testCollSharded"; const ns = dbName + "." + collName; const coll = st.s.getCollection(ns); @@ -163,6 +172,7 @@ function testExistingShardedCollection(st, testCases) { } function testNotSupportReadWriteConcern(writeConn, testCases) { + const dbName = dbNameBase; const collName = "testCollReadWriteConcern"; const ns = dbName + "." + collName; const coll = writeConn.getCollection(ns); @@ -195,12 +205,12 @@ function testNotSupportReadWriteConcern(writeConn, testCases) { } }); - assert.commandWorked(st.s.adminCommand({enableSharding: dbName})); - st.ensurePrimaryShard(dbName, st.shard0.name); + assert.commandWorked(st.s.adminCommand({enableSharding: dbNameBase})); + st.ensurePrimaryShard(dbNameBase, st.shard0.name); + const testCases = []; // The analyzeShardKey command is supported on mongos and all shardsvr mongods (both primary and // secondary). - const testCases = []; testCases.push({conn: st.s, isSupported: true, isMongos: true}); st.rs0.nodes.forEach(node => { testCases.push({conn: node, isSupported: true, isPrimaryShardMongod: true}); @@ -226,15 +236,15 @@ function testNotSupportReadWriteConcern(writeConn, testCases) { } { - const rst = new ReplSetTest({nodes: 2}); + const rst = new ReplSetTest({name: jsTest.name() + "_non_multitenant", nodes: 2}); rst.startSet(); rst.initiate(); const primary = rst.getPrimary(); - // The analyzeShardKey command is supported on all mongods (both primary and secondary). const testCases = []; + // The analyzeShardKey command is supported on all mongods (both primary and secondary). rst.nodes.forEach(node => { - testCases.push({conn: node, isSupported: true, isNonShardsvrMongod: true}); + testCases.push({conn: node, isSupported: true, isReplSetMongod: true}); }); testExistingUnshardedCollection(primary, testCases); @@ -243,4 +253,38 @@ function testNotSupportReadWriteConcern(writeConn, testCases) { rst.stopSet(); } + +if (!TestData.auth) { + const rst = new ReplSetTest({ + name: jsTest.name() + "_multitenant", + nodes: 1, + nodeOptions: {auth: "", setParameter: {multitenancySupport: true}} + }); + rst.startSet({keyFile: "jstests/libs/key1"}); + rst.initiate(); + const primary = rst.getPrimary(); + const adminDb = primary.getDB("admin"); + const tenantId = ObjectId(); + + // Prepare a user for testing multitenancy via $tenant. + // Must be authenticated as a user with ActionType::useTenant in order to use $tenant. + assert.commandWorked( + adminDb.runCommand({createUser: "admin", pwd: "pwd", roles: ["__system"]})); + assert(adminDb.auth("admin", "pwd")); + + // The analyzeShardKey command is supported on any mongod. + const testCases = [{conn: adminDb, isSupported: true}]; + testNonExistingCollection(testCases, tenantId); + rst.stopSet(); +} + +{ + const mongod = MongoRunner.runMongod(); + + // The analyzeShardKey command is not supported on standalone mongod. + const testCases = [{conn: mongod, isSupported: false}]; + testExistingUnshardedCollection(mongod, testCases); + + MongoRunner.stopMongod(mongod); +} })(); diff --git a/jstests/sharding/analyze_shard_key/configure_query_analyzer_basic.js b/jstests/sharding/analyze_shard_key/configure_query_analyzer_basic.js index 56a9f4d4b4f..fc7ee9329c3 100644 --- a/jstests/sharding/analyze_shard_key/configure_query_analyzer_basic.js +++ b/jstests/sharding/analyze_shard_key/configure_query_analyzer_basic.js @@ -6,148 +6,181 @@ (function() { "use strict"; -function testNonExistingCollection(conn, ns) { - jsTest.log(`Running configureQueryAnalyzer command against an non-existing collection ${ - ns} on ${conn}`); - assert.commandFailedWithCode( - conn.adminCommand({configureQueryAnalyzer: ns, mode: "full", sampleRate: 1}), - ErrorCodes.NamespaceNotFound); +const dbNameBase = "testDb"; + +function testNonExistingCollection(testCases, tenantId) { + const dbName = tenantId ? (tenantId + "-" + dbNameBase) : dbNameBase; + const collName = "testCollNonExisting"; + const ns = dbName + "." + collName; + + testCases.forEach(testCase => { + jsTest.log(`Running configureQueryAnalyzer command against an non-existing collection: ${ + tojson(testCase)}`); + const cmdObj = {configureQueryAnalyzer: ns, mode: "full", sampleRate: 1}; + if (tenantId) { + cmdObj.$tenant = tenantId; + } + const res = testCase.conn.adminCommand(cmdObj); + // If the command is not supported, it should fail even before the collection validation + // step. That is, it should fail with an IllegalOperation error instead of a + // NamespaceNotFound error. + const expectedErrorCode = + testCase.isSupported ? ErrorCodes.NamespaceNotFound : testCase.expectedErrorCode; + assert.commandFailedWithCode(res, expectedErrorCode); + }); } -function testExistingCollection(conn, ns) { - jsTest.log( - `Running configureQueryAnalyzer command against an existing collection ${ns} on ${conn}`); - - // Cannot set 'sampleRate' to 0. - assert.commandFailedWithCode( - conn.adminCommand({configureQueryAnalyzer: ns, mode: "full", sampleRate: 0}), - ErrorCodes.InvalidOptions); +function testExistingCollection(writeConn, testCases) { + const dbName = dbNameBase; + const collName = "testCollUnsharded"; + const ns = dbName + "." + collName; + const db = writeConn.getDB(dbName); + assert.commandWorked(db.createCollection(collName)); + + testCases.forEach(testCase => { + jsTest.log( + `Running configureQueryAnalyzer command against an existing collection: + ${tojson(testCase)}`); + + // Can set 'sampleRate' to > 0. + const basicRes = + testCase.conn.adminCommand({configureQueryAnalyzer: ns, mode: "full", sampleRate: 0.1}); + if (!testCase.isSupported) { + assert.commandFailedWithCode(basicRes, testCase.expectedErrorCode); + // There is no need to test the remaining cases. + return; + } + assert.commandWorked(basicRes); + assert.commandWorked( + testCase.conn.adminCommand({configureQueryAnalyzer: ns, mode: "full", sampleRate: 1})); + assert.commandWorked(testCase.conn.adminCommand( + {configureQueryAnalyzer: ns, mode: "full", sampleRate: 1000})); + + // Cannot set 'sampleRate' to 0. + assert.commandFailedWithCode( + testCase.conn.adminCommand({configureQueryAnalyzer: ns, mode: "full", sampleRate: 0}), + ErrorCodes.InvalidOptions); - // Cannot set 'sampleRate' to larger than 1'000'000. - assert.commandFailedWithCode( - conn.adminCommand({configureQueryAnalyzer: ns, mode: "full", sampleRate: 1000001}), - ErrorCodes.InvalidOptions); + // Cannot set 'sampleRate' to larger than 1'000'000. + assert.commandFailedWithCode( + testCase.conn.adminCommand( + {configureQueryAnalyzer: ns, mode: "full", sampleRate: 1000001}), + ErrorCodes.InvalidOptions); - // Can set 'sampleRate' to > 0. - assert.commandWorked( - conn.adminCommand({configureQueryAnalyzer: ns, mode: "full", sampleRate: 0.1})); - assert.commandWorked( - conn.adminCommand({configureQueryAnalyzer: ns, mode: "full", sampleRate: 1})); - assert.commandWorked( - conn.adminCommand({configureQueryAnalyzer: ns, mode: "full", sampleRate: 1000})); - - // Cannot specify 'sampleRate' when 'mode' is "off". - assert.commandFailedWithCode( - conn.adminCommand({configureQueryAnalyzer: ns, mode: "off", sampleRate: 1}), - ErrorCodes.InvalidOptions); - assert.commandWorked(conn.adminCommand({configureQueryAnalyzer: ns, mode: "off"})); - - // Cannot specify read/write concern. - assert.commandFailedWithCode(conn.adminCommand({ - configureQueryAnalyzer: ns, - mode: "full", - sampleRate: 1, - readConcern: {level: "available"} - }), - ErrorCodes.InvalidOptions); - assert.commandFailedWithCode(conn.adminCommand({ - configureQueryAnalyzer: ns, - mode: "full", - sampleRate: 1, - writeConcern: {w: "majority"} - }), - ErrorCodes.InvalidOptions); + // Cannot specify 'sampleRate' when 'mode' is "off". + assert.commandFailedWithCode( + testCase.conn.adminCommand({configureQueryAnalyzer: ns, mode: "off", sampleRate: 1}), + ErrorCodes.InvalidOptions); + assert.commandWorked(testCase.conn.adminCommand({configureQueryAnalyzer: ns, mode: "off"})); + + // Cannot specify read/write concern. + assert.commandFailedWithCode(testCase.conn.adminCommand({ + configureQueryAnalyzer: ns, + mode: "full", + sampleRate: 1, + readConcern: {level: "available"} + }), + ErrorCodes.InvalidOptions); + assert.commandFailedWithCode(testCase.conn.adminCommand({ + configureQueryAnalyzer: ns, + mode: "full", + sampleRate: 1, + writeConcern: {w: "majority"} + }), + ErrorCodes.InvalidOptions); + }); } { const st = new ShardingTest({shards: 1, rs: {nodes: 2}}); + const shard0Primary = st.rs0.getPrimary(); const shard0Secondaries = st.rs0.getSecondaries(); const configPrimary = st.configRS.getPrimary(); const configSecondaries = st.configRS.getSecondaries(); - const dbName = "testDb"; - const nonExistingNs = dbName + ".nonExistingColl"; - - const shardedNs = dbName + ".shardedColl"; - const shardKey = {key: 1}; - assert.commandWorked(st.s.adminCommand({enableSharding: dbName})); - st.ensurePrimaryShard(dbName, st.shard0.name); - assert.commandWorked(st.s.adminCommand({shardCollection: shardedNs, key: shardKey})); - - const unshardedCollName = "unshardedColl"; - const unshardedNs = dbName + "." + unshardedCollName; - assert.commandWorked(st.s.getDB(dbName).createCollection(unshardedCollName)); - - // Verify that the command is supported on mongos and configsvr primary mongod. - function testSupported(conn) { - testNonExistingCollection(conn, nonExistingNs); - testExistingCollection(conn, unshardedNs); - testExistingCollection(conn, shardedNs); - } - - testSupported(st.s); - testSupported(configPrimary); - - // Verify that the command is not supported on configsvr secondary mongods or any shardvr - // mongods. - function testNotSupported(conn, errorCode) { - assert.commandFailedWithCode( - conn.adminCommand({configureQueryAnalyzer: unshardedNs, mode: "full", sampleRate: 1}), - errorCode); - assert.commandFailedWithCode( - conn.adminCommand({configureQueryAnalyzer: shardedNs, mode: "full", sampleRate: 1}), - errorCode); - } - + const testCases = []; + // The configureQueryAnalyzer command is only supported on mongos and configsvr primary mongod. + testCases.push({conn: st.s, isSupported: true}); + testCases.push({conn: configPrimary, isSupported: true}); configSecondaries.forEach(node => { - testNotSupported(node, ErrorCodes.NotWritablePrimary); + testCases.push( + {conn: node, isSupported: false, expectedErrorCode: ErrorCodes.NotWritablePrimary}); }); - if (!TestData.catalogShard) { - // If there's a catalog shard, shard0 will be the config server and can accept - // configureQueryAnalyzer. - testNotSupported(shard0Primary, ErrorCodes.IllegalOperation); - } + // If there's a catalog shard, shard0 will be the config server and can accept + // configureQueryAnalyzer. + testCases.push( + Object.assign({conn: shard0Primary}, + TestData.catalogShard + ? {isSupported: true} + : {isSupported: false, expectedErrorCode: ErrorCodes.IllegalOperation})); shard0Secondaries.forEach(node => { - testNotSupported(node, ErrorCodes.NotWritablePrimary); + testCases.push( + {conn: node, isSupported: false, expectedErrorCode: ErrorCodes.NotWritablePrimary}); }); + testNonExistingCollection(testCases); + testExistingCollection(st.s, testCases); + st.stop(); } { - const rst = new ReplSetTest({nodes: 2}); + const rst = new ReplSetTest({name: jsTest.name() + "_non_multitenant", nodes: 2}); rst.startSet(); rst.initiate(); const primary = rst.getPrimary(); const secondaries = rst.getSecondaries(); - const dbName = "testDb"; - const nonExistingNs = dbName + ".nonExistingColl"; + const testCases = []; + // The configureQueryAnalyzer command is only supported on primary mongod. + testCases.push(Object.assign({conn: primary, isSupported: true})); + secondaries.forEach(node => { + testCases.push( + {conn: node, isSupported: false, expectedErrorCode: ErrorCodes.NotWritablePrimary}); + }); - const unshardedCollName = "unshardedColl"; - const unshardedNs = dbName + "." + unshardedCollName; - assert.commandWorked(primary.getDB(dbName).createCollection(unshardedCollName)); + testNonExistingCollection(testCases); + testExistingCollection(primary, testCases); - // Verify that the command is supported on primary mongod. - function testSupported(conn) { - testNonExistingCollection(conn, nonExistingNs); - testExistingCollection(conn, unshardedNs); - } + rst.stopSet(); +} - testSupported(primary, unshardedNs); +if (!TestData.auth) { + const rst = new ReplSetTest({ + name: jsTest.name() + "_multitenant", + nodes: 2, + nodeOptions: {setParameter: {multitenancySupport: true}} + }); + rst.startSet({keyFile: "jstests/libs/key1"}); + rst.initiate(); + const primary = rst.getPrimary(); + const adminDb = primary.getDB("admin"); + const tenantId = ObjectId(); - // Verify that the command is not supported on secondary mongods. - function testNotSupported(conn, errorCode) { - assert.commandFailedWithCode( - conn.adminCommand({configureQueryAnalyzer: unshardedNs, mode: "full", sampleRate: 1}), - errorCode); - } + // Prepare a user for testing multitenancy via $tenant. + // Must be authenticated as a user with ActionType::useTenant in order to use $tenant. + assert.commandWorked( + adminDb.runCommand({createUser: "admin", pwd: "pwd", roles: ["__system"]})); + assert(adminDb.auth("admin", "pwd")); - secondaries.forEach(node => { - testNotSupported(node, ErrorCodes.NotWritablePrimary); - }); + // The configureQueryAnalyzer command is not supported even on primary mongod. + const testCases = []; + testCases.push(Object.assign( + {conn: primary, isSupported: false, expectedErrorCode: ErrorCodes.IllegalOperation})); + testNonExistingCollection(testCases, tenantId); rst.stopSet(); } + +{ + const mongod = MongoRunner.runMongod(); + + // The configureQueryAnalyzer command is not supported on standalone mongod. + const testCases = + [{conn: mongod, isSupported: false, expectedErrorCode: ErrorCodes.IllegalOperation}]; + testNonExistingCollection(testCases); + + MongoRunner.stopMongod(mongod); +} })(); diff --git a/src/mongo/db/s/analyze_shard_key_cmd.cpp b/src/mongo/db/s/analyze_shard_key_cmd.cpp index 303d255d2cd..c2b8247481e 100644 --- a/src/mongo/db/s/analyze_shard_key_cmd.cpp +++ b/src/mongo/db/s/analyze_shard_key_cmd.cpp @@ -68,9 +68,11 @@ public: Response typedRun(OperationContext* opCtx) { uassert(ErrorCodes::IllegalOperation, + "analyzeShardKey command is not supported on a standalone mongod", + repl::ReplicationCoordinator::get(opCtx)->isReplEnabled()); + uassert(ErrorCodes::IllegalOperation, "analyzeShardKey command is not supported on a configsvr mongod", - serverGlobalParams.clusterRole == ClusterRole::None || - serverGlobalParams.clusterRole.isShardRole()); + !serverGlobalParams.clusterRole.isExclusivelyConfigSvrRole()); const auto& nss = ns(); const auto& key = request().getKey(); diff --git a/src/mongo/db/s/configure_query_analyzer_cmd.cpp b/src/mongo/db/s/configure_query_analyzer_cmd.cpp index 94eadcf75e9..e1c68911c8e 100644 --- a/src/mongo/db/s/configure_query_analyzer_cmd.cpp +++ b/src/mongo/db/s/configure_query_analyzer_cmd.cpp @@ -64,6 +64,12 @@ public: Response typedRun(OperationContext* opCtx) { uassert(ErrorCodes::IllegalOperation, + "configQueryAnalyzer command is not supported on a standalone mongod", + repl::ReplicationCoordinator::get(opCtx)->isReplEnabled()); + uassert(ErrorCodes::IllegalOperation, + "configQueryAnalyzer command is not supported on a multitenant replica set", + !gMultitenancySupport); + uassert(ErrorCodes::IllegalOperation, "configQueryAnalyzer command is not supported on a shardsvr mongod", !serverGlobalParams.clusterRole.isExclusivelyShardRole()); |