summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHaley Connelly <haley.connelly@mongodb.com>2021-08-02 23:12:00 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-10-14 21:58:19 +0000
commit5bafc36d26421b80101a45aab2ce6bf3ec30b1f5 (patch)
tree702dd2d4ada5e811ae28048ae9e263673196645d
parent545f68ac4ceb4c7f88dec633a1a6eb96c922c9fd (diff)
downloadmongo-5bafc36d26421b80101a45aab2ce6bf3ec30b1f5.tar.gz
SERVER-56887 setIndexCommitQuorum command does not return {ok: 1} when run against a mongos on a non-existent index build
(cherry picked from commit d0f6a9a599a81e5563c353177a1e7c2df9c21302)
-rw-r--r--jstests/sharding/database_versioning_all_commands.js8
-rw-r--r--jstests/sharding/set_index_commit_quorum_through_mongos.js118
-rw-r--r--src/mongo/s/commands/cluster_set_index_commit_quorum_cmd.cpp87
3 files changed, 176 insertions, 37 deletions
diff --git a/jstests/sharding/database_versioning_all_commands.js b/jstests/sharding/database_versioning_all_commands.js
index d932a2f8312..2121fdcc632 100644
--- a/jstests/sharding/database_versioning_all_commands.js
+++ b/jstests/sharding/database_versioning_all_commands.js
@@ -117,6 +117,9 @@ function testCommandAfterMovePrimary(testCase, st, dbName, collName) {
// Run the test case's command.
if (testCase.runsAgainstAdminDb) {
assert.commandWorked(st.s0.adminCommand(command));
+ } else if (testCase.expectedFailureCode) {
+ assert.commandFailedWithCode(st.s0.getDB(dbName).runCommand(command),
+ testCase.expectedFailureCode);
} else {
assert.commandWorked(st.s0.getDB(dbName).runCommand(command));
}
@@ -198,6 +201,9 @@ function testCommandAfterDropRecreateDatabase(testCase, st) {
// Run the test case's command.
if (testCase.runsAgainstAdminDb) {
assert.commandWorked(st.s0.adminCommand(command));
+ } else if (testCase.expectedFailureCode) {
+ assert.commandFailedWithCode(st.s0.getDB(dbName).runCommand(command),
+ testCase.expectedFailureCode);
} else {
assert.commandWorked(st.s0.getDB(dbName).runCommand(command));
}
@@ -606,6 +612,8 @@ let testCases = {
run: {
sendsDbVersion: true,
explicitlyCreateCollection: true,
+ // The command should fail if there is no active index build on the collection.
+ expectedFailureCode: ErrorCodes.IndexNotFound,
command: function(dbName, collName) {
return {
setIndexCommitQuorum: collName,
diff --git a/jstests/sharding/set_index_commit_quorum_through_mongos.js b/jstests/sharding/set_index_commit_quorum_through_mongos.js
new file mode 100644
index 00000000000..fabcc3e161b
--- /dev/null
+++ b/jstests/sharding/set_index_commit_quorum_through_mongos.js
@@ -0,0 +1,118 @@
+/**
+ * Tests the behavior of setIndexCommitQuorum when routed through a mongos.
+ *
+ * @tags: [requires_fcv_44]
+ */
+(function() {
+"use strict";
+load("jstests/libs/fail_point_util.js");
+load('jstests/libs/parallelTester.js');
+
+const st = new ShardingTest({shards: 2, mongos: 1, rs: {nodes: 2}});
+const dbName = "testDB";
+const collName = "coll";
+const mongosDB = st.s0.getDB(dbName);
+const shard0 = st.shard0.rs.getPrimary();
+
+const generateCreateIndexThread = (host, dbName, collName) => {
+ return new Thread(function(host, dbName, collName) {
+ const mongos = new Mongo(host);
+ const db = mongos.getDB(dbName);
+ // Use the index builds coordinator for a two-phase index build.
+ assert.commandWorked(db.runCommand({
+ createIndexes: collName,
+ indexes: [{key: {idxKey: 1}, name: 'idxKey_1'}],
+ commitQuorum: "majority"
+ }));
+ }, host, dbName, collName);
+};
+
+// Create a sharded collection with primary shard shard0.
+const shardCollectionWithPrimaryShard0 = (dbName, collName, shardKeyPattern) => {
+ assert.commandWorked(st.s.adminCommand({enableSharding: dbName}));
+ st.ensurePrimaryShard(dbName, st.shard0.shardName);
+ assert.commandWorked(st.s.adminCommand(
+ {shardCollection: mongosDB[collName].getFullName(), key: shardKeyPattern}));
+};
+
+// When setIndexCommitQuorum is sent by the mongos, the response follows the format:
+// {
+// raw: {
+// "<shard0URL>": {
+// <response from setIndexCommitQuorum being run on shard>
+// },
+// "<shard1URL>": {....}
+// }
+// ok: <>
+// ...
+// }
+//
+// Returns the shard's corresponding entry in the "raw" field of the mongosResponse.
+const extractShardReplyFromResponse = (shard, mongosResponse) => {
+ assert(mongosResponse.raw);
+ const shardURL = shard.rs.getURL();
+ return mongosResponse.raw[shardURL];
+};
+
+jsTest.log("Testing setIndexCommitQuorum from a mongos can succeed");
+
+// Create a sharded collection where only shard0 owns chunks.
+shardCollectionWithPrimaryShard0(dbName, collName, {_id: 1});
+assert.commandWorked(mongosDB[collName].insert({_id: 1}));
+
+let createIndexThread = generateCreateIndexThread(st.s0.host, dbName, collName);
+let createIndexFailpoint = configureFailPoint(shard0, "hangAfterIndexBuildFirstDrain");
+createIndexThread.start();
+createIndexFailpoint.wait();
+
+// Confirm mongos succeeds when only one shard owns chunks (and data for the collection).
+assert.commandWorked(mongosDB.runCommand(
+ {setIndexCommitQuorum: collName, indexNames: ["idxKey_1"], commitQuorum: 2}));
+createIndexFailpoint.off();
+createIndexThread.join();
+assert(mongosDB[collName].drop());
+
+jsTest.log("Testing setIndexCommitQuorum from a mongos fails but reports partial success");
+// In the event that setIndexCommitQuorum succeeds on one shard, but fails on another, the mongos
+// setIndexCommitQuorum should return an error and include information on what shards
+// failed/succeeded.
+
+// Create a sharded collection where both shards own chunks but only shard0 bears data.
+shardCollectionWithPrimaryShard0(dbName, collName, {_id: 1});
+const ns = mongosDB[collName].getFullName();
+
+// Chunk distribution - shard0: [minKey, 10), shard1: [10, maxKey).
+assert.commandWorked(st.s.adminCommand({split: ns, middle: {_id: 10}}));
+assert.commandWorked(st.s.adminCommand(
+ {movechunk: ns, find: {_id: 10}, to: st.shard1.shardName, waitForDelete: true}));
+
+// Insert data into shard0 only.
+assert.commandWorked(mongosDB[collName].insert({_id: 1}));
+
+createIndexThread = generateCreateIndexThread(st.s0.host, dbName, collName);
+createIndexFailpoint = configureFailPoint(shard0, "hangAfterIndexBuildFirstDrain");
+
+createIndexThread.start();
+createIndexFailpoint.wait();
+
+const res = assert.commandFailedWithCode(
+ mongosDB.runCommand(
+ {setIndexCommitQuorum: collName, indexNames: ["idxKey_1"], commitQuorum: 2}),
+ ErrorCodes.IndexNotFound);
+
+// Confirm the mongos reports shard0 successfully set its index commit quorum despite the mongos
+// command failing.
+const shard0Reply = extractShardReplyFromResponse(st.shard0, res);
+assert.eq(shard0Reply.ok, 1);
+
+// Confirm shard1 caused the command to fail since it didn't actually own any data for the
+// collection.
+const shard1Reply = extractShardReplyFromResponse(st.shard1, res);
+assert.eq(shard1Reply.ok, 0);
+assert.eq(shard1Reply.code, ErrorCodes.IndexNotFound);
+
+createIndexFailpoint.off();
+createIndexThread.join();
+
+st.stop();
+}());
diff --git a/src/mongo/s/commands/cluster_set_index_commit_quorum_cmd.cpp b/src/mongo/s/commands/cluster_set_index_commit_quorum_cmd.cpp
index 974510960e2..8727a9da078 100644
--- a/src/mongo/s/commands/cluster_set_index_commit_quorum_cmd.cpp
+++ b/src/mongo/s/commands/cluster_set_index_commit_quorum_cmd.cpp
@@ -36,9 +36,9 @@
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/commands.h"
-#include "mongo/db/commands/set_index_commit_quorum_gen.h"
#include "mongo/logv2/log.h"
#include "mongo/s/cluster_commands_helpers.h"
+#include "mongo/s/grid.h"
namespace mongo {
namespace {
@@ -53,9 +53,9 @@ namespace {
* commitQuorum: "majority" / 3 / {"replTagName": "replTagValue"},
* }
*/
-class SetIndexCommitQuorumCommand final : public TypedCommand<SetIndexCommitQuorumCommand> {
+class SetIndexCommitQuorumCommand : public BasicCommand {
public:
- using Request = SetIndexCommitQuorum;
+ SetIndexCommitQuorumCommand() : BasicCommand("setIndexCommitQuorum") {}
std::string help() const override {
std::stringstream ss;
@@ -79,45 +79,58 @@ public:
return AllowedOnSecondary::kNever;
}
- class Invocation final : public InvocationBase {
- public:
- using InvocationBase::InvocationBase;
-
- void typedRun(OperationContext* opCtx) {
- BSONObj cmdObj = request().toBSON(BSONObj());
- LOGV2_DEBUG(22757,
- 1,
- "setIndexCommitQuorum",
- "namespace"_attr = request().getNamespace(),
- "command"_attr = redact(cmdObj));
-
- scatterGatherOnlyVersionIfUnsharded(
- opCtx,
- request().getNamespace(),
- applyReadWriteConcern(
- opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmdObj)),
- ReadPreferenceSetting::get(opCtx),
- Shard::RetryPolicy::kNotIdempotent);
- }
+ bool supportsWriteConcern(const BSONObj& cmd) const final {
+ return true;
+ }
- private:
- NamespaceString ns() const override {
- return request().getNamespace();
+ Status checkAuthForOperation(OperationContext* opCtx,
+ const std::string& dbName,
+ const BSONObj& cmdObj) const override {
+ const NamespaceString nss(CommandHelpers::parseNsCollectionRequired(dbName, cmdObj));
+ if (!AuthorizationSession::get(opCtx->getClient())
+ ->isAuthorizedForActionsOnResource(ResourcePattern::forExactNamespace(nss),
+ ActionType::createIndex)) {
+ return Status(ErrorCodes::Unauthorized, "Unauthorized");
}
+ return Status::OK();
+ }
- bool supportsWriteConcern() const override {
- return true;
+ bool run(OperationContext* opCtx,
+ const std::string& dbName,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result) override {
+ const NamespaceString nss(CommandHelpers::parseNsCollectionRequired(dbName, cmdObj));
+ LOGV2_DEBUG(22757,
+ 1,
+ "setIndexCommitQuorum",
+ "namespace"_attr = nss,
+ "command"_attr = redact(cmdObj));
+
+ auto routingInfo =
+ uassertStatusOK(Grid::get(opCtx)->catalogCache()->getCollectionRoutingInfo(opCtx, nss));
+ auto shardResponses = scatterGatherVersionedTargetByRoutingTable(
+ opCtx,
+ nss.db(),
+ nss,
+ routingInfo,
+ applyReadWriteConcern(
+ opCtx, this, CommandHelpers::filterCommandRequestForPassthrough(cmdObj)),
+ ReadPreferenceSetting::get(opCtx),
+ Shard::RetryPolicy::kNotIdempotent,
+ BSONObj() /* query */,
+ BSONObj() /* collation */);
+
+ std::string errmsg;
+ const bool ok =
+ appendRawResponses(opCtx, &errmsg, &result, std::move(shardResponses)).responseOK;
+ CommandHelpers::appendSimpleCommandStatus(result, ok, errmsg);
+
+ if (ok) {
+ LOGV2(5688700, "Index commit quorums set", "namespace"_attr = nss);
}
- void doCheckAuthorization(OperationContext* opCtx) const override {
- uassert(ErrorCodes::Unauthorized,
- "Unauthorized",
- AuthorizationSession::get(opCtx->getClient())
- ->isAuthorizedForActionsOnResource(
- ResourcePattern::forExactNamespace(request().getNamespace()),
- ActionType::createIndex));
- }
- };
+ return ok;
+ }
} setCommitQuorumCmd;