GameLiftPlugin/Source/GameLiftCore/Private/AWSScenariosDeployer.cpp (407 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#include "AWSScenariosDeployer.h"
#include <Misc/FileHelper.h>
#include "aws/gamelift/core/exports.h"
#include "aws/gamelift/core/errors.h"
#include "GameLiftCoreLog.h"
#include "GameLiftCoreConstants.h"
#include "AwsAccount/AwsAccountCredentials.h"
#include "AwsAccount/AwsAccountInstanceManager.h"
#include "AwsAccount/AwsAccountInfo.h"
#include "Utils/StringPaths.h"
#include "Utils/StringConvertors.h"
#include "Utils/LogMessageStorage.h"
#include "Utils/NativeLogPrinter.h"
#include "Utils/UnrealVersion.h"
#include "AwsErrors/Converter.h"
#include "AwsScenarios/ContainerFlexMatch.h"
#include "AwsScenarios/ContainerSingleRegionFleet.h"
#include "AwsScenarios/FlexMatch.h"
#include "AwsScenarios/SingleRegionFleet.h"
#include "AwsScenarios/CustomScenario.h"
#define LOCTEXT_NAMESPACE "AWSScenariosDeployer"
namespace AwsDeployerInternal
{
static Logs::MessageStorage sLatestDeploymentLogErrorMessage = {};
void LogCallback(unsigned int InLevel, const char* InMessage, int InSize)
{
auto Level = Logs::PrintAwsLog(InLevel, InMessage, InSize, Deploy::Logs::kLogReceived);
if (Level == ELogVerbosity::Error)
{
sLatestDeploymentLogErrorMessage.Set(InMessage);
}
}
void ReceiveReplacementId(DISPATCH_RECEIVER_HANDLE DispatchReceiver, const char* ReplacementId)
{
UE_LOG(GameLiftCoreLog, Verbose, TEXT("%s %s"), Deploy::Logs::kReceiveReplacementIdCallback, *Convertors::ASToFS(ReplacementId));
((AWSScenariosDeployer*)DispatchReceiver)->SetMainFunctionsReplacementId(ReplacementId);
}
void ReceiveClientConfigPath(DISPATCH_RECEIVER_HANDLE DispatchReceiver, const char* ConfigPath)
{
UE_LOG(GameLiftCoreLog, Verbose, TEXT("%s %s"), Deploy::Logs::kReceiveClientConfigPathCallback, *Convertors::ASToFS(ConfigPath));
((AWSScenariosDeployer*)DispatchReceiver)->SetClientConfigPath(ConfigPath);
}
auto BuildAwsScenarios(const IAWSScenariosCategory Category)
{
TMap<FString, TUniquePtr<AwsScenarios::IAWSScenario>> ScenarioMap;
switch (Category)
{
case IAWSScenariosCategory::ManagedEC2:
{
ScenarioMap.Add(AwsScenarios::SingleRegionFleet::Name().ToString(),
AwsScenarios::SingleRegionFleet::Create());
ScenarioMap.Add(AwsScenarios::FlexMatch::Name().ToString(),
AwsScenarios::FlexMatch::Create());
break;
}
case IAWSScenariosCategory::Containers:
{
ScenarioMap.Add(AwsScenarios::ContainersSingleRegionFleet::Name().ToString(),
AwsScenarios::ContainersSingleRegionFleet::Create());
ScenarioMap.Add(AwsScenarios::ContainersFlexMatch::Name().ToString(),
AwsScenarios::ContainersFlexMatch::Create());
break;
}
}
return ScenarioMap;
}
const auto& GetAwsScenarios(const IAWSScenariosCategory Category)
{
static TMap<FString, TUniquePtr<AwsScenarios::IAWSScenario>>
ManagedEC2ScenarioMap(BuildAwsScenarios(IAWSScenariosCategory::ManagedEC2));
static TMap<FString, TUniquePtr<AwsScenarios::IAWSScenario>>
ContainersScenarioMap(BuildAwsScenarios(IAWSScenariosCategory::Containers));
switch (Category)
{
case IAWSScenariosCategory::ManagedEC2:
{
return ManagedEC2ScenarioMap;
}
case IAWSScenariosCategory::Containers:
{
return ContainersScenarioMap;
}
default:
{
static TMap<FString, TUniquePtr<AwsScenarios::IAWSScenario>> DefaultMap = {};
return DefaultMap;
}
}
}
AwsScenarios::IAWSScenario* GetAwsScenarioByName(const FText& ScenarioName, const IAWSScenariosCategory Category)
{
auto Invariant = FText::AsCultureInvariant(ScenarioName);
return GetAwsScenarios(Category).FindChecked(Invariant.ToString()).Get();
}
FString ExtractYamlValue(const TArray<FString>& YamlStrings, const char* Name)
{
for (const FString& Line : YamlStrings)
{
auto FoundPosition = Line.Find(Name);
if (FoundPosition >= 0)
{
FoundPosition += strlen(Name);
return Line.RightChop(FoundPosition + 1);
}
}
return FString();
}
class ResourcesInstanceGuard
{
public:
ResourcesInstanceGuard(IAWSAccountInstance* AwsAccountInstance) :
Instance(GameLiftAccountCreateGameLiftResourcesInstance(AwsAccountInstance->GetInstance()))
{
}
~ResourcesInstanceGuard()
{
GameLiftResourcesInstanceRelease(Instance);
}
GAMELIFT_FEATURERESOURCES_INSTANCE_HANDLE Get()
{
return Instance;
}
private:
GAMELIFT_FEATURERESOURCES_INSTANCE_HANDLE Instance;
};
bool IsDeployedSuccessfully(const std::string& StackStatus)
{
static std::unordered_set<std::string> SuccessfulStatuses
{
Aws::Status::kStackUpdateComplete, Aws::Status::kStackCreateComplete
};
return SuccessfulStatuses.find(StackStatus) != SuccessfulStatuses.end();
}
} // namespace AwsDeployerInternal
void AWSScenariosDeployer::SetMainFunctionsReplacementId(const char* ReplacementId)
{
MainFunctionsReplacementId = ReplacementId;
}
void AWSScenariosDeployer::SetClientConfigPath(const char* ConfigPath)
{
ClientConfigPath = Convertors::ASToFS(ConfigPath);
}
void AWSScenariosDeployer::SetLastCognitoClientId(const FString& ClientId)
{
LastCognitoClientId = ClientId;
}
void AWSScenariosDeployer::SetLastApiGatewayEndpoint(const FString& ApiGateway)
{
LastApiGatewayEndpoint = ApiGateway;
}
bool AWSScenariosDeployer::DeployManagedEC2Scenario(
const FText& Scenario,
IAWSAccountInstance* AwsAccountInstance,
const FString& GameName,
const FString& BuildOperatingSystem,
const FString& BuildFolderPath,
const FString& BuildFilePath,
const FString& OutConfigFilePath,
const FString& ExtraServerResourcesPath
)
{
UE_LOG(GameLiftCoreLog, Log, TEXT("%s %s"), Deploy::Logs::kRunAwsScenario, *Scenario.ToString());
AwsScenarios::IAWSScenario* BaseAwsScenario = AwsDeployerInternal::GetAwsScenarioByName(Scenario, IAWSScenariosCategory::ManagedEC2);
AwsScenarios::ScenarioWithGameServer* AwsScenario = static_cast<AwsScenarios::ScenarioWithGameServer*>(BaseAwsScenario);
std::string stdLaunchPathParameter;
AwsScenario->CreateLaunchPathParameter(BuildOperatingSystem, BuildFolderPath, BuildFilePath, stdLaunchPathParameter);
AwsScenarios::ManagedEC2InstanceTemplateParams Params;
Params.GameNameParameter = Convertors::FSToStdS(GameName);
Params.BuildFolderPath = Convertors::FSToStdS(BuildFolderPath);
Params.ExtraServerResourcesPath = Convertors::FSToStdS(ExtraServerResourcesPath);
Params.BuildOperatingSystemParameter = Convertors::FSToStdS(BuildOperatingSystem);
Params.LaunchPathParameter = stdLaunchPathParameter;
return DeployScenarioImpl(AwsAccountInstance, AwsScenario, Params, OutConfigFilePath);
}
bool AWSScenariosDeployer::DeployCustomScenario(
const FString& CustomScenarioPath,
IAWSAccountInstance* AwsAccountInstance,
const FString& GameName,
const FString& BuildOperatingSystem,
const FString& BuildFolderPath,
const FString& BuildFilePath,
const FString& OutConfigFilePath,
const FString& ExtraServerResourcesPath
)
{
UE_LOG(GameLiftCoreLog, Log, TEXT("%s %s"), Deploy::Logs::kRunCustomScenario, *CustomScenarioPath);
TUniquePtr<AwsScenarios::CustomScenario> AwsScenario = AwsScenarios::CustomScenario::Create(CustomScenarioPath);
std::string stdLaunchPathParameter;
AwsScenario->CreateLaunchPathParameter(BuildOperatingSystem, BuildFolderPath, BuildFilePath, stdLaunchPathParameter);
AwsScenarios::ManagedEC2InstanceTemplateParams Params;
Params.GameNameParameter = Convertors::FSToStdS(GameName);
Params.BuildFolderPath = Convertors::FSToStdS(BuildFolderPath);
Params.ExtraServerResourcesPath = Convertors::FSToStdS(ExtraServerResourcesPath);
Params.BuildOperatingSystemParameter = Convertors::FSToStdS(BuildOperatingSystem);
Params.LaunchPathParameter = stdLaunchPathParameter;
return DeployScenarioImpl(AwsAccountInstance, AwsScenario.Get(), Params, OutConfigFilePath);
}
bool AWSScenariosDeployer::DeployContainerScenario(
const FText& Scenario, IAWSAccountInstance* AwsAccountInstance, const FString& ContainerGroupDefinitionName,
const FString& ContainerImageName, const FString& ContainerImageUri, const FString& IntraContainerLaunchPath,
const FString& GameName, const FString& OutConfigFilePath, const FText& ConnectionPortRange, const FString& TotalVcpuLimit,
const FString& TotalMemoryLimit)
{
AwsScenarios::IAWSScenario* AwsScenario = AwsDeployerInternal::GetAwsScenarioByName(
Scenario,
IAWSScenariosCategory::Containers);
auto StdBootstrapBucketName = Convertors::FSToStdS(AwsAccountInstance->GetBucketName());
AwsScenarios::ContainerInstanceTemplateParams Params;
Params.GameNameParameter = Convertors::FSToStdS(GameName);
Params.ContainerGroupDefinitionNameParameter = Convertors::FSToStdS(ContainerGroupDefinitionName);
Params.ContainerImageNameParameter = Convertors::FSToStdS(ContainerImageName);
Params.ContainerImageUriParameter = Convertors::FSToStdS(ContainerImageUri);
Params.LaunchPathParameter = Convertors::FSToStdS(IntraContainerLaunchPath);
FString FleetUdpFromPort;
FString FleetUdpToPort;
ConnectionPortRange.ToString().Split("-", &FleetUdpFromPort, &FleetUdpToPort);
Params.FleetUdpFromPortParameter = Convertors::FSToStdS(FleetUdpFromPort);
Params.FleetUdpToPortParameter = Convertors::FSToStdS(FleetUdpToPort);
Params.TotalVcpuLimitParameter = Convertors::FSToStdS(TotalVcpuLimit);
Params.TotalMemoryLimitParameter = Convertors::FSToStdS(TotalMemoryLimit);
return DeployScenarioImpl(AwsAccountInstance, AwsScenario, Params, OutConfigFilePath);
}
bool AWSScenariosDeployer::StopDeployment(IAWSAccountInstance* AwsAccountInstance)
{
UE_LOG(GameLiftCoreLog, Log, TEXT("%s"), Deploy::Logs::kDeploymentStop);
AwsDeployerInternal::sLatestDeploymentLogErrorMessage.Clear();
if (!AwsAccountInstance->IsValid())
{
AwsDeployerInternal::sLatestDeploymentLogErrorMessage.Set(Deploy::Errors::kAccountIsInvalidText);
return false;
}
AwsDeployerInternal::ResourcesInstanceGuard ResourcesInstance(AwsAccountInstance);
std::string StackStatus = GameLiftResourcesGetCurrentStackStatus(ResourcesInstance.Get());
if (StackStatus.compare(Aws::Status::kStackUndeployed) == 0)
{
AwsDeployerInternal::sLatestDeploymentLogErrorMessage.Set(Deploy::Errors::kNoStacksToStopDeployment);
return false;
}
ShouldBeStopped = true;
bool IsStopped = GameLiftResourcesInstanceCancelUpdateStack(ResourcesInstance.Get()) == GameLift::GAMELIFT_SUCCESS;
if (!IsStopped)
{
AwsDeployerInternal::sLatestDeploymentLogErrorMessage.Set(Deploy::Errors::kUnableToStopDeployment);
}
return IsStopped;
}
bool AWSScenariosDeployer::DeployScenarioImpl(
IAWSAccountInstance* AwsAccountInstance,
AwsScenarios::IAWSScenario* AwsScenario,
AwsScenarios::BaseInstanceTemplateParams& Params,
const FString& OutConfigFilePath
)
{
constexpr auto DeployAbortResult = false;
constexpr auto DeployCompletedResult = true;
AwsDeployerInternal::sLatestDeploymentLogErrorMessage.Clear();
ShouldBeStopped = false;
auto AccountHandle = AwsAccountInstance->GetInstance();
if (AccountHandle == nullptr)
{
AwsDeployerInternal::sLatestDeploymentLogErrorMessage.Set(Deploy::Errors::kAccountIsInvalidText);
LastAwsError = GameLift::GAMELIFT_ERROR_GENERAL;
UE_LOG(GameLiftCoreLog, Error, TEXT("%s"), Deploy::Logs::kAccountInstanceIsNull);
return DeployAbortResult;
}
if (Params.GameNameParameter.size() > Deploy::kMaxGameNameWithPrefixLength)
{
AwsDeployerInternal::sLatestDeploymentLogErrorMessage.Set(Deploy::Errors::kGameNameIsTooLongText);
LastAwsError = GameLift::GAMELIFT_ERROR_GENERAL;
return DeployAbortResult;
}
GameLiftAccountSetGameName(AccountHandle, Params.GameNameParameter.c_str());
auto ScenarioPath = AwsScenario->GetScenarioPath();
auto StdScenarioPath = Convertors::FSToStdS(ScenarioPath);
GameLiftAccountSetPluginRootPath(AccountHandle, StdScenarioPath.c_str());
auto ScenarioInstancePath = AwsScenario->GetScenarioInstancePath();
auto StdScenarioInstancePath = Convertors::FSToStdS(ScenarioInstancePath);
GameLiftAccountSetRootPath(AccountHandle, StdScenarioInstancePath.c_str());
std::string StdAwsAccountId = GameLiftGetAwsAccountIdByAccountInstance(AccountHandle);
if (ShouldBeStopped)
{
LastAwsError = GameLift::GAMELIFT_ERROR_GENERAL;
UE_LOG(GameLiftCoreLog, Error, TEXT("%s"), Deploy::Logs::kDeploymentHasBeenStopped);
return DeployAbortResult;
}
auto ShouldDeployBeAborted = [this](int ErrorCode, auto&& ErrorDebugString)
{
LastAwsError = ErrorCode;
if (LastAwsError != GameLift::GAMELIFT_SUCCESS)
{
UE_LOG(GameLiftCoreLog, Error, TEXT("%s %s"), ErrorDebugString, *GameLiftErrorAsString::Convert(LastAwsError));
return true;
}
if (ShouldBeStopped)
{
LastAwsError = GameLift::GAMELIFT_ERROR_GENERAL;
UE_LOG(GameLiftCoreLog, Error, TEXT("%s"), Deploy::Logs::kDeploymentHasBeenStopped);
return true;
}
return false;
};
if (ShouldDeployBeAborted(GameLiftAccountCreateAndSetFunctionsReplacementId(AccountHandle), Deploy::Logs::kCreateAndSetFunctionsReplacementIdFailed))
{
return DeployAbortResult;
}
if (ShouldDeployBeAborted(GameLiftAccountGetMainFunctionsReplacementId(AccountHandle, this, AwsDeployerInternal::ReceiveReplacementId),
Deploy::Logs::kGetMainFunctionsReplacementIdFailed))
{
return DeployAbortResult;
}
auto StdBootstrapBucketName = Convertors::FSToStdS(AwsAccountInstance->GetBucketName());
if (ShouldDeployBeAborted(AwsScenario->UploadGameServer(AwsAccountInstance, Params.BuildFolderPath.c_str(), Params.ExtraServerResourcesPath.c_str()),
Deploy::Logs::kUploadGameServerFailed))
{
return DeployAbortResult;
}
Params.AccountId = StdAwsAccountId;
Params.ApiGatewayStageNameParameter = AwsAccountInstance->GetBuildConfiguration();
Params.BuildS3BucketParameter = StdBootstrapBucketName;
Params.LambdaZipS3BucketParameter = StdBootstrapBucketName;
Params.LambdaZipS3KeyParameter = AwsScenarios::GetLambdaS3Key(Params.GameNameParameter, MainFunctionsReplacementId);
Params.UnrealEngineVersionParameter = Convertors::FSToStdS(UnrealVersion::GetCurrentEngineVersion());
if (ShouldDeployBeAborted(AwsScenario->SaveFeatureInstanceTemplate(AwsAccountInstance, Params.ToMap()),
Deploy::Logs::kSaveFeatureInstanceTemplatesFailed))
{
return DeployAbortResult;
}
if (ShouldDeployBeAborted(GameLiftAccountUploadFunctions(AccountHandle), Deploy::Logs::kAccountUploadFunctionsFailed))
{
return DeployAbortResult;
}
if (ShouldDeployBeAborted(GameLiftAccountCreateOrUpdateMainStack(AccountHandle), Deploy::Logs::kCreateOrUpdateMainStackFailed))
{
return DeployAbortResult;
}
if (ShouldDeployBeAborted(GameLiftAccountDeployApiGatewayStage(AccountHandle), Deploy::Logs::kDeployApiGatewayStageFailed))
{
return DeployAbortResult;
}
if (ShouldDeployBeAborted(UpdateDeploymentResults(AwsAccountInstance, ScenarioInstancePath, FString(Params.GameNameParameter.c_str()), AwsAccountInstance->GetBucketName(), OutConfigFilePath),
Deploy::Logs::kDeploymentStackStatusFailed))
{
return DeployAbortResult;
}
return DeployCompletedResult;
}
int AWSScenariosDeployer::UpdateDeploymentResults(IAWSAccountInstance* AwsAccountInstance, const FString& ScenarioInstancePath, const FString& GameName, const FString& BucketName, const FString& OutConfigFilePath)
{
AwsDeployerInternal::ResourcesInstanceGuard ResourcesInstance(AwsAccountInstance);
// Result will be stored to ClientConfigPath with sync call.
GameLiftResourcesGetInstanceClientConfigPath(ResourcesInstance.Get(), this, AwsDeployerInternal::ReceiveClientConfigPath);
std::string StackStatus = GameLiftResourcesGetCurrentStackStatus(ResourcesInstance.Get());
UE_LOG(GameLiftCoreLog, Log, TEXT("%s %s"), Deploy::Logs::kDeploymentStackStatus, *Convertors::ASToFS(StackStatus.c_str()));
auto CurrentScenarioInstancePath = Paths::ScenarioInstancePath(Menu::DeploymentServer::kCurrentScenarioFolder);
FString ConfigPathPart, ConfigFilenamePart, ConfigExtensionPart;
FPaths::Split(ClientConfigPath, ConfigPathPart, ConfigFilenamePart, ConfigExtensionPart);
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
PlatformFile.DeleteDirectoryRecursively(*CurrentScenarioInstancePath);
PlatformFile.CopyDirectoryTree(*CurrentScenarioInstancePath, *ConfigPathPart, true);
FString DestinationFileName = ConfigFilenamePart + "." + ConfigExtensionPart;
PlatformFile.CreateDirectoryTree(*OutConfigFilePath);
PlatformFile.CopyFile(*FPaths::Combine(OutConfigFilePath, DestinationFileName), *ClientConfigPath);
FString UUID;
FString Temp;
BucketName.Split("-", &Temp, &UUID, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
FString Region;
Temp.Split("-", &Temp, &Region, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
FString DestinationFileNameExtra = GameName + "_" + UUID + "_" + Region + "_" + ConfigFilenamePart + "." + ConfigExtensionPart;
PlatformFile.CopyFile(*FPaths::Combine(OutConfigFilePath, DestinationFileNameExtra), *ClientConfigPath);
TArray<FString> YamlStrings;
FFileHelper::LoadANSITextFileToStrings(*ClientConfigPath, NULL, YamlStrings);
LastCognitoClientId = AwsDeployerInternal::ExtractYamlValue(YamlStrings, Aws::Config::kUserPoolClientIdYamlName).TrimStartAndEnd();
LastApiGatewayEndpoint = AwsDeployerInternal::ExtractYamlValue(YamlStrings, Aws::Config::kApiGatewayEndpointYamlName).TrimStartAndEnd();
return AwsDeployerInternal::IsDeployedSuccessfully(StackStatus) ?
GameLift::GAMELIFT_SUCCESS : GameLift::GAMELIFT_ERROR_GENERAL;
}
FString AWSScenariosDeployer::GetLastCognitoClientId() const
{
return LastCognitoClientId;
}
FString AWSScenariosDeployer::GetLastApiGatewayEndpoint() const
{
return LastApiGatewayEndpoint;
}
FString AWSScenariosDeployer::GetLastError() const
{
return GameLiftErrorAsString::Convert(LastAwsError);
}
FString AWSScenariosDeployer::GetLastErrorMessage() const
{
return AwsDeployerInternal::sLatestDeploymentLogErrorMessage.Get();
}
TArray<FText> AWSScenariosDeployer::GetScenarios(const IAWSScenariosCategory Category) const
{
TArray<FText> Result;
const auto& Scenarios = AwsDeployerInternal::GetAwsScenarios(Category);
Result.Reserve(Scenarios.Num());
for (const auto& Item : Scenarios)
{
Result.Add(Item.Value->GetUserName());
}
return Result;
}
FText AWSScenariosDeployer::GetToolTip(const FText& ScenarioName, const IAWSScenariosCategory Category) const
{
return AwsDeployerInternal::GetAwsScenarioByName(ScenarioName, Category)->GetTooltip();
}
#undef LOCTEXT_NAMESPACE