diff options
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 189 | ||||
-rw-r--r-- | src/mongo/db/write_concern.cpp | 21 | ||||
-rw-r--r-- | src/mongo/db/write_concern_options.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/write_concern_options.h | 6 | ||||
-rw-r--r-- | src/mongo/s/catalog/replset/catalog_manager_replica_set.cpp | 45 | ||||
-rw-r--r-- | src/mongo/s/catalog/replset/catalog_manager_replica_set_test.cpp | 82 | ||||
-rw-r--r-- | src/mongo/s/cluster_write.cpp | 58 |
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()); |