diff options
author | Marcos José Grillo Ramirez <marcos.grillo@mongodb.com> | 2022-12-28 17:50:01 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-12-28 18:32:16 +0000 |
commit | 07b5f334c547b5ab254e2952ae7b653fa9a8bcb4 (patch) | |
tree | 41a4aed1db9f248bfd7eebaa4e641145660e853e | |
parent | d0d9e95c07df276f4fa3cef07982803d689da5c7 (diff) | |
download | mongo-07b5f334c547b5ab254e2952ae7b653fa9a8bcb4.tar.gz |
SERVER-64175 Modify refine shard key index validations so they are performed in each shard
-rw-r--r-- | jstests/core/views/views_all_commands.js | 1 | ||||
-rw-r--r-- | jstests/replsets/all_commands_downgrading_to_upgraded.js | 1 | ||||
-rw-r--r-- | jstests/replsets/db_reads_while_recovering_all_commands.js | 1 | ||||
-rw-r--r-- | jstests/sharding/read_write_concern_defaults_application.js | 1 | ||||
-rw-r--r-- | jstests/sharding/refine_collection_shard_key_basic.js | 128 | ||||
-rw-r--r-- | src/mongo/db/s/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/s/config/configsvr_refine_collection_shard_key_command.cpp | 71 | ||||
-rw-r--r-- | src/mongo/db/s/shard_key_util.cpp | 51 | ||||
-rw-r--r-- | src/mongo/db/s/shard_key_util.h | 26 | ||||
-rw-r--r-- | src/mongo/db/s/sharding_util.cpp | 42 | ||||
-rw-r--r-- | src/mongo/db/s/sharding_util.h | 14 | ||||
-rw-r--r-- | src/mongo/db/s/shardsvr_validate_shard_key_candidate.cpp | 112 | ||||
-rw-r--r-- | src/mongo/s/request_types/sharded_ddl_commands.idl | 18 |
13 files changed, 378 insertions, 89 deletions
diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js index 31baa00ec8e..e2df5dcb06d 100644 --- a/jstests/core/views/views_all_commands.js +++ b/jstests/core/views/views_all_commands.js @@ -185,6 +185,7 @@ let viewsCommandTests = { _shardsvrSetAllowMigrations: {skip: isAnInternalCommand}, _shardsvrSetClusterParameter: {skip: isAnInternalCommand}, _shardsvrSetUserWriteBlockMode: {skip: isAnInternalCommand}, + _shardsvrValidateShardKeyCandidate: {skip: isAnInternalCommand}, _shardsvrCollMod: {skip: isAnInternalCommand}, _shardsvrCollModParticipant: {skip: isAnInternalCommand}, _shardsvrParticipantBlock: {skip: isAnInternalCommand}, diff --git a/jstests/replsets/all_commands_downgrading_to_upgraded.js b/jstests/replsets/all_commands_downgrading_to_upgraded.js index e6f340ab04e..186a52244fe 100644 --- a/jstests/replsets/all_commands_downgrading_to_upgraded.js +++ b/jstests/replsets/all_commands_downgrading_to_upgraded.js @@ -129,6 +129,7 @@ const allCommands = { _shardsvrSetClusterParameter: {skip: isAnInternalCommand}, _shardsvrSetUserWriteBlockMode: {skip: isAnInternalCommand}, _shardsvrUnregisterIndex: {skip: isAnInternalCommand}, + _shardsvrValidateShardKeyCandidate: {skip: isAnInternalCommand}, _shardsvrCollMod: {skip: isAnInternalCommand}, _shardsvrCollModParticipant: {skip: isAnInternalCommand}, _shardsvrParticipantBlock: {skip: isAnInternalCommand}, diff --git a/jstests/replsets/db_reads_while_recovering_all_commands.js b/jstests/replsets/db_reads_while_recovering_all_commands.js index 27e730a0dad..ed125348087 100644 --- a/jstests/replsets/db_reads_while_recovering_all_commands.js +++ b/jstests/replsets/db_reads_while_recovering_all_commands.js @@ -116,6 +116,7 @@ const allCommands = { _shardsvrSetClusterParameter: {skip: isAnInternalCommand}, _shardsvrSetUserWriteBlockMode: {skip: isPrimaryOnly}, _shardsvrUnregisterIndex: {skip: isPrimaryOnly}, + _shardsvrValidateShardKeyCandidate: {skip: isPrimaryOnly}, _shardsvrCollMod: {skip: isPrimaryOnly}, _shardsvrCollModParticipant: {skip: isAnInternalCommand}, _shardsvrParticipantBlock: {skip: isAnInternalCommand}, diff --git a/jstests/sharding/read_write_concern_defaults_application.js b/jstests/sharding/read_write_concern_defaults_application.js index 2410372d48f..635d68cc805 100644 --- a/jstests/sharding/read_write_concern_defaults_application.js +++ b/jstests/sharding/read_write_concern_defaults_application.js @@ -174,6 +174,7 @@ let testCases = { _shardsvrSetAllowMigrations: {skip: "internal command"}, _shardsvrSetClusterParameter: {skip: "internal command"}, _shardsvrSetUserWriteBlockMode: {skip: "internal command"}, + _shardsvrValidateShardKeyCandidate: {skip: "internal command"}, _shardsvrCollMod: {skip: "internal command"}, _shardsvrCollModParticipant: {skip: "internal command"}, _shardsvrParticipantBlock: {skip: "internal command"}, diff --git a/jstests/sharding/refine_collection_shard_key_basic.js b/jstests/sharding/refine_collection_shard_key_basic.js index 5f98a1c24aa..4b60fc5d63c 100644 --- a/jstests/sharding/refine_collection_shard_key_basic.js +++ b/jstests/sharding/refine_collection_shard_key_basic.js @@ -712,67 +712,73 @@ if (!isStepdownSuite) { .metadata.shardVersionEpoch.toString()); } -(() => { - // - // Verify listIndexes and checkShardingIndexes are retried on shard version errors and are sent - // with shard versions. - // - - // Create a sharded collection with one chunk on shard0. - const dbName = "testShardVersions"; - const collName = "fooShardVersions"; - const ns = dbName + "." + collName; - assert.commandWorked(st.s.adminCommand({enableSharding: dbName})); - st.ensurePrimaryShard(dbName, st.shard0.shardName); - assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {x: 1}})); - - const minKeyShardDB = st.rs0.getPrimary().getDB(dbName); - assert.commandWorked(minKeyShardDB.setProfilingLevel(2)); - - // Refining the shard key should internally retry on a stale epoch error for listIndexes and - // succeed. - assert.commandWorked(minKeyShardDB.adminCommand({ - configureFailPoint: "failCommand", - mode: {times: 5}, - data: { - errorCode: ErrorCodes.StaleEpoch, - failCommands: ["listIndexes"], - failInternalCommands: true - } - })); - assert.commandWorked(st.s.getCollection(ns).createIndex({x: 1, y: 1})); - assert.commandWorked(st.s.adminCommand({refineCollectionShardKey: ns, key: {x: 1, y: 1}})); - - // Refining the shard key should internally retry on a stale epoch error for checkShardingIndex - // and succeed. - assert.commandWorked(minKeyShardDB.adminCommand({ - configureFailPoint: "failCommand", - mode: {times: 5}, - data: { - errorCode: ErrorCodes.StaleEpoch, - failCommands: ["checkShardingIndex"], - failInternalCommands: true - } - })); - assert.commandWorked(st.s.getCollection(ns).createIndex({x: 1, y: 1, z: 1})); - assert.commandWorked( - st.s.adminCommand({refineCollectionShardKey: ns, key: {x: 1, y: 1, z: 1}})); - - // Verify both commands were sent with shard versions through the profiler. - profilerHasAtLeastOneMatchingEntryOrThrow({ - profileDB: minKeyShardDB, - filter: {"command.listIndexes": collName, "command.shardVersion": {"$exists": true}} - }); - - profilerHasAtLeastOneMatchingEntryOrThrow({ - profileDB: minKeyShardDB, - filter: {"command.checkShardingIndex": ns, "command.shardVersion": {"$exists": true}} - }); - - // Clean up. - assert.commandWorked(minKeyShardDB.setProfilingLevel(0)); - assert(minKeyShardDB.system.profile.drop()); -})(); +// TODO: remove once 7.0 becomes last-lts. +const fcvDoc = assert.commandWorked( + st.configRS.getPrimary().adminCommand({getParameter: 1, featureCompatibilityVersion: 1})); +if (fcvDoc.featureCompatibilityVersion.version == lastLTSFCV && + !jsTestOptions().shardMixedBinVersions) { + (() => { + // + // Verify listIndexes and checkShardingIndexes are retried on shard version errors and are + // sent with shard versions. + // + + // Create a sharded collection with one chunk on shard0. + const dbName = "testShardVersions"; + const collName = "fooShardVersions"; + const ns = dbName + "." + collName; + assert.commandWorked(st.s.adminCommand({enableSharding: dbName})); + st.ensurePrimaryShard(dbName, st.shard0.shardName); + assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {x: 1}})); + + const minKeyShardDB = st.rs0.getPrimary().getDB(dbName); + assert.commandWorked(minKeyShardDB.setProfilingLevel(2)); + + // Refining the shard key should internally retry on a stale epoch error for listIndexes and + // succeed. + assert.commandWorked(minKeyShardDB.adminCommand({ + configureFailPoint: "failCommand", + mode: {times: 5}, + data: { + errorCode: ErrorCodes.StaleEpoch, + failCommands: ["listIndexes"], + failInternalCommands: true + } + })); + assert.commandWorked(st.s.getCollection(ns).createIndex({x: 1, y: 1})); + assert.commandWorked(st.s.adminCommand({refineCollectionShardKey: ns, key: {x: 1, y: 1}})); + + // Refining the shard key should internally retry on a stale epoch error for + // checkShardingIndex and succeed. + assert.commandWorked(minKeyShardDB.adminCommand({ + configureFailPoint: "failCommand", + mode: {times: 5}, + data: { + errorCode: ErrorCodes.StaleEpoch, + failCommands: ["checkShardingIndex"], + failInternalCommands: true + } + })); + assert.commandWorked(st.s.getCollection(ns).createIndex({x: 1, y: 1, z: 1})); + assert.commandWorked( + st.s.adminCommand({refineCollectionShardKey: ns, key: {x: 1, y: 1, z: 1}})); + + // Verify both commands were sent with shard versions through the profiler. + profilerHasAtLeastOneMatchingEntryOrThrow({ + profileDB: minKeyShardDB, + filter: {"command.listIndexes": collName, "command.shardVersion": {"$exists": true}} + }); + + profilerHasAtLeastOneMatchingEntryOrThrow({ + profileDB: minKeyShardDB, + filter: {"command.checkShardingIndex": ns, "command.shardVersion": {"$exists": true}} + }); + + // Clean up. + assert.commandWorked(minKeyShardDB.setProfilingLevel(0)); + assert(minKeyShardDB.system.profile.drop()); + })(); +} // Assumes the given arrays are sorted by the max field. function compareMinAndMaxFields(shardedArr, refinedArr) { diff --git a/src/mongo/db/s/SConscript b/src/mongo/db/s/SConscript index 0580a9aa471..a74c29d22fc 100644 --- a/src/mongo/db/s/SConscript +++ b/src/mongo/db/s/SConscript @@ -544,6 +544,7 @@ env.Library( 'shardsvr_set_cluster_parameter_command.cpp', 'shardsvr_set_user_write_block_mode_command.cpp', 'shardsvr_split_chunk_command.cpp', + 'shardsvr_validate_shard_key_candidate.cpp', 'split_vector_command.cpp', 'txn_two_phase_commit_cmds.cpp', 'wait_for_ongoing_chunk_splits_command.cpp', diff --git a/src/mongo/db/s/config/configsvr_refine_collection_shard_key_command.cpp b/src/mongo/db/s/config/configsvr_refine_collection_shard_key_command.cpp index e952b0e9cd4..559e71b0c40 100644 --- a/src/mongo/db/s/config/configsvr_refine_collection_shard_key_command.cpp +++ b/src/mongo/db/s/config/configsvr_refine_collection_shard_key_command.cpp @@ -34,6 +34,7 @@ #include "mongo/db/repl/repl_client_info.h" #include "mongo/db/s/config/sharding_catalog_manager.h" #include "mongo/db/s/shard_key_util.h" +#include "mongo/db/s/sharding_util.h" #include "mongo/logv2/log.h" #include "mongo/s/grid.h" #include "mongo/s/request_types/sharded_ddl_commands_gen.h" @@ -114,26 +115,56 @@ public: << oldShardKeyPattern.toString(), oldShardKeyPattern.isExtendedBy(newShardKeyPattern)); - // Indexes are loaded using shard versions, so validating the shard key may need to be - // retried on StaleConfig errors. - auto catalogCache = Grid::get(opCtx)->catalogCache(); - shardVersionRetry(opCtx, - catalogCache, - nss, - "validating indexes for refineCollectionShardKey"_sd, - [&] { - // Note a shard key index will never be created automatically for - // refining a shard key, so no default collation is needed. - shardkeyutil::validateShardKeyIndexExistsOrCreateIfPossible( - opCtx, - nss, - newShardKeyPattern, - boost::none, - collType.getUnique(), - request().getEnforceUniquenessCheck().value_or(true), - shardkeyutil::ValidationBehaviorsRefineShardKey(opCtx, nss)); - }); - + { + // Indexes are loaded using shard versions, so validating the shard key may need to + // be retried on StaleConfig errors. + auto catalogCache = Grid::get(opCtx)->catalogCache(); + shardVersionRetry( + opCtx, + catalogCache, + nss, + "validating indexes for refineCollectionShardKey"_sd, + [&] { + auto cm = + uassertStatusOK(catalogCache->getCollectionPlacementInfo(opCtx, nss)); + std::set<ShardId> shardsIds; + + cm.getAllShardIds(&shardsIds); + std::vector<ShardId> shardsIdsVec{shardsIds.begin(), shardsIds.end()}; + + ShardsvrValidateShardKeyCandidate validateRequest(nss); + validateRequest.setKey(newShardKeyPattern.getKeyPattern()); + validateRequest.setEnforceUniquenessCheck( + request().getEnforceUniquenessCheck()); + validateRequest.setDbName(NamespaceString::kAdminDb); + try { + sharding_util::sendCommandToShardsWithVersion( + opCtx, + nss.db(), + validateRequest.toBSON({}), + shardsIdsVec, + Grid::get(opCtx)->getExecutorPool()->getFixedExecutor(), + uassertStatusOK(catalogCache->getCollectionRoutingInfo(opCtx, nss)), + true); + return; + } catch (const DBException& ex) { + if (ex.code() != ErrorCodes::CommandNotFound) { + throw; + } + } + // Fallback mode, use lastLTS way. + // Note a shard key index will never be created automatically for + // refining a shard key, so no default collation is needed. + shardkeyutil::validateShardKeyIndexExistsOrCreateIfPossible( + opCtx, + nss, + newShardKeyPattern, + boost::none, + collType.getUnique(), + request().getEnforceUniquenessCheck().value_or(true), + shardkeyutil::ValidationBehaviorsRefineShardKey(opCtx, nss)); + }); + } LOGV2(21922, "CMD: refineCollectionShardKey: {request}", "CMD: refineCollectionShardKey", diff --git a/src/mongo/db/s/shard_key_util.cpp b/src/mongo/db/s/shard_key_util.cpp index da88bb914a5..8bc0929970a 100644 --- a/src/mongo/db/s/shard_key_util.cpp +++ b/src/mongo/db/s/shard_key_util.cpp @@ -37,6 +37,7 @@ #include "mongo/db/hasher.h" #include "mongo/db/index/index_descriptor.h" #include "mongo/db/query/collation/collator_factory_interface.h" +#include "mongo/db/s/shard_key_index_util.h" #include "mongo/rpc/get_status_from_command_result.h" #include "mongo/s/cluster_commands_helpers.h" #include "mongo/s/grid.h" @@ -364,7 +365,7 @@ void ValidationBehaviorsRefineShardKey::verifyCanCreateShardKeyIndex(const Names uasserted( ErrorCodes::InvalidOptions, str::stream() << "Please create an index that starts with the proposed shard key before" - " sharding the collection. " + " refining the collection's shard key. " << *errMsg); } @@ -376,5 +377,53 @@ void ValidationBehaviorsRefineShardKey::createShardKeyIndex( MONGO_UNREACHABLE; } +ValidationBehaviorsLocalRefineShardKey::ValidationBehaviorsLocalRefineShardKey( + OperationContext* opCtx, const CollectionPtr& coll) + : _opCtx(opCtx), _coll(coll) {} + + +std::vector<BSONObj> ValidationBehaviorsLocalRefineShardKey::loadIndexes( + const NamespaceString& nss) const { + std::vector<BSONObj> indexes; + auto it = _coll->getIndexCatalog()->getIndexIterator( + _opCtx, mongo::IndexCatalog::InclusionPolicy::kReady); + while (it->more()) { + auto entry = it->next(); + indexes.push_back(entry->descriptor()->toBSON()); + } + return indexes; +} + +void ValidationBehaviorsLocalRefineShardKey::verifyUsefulNonMultiKeyIndex( + const NamespaceString& nss, const BSONObj& proposedKey) const { + std::string tmpErrMsg = "couldn't find valid index for shard key"; + uassert(ErrorCodes::InvalidOptions, + tmpErrMsg, + findShardKeyPrefixedIndex(_opCtx, + _coll, + _coll->getIndexCatalog(), + proposedKey, + /*requireSingleKey=*/true, + &tmpErrMsg)); +} + +void ValidationBehaviorsLocalRefineShardKey::verifyCanCreateShardKeyIndex( + const NamespaceString& nss, std::string* errMsg) const { + uasserted( + ErrorCodes::InvalidOptions, + str::stream() << "Please create an index that starts with the proposed shard key before" + " refining the collection's shard key. " + << *errMsg); +} + +void ValidationBehaviorsLocalRefineShardKey::createShardKeyIndex( + const NamespaceString& nss, + const BSONObj& proposedKey, + const boost::optional<BSONObj>& defaultCollation, + bool unique) const { + MONGO_UNREACHABLE; +} + + } // namespace shardkeyutil } // namespace mongo diff --git a/src/mongo/db/s/shard_key_util.h b/src/mongo/db/s/shard_key_util.h index 9aa5926b271..82dbb3ee134 100644 --- a/src/mongo/db/s/shard_key_util.h +++ b/src/mongo/db/s/shard_key_util.h @@ -115,6 +115,32 @@ private: }; /** + * Implementation of steps for validating a shard key for refineCollectionShardKey locally. + */ +class ValidationBehaviorsLocalRefineShardKey final : public ShardKeyValidationBehaviors { +public: + ValidationBehaviorsLocalRefineShardKey(OperationContext* opCtx, const CollectionPtr& coll); + + std::vector<BSONObj> loadIndexes(const NamespaceString& nss) const override; + + void verifyUsefulNonMultiKeyIndex(const NamespaceString& nss, + const BSONObj& proposedKey) const override; + + void verifyCanCreateShardKeyIndex(const NamespaceString& nss, + std::string* errMsg) const override; + + void createShardKeyIndex(const NamespaceString& nss, + const BSONObj& proposedKey, + const boost::optional<BSONObj>& defaultCollation, + bool unique) const override; + +private: + OperationContext* _opCtx; + + const CollectionPtr& _coll; +}; + +/** * Compares the proposed shard key with the collection's existing indexes to ensure they are a legal * combination. * diff --git a/src/mongo/db/s/sharding_util.cpp b/src/mongo/db/s/sharding_util.cpp index 41c93ddec39..b74d1867e66 100644 --- a/src/mongo/db/s/sharding_util.cpp +++ b/src/mongo/db/s/sharding_util.cpp @@ -40,6 +40,7 @@ #include "mongo/logv2/log.h" #include "mongo/s/catalog/type_collection.h" #include "mongo/s/catalog/type_index_catalog_gen.h" +#include "mongo/s/cluster_commands_helpers.h" #include "mongo/s/request_types/flush_routing_table_cache_updates_gen.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kSharding @@ -61,17 +62,13 @@ void tellShardsToRefreshCollection(OperationContext* opCtx, sendCommandToShards(opCtx, NamespaceString::kAdminDb, cmdObj, shardIds, executor); } -std::vector<AsyncRequestsSender::Response> sendCommandToShards( +std::vector<AsyncRequestsSender::Response> processShardResponses( OperationContext* opCtx, StringData dbName, const BSONObj& command, - const std::vector<ShardId>& shardIds, + const std::vector<AsyncRequestsSender::Request>& requests, const std::shared_ptr<executor::TaskExecutor>& executor, - const bool throwOnError) { - std::vector<AsyncRequestsSender::Request> requests; - for (const auto& shardId : shardIds) { - requests.emplace_back(shardId, command); - } + bool throwOnError) { std::vector<AsyncRequestsSender::Response> responses; if (!requests.empty()) { @@ -113,6 +110,37 @@ std::vector<AsyncRequestsSender::Response> sendCommandToShards( return responses; } +std::vector<AsyncRequestsSender::Response> sendCommandToShards( + OperationContext* opCtx, + StringData dbName, + const BSONObj& command, + const std::vector<ShardId>& shardIds, + const std::shared_ptr<executor::TaskExecutor>& executor, + const bool throwOnError) { + std::vector<AsyncRequestsSender::Request> requests; + for (const auto& shardId : shardIds) { + requests.emplace_back(shardId, command); + } + + return processShardResponses(opCtx, dbName, command, requests, executor, throwOnError); +} + +std::vector<AsyncRequestsSender::Response> sendCommandToShardsWithVersion( + OperationContext* opCtx, + StringData dbName, + const BSONObj& command, + const std::vector<ShardId>& shardIds, + const std::shared_ptr<executor::TaskExecutor>& executor, + const CollectionRoutingInfo& cri, + const bool throwOnError) { + std::vector<AsyncRequestsSender::Request> requests; + for (const auto& shardId : shardIds) { + requests.emplace_back(shardId, appendShardVersion(command, cri.getShardVersion(shardId))); + } + return processShardResponses(opCtx, dbName, command, requests, executor, throwOnError); +} + + // TODO SERVER-67593: Investigate if DBDirectClient can be used instead. Status createIndexOnCollection(OperationContext* opCtx, const NamespaceString& ns, diff --git a/src/mongo/db/s/sharding_util.h b/src/mongo/db/s/sharding_util.h index e968a3a5b7c..a1cd0009313 100644 --- a/src/mongo/db/s/sharding_util.h +++ b/src/mongo/db/s/sharding_util.h @@ -36,6 +36,7 @@ #include "mongo/db/shard_id.h" #include "mongo/executor/task_executor.h" #include "mongo/s/async_requests_sender.h" +#include "mongo/s/catalog_cache.h" namespace mongo { namespace sharding_util { @@ -62,6 +63,19 @@ std::vector<AsyncRequestsSender::Response> sendCommandToShards( bool throwOnError = true); /** + * Generic utility to send a command to a list of shards attaching the shard version to the request. + * If `throwOnError=true`, throws in case one of the commands fails. + */ +std::vector<AsyncRequestsSender::Response> sendCommandToShardsWithVersion( + OperationContext* opCtx, + StringData dbName, + const BSONObj& command, + const std::vector<ShardId>& shardIds, + const std::shared_ptr<executor::TaskExecutor>& executor, + const CollectionRoutingInfo& cri, + bool throwOnError = true); + +/** * Creates the necessary indexes for the globalIndexes collections. */ Status createGlobalIndexesIndexes(OperationContext* opCtx); diff --git a/src/mongo/db/s/shardsvr_validate_shard_key_candidate.cpp b/src/mongo/db/s/shardsvr_validate_shard_key_candidate.cpp new file mode 100644 index 00000000000..fc61a0113e0 --- /dev/null +++ b/src/mongo/db/s/shardsvr_validate_shard_key_candidate.cpp @@ -0,0 +1,112 @@ +/** + * 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. + */ + + +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/commands.h" +#include "mongo/db/db_raii.h" +#include "mongo/db/s/shard_key_util.h" +#include "mongo/db/s/sharding_state.h" +#include "mongo/logv2/log.h" +#include "mongo/s/grid.h" +#include "mongo/s/request_types/sharded_ddl_commands_gen.h" + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kSharding + +namespace mongo { +namespace { + +class ShardsvrValidateShardKeyCandidateCommand final + : public TypedCommand<ShardsvrValidateShardKeyCandidateCommand> { +public: + AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { + return Command::AllowedOnSecondary::kNever; + } + + bool skipApiVersionCheck() const override { + // Internal command (server to server). + return true; + } + + std::string help() const override { + return "Internal command, which is exported by the primary sharding server. Do not call " + "directly. Validates a collection shard key candidate."; + } + + using Request = ShardsvrValidateShardKeyCandidate; + + class Invocation final : public InvocationBase { + public: + using InvocationBase::InvocationBase; + + void typedRun(OperationContext* opCtx) { + + const ShardKeyPattern keyPattern(request().getKey()); + uassertStatusOK(ShardingState::get(opCtx)->canAcceptShardedCommands()); + + const auto cm = uassertStatusOK( + Grid::get(opCtx)->catalogCache()->getCollectionPlacementInfo(opCtx, ns())); + { + AutoGetCollectionForReadCommandMaybeLockFree coll{ + opCtx, + ns(), + AutoGetCollection::Options{}.viewMode( + auto_get_collection::ViewMode::kViewsForbidden)}; + + shardkeyutil::validateShardKeyIndexExistsOrCreateIfPossible( + opCtx, + ns(), + keyPattern, + boost::none, + cm.isUnique(), + request().getEnforceUniquenessCheck().value_or(true), + shardkeyutil::ValidationBehaviorsLocalRefineShardKey(opCtx, + coll.getCollection())); + } + shardkeyutil::validateShardKeyIsNotEncrypted(opCtx, ns(), keyPattern); + } + + bool supportsWriteConcern() const override { + return false; + } + + void doCheckAuthorization(OperationContext*) const override {} + + /** + * The ns() for when Request's IDL specifies "namespace: concatenate_with_db". + */ + NamespaceString ns() const override { + return request().getCommandParameter(); + } + }; + +} shardsvrValidateShardKeyCandidateCommand; + +} // namespace +} // namespace mongo diff --git a/src/mongo/s/request_types/sharded_ddl_commands.idl b/src/mongo/s/request_types/sharded_ddl_commands.idl index 95d62f2113c..024ba2b31cd 100644 --- a/src/mongo/s/request_types/sharded_ddl_commands.idl +++ b/src/mongo/s/request_types/sharded_ddl_commands.idl @@ -403,6 +403,24 @@ commands: strict: false chained_structs: RefineCollectionShardKeyRequest: RefineCollectionShardKeyRequest + + _shardsvrValidateShardKeyCandidate: + description: "Command run on shards to ensure we have a viable key to refine the shard key." + command_name: _shardsvrValidateShardKeyCandidate + cpp_name: ShardsvrValidateShardKeyCandidate + strict: false + namespace: type + api_version: "" + type: namespacestring + fields: + key: + type: KeyPattern + description: "The index specification document to use as the new shard key." + optional: false + enforceUniquenessCheck: + description: >- + Verifies that the shard key index has the same unique setting as the command. + type: optionalBool _configsvrRefineCollectionShardKey: command_name: _configsvrRefineCollectionShardKey |