protected SessionHostConfigurationBase(VmConfiguration vmConfiguration, MultiLogger logger, ISystemOperations systemOperations, SessionHostsStartInfo sessionHostsStartInfo) { _logger = logger; VmConfiguration = vmConfiguration; _systemOperations = systemOperations; _sessionHostsStartInfo = sessionHostsStartInfo; }
/// <summary> /// Gets the set of environment variables that's common to scripts running at the VM level and for game servers. /// </summary> /// <param name="sessionHostsStartInfo">The details for starting the game servers.</param> /// <param name="vmConfiguration">The details for the VM.</param> /// <returns>A dictionary of environment variables</returns> /// <remarks>This method is expected to be called only after the VM is assigned (i.e sessionHostsStartInfo is not null).</remarks> public static IDictionary <string, string> GetCommonEnvironmentVariables(SessionHostsStartInfo sessionHostsStartInfo, VmConfiguration vmConfiguration) { ArgumentValidator.ThrowIfNull(sessionHostsStartInfo, nameof(sessionHostsStartInfo)); VmConfiguration.ParseAssignmentId(sessionHostsStartInfo.AssignmentId, out Guid titleId, out Guid deploymentId, out string region); var environmentVariables = new Dictionary <string, string>() { { TitleIdEnvVariable, VmConfiguration.GetPlayFabTitleId(titleId) }, { BuildIdEnvVariable, deploymentId.ToString() }, { RegionEnvVariable, region }, { VmIdEnvVariable, vmConfiguration.VmId }, { PublicIPv4AddressEnvVariable, sessionHostsStartInfo.PublicIpV4Address }, { PublicIPv4AddressEnvVariableV2, sessionHostsStartInfo.PublicIpV4Address }, { FqdnEnvVariable, sessionHostsStartInfo.FQDN } }; sessionHostsStartInfo.DeploymentMetadata?.ForEach(x => environmentVariables.Add(x.Key, x.Value)); return(environmentVariables); }
public SessionHostsStartInfo ToSessionHostsStartInfo() { // Clear mount path in process based, otherwise, agent will kick into back-compat mode and try to strip the // mount path from the start game command before running AssetDetail[] assetDetails = AssetDetails?.Select(x => new AssetDetail() { MountPath = RunContainer ? x.MountPath : null, LocalFilePath = x.LocalFilePath }).ToArray(); var startInfo = new SessionHostsStartInfo { AssignmentId = $"{VmConfiguration.GetGuidFromTitleId(TitleIdUlong)}:{BuildId}:{Region}", SessionHostType = RunContainer ? SessionHostType.Container : SessionHostType.Process, PublicIpV4Address = "127.0.0.1", FQDN = "localhost", HostConfigOverrides = GetHostConfig(), ImageDetails = ContainerStartParameters.ImageDetails, AssetDetails = assetDetails, StartGameCommand = RunContainer ? ContainerStartParameters.StartGameCommand : ProcessStartParameters.StartGameCommand, PortMappingsList = PortMappingsList }; return(startInfo); }
/// <summary> /// Creates a list whose first element contains /// the command we want to use to run the game /// </summary> /// <param name="request"></param> /// <returns></returns> private IList <string> GetStartGameCmd(SessionHostsStartInfo request) { if (!string.IsNullOrEmpty(request.StartGameCommand)) { if (_systemOperations.IsOSPlatform(OSPlatform.Windows)) { var windowsStartCommand = new List <string>(); if (request.WindowsCrashDumpConfiguration?.IsEnabled == true) { // set crash dump registry keys on startup windowsStartCommand.AddRange(new string[] { $"cmd /c reg add \"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\LocalDumps\" /v DumpFolder /t REG_EXPAND_SZ /d %PF_SERVER_DUMP_DIRECTORY% /f ;", $"cmd /c reg add \"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\LocalDumps\" /v DumpCount /t REG_DWORD /d 10 /f ;", $"cmd /c reg add \"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\LocalDumps\" /v DumpType /t REG_DWORD /d {request.WindowsCrashDumpConfiguration.DumpType} /f ;", $"cmd /c reg add \"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\Windows Error Reporting\\LocalDumps\" /v CustomDumpFlags /t REG_DWORD /d {request.WindowsCrashDumpConfiguration.CustomDumpFlags} /f ;", }); } windowsStartCommand.Add($"cmd /c {request.StartGameCommand}"); return(windowsStartCommand); } return(new List <string>() { request.StartGameCommand }); } return(null); }
public void EnvVariablesWithoutBuildMetadata() { SessionHostsStartInfo sessionHostsStartInfo = CreateSessionHostStartInfo(); IDictionary <string, string> envVariables = VmConfiguration.GetCommonEnvironmentVariables(sessionHostsStartInfo, VmConfiguration); ValidateEnvironmentVariables(envVariables, sessionHostsStartInfo); }
private void DownloadGameCertificates(SessionHostsStartInfo gameResourceDetails) { if (Globals.Settings.GameCertificateDetails?.Length > 0) { List <CertificateDetail> certs = new List <CertificateDetail>(); foreach (GameCertificateDetails certUserDetails in Globals.Settings.GameCertificateDetails) { if (!certUserDetails.Path.EndsWith(".pfx")) { Console.WriteLine($"Cert {certUserDetails.Path} is not a valid .pfx file. Skipping"); continue; } _systemOperations.FileCopy( certUserDetails.Path, Path.Combine(Globals.VmConfiguration.VmDirectories.CertificateRootFolderVm, Path.GetFileName(certUserDetails.Path))); X509Certificate2 cert = new X509Certificate2(certUserDetails.Path); certs.Add(new CertificateDetail() { Name = certUserDetails.Name, Thumbprint = cert.Thumbprint, PfxContents = cert.RawData }); } if (Globals.Settings.RunContainer == false) { // we're running in Process mode, so we'll let the user know that they must install the certs themselves in the cert store if needed Console.WriteLine($@"Caution: Certificates {string.Join(",", Globals.Settings.GameCertificateDetails.Select(x=>x.Name).ToList())} were copied to game's certificate folder (accessible via GSDK) but not installed in machine's cert store. you may do that manually if you need to."); } gameResourceDetails.GameCertificates = certs.ToArray(); } }
public SessionHostContainerConfiguration( VmConfiguration vmConfiguration, MultiLogger logger, Interfaces.ISystemOperations systemOperations, IDockerClient dockerClient, SessionHostsStartInfo sessionHostsStartInfo) : base(vmConfiguration, logger, systemOperations, sessionHostsStartInfo) { _dockerClient = dockerClient; }
private IDictionary <string, string> GetPortMappingsDict(SessionHostsStartInfo sessionHostsStartInfo, int instanceNumber) { if (sessionHostsStartInfo.PortMappingsList != null && sessionHostsStartInfo.PortMappingsList.Count > 0) { return(GetPortMappingsInternal(sessionHostsStartInfo.PortMappingsList[instanceNumber])); } return(null); }
/// <summary> /// Creates a port mapping list of the format: /// "Container Port", "VM Port", "Protocol" /// Ex: "81", "8001", "TCP" /// </summary> /// <param name="request"></param> /// <param name="sessionHostInstance"></param> /// <returns></returns> private IList <PortMapping> GetPortMappings(SessionHostsStartInfo request, int sessionHostInstance) { if (request.PortMappingsList != null && request.PortMappingsList.Count > 0) { return(request.PortMappingsList[sessionHostInstance]); } return(null); }
private void DownloadAndExtractAllAssets(SessionHostsStartInfo gameResourceDetails) { if (gameResourceDetails.AssetDetails?.Length > 0) { for (int i = 0; i < gameResourceDetails.AssetDetails.Length; i++) { ExtractAndCopyAsset((i, gameResourceDetails.AssetDetails[i].LocalFilePath)); } } }
public override async Task RetrieveResources(SessionHostsStartInfo sessionHostsStartInfo) { string registryWithImageName = $"{sessionHostsStartInfo.ImageDetails.Registry}/{sessionHostsStartInfo.ImageDetails.ImageName}"; string imageTag = sessionHostsStartInfo.ImageDetails.ImageTag; string username = sessionHostsStartInfo.ImageDetails.Username; string password = sessionHostsStartInfo.ImageDetails.Password; if (string.IsNullOrEmpty(imageTag)) { imageTag = "latest"; } _logger.LogInformation($"Starting image pull for: {registryWithImageName}:{imageTag}."); LogReporter logReporter = new LogReporter(_logger); Polly.Retry.AsyncRetryPolicy retryPolicy = Policy .Handle <Exception>((Exception e) => { _logger.LogError($"Exception encountered when creating image: {e.ToString()}"); return(true); }) .WaitAndRetryAsync(_maxRetryAttempts, i => TimeSpan.FromMinutes(_createImageRetryTimeMins / _maxRetryAttempts)); await retryPolicy.ExecuteAsync(async() => { await _dockerClient.Images.CreateImageAsync( new ImagesCreateParameters { FromImage = registryWithImageName, Tag = imageTag }, new AuthConfig() { Username = username, Password = password }, logReporter); // Making sure that the image was actually downloaded properly // We have seen some cases where Docker Registry API returns 'success' on pull while the image has not been properly downloaded IEnumerable <ImagesListResponse> images = await _dockerClient.Images.ListImagesAsync(new ImagesListParameters { All = true }); if (images.All(image => !image.RepoTags.Contains($"{registryWithImageName}:{imageTag}"))) { throw new ApplicationException("CreateImageAsync is completed but the image doesn't exist"); } }); _logger.LogEvent(MetricConstants.PullImage, null, new Dictionary <string, double> { { MetricConstants.DownloadDurationInMilliseconds, logReporter.DownloadSummary?.DurationInMilliseconds ?? 0d }, { MetricConstants.ExtractDurationInMilliseconds, logReporter.ExtractionSummary?.DurationInMilliseconds ?? 0d }, { MetricConstants.SizeInBytes, logReporter.DownloadSummary?.TotalSizeInBytes ?? 0d } } ); }
public void EnvVariablesWithBuildMetadata() { var metadata = new Dictionary <string, string>() { { "key1", "value1" }, { "key2", "value2" } }; SessionHostsStartInfo sessionHostsStartInfo = CreateSessionHostStartInfo(metadata); IDictionary <string, string> envVariables = VmConfiguration.GetCommonEnvironmentVariables(sessionHostsStartInfo, VmConfiguration); ValidateEnvironmentVariables(envVariables, sessionHostsStartInfo); }
public SessionHostContainerConfiguration( VmConfiguration vmConfiguration, MultiLogger logger, Interfaces.ISystemOperations systemOperations, IDockerClient dockerClient, SessionHostsStartInfo sessionHostsStartInfo, bool isRunningLinuxContainersOnWindows = false, bool shouldPublicPortMatchGamePort = false) : base(vmConfiguration, logger, systemOperations, sessionHostsStartInfo) { _dockerClient = dockerClient; _isRunningLinuxContainersOnWindows = isRunningLinuxContainersOnWindows; _shouldPublicPortMatchGamePort = shouldPublicPortMatchGamePort; }
public async Task CreateAndStartContainerWaitForExit(SessionHostsStartInfo startParameters) { IList <PortMapping> portMappings = GetPortMappings(startParameters, 0); if (startParameters.SessionHostType == SessionHostType.Container) { foreach (PortMapping mapping in portMappings) { _logger.LogInformation( $"{mapping.GamePort.Name} ({mapping.GamePort.Protocol}): Local port {mapping.NodePort} mapped to container port {mapping.GamePort.Number} "); } } DownloadAndExtractAllAssets(startParameters); DownloadGameCertificates(startParameters); ISessionHostRunner sessionHostRunner = _sessionHostRunnerFactory.CreateSessionHostRunner(startParameters.SessionHostType, _vmConfiguration, _logger); // RetrieveResources does a docker pull // If we're running Linux containers on Windows, we might want to pull image from ACR first to save the image on local. // In this case, you need to simply change the value to true for forcePullFromAcrOnLinuxContainersOnWindows in MultiplayerSettings.json // so if this image is not locally built, docker create will do a docker pull first // In another case, we might have built the image locally but tagged with a fake registry name (e.g. myacr.io/mygame:0.1), // Then make sure to change the value to false if you want to use the image from local. if (Globals.GameServerEnvironment == GameServerEnvironment.Windows || Globals.Settings.ForcePullFromAcrOnLinuxContainersOnWindows) { await sessionHostRunner.RetrieveResources(startParameters); } NoOpSessionHostManager sessionHostManager = new NoOpSessionHostManager(); SessionHostInfo sessionHostInfo = await sessionHostRunner.CreateAndStart(0, new GameResourceDetails { SessionHostsStartInfo = startParameters }, sessionHostManager); if (sessionHostInfo == null) { return; } string typeSpecificId = sessionHostInfo.TypeSpecificId; _logger.LogInformation("Waiting for heartbeats from the game server....."); await sessionHostRunner.WaitOnServerExit(typeSpecificId).ConfigureAwait(false); string logFolder = Path.Combine(Globals.VmConfiguration.VmDirectories.GameLogsRootFolderVm, sessionHostInfo.LogFolderId); await sessionHostRunner.CollectLogs(typeSpecificId, logFolder, sessionHostManager); await sessionHostRunner.TryDelete(typeSpecificId); }
private (string, string) GetExecutableAndArguments(SessionHostsStartInfo sessionHostsStartInfo, int instanceNumber) { string[] parts = sessionHostsStartInfo.StartGameCommand.Split(' ', StringSplitOptions.RemoveEmptyEntries); string localPathForAsset0 = sessionHostsStartInfo.UseReadOnlyAssets ? _vmConfiguration.GetAssetExtractionFolderPathForSessionHost(0, 0) : _vmConfiguration.GetAssetExtractionFolderPathForSessionHost(instanceNumber, 0); // Replacing the mount path is for back compat when we didn't have first class support for process based servers // (and were based off of the parameters for containers). string executablePath = sessionHostsStartInfo.AssetDetails[0].MountPath?.Length > 0 ? parts[0].Replace(sessionHostsStartInfo.AssetDetails[0].MountPath, $"{localPathForAsset0}\\") : Path.Combine(localPathForAsset0, parts[0]); string args = parts.Length > 1 ? string.Join(' ', parts.Skip(1)) : string.Empty; return(executablePath, args); }
public void BeforeEachTest() { string AssignmentId = $"{TestTitleIdUlong}:{TestBuildId}:{TestRegion}"; _logger = new MultiLogger(NullLogger.Instance, new TelemetryClient(TelemetryConfiguration.CreateDefault())); _systemOperations = new SystemOperations(); _dockerClient = new Mock <IDockerClient>(); _sessionHostManager = new Mock <ISessionHostManager>(); _vmConfiguration = new VmConfiguration(56001, TestVmId, new VmDirectories(_VmDirectoryRoot), true); _sessionHostsStartInfo = new SessionHostsStartInfo(); _sessionHostsStartInfo.AssignmentId = AssignmentId; _sessionHostsStartInfo.PublicIpV4Address = TestPublicIpV4Address; _sessionHostManager.Setup(x => x.VmAgentSettings).Returns(new VmAgentSettings()); }
public override Task <SessionHostInfo> CreateAndStart(int instanceNumber, GameResourceDetails gameResourceDetails, ISessionHostManager sessionHostManager) { SessionHostsStartInfo sessionHostStartInfo = gameResourceDetails.SessionHostsStartInfo; string sessionHostUniqueId = Guid.NewGuid().ToString("D"); string logFolderPathOnVm = Path.Combine(_vmConfiguration.VmDirectories.GameLogsRootFolderVm, sessionHostUniqueId); _systemOperations.CreateDirectory(logFolderPathOnVm); // Create the dumps folder as a subfolder of the logs folder string dumpFolderPathOnVm = Path.Combine(logFolderPathOnVm, VmDirectories.GameDumpsFolderName); _systemOperations.CreateDirectory(dumpFolderPathOnVm); ISessionHostConfiguration sessionHostConfiguration = new SessionHostProcessConfiguration(_vmConfiguration, _logger, _systemOperations, sessionHostStartInfo); string configFolderPathOnVm = _vmConfiguration.GetConfigRootFolderForSessionHost(instanceNumber); _systemOperations.CreateDirectory(configFolderPathOnVm); ProcessStartInfo processStartInfo = new ProcessStartInfo(); (string executableFileName, string arguments) = GetExecutableAndArguments(sessionHostStartInfo, instanceNumber); processStartInfo.FileName = executableFileName; processStartInfo.Arguments = arguments; processStartInfo.WorkingDirectory = sessionHostStartInfo.GameWorkingDirectory ?? Path.GetDirectoryName(executableFileName); processStartInfo.Environment.AddRange(sessionHostConfiguration.GetEnvironmentVariablesForSessionHost(instanceNumber, sessionHostUniqueId, sessionHostManager.VmAgentSettings)); _logger.LogInformation($"Starting process for session host with instance number {instanceNumber} and process info: FileName - {executableFileName}, Args - {arguments}."); SessionHostInfo sessionHost = sessionHostManager.AddNewSessionHost(sessionHostUniqueId, sessionHostStartInfo.AssignmentId, instanceNumber, sessionHostUniqueId, SessionHostType.Process); sessionHostConfiguration.Create(instanceNumber, sessionHostUniqueId, GetVmAgentIpAddress(), _vmConfiguration, sessionHostUniqueId); try { string processId = _processWrapper.Start(processStartInfo).ToString(); sessionHostManager.UpdateSessionHostTypeSpecificId(sessionHostUniqueId, processId); _logger.LogInformation($"Started process for session host. Instance Number: {instanceNumber}, UniqueId: {sessionHostUniqueId}, ProcessId: {processId}"); } catch (Exception exception) { _logger.LogException($"Failed to start process based host with instance number {instanceNumber}", exception); sessionHostManager.RemoveSessionHost(sessionHostUniqueId); sessionHost = null; } return(Task.FromResult(sessionHost)); }
private IList <string> GetVolumeBindings(SessionHostsStartInfo request, int sessionHostInstance, string logFolderId, VmAgentSettings agentSettings) { List <string> volumeBindings = new List <string>(); if (request.AssetDetails?.Length > 0) { for (int i = 0; i < request.AssetDetails.Length; i++) { string pathOnHost = request.UseReadOnlyAssets ? _vmConfiguration.GetAssetExtractionFolderPathForSessionHost(0, i) : _vmConfiguration.GetAssetExtractionFolderPathForSessionHost(sessionHostInstance, i); //Set for when containers are run as non root _systemOperations.SetUnixOwnerIfNeeded(pathOnHost, true); volumeBindings.Add($"{pathOnHost}:{request.AssetDetails[i].MountPath}"); } } // The folder needs to exist before the mount can happen. string logFolderPathOnVm = Path.Combine(_vmConfiguration.VmDirectories.GameLogsRootFolderVm, logFolderId); _systemOperations.CreateDirectory(logFolderPathOnVm); // Create the dumps folder as a subfolder of the logs folder string dumpFolderPathOnVm = Path.Combine(logFolderPathOnVm, VmDirectories.GameDumpsFolderName); _systemOperations.CreateDirectory(dumpFolderPathOnVm); // Set up the log folder. Maps D:\GameLogs\{logFolderId} on the container host to C:\GameLogs on the container. // TODO: TBD whether the log folder should be taken as input from developer during ingestion. volumeBindings.Add($"{logFolderPathOnVm}:{_vmConfiguration.VmDirectories.GameLogsRootFolderContainer}"); //Set for when containers are run as non root _systemOperations.SetUnixOwnerIfNeeded(_vmConfiguration.VmDirectories.CertificateRootFolderVm, true); // All containers will have the certificate folder mapped volumeBindings.Add($"{_vmConfiguration.VmDirectories.CertificateRootFolderVm}:{_vmConfiguration.VmDirectories.CertificateRootFolderContainer}"); // All containers have the same shared content folder mapped. _systemOperations.CreateDirectory(_vmConfiguration.VmDirectories.GameSharedContentFolderVm); volumeBindings.Add($"{_vmConfiguration.VmDirectories.GameSharedContentFolderVm}:{_vmConfiguration.VmDirectories.GameSharedContentFolderContainer}"); // Map the folder that will contain this session host's configuration file string configFolderPathOnVm = _vmConfiguration.GetConfigRootFolderForSessionHost(sessionHostInstance); _systemOperations.CreateDirectory(configFolderPathOnVm); volumeBindings.Add($"{configFolderPathOnVm}:{_vmConfiguration.VmDirectories.GsdkConfigRootFolderContainer}"); return(volumeBindings); }
public async Task CreateAndStartContainerWaitForExit(SessionHostsStartInfo startParameters) { IList <PortMapping> portMappings = GetPortMappings(startParameters, 0); if (startParameters.SessionHostType == SessionHostType.Container) { foreach (PortMapping mapping in portMappings) { _logger.LogInformation( $"{mapping.GamePort.Name} ({mapping.GamePort.Protocol}): Local port {mapping.NodePort} mapped to container port {mapping.GamePort.Number} "); } } DownloadAndExtractAllAssets(startParameters); DownloadGameCertificates(startParameters); ISessionHostRunner sessionHostRunner = _sessionHostRunnerFactory.CreateSessionHostRunner(startParameters.SessionHostType, _vmConfiguration, _logger); // RetrieveResources does a docker pull // if we're running Linux containers on Windows we do not want that, since we might // have built the image locally but tagged with a fake registry name (e.g. myacr.io/mygame:0.1) // docker pull will try to connect to myacr.io and fail // if this image is not locally built, docker create (executed next) will do a docker pull first if (Globals.GameServerEnvironment == GameServerEnvironment.Windows) { await sessionHostRunner.RetrieveResources(startParameters); } NoOpSessionHostManager sessionHostManager = new NoOpSessionHostManager(); SessionHostInfo sessionHostInfo = await sessionHostRunner.CreateAndStart(0, new GameResourceDetails { SessionHostsStartInfo = startParameters }, sessionHostManager); string containerId = sessionHostInfo.TypeSpecificId; _logger.LogInformation("Waiting for heartbeats from the game server....."); await sessionHostRunner.WaitOnServerExit(containerId).ConfigureAwait(false); string logFolder = Path.Combine(Globals.VmConfiguration.VmDirectories.GameLogsRootFolderVm, sessionHostInfo.LogFolderId); await sessionHostRunner.CollectLogs(containerId, logFolder, sessionHostManager); await sessionHostRunner.TryDelete(containerId); }
/// <summary> /// Creates a list whose first element contains /// the command we want to use to run the game /// </summary> /// <param name="request"></param> /// <returns></returns> private IList <string> GetStartGameCmd(SessionHostsStartInfo request) { if (!string.IsNullOrEmpty(request.StartGameCommand)) { if (_systemOperations.IsOSPlatform(OSPlatform.Windows)) { return(new List <string>() { $"cmd /c {request.StartGameCommand}" }); } return(new List <string>() { request.StartGameCommand }); } return(null); }
public async Task DeleteResources(SessionHostsStartInfo sessionHostsStartInfo) { ContainerImageDetails imageDetails = sessionHostsStartInfo.ImageDetails; string imageName = $"{imageDetails.Registry}/{imageDetails.ImageName}:{imageDetails.ImageTag ?? "latest"}"; _logger.LogInformation($"Starting deletion of container image {imageName}"); try { await _dockerClient.Images.DeleteImageAsync(imageName, new ImageDeleteParameters() { Force = true }); _logger.LogInformation($"Deleted container image {imageName}"); } catch (DockerImageNotFoundException) { _logger.LogInformation($"Image {imageName} not found."); } }
public void SessionHostStartWithProcess() { dynamic config = GetValidConfig(); config.RunContainer = false; MultiplayerSettings settings = JsonConvert.DeserializeObject <MultiplayerSettings>(config.ToString()); settings.SetDefaultsIfNotSpecified(); new MultiplayerSettingsValidator(settings, _mockSystemOperations.Object).IsValid().Should().BeTrue(); SessionHostsStartInfo startInfo = settings.ToSessionHostsStartInfo(); startInfo.HostConfigOverrides.Should().BeNull(); startInfo.SessionHostType.Should().Be(SessionHostType.Process); foreach (AssetDetail assetDetail in startInfo.AssetDetails) { assetDetail.MountPath.Should().BeNull(); } }
/// <summary> /// Gets the game working directory based off the /// command we want to use to run the game /// </summary> /// <param name="request"></param> /// <returns></returns> private string GetGameWorkingDir(SessionHostsStartInfo request) { // Only set the working directory for managed containers (aka. Windows) if (_systemOperations.IsOSPlatform(OSPlatform.Windows)) { if (!string.IsNullOrEmpty(request.GameWorkingDirectory)) { _logger.LogInformation($"Container working dir set from GameWorkingDirectory: {request.GameWorkingDirectory}"); return(request.GameWorkingDirectory); } if (!string.IsNullOrEmpty(request.StartGameCommand)) { // Strip off any arguments that might come after the game executable string executable = request.StartGameCommand.Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]; // Return the path to the folder where the executable lives, note that this returns the full path return(Path.GetDirectoryName(executable)); } } return(null); }
public void SessionHostStartWithContainer() { dynamic config = GetValidConfig(); config.RunContainer = true; MultiplayerSettings settings = JsonConvert.DeserializeObject <MultiplayerSettings>(config.ToString()); settings.SetDefaultsIfNotSpecified(); new MultiplayerSettingsValidator(settings, _mockSystemOperations.Object).IsValid().Should().BeTrue(); SessionHostsStartInfo startInfo = settings.ToSessionHostsStartInfo(); long expectedNanoCpus = (long)((double)config.ContainerStartParameters.ResourceLimits.Cpus * 1_000_000_000); long expectedMemory = config.ContainerStartParameters.ResourceLimits.MemoryGib * Math.Pow(1024, 3); startInfo.SessionHostType.Should().Be(SessionHostType.Container); startInfo.HostConfigOverrides.NanoCPUs.Should().Be(expectedNanoCpus); startInfo.HostConfigOverrides.Memory.Should().Be(expectedMemory); foreach (AssetDetail assetDetail in startInfo.AssetDetails) { assetDetail.MountPath.Should().NotBeNull(); } }
public override Task RetrieveResources(SessionHostsStartInfo sessionHostsStartInfo) { // no-op return(Task.CompletedTask); }
/// <summary> /// Creates and starts a container, assigning <paramref name="instanceNumber"/> to it. /// </summary> /// <param name="instanceNumber"> /// An instance number associated with a container. It is used to map assets folder to the container /// and then re-use for container recycling. /// </param> /// <returns>A <see cref="Task"/>.</returns> public async Task <SessionHostInfo> CreateAndStart(int instanceNumber, GameResourceDetails gameResourceDetails, ISessionHostManager sessionHostManager) { // The current Docker client doesn't yet allow specifying a local name for the image. // It is stored with as the remote path name. Thus, the parameter to CreateAndStartContainers // is the same as the remote image path. SessionHostsStartInfo sessionHostStartInfo = gameResourceDetails.SessionHostsStartInfo; ContainerImageDetails imageDetails = sessionHostStartInfo.ImageDetails; string imageName = $"{imageDetails.Registry}/{imageDetails.ImageName}:{imageDetails.ImageTag ?? "latest"}"; // The game containers need a unique folder to write their logs. Ideally, // we would specify the containerId itself as the subfolder. However, we have to // specify volume bindings before docker gives us the container id, so using // a random guid here instead string logFolderId = _systemOperations.NewGuid().ToString("D"); ISessionHostConfiguration sessionHostConfiguration = new SessionHostContainerConfiguration(_vmConfiguration, _logger, _systemOperations, _dockerClient, sessionHostStartInfo); IList <PortMapping> portMappings = sessionHostConfiguration.GetPortMappings(instanceNumber); List <string> environmentValues = sessionHostConfiguration.GetEnvironmentVariablesForSessionHost(instanceNumber, logFolderId) .Select(x => $"{x.Key}={x.Value}").ToList(); string dockerId = await CreateContainer( imageName, environmentValues, GetVolumeBindings(sessionHostStartInfo, instanceNumber, logFolderId), portMappings, GetStartGameCmd(sessionHostStartInfo), sessionHostStartInfo.HostConfigOverrides, GetGameWorkingDir(sessionHostStartInfo)); SessionHostInfo sessionHost = sessionHostManager.AddNewSessionHost(dockerId, sessionHostStartInfo.AssignmentId, instanceNumber, logFolderId); // https://docs.docker.com/docker-for-windows/networking/ string agentIPaddress = sessionHostManager.LinuxContainersOnWindows ? "host.docker.internal" : GetVmAgentIpAddress(); sessionHostConfiguration.Create(instanceNumber, dockerId, agentIPaddress, _vmConfiguration, logFolderId); // on LinuxContainersForWindows, VMAgent will run in a Windows environment // but we want the Linux directory separator char if (sessionHostManager.LinuxContainersOnWindows) { string configFilePath = Path.Combine(_vmConfiguration.GetConfigRootFolderForSessionHost(instanceNumber), VmDirectories.GsdkConfigFilename); File.WriteAllText(configFilePath, File.ReadAllText(configFilePath). Replace($"{_vmConfiguration.VmDirectories.GameLogsRootFolderContainer}\\\\", $"{_vmConfiguration.VmDirectories.GameLogsRootFolderContainer}/")); } try { await StartContainer(dockerId); _logger.LogInformation($"Started container {dockerId}, with assignmentId {sessionHostStartInfo.AssignmentId}, instance number {instanceNumber}, and logFolderId {logFolderId}"); } catch (Exception exception) { _logger.LogException($"Failed to start container based host with instance number {instanceNumber}", exception); sessionHostManager.RemoveSessionHost(dockerId); sessionHost = null; } return(sessionHost); }
private void ValidateEnvironmentVariables(IDictionary <string, string> envVariables, SessionHostsStartInfo sessionHostsStartInfo) { envVariables.Should().Contain(VmConfiguration.PublicIPv4AddressEnvVariable, sessionHostsStartInfo.PublicIpV4Address); envVariables.Should().Contain(VmConfiguration.PublicIPv4AddressEnvVariableV2, sessionHostsStartInfo.PublicIpV4Address); envVariables.Should().Contain(VmConfiguration.FqdnEnvVariable, sessionHostsStartInfo.FQDN); envVariables.Should().Contain(VmConfiguration.TitleIdEnvVariable, PlayFabTitleId); envVariables.Should().Contain(VmConfiguration.BuildIdEnvVariable, DeploymentId.ToString()); envVariables.Should().Contain(VmConfiguration.RegionEnvVariable, Region); sessionHostsStartInfo.DeploymentMetadata?.ForEach(x => envVariables.Should().Contain(x.Key, x.Value)); }
public void Assign(SessionHostsStartInfo request) { }
abstract public Task DeleteResources(SessionHostsStartInfo sessionHostsStartInfo);
abstract public Task RetrieveResources(SessionHostsStartInfo sessionHostsStartInfo);