Beispiel #1
0
 protected SessionHostConfigurationBase(VmConfiguration vmConfiguration, MultiLogger logger, ISystemOperations systemOperations, SessionHostsStartInfo sessionHostsStartInfo)
 {
     _logger                = logger;
     VmConfiguration        = vmConfiguration;
     _systemOperations      = systemOperations;
     _sessionHostsStartInfo = sessionHostsStartInfo;
 }
Beispiel #2
0
        /// <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();
     }
 }
Beispiel #7
0
 public SessionHostContainerConfiguration(
     VmConfiguration vmConfiguration,
     MultiLogger logger,
     Interfaces.ISystemOperations systemOperations,
     IDockerClient dockerClient,
     SessionHostsStartInfo sessionHostsStartInfo) : base(vmConfiguration, logger, systemOperations, sessionHostsStartInfo)
 {
     _dockerClient = dockerClient;
 }
Beispiel #8
0
        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);
        }
Beispiel #15
0
        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());
        }
Beispiel #17
0
        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);
        }
Beispiel #20
0
        /// <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);
        }
Beispiel #21
0
        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.");
            }
        }
Beispiel #22
0
        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();
            }
        }
Beispiel #23
0
        /// <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);
        }
Beispiel #24
0
        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();
            }
        }
Beispiel #25
0
 public override Task RetrieveResources(SessionHostsStartInfo sessionHostsStartInfo)
 {
     // no-op
     return(Task.CompletedTask);
 }
Beispiel #26
0
        /// <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)
 {
 }
Beispiel #29
0
 abstract public Task DeleteResources(SessionHostsStartInfo sessionHostsStartInfo);
Beispiel #30
0
 abstract public Task RetrieveResources(SessionHostsStartInfo sessionHostsStartInfo);