summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcos José Grillo Ramirez <marcos.grillo@mongodb.com>2022-12-28 17:50:01 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-12-28 18:32:16 +0000
commit07b5f334c547b5ab254e2952ae7b653fa9a8bcb4 (patch)
tree41a4aed1db9f248bfd7eebaa4e641145660e853e
parentd0d9e95c07df276f4fa3cef07982803d689da5c7 (diff)
downloadmongo-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.js1
-rw-r--r--jstests/replsets/all_commands_downgrading_to_upgraded.js1
-rw-r--r--jstests/replsets/db_reads_while_recovering_all_commands.js1
-rw-r--r--jstests/sharding/read_write_concern_defaults_application.js1
-rw-r--r--jstests/sharding/refine_collection_shard_key_basic.js128
-rw-r--r--src/mongo/db/s/SConscript1
-rw-r--r--src/mongo/db/s/config/configsvr_refine_collection_shard_key_command.cpp71
-rw-r--r--src/mongo/db/s/shard_key_util.cpp51
-rw-r--r--src/mongo/db/s/shard_key_util.h26
-rw-r--r--src/mongo/db/s/sharding_util.cpp42
-rw-r--r--src/mongo/db/s/sharding_util.h14
-rw-r--r--src/mongo/db/s/shardsvr_validate_shard_key_candidate.cpp112
-rw-r--r--src/mongo/s/request_types/sharded_ddl_commands.idl18
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