summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/commands/user_management_commands.cpp189
-rw-r--r--src/mongo/db/write_concern.cpp21
-rw-r--r--src/mongo/db/write_concern_options.cpp4
-rw-r--r--src/mongo/db/write_concern_options.h6
-rw-r--r--src/mongo/s/catalog/replset/catalog_manager_replica_set.cpp45
-rw-r--r--src/mongo/s/catalog/replset/catalog_manager_replica_set_test.cpp82
-rw-r--r--src/mongo/s/cluster_write.cpp58
7 files changed, 292 insertions, 113 deletions
diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp
index d60c54b35f7..30387107ff4 100644
--- a/src/mongo/db/commands/user_management_commands.cpp
+++ b/src/mongo/db/commands/user_management_commands.cpp
@@ -532,14 +532,15 @@ Status removePrivilegeDocuments(OperationContext* txn,
*/
Status writeAuthSchemaVersionIfNeeded(OperationContext* txn,
AuthorizationManager* authzManager,
- int foundSchemaVersion) {
+ int foundSchemaVersion,
+ const BSONObj& writeConcern) {
Status status = updateOneAuthzDocument(
txn,
AuthorizationManager::versionCollectionNamespace,
AuthorizationManager::versionDocumentQuery,
BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName << foundSchemaVersion)),
- true, // upsert
- BSONObj()); // write concern
+ true, // upsert
+ writeConcern);
if (status == ErrorCodes::NoMatchingDocument) { // SERVER-11492
status = Status::OK();
}
@@ -552,7 +553,9 @@ Status writeAuthSchemaVersionIfNeeded(OperationContext* txn,
* for the MongoDB 2.6 and 3.0 MongoDB-CR/SCRAM mixed auth mode.
* Returns an error otherwise.
*/
-Status requireAuthSchemaVersion26Final(OperationContext* txn, AuthorizationManager* authzManager) {
+Status requireAuthSchemaVersion26Final(OperationContext* txn,
+ AuthorizationManager* authzManager,
+ const BSONObj& writeConcern) {
int foundSchemaVersion;
Status status = authzManager->getAuthorizationVersion(txn, &foundSchemaVersion);
if (!status.isOK()) {
@@ -567,7 +570,7 @@ Status requireAuthSchemaVersion26Final(OperationContext* txn, AuthorizationManag
<< AuthorizationManager::schemaVersion26Final << " but found "
<< foundSchemaVersion);
}
- return writeAuthSchemaVersionIfNeeded(txn, authzManager, foundSchemaVersion);
+ return writeAuthSchemaVersionIfNeeded(txn, authzManager, foundSchemaVersion, writeConcern);
}
/**
@@ -712,7 +715,7 @@ public:
stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
- status = requireAuthSchemaVersion26Final(txn, authzManager);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, args.writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
@@ -822,7 +825,7 @@ public:
stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- status = requireAuthSchemaVersion26Final(txn, authzManager);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, args.writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
@@ -886,26 +889,25 @@ public:
int options,
string& errmsg,
BSONObjBuilder& result) {
- ServiceContext* serviceContext = txn->getClient()->getServiceContext();
- stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
- AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- Status status = requireAuthSchemaVersion26Final(txn, authzManager);
+ UserName userName;
+ BSONObj writeConcern;
+ Status status =
+ auth::parseAndValidateDropUserCommand(cmdObj, dbname, &userName, &writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
-
- UserName userName;
- BSONObj writeConcern;
- status = auth::parseAndValidateDropUserCommand(cmdObj, dbname, &userName, &writeConcern);
+ ServiceContext* serviceContext = txn->getClient()->getServiceContext();
+ stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
+ AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
- int nMatched;
-
audit::logDropUser(ClientBasic::getCurrent(), userName);
+ int nMatched;
status = removePrivilegeDocuments(txn,
BSON(AuthorizationManager::USER_NAME_FIELD_NAME
<< userName.getUser()
@@ -959,26 +961,24 @@ public:
int options,
string& errmsg,
BSONObjBuilder& result) {
- ServiceContext* serviceContext = txn->getClient()->getServiceContext();
- stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
-
- AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- Status status = requireAuthSchemaVersion26Final(txn, authzManager);
+ BSONObj writeConcern;
+ Status status =
+ auth::parseAndValidateDropAllUsersFromDatabaseCommand(cmdObj, dbname, &writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
+ ServiceContext* serviceContext = txn->getClient()->getServiceContext();
+ stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
- BSONObj writeConcern;
- status =
- auth::parseAndValidateDropAllUsersFromDatabaseCommand(cmdObj, dbname, &writeConcern);
+ AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
- int numRemoved;
-
audit::logDropAllUsersFromDatabase(ClientBasic::getCurrent(), dbname);
+ int numRemoved;
status = removePrivilegeDocuments(txn,
BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname),
writeConcern,
@@ -1023,24 +1023,24 @@ public:
int options,
string& errmsg,
BSONObjBuilder& result) {
- ServiceContext* serviceContext = txn->getClient()->getServiceContext();
- stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
-
- AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- Status status = requireAuthSchemaVersion26Final(txn, authzManager);
- if (!status.isOK()) {
- return appendCommandStatus(result, status);
- }
-
std::string userNameString;
std::vector<RoleName> roles;
BSONObj writeConcern;
- status = auth::parseRolePossessionManipulationCommands(
+ Status status = auth::parseRolePossessionManipulationCommands(
cmdObj, "grantRolesToUser", dbname, &userNameString, &roles, &writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
+ ServiceContext* serviceContext = txn->getClient()->getServiceContext();
+ stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
+
+ AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, writeConcern);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
+ }
+
UserName userName(userNameString, dbname);
unordered_set<RoleName> userRoles;
status = getCurrentUserRoles(txn, authzManager, userName, &userRoles);
@@ -1098,24 +1098,24 @@ public:
int options,
string& errmsg,
BSONObjBuilder& result) {
- ServiceContext* serviceContext = txn->getClient()->getServiceContext();
- stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
-
- AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- Status status = requireAuthSchemaVersion26Final(txn, authzManager);
- if (!status.isOK()) {
- return appendCommandStatus(result, status);
- }
-
std::string userNameString;
std::vector<RoleName> roles;
BSONObj writeConcern;
- status = auth::parseRolePossessionManipulationCommands(
+ Status status = auth::parseRolePossessionManipulationCommands(
cmdObj, "revokeRolesFromUser", dbname, &userNameString, &roles, &writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
+ ServiceContext* serviceContext = txn->getClient()->getServiceContext();
+ stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
+
+ AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, writeConcern);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
+ }
+
UserName userName(userNameString, dbname);
unordered_set<RoleName> userRoles;
status = getCurrentUserRoles(txn, authzManager, userName, &userRoles);
@@ -1346,7 +1346,7 @@ public:
stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- status = requireAuthSchemaVersion26Final(txn, authzManager);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, args.writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
@@ -1430,7 +1430,7 @@ public:
stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- status = requireAuthSchemaVersion26Final(txn, authzManager);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, args.writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
@@ -1497,24 +1497,24 @@ public:
int options,
string& errmsg,
BSONObjBuilder& result) {
- ServiceContext* serviceContext = txn->getClient()->getServiceContext();
- stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
-
- AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- Status status = requireAuthSchemaVersion26Final(txn, authzManager);
- if (!status.isOK()) {
- return appendCommandStatus(result, status);
- }
-
RoleName roleName;
PrivilegeVector privilegesToAdd;
BSONObj writeConcern;
- status = auth::parseAndValidateRolePrivilegeManipulationCommands(
+ Status status = auth::parseAndValidateRolePrivilegeManipulationCommands(
cmdObj, "grantPrivilegesToRole", dbname, &roleName, &privilegesToAdd, &writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
+ ServiceContext* serviceContext = txn->getClient()->getServiceContext();
+ stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
+
+ AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, writeConcern);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
+ }
+
if (RoleGraph::isBuiltinRole(roleName)) {
return appendCommandStatus(
result,
@@ -1605,24 +1605,25 @@ public:
int options,
string& errmsg,
BSONObjBuilder& result) {
- ServiceContext* serviceContext = txn->getClient()->getServiceContext();
- stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
-
- AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- Status status = requireAuthSchemaVersion26Final(txn, authzManager);
+ RoleName roleName;
+ PrivilegeVector privilegesToRemove;
+ BSONObj writeConcern;
+ Status status =
+ auth::parseAndValidateRolePrivilegeManipulationCommands(cmdObj,
+ "revokePrivilegesFromRole",
+ dbname,
+ &roleName,
+ &privilegesToRemove,
+ &writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
- RoleName roleName;
- PrivilegeVector privilegesToRemove;
- BSONObj writeConcern;
- status = auth::parseAndValidateRolePrivilegeManipulationCommands(cmdObj,
- "revokePrivilegesFromRole",
- dbname,
- &roleName,
- &privilegesToRemove,
- &writeConcern);
+ ServiceContext* serviceContext = txn->getClient()->getServiceContext();
+ stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
+
+ AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
@@ -1742,7 +1743,7 @@ public:
stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- status = requireAuthSchemaVersion26Final(txn, authzManager);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
@@ -1815,24 +1816,24 @@ public:
int options,
string& errmsg,
BSONObjBuilder& result) {
- ServiceContext* serviceContext = txn->getClient()->getServiceContext();
- stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
-
- AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- Status status = requireAuthSchemaVersion26Final(txn, authzManager);
- if (!status.isOK()) {
- return appendCommandStatus(result, status);
- }
-
std::string roleNameString;
std::vector<RoleName> rolesToRemove;
BSONObj writeConcern;
- status = auth::parseRolePossessionManipulationCommands(
+ Status status = auth::parseRolePossessionManipulationCommands(
cmdObj, "revokeRolesFromRole", dbname, &roleNameString, &rolesToRemove, &writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
+ ServiceContext* serviceContext = txn->getClient()->getServiceContext();
+ stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
+
+ AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, writeConcern);
+ if (!status.isOK()) {
+ return appendCommandStatus(result, status);
+ }
+
RoleName roleName(roleNameString, dbname);
if (RoleGraph::isBuiltinRole(roleName)) {
return appendCommandStatus(
@@ -1907,18 +1908,18 @@ public:
int options,
string& errmsg,
BSONObjBuilder& result) {
- ServiceContext* serviceContext = txn->getClient()->getServiceContext();
- stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
-
- AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- Status status = requireAuthSchemaVersion26Final(txn, authzManager);
+ RoleName roleName;
+ BSONObj writeConcern;
+ Status status = auth::parseDropRoleCommand(cmdObj, dbname, &roleName, &writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
- RoleName roleName;
- BSONObj writeConcern;
- status = auth::parseDropRoleCommand(cmdObj, dbname, &roleName, &writeConcern);
+ ServiceContext* serviceContext = txn->getClient()->getServiceContext();
+ stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
+
+ AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
@@ -2072,7 +2073,7 @@ public:
stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- status = requireAuthSchemaVersion26Final(txn, authzManager);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
@@ -2697,7 +2698,7 @@ public:
stdx::lock_guard<stdx::mutex> lk(getAuthzDataMutex(serviceContext));
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
- status = requireAuthSchemaVersion26Final(txn, authzManager);
+ status = requireAuthSchemaVersion26Final(txn, authzManager, args.writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
diff --git a/src/mongo/db/write_concern.cpp b/src/mongo/db/write_concern.cpp
index f035b3f5bfd..64f2ad068db 100644
--- a/src/mongo/db/write_concern.cpp
+++ b/src/mongo/db/write_concern.cpp
@@ -130,13 +130,24 @@ Status validateWriteConcern(const WriteConcernOptions& writeConcern) {
const repl::ReplicationCoordinator::Mode replMode =
repl::getGlobalReplicationCoordinator()->getReplicationMode();
- if (isConfigServer && replMode != repl::ReplicationCoordinator::modeReplSet) {
- // SCCC config servers can have a master-slave oplog, but we still don't allow w > 1.
- if (writeConcern.wNumNodes > 1) {
- return Status(ErrorCodes::BadValue,
- "cannot use 'w' > 1 on a sync cluster connection config server host");
+ if (isConfigServer) {
+ if (!writeConcern.validForConfigServers()) {
+ return Status(
+ ErrorCodes::BadValue,
+ str::stream()
+ << "w:1 and w:'majority' are the only valid write concerns when writing to "
+ "config servers, got: " << writeConcern.toBSON().toString());
+ }
+ if (replMode == repl::ReplicationCoordinator::modeReplSet && writeConcern.wMode == "") {
+ invariant(writeConcern.wNumNodes == 1);
+ return Status(
+ ErrorCodes::BadValue,
+ str::stream()
+ << "w: 'majority' is the only valid write concern when writing to config "
+ "server replica sets, got: " << writeConcern.toBSON().toString());
}
}
+
if (replMode == repl::ReplicationCoordinator::modeNone) {
if (writeConcern.wNumNodes > 1) {
return Status(ErrorCodes::BadValue, "cannot use 'w' > 1 when a host is not replicated");
diff --git a/src/mongo/db/write_concern_options.cpp b/src/mongo/db/write_concern_options.cpp
index e24f652517d..b099d868a96 100644
--- a/src/mongo/db/write_concern_options.cpp
+++ b/src/mongo/db/write_concern_options.cpp
@@ -187,4 +187,8 @@ bool WriteConcernOptions::shouldWaitForOtherNodes() const {
return !wMode.empty() || wNumNodes > 1;
}
+bool WriteConcernOptions::validForConfigServers() const {
+ return wNumNodes == 1 || wMode == kMajority;
+}
+
} // namespace mongo
diff --git a/src/mongo/db/write_concern_options.h b/src/mongo/db/write_concern_options.h
index a0cb45f8da9..1bac963f16f 100644
--- a/src/mongo/db/write_concern_options.h
+++ b/src/mongo/db/write_concern_options.h
@@ -87,6 +87,12 @@ public:
*/
bool shouldWaitForOtherNodes() const;
+ /**
+ * Returns true if this is a valid write concern to use against a config server.
+ * TODO(spencer): Once we stop supporting SCCC config servers, forbid this from allowing w:1
+ */
+ bool validForConfigServers() const;
+
void reset() {
syncMode = NONE;
wNumNodes = 0;
diff --git a/src/mongo/s/catalog/replset/catalog_manager_replica_set.cpp b/src/mongo/s/catalog/replset/catalog_manager_replica_set.cpp
index acb2162f823..bb0c14fe7a0 100644
--- a/src/mongo/s/catalog/replset/catalog_manager_replica_set.cpp
+++ b/src/mongo/s/catalog/replset/catalog_manager_replica_set.cpp
@@ -867,6 +867,49 @@ bool CatalogManagerReplicaSet::runUserManagementWriteCommand(OperationContext* t
const std::string& dbname,
const BSONObj& cmdObj,
BSONObjBuilder* result) {
+ BSONObj cmdToRun = cmdObj;
+ {
+ // Make sure that the if the command has a write concern that it is w:1 or w:majority, and
+ // convert w:1 or no write concern to w:majority before sending.
+ WriteConcernOptions writeConcern;
+ const char* writeConcernFieldName = "writeConcern";
+ BSONElement writeConcernElement = cmdObj[writeConcernFieldName];
+ bool initialCmdHadWriteConcern = !writeConcernElement.eoo();
+ if (initialCmdHadWriteConcern) {
+ Status status = writeConcern.parse(writeConcernElement.Obj());
+ if (!status.isOK()) {
+ return Command::appendCommandStatus(*result, status);
+ }
+ if (!writeConcern.validForConfigServers()) {
+ return Command::appendCommandStatus(
+ *result,
+ Status(ErrorCodes::InvalidOptions,
+ str::stream()
+ << "Invalid replication write concern. Writes to config server "
+ "replica sets must use w:'majority', got: "
+ << writeConcern.toBSON()));
+ }
+ }
+ writeConcern.wMode = WriteConcernOptions::kMajority;
+ writeConcern.wNumNodes = 0;
+
+ BSONObjBuilder modifiedCmd;
+ if (!initialCmdHadWriteConcern) {
+ modifiedCmd.appendElements(cmdObj);
+ } else {
+ BSONObjIterator cmdObjIter(cmdObj);
+ while (cmdObjIter.more()) {
+ BSONElement e = cmdObjIter.next();
+ if (str::equals(e.fieldName(), writeConcernFieldName)) {
+ continue;
+ }
+ modifiedCmd.append(e);
+ }
+ }
+ modifiedCmd.append(writeConcernFieldName, writeConcern.toBSON());
+ cmdToRun = modifiedCmd.obj();
+ }
+
auto scopedDistLock =
getDistLockManager()->lock(txn, "authorizationData", commandName, Seconds{5});
if (!scopedDistLock.isOK()) {
@@ -874,7 +917,7 @@ bool CatalogManagerReplicaSet::runUserManagementWriteCommand(OperationContext* t
}
auto response =
- grid.shardRegistry()->runCommandOnConfigWithNotMasterRetries(txn, dbname, cmdObj);
+ grid.shardRegistry()->runCommandOnConfigWithNotMasterRetries(txn, dbname, cmdToRun);
if (!response.isOK()) {
return Command::appendCommandStatus(*result, response.getStatus());
}
diff --git a/src/mongo/s/catalog/replset/catalog_manager_replica_set_test.cpp b/src/mongo/s/catalog/replset/catalog_manager_replica_set_test.cpp
index 47dd2befb6b..e46c2582ea3 100644
--- a/src/mongo/s/catalog/replset/catalog_manager_replica_set_test.cpp
+++ b/src/mongo/s/catalog/replset/catalog_manager_replica_set_test.cpp
@@ -700,9 +700,84 @@ TEST_F(CatalogManagerReplSetTest, RunUserManagementWriteCommandSuccess) {
onCommand([](const RemoteCommandRequest& request) {
ASSERT_EQUALS("test", request.dbname);
+ // Since no write concern was sent we will add w:majority
ASSERT_EQUALS(BSON("dropUser"
<< "test"
- << "maxTimeMS" << 30000),
+ << "writeConcern" << BSON("w"
+ << "majority"
+ << "wtimeout" << 0) << "maxTimeMS" << 30000),
+ request.cmdObj);
+
+ ASSERT_EQUALS(BSON(rpc::kReplSetMetadataFieldName << 1), request.metadata);
+
+ BSONObjBuilder responseBuilder;
+ Command::appendCommandStatus(responseBuilder,
+ Status(ErrorCodes::UserNotFound, "User test@test not found"));
+ return responseBuilder.obj();
+ });
+
+ // Now wait for the runUserManagementWriteCommand call to return
+ future.timed_get(kFutureTimeout);
+}
+
+TEST_F(CatalogManagerReplSetTest, RunUserManagementWriteCommandInvalidWriteConcern) {
+ configTargeter()->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ BSONObjBuilder responseBuilder;
+ bool ok =
+ catalogManager()->runUserManagementWriteCommand(operationContext(),
+ "dropUser",
+ "test",
+ BSON("dropUser"
+ << "test"
+ << "writeConcern" << BSON("w" << 2)),
+ &responseBuilder);
+ ASSERT_FALSE(ok);
+
+ Status commandStatus = Command::getStatusFromCommandResult(responseBuilder.obj());
+ ASSERT_EQUALS(ErrorCodes::InvalidOptions, commandStatus);
+ ASSERT_STRING_CONTAINS(commandStatus.reason(), "Invalid replication write concern");
+}
+
+TEST_F(CatalogManagerReplSetTest, RunUserManagementWriteCommandRewriteWriteConcern) {
+ // Tests that if you send a w:1 write concern it gets replaced with w:majority
+ configTargeter()->setFindHostReturnValue(HostAndPort("TestHost1"));
+
+ distLock()->expectLock(
+ [](StringData name,
+ StringData whyMessage,
+ milliseconds waitFor,
+ milliseconds lockTryInterval) {
+ ASSERT_EQUALS("authorizationData", name);
+ ASSERT_EQUALS("dropUser", whyMessage);
+ },
+ Status::OK());
+
+ auto future =
+ launchAsync([this] {
+ BSONObjBuilder responseBuilder;
+ bool ok =
+ catalogManager()->runUserManagementWriteCommand(
+ operationContext(),
+ "dropUser",
+ "test",
+ BSON("dropUser"
+ << "test"
+ << "writeConcern" << BSON("w" << 1 << "wtimeout" << 30)),
+ &responseBuilder);
+ ASSERT_FALSE(ok);
+
+ Status commandStatus = Command::getStatusFromCommandResult(responseBuilder.obj());
+ ASSERT_EQUALS(ErrorCodes::UserNotFound, commandStatus);
+ });
+
+ onCommand([](const RemoteCommandRequest& request) {
+ ASSERT_EQUALS("test", request.dbname);
+ ASSERT_EQUALS(BSON("dropUser"
+ << "test"
+ << "writeConcern" << BSON("w"
+ << "majority"
+ << "wtimeout" << 30) << "maxTimeMS" << 30000),
request.cmdObj);
ASSERT_EQUALS(BSON(rpc::kReplSetMetadataFieldName << 1), request.metadata);
@@ -803,9 +878,12 @@ TEST_F(CatalogManagerReplSetTest, RunUserManagementWriteCommandNotMasterRetrySuc
onCommand([host2](const RemoteCommandRequest& request) {
ASSERT_EQUALS(host2, request.target);
ASSERT_EQUALS("test", request.dbname);
+ // Since no write concern was sent we will add w:majority
ASSERT_EQUALS(BSON("dropUser"
<< "test"
- << "maxTimeMS" << 30000),
+ << "writeConcern" << BSON("w"
+ << "majority"
+ << "wtimeout" << 0) << "maxTimeMS" << 30000),
request.cmdObj);
ASSERT_EQUALS(BSON(rpc::kReplSetMetadataFieldName << 1), request.metadata);
diff --git a/src/mongo/s/cluster_write.cpp b/src/mongo/s/cluster_write.cpp
index 5bc28be8fb3..cd2ca6fa233 100644
--- a/src/mongo/s/cluster_write.cpp
+++ b/src/mongo/s/cluster_write.cpp
@@ -204,9 +204,9 @@ void ClusterWriter::write(OperationContext* txn,
BatchedCommandResponse* response) {
// Add _ids to insert request if req'd
unique_ptr<BatchedCommandRequest> idRequest(BatchedCommandRequest::cloneWithIds(origRequest));
- const BatchedCommandRequest& request = NULL != idRequest.get() ? *idRequest : origRequest;
+ const BatchedCommandRequest* request = NULL != idRequest.get() ? idRequest.get() : &origRequest;
- const NamespaceString& nss = request.getNS();
+ const NamespaceString& nss = request->getNS();
if (!nss.isValid()) {
toBatchError(Status(ErrorCodes::InvalidNamespace, nss.ns() + " is not a valid namespace"),
response);
@@ -220,13 +220,13 @@ void ClusterWriter::write(OperationContext* txn,
return;
}
- if (request.sizeWriteOps() == 0u) {
+ if (request->sizeWriteOps() == 0u) {
toBatchError(Status(ErrorCodes::InvalidLength, "no write ops were included in the batch"),
response);
return;
}
- if (request.sizeWriteOps() > BatchedCommandRequest::kMaxWriteBatchSize) {
+ if (request->sizeWriteOps() > BatchedCommandRequest::kMaxWriteBatchSize) {
toBatchError(Status(ErrorCodes::InvalidLength,
str::stream() << "exceeded maximum write batch size of "
<< BatchedCommandRequest::kMaxWriteBatchSize),
@@ -235,7 +235,7 @@ void ClusterWriter::write(OperationContext* txn,
}
string errMsg;
- if (request.isInsertIndexRequest() && !request.isValidIndexRequest(&errMsg)) {
+ if (request->isInsertIndexRequest() && !request->isValidIndexRequest(&errMsg)) {
toBatchError(Status(ErrorCodes::InvalidOptions, errMsg), response);
return;
}
@@ -243,26 +243,62 @@ void ClusterWriter::write(OperationContext* txn,
// Config writes and shard writes are done differently
const string dbName = nss.db().toString();
+ unique_ptr<BatchedCommandRequest> requestWithWriteConcern;
if (dbName == "config" || dbName == "admin") {
- grid.catalogManager(txn)->writeConfigServerDirect(txn, request, response);
+ // w:majority is the only valid write concern for writes to the config servers.
+ // We also allow w:1 to come in on a user-initiated write, though we convert it here to
+ // w:majority before sending it to the config servers.
+ bool rewriteCmdWithWriteConcern = false;
+ WriteConcernOptions writeConcern;
+ if (request->isWriteConcernSet()) {
+ Status status = writeConcern.parse(request->getWriteConcern());
+ if (!status.isOK()) {
+ toBatchError(status, response);
+ return;
+ }
+ if (!writeConcern.validForConfigServers()) {
+ toBatchError(Status(ErrorCodes::InvalidOptions,
+ "Invalid replication write concern. Writes to config servers "
+ "must use w:'majority'"),
+ response);
+ return;
+ }
+ if (writeConcern.wMode == "") {
+ invariant(writeConcern.wNumNodes == 1);
+ rewriteCmdWithWriteConcern = true;
+ }
+ } else {
+ rewriteCmdWithWriteConcern = true;
+ }
+
+ if (rewriteCmdWithWriteConcern) {
+ requestWithWriteConcern.reset(new BatchedCommandRequest(request->getBatchType()));
+ request->cloneTo(requestWithWriteConcern.get());
+ writeConcern.wMode = WriteConcernOptions::kMajority;
+ writeConcern.wNumNodes = 0;
+ requestWithWriteConcern->setWriteConcern(writeConcern.toBSON());
+ request = requestWithWriteConcern.get();
+ }
+
+ grid.catalogManager(txn)->writeConfigServerDirect(txn, *request, response);
} else {
- ChunkManagerTargeter targeter(request.getTargetingNSS());
+ ChunkManagerTargeter targeter(request->getTargetingNSS());
Status targetInitStatus = targeter.init(txn);
if (!targetInitStatus.isOK()) {
// Errors will be reported in response if we are unable to target
warning() << "could not initialize targeter for"
- << (request.isInsertIndexRequest() ? " index" : "")
- << " write op in collection " << request.getTargetingNS();
+ << (request->isInsertIndexRequest() ? " index" : "")
+ << " write op in collection " << request->getTargetingNS();
}
DBClientShardResolver resolver;
DBClientMultiCommand dispatcher;
BatchWriteExec exec(&targeter, &resolver, &dispatcher);
- exec.executeBatch(txn, request, response);
+ exec.executeBatch(txn, *request, response);
if (_autoSplit) {
- splitIfNeeded(txn, request.getNS(), *targeter.getStats());
+ splitIfNeeded(txn, request->getNS(), *targeter.getStats());
}
_stats->setShardStats(exec.releaseStats());