BSONObj ShardingCatalogManagerImpl::createShardIdentityUpsertForAddShard( OperationContext* opCtx, const std::string& shardName) { std::unique_ptr<BatchedUpdateDocument> updateDoc(new BatchedUpdateDocument()); BSONObjBuilder query; query.append("_id", "shardIdentity"); query.append(ShardIdentityType::shardName(), shardName); query.append(ShardIdentityType::clusterId(), ClusterIdentityLoader::get(opCtx)->getClusterId()); updateDoc->setQuery(query.obj()); BSONObjBuilder update; { BSONObjBuilder set(update.subobjStart("$set")); set.append( ShardIdentityType::configsvrConnString(), repl::ReplicationCoordinator::get(opCtx)->getConfig().getConnectionString().toString()); } updateDoc->setUpdateExpr(update.obj()); updateDoc->setUpsert(true); std::unique_ptr<BatchedUpdateRequest> updateRequest(new BatchedUpdateRequest()); updateRequest->addToUpdates(updateDoc.release()); BatchedCommandRequest commandRequest(updateRequest.release()); commandRequest.setNS(NamespaceString::kConfigCollectionNamespace); commandRequest.setWriteConcern(ShardingCatalogClient::kMajorityWriteConcern.toBSON()); return commandRequest.toBSON(); }
StatusWith<string> ShardingCatalogManagerImpl::addShard( OperationContext* txn, const std::string* shardProposedName, const ConnectionString& shardConnectionString, const long long maxSize) { if (shardConnectionString.type() == ConnectionString::INVALID) { return {ErrorCodes::BadValue, "Invalid connection string"}; } if (shardProposedName && shardProposedName->empty()) { return {ErrorCodes::BadValue, "shard name cannot be empty"}; } // TODO: Don't create a detached Shard object, create a detached RemoteCommandTargeter instead. const std::shared_ptr<Shard> shard{ Grid::get(txn)->shardRegistry()->createConnection(shardConnectionString)}; invariant(shard); auto targeter = shard->getTargeter(); // Validate the specified connection string may serve as shard at all auto shardStatus = _validateHostAsShard(txn, targeter, shardProposedName, shardConnectionString); if (!shardStatus.isOK()) { // TODO: This is a workaround for the case were we could have some bad shard being // requested to be added and we put that bad connection string on the global replica set // monitor registry. It needs to be cleaned up so that when a correct replica set is added, // it will be recreated. ReplicaSetMonitor::remove(shardConnectionString.getSetName()); return shardStatus.getStatus(); } ShardType& shardType = shardStatus.getValue(); auto dbNamesStatus = _getDBNamesListFromShard(txn, targeter); if (!dbNamesStatus.isOK()) { return dbNamesStatus.getStatus(); } // Check that none of the existing shard candidate's dbs exist already for (const string& dbName : dbNamesStatus.getValue()) { auto dbt = _catalogClient->getDatabase(txn, dbName); if (dbt.isOK()) { const auto& dbDoc = dbt.getValue().value; return Status(ErrorCodes::OperationFailed, str::stream() << "can't add shard " << "'" << shardConnectionString.toString() << "'" << " because a local database '" << dbName << "' exists in another " << dbDoc.getPrimary()); } else if (dbt != ErrorCodes::NamespaceNotFound) { return dbt.getStatus(); } } // If a name for a shard wasn't provided, generate one if (shardType.getName().empty()) { StatusWith<string> result = _generateNewShardName(txn); if (!result.isOK()) { return result.getStatus(); } shardType.setName(result.getValue()); } if (maxSize > 0) { shardType.setMaxSizeMB(maxSize); } ShardIdentityType shardIdentity; shardIdentity.setConfigsvrConnString( Grid::get(txn)->shardRegistry()->getConfigServerConnectionString()); shardIdentity.setShardName(shardType.getName()); shardIdentity.setClusterId(Grid::get(txn)->shardRegistry()->getClusterId()); auto validateStatus = shardIdentity.validate(); if (!validateStatus.isOK()) { return validateStatus; } log() << "going to insert shardIdentity document into shard: " << shardIdentity.toString(); auto updateRequest = shardIdentity.createUpsertForAddShard(); BatchedCommandRequest commandRequest(updateRequest.release()); commandRequest.setNS(NamespaceString::kConfigCollectionNamespace); commandRequest.setWriteConcern(kMajorityWriteConcern.toBSON()); auto swCommandResponse = _runCommandForAddShard(txn, targeter.get(), "admin", commandRequest.toBSON()); if (!swCommandResponse.isOK()) { return swCommandResponse.getStatus(); } auto commandResponse = std::move(swCommandResponse.getValue()); BatchedCommandResponse batchResponse; auto batchResponseStatus = Shard::CommandResponse::processBatchWriteResponse(commandResponse, &batchResponse); if (!batchResponseStatus.isOK()) { return batchResponseStatus; } log() << "going to insert new entry for shard into config.shards: " << shardType.toString(); Status result = _catalogClient->insertConfigDocument(txn, ShardType::ConfigNS, shardType.toBSON()); if (!result.isOK()) { log() << "error adding shard: " << shardType.toBSON() << " err: " << result.reason(); if (result == ErrorCodes::DuplicateKey) { // TODO(SERVER-24213): adding a shard that already exists should be considered success, // however this approach does no validation that we are adding the shard with the same // options. It also does not protect against adding the same shard with a different // shard name and slightly different connection string. This is a temporary hack to // get the continuous stepdown suite passing. warning() << "Received duplicate key error when inserting new shard with name " << shardType.getName() << " and connection string " << shardConnectionString.toString() << " to config.shards collection. This most likely means that there was an " "attempt to add a shard that already exists in the cluster"; return shardType.getName(); } return result; } // Add all databases which were discovered on the new shard for (const string& dbName : dbNamesStatus.getValue()) { DatabaseType dbt; dbt.setName(dbName); dbt.setPrimary(shardType.getName()); dbt.setSharded(false); Status status = _catalogClient->updateDatabase(txn, dbName, dbt); if (!status.isOK()) { log() << "adding shard " << shardConnectionString.toString() << " even though could not add database " << dbName; } } // Record in changelog BSONObjBuilder shardDetails; shardDetails.append("name", shardType.getName()); shardDetails.append("host", shardConnectionString.toString()); _catalogClient->logChange(txn, "addShard", "", shardDetails.obj()); // Ensure the added shard is visible to this process. auto shardRegistry = Grid::get(txn)->shardRegistry(); if (!shardRegistry->getShard(txn, shardType.getName())) { return {ErrorCodes::OperationFailed, "Could not find shard metadata for shard after adding it. This most likely " "indicates that the shard was removed immediately after it was added."}; } return shardType.getName(); }