/// <summary> /// Initializes a new instance of the <see cref="BasicWatchdog"/> <see langword="class"/>. /// </summary> /// <param name="chat">The <see cref="IChatManager"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="sessionPersistor">The <see cref="ISessionPersistor"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="diagnosticsIOManager">The <see cref="IIOManager"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="eventConsumer">The <see cref="IEventConsumer"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="remoteDeploymentManagerFactory">The <see cref="IRemoteDeploymentManagerFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="logger">The <see cref="ILogger"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="initialLaunchParameters">The <see cref="DreamDaemonLaunchParameters"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="instance">The <see cref="Api.Models.Instance"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="autoStart">The autostart value for the <see cref="WatchdogBase"/>.</param> public BasicWatchdog( IChatManager chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ISessionPersistor sessionPersistor, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, IEventConsumer eventConsumer, IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory, ILogger <BasicWatchdog> logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) : base( chat, sessionControllerFactory, dmbFactory, sessionPersistor, jobManager, serverControl, asyncDelayer, diagnosticsIOManager, eventConsumer, remoteDeploymentManagerFactory, logger, initialLaunchParameters, instance, autoStart) { }
/// <summary> /// Construct a <see cref="IWatchdog"/> /// </summary> /// <param name="chat">The value of <see cref="chat"/></param> /// <param name="sessionControllerFactory">The value of <see cref="sessionControllerFactory"/></param> /// <param name="dmbFactory">The value of <see cref="dmbFactory"/></param> /// <param name="serverUpdater">The <see cref="IServerControl"/> for the <see cref="Watchdog"/></param> /// <param name="logger">The value of <see cref="logger"/></param> /// <param name="reattachInfoHandler">The value of <see cref="reattachInfoHandler"/></param> /// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param> /// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param> /// <param name="initialLaunchParameters">The initial value of <see cref="ActiveLaunchParameters"/></param> /// <param name="instance">The value of <see cref="instance"/></param> /// <param name="autoStart">The value of <see cref="autoStart"/></param> /// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param> public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IServerControl serverUpdater, ILogger <Watchdog> logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); this.autoStart = autoStart; if (serverUpdater == null) { throw new ArgumentNullException(nameof(serverUpdater)); } serverUpdater.RegisterForRestart(() => releaseServers = true); chat.RegisterCommandHandler(this); AlphaIsActive = true; ActiveLaunchParameters = initialLaunchParameters; releaseServers = false; semaphore = new SemaphoreSlim(1); activeParametersUpdated = new TaskCompletionSource <object>(); }
/// <summary> /// Initializes a new instance of the <see cref="BasicWatchdog"/> <see langword="class"/>. /// </summary> /// <param name="chat">The <see cref="IChatManager"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="reattachInfoHandler">The <see cref="IReattachInfoHandler"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="logger">The <see cref="ILogger"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="initialLaunchParameters">The <see cref="DreamDaemonLaunchParameters"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="instance">The <see cref="Api.Models.Instance"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="autoStart">The autostart value for the <see cref="WatchdogBase"/>.</param> public BasicWatchdog( IChatManager chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILogger <BasicWatchdog> logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) : base( chat, sessionControllerFactory, dmbFactory, reattachInfoHandler, databaseContextFactory, jobManager, serverControl, asyncDelayer, logger, initialLaunchParameters, instance, autoStart) { }
/// <inheritdoc /> public async Task ChangeSettings(DreamDaemonLaunchParameters launchParameters, CancellationToken cancellationToken) { using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { ActiveLaunchParameters = launchParameters; if (Running) { //queue an update activeParametersUpdated.TrySetResult(null); } } }
/// <summary> /// Initializes a new instance of the <see cref="WatchdogBase"/> <see langword="class"/>. /// </summary> /// <param name="chat">The value of <see cref="Chat"/></param> /// <param name="sessionControllerFactory">The value of <see cref="SessionControllerFactory"/></param> /// <param name="dmbFactory">The value of <see cref="DmbFactory"/></param> /// <param name="reattachInfoHandler">The value of <see cref="reattachInfoHandler"/></param> /// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param> /// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param> /// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param> /// <param name="jobManager">The value of <see cref="jobManager"/></param> /// <param name="serverControl">The <see cref="IServerControl"/> to populate <see cref="restartRegistration"/> with</param> /// <param name="asyncDelayer">The value of <see cref="AsyncDelayer"/>.</param> /// <param name="logger">The value of <see cref="Logger"/></param> /// <param name="initialLaunchParameters">The initial value of <see cref="ActiveLaunchParameters"/>. May be modified</param> /// <param name="instance">The value of <see cref="instance"/></param> /// <param name="autoStart">The value of <see cref="autoStart"/></param> protected WatchdogBase( IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILogger logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) { Chat = chat ?? throw new ArgumentNullException(nameof(chat)); SessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); DmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); AsyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); ActiveLaunchParameters = initialLaunchParameters ?? throw new ArgumentNullException(nameof(initialLaunchParameters)); this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); this.autoStart = autoStart; if (serverControl == null) { throw new ArgumentNullException(nameof(serverControl)); } chat.RegisterCommandHandler(this); ActiveLaunchParameters = initialLaunchParameters; releaseServers = false; ActiveParametersUpdated = new TaskCompletionSource <object>(); restartRegistration = serverControl.RegisterForRestart(this); try { Semaphore = new SemaphoreSlim(1); } catch { restartRegistration.Dispose(); throw; } }
/// <summary> /// Initializes a new instance of the <see cref="WindowsWatchdog"/> class. /// </summary> /// <param name="chat">The <see cref="IChatManager"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="sessionPersistor">The <see cref="ISessionPersistor"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="diagnosticsIOManager">The <see cref="IIOManager"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="eventConsumer">The <see cref="IEventConsumer"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="remoteDeploymentManagerFactory">The <see cref="IRemoteDeploymentManagerFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="gameIOManager">The value of <see cref="GameIOManager"/>.</param> /// <param name="symlinkFactory">The value of <see cref="symlinkFactory"/>.</param> /// <param name="logger">The <see cref="ILogger"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="initialLaunchParameters">The <see cref="DreamDaemonLaunchParameters"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="instance">The <see cref="Api.Models.Instance"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="autoStart">The autostart value for the <see cref="WatchdogBase"/>.</param> public WindowsWatchdog( IChatManager chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ISessionPersistor sessionPersistor, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager diagnosticsIOManager, IEventConsumer eventConsumer, IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory, IIOManager gameIOManager, ISymlinkFactory symlinkFactory, ILogger <WindowsWatchdog> logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) : base( chat, sessionControllerFactory, dmbFactory, sessionPersistor, jobManager, serverControl, asyncDelayer, diagnosticsIOManager, eventConsumer, remoteDeploymentManagerFactory, logger, initialLaunchParameters, instance, autoStart) { try { GameIOManager = gameIOManager ?? throw new ArgumentNullException(nameof(gameIOManager)); this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); } catch { _ = DisposeAsync(); throw; } }
/// <summary> /// Initializes a new instance of the <see cref="WindowsWatchdog"/> <see langword="class"/>. /// </summary> /// <param name="chat">The <see cref="IChat"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="reattachInfoHandler">The <see cref="IReattachInfoHandler"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="byondTopicSender">The <see cref="IByondTopicSender"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="eventConsumer">The <see cref="IEventConsumer"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="ioManager">The value of <see cref="ioManager"/>.</param> /// <param name="symlinkFactory">The value of <see cref="symlinkFactory"/>.</param> /// <param name="logger">The <see cref="ILogger"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="initialLaunchParameters">The <see cref="DreamDaemonLaunchParameters"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="instance">The <see cref="Api.Models.Instance"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="autoStart">The autostart value for the <see cref="WatchdogBase"/>.</param> public WindowsWatchdog( IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, IIOManager ioManager, ISymlinkFactory symlinkFactory, ILogger <WindowsWatchdog> logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) : base( chat, sessionControllerFactory, dmbFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, serverControl, asyncDelayer, logger, initialLaunchParameters, instance, autoStart) { try { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); } catch { Dispose(); throw; } }
/// <summary> /// Run a quick DD instance to test the DMAPI is installed on the target code /// </summary> /// <param name="timeout">The timeout in seconds for validation</param> /// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level to use to validate the API</param> /// <param name="job">The <see cref="Models.CompileJob"/> for the operation</param> /// <param name="byondLock">The current <see cref="IByondExecutableLock"/></param> /// <param name="portToUse">The port to use for API validation</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param> /// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the DMAPI was successfully validated, <see langword="false"/> otherwise</returns> async Task <bool> VerifyApi(uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, ushort portToUse, CancellationToken cancellationToken) { logger.LogTrace("Verifying DMAPI..."); var launchParameters = new DreamDaemonLaunchParameters { AllowWebClient = false, PrimaryPort = portToUse, SecurityLevel = securityLevel, //all it needs to read the file and exit StartupTimeout = timeout }; var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); var provider = new TemporaryDmbProvider(ioManager.ResolvePath(dirA), String.Concat(job.DmeName, DmbExtension), job); var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout); using (var controller = await sessionControllerFactory.LaunchNew(launchParameters, provider, byondLock, true, true, true, cancellationToken).ConfigureAwait(false)) { var launchResult = await controller.LaunchResult.ConfigureAwait(false); var now = DateTimeOffset.Now; if (now < timeoutAt && launchResult.StartupTime.HasValue) { var timeoutTask = Task.Delay(timeoutAt - now, cancellationToken); await Task.WhenAny(controller.Lifetime, timeoutTask).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); } if (!controller.Lifetime.IsCompleted) { logger.LogDebug("API validation timed out!"); return(false); } var validated = controller.ApiValidated; logger.LogTrace("API valid: {0}", validated); return(validated); } }
/// <summary> /// Run a quick DD instance to test the DMAPI is installed on the target code /// </summary> /// <param name="timeout">The timeout in seconds for validation</param> /// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level to use to validate the API</param> /// <param name="job">The <see cref="Models.CompileJob"/> for the operation</param> /// <param name="byondLock">The current <see cref="IByondExecutableLock"/></param> /// <param name="portToUse">The port to use for API validation</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param> /// <returns>A <see cref="Task"/> representing the running operation</returns> async Task VerifyApi(uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, ushort portToUse, CancellationToken cancellationToken) { logger.LogTrace("Verifying DMAPI..."); var launchParameters = new DreamDaemonLaunchParameters { AllowWebClient = false, PrimaryPort = portToUse, SecurityLevel = securityLevel, StartupTimeout = timeout }; var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName); job.MinimumSecurityLevel = securityLevel; // needed for the TempDmbProvider var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout); using (var provider = new TemporaryDmbProvider(ioManager.ResolvePath(dirA), String.Concat(job.DmeName, DmbExtension), job)) using (var controller = await sessionControllerFactory.LaunchNew(launchParameters, provider, byondLock, true, true, true, cancellationToken).ConfigureAwait(false)) { var launchResult = await controller.LaunchResult.ConfigureAwait(false); var now = DateTimeOffset.Now; if (now < timeoutAt && launchResult.StartupTime.HasValue) { var timeoutTask = Task.Delay(timeoutAt - now, cancellationToken); await Task.WhenAny(controller.Lifetime, timeoutTask).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); } if (controller.Lifetime.IsCompleted) { var validationStatus = controller.ApiValidationStatus; logger.LogTrace("API validation status: {0}", validationStatus); switch (validationStatus) { case ApiValidationStatus.RequiresUltrasafe: job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe; return; case ApiValidationStatus.RequiresSafe: if (securityLevel == DreamDaemonSecurity.Ultrasafe) { throw new JobException("This game must be run with at least the 'Safe' DreamDaemon security level!"); } job.MinimumSecurityLevel = DreamDaemonSecurity.Safe; return; case ApiValidationStatus.RequiresTrusted: if (securityLevel != DreamDaemonSecurity.Trusted) { throw new JobException("This game must be run with at least the 'Trusted' DreamDaemon security level!"); } job.MinimumSecurityLevel = DreamDaemonSecurity.Trusted; return; case ApiValidationStatus.NeverValidated: break; case ApiValidationStatus.BadValidationRequest: throw new JobException("Recieved an unrecognized API validation request from DreamDaemon!"); case ApiValidationStatus.UnaskedValidationRequest: default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Session controller returned unexpected ApiValidationStatus: {0}", validationStatus)); } } throw new JobException("DMAPI validation timed out!"); } }
/// <inheritdoc /> #pragma warning disable CA1506 // TODO: Decomplexify public async Task <ISessionController> LaunchNew(DreamDaemonLaunchParameters launchParameters, IDmbProvider dmbProvider, IByondExecutableLock currentByondLock, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken) { var portToUse = primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort; if (!portToUse.HasValue) { throw new InvalidOperationException("Given port is null!"); } var accessIdentifier = cryptographySuite.GetSecureString(); const string JsonPostfix = "tgs.json"; var basePath = primaryDirectory ? dmbProvider.PrimaryDirectory : dmbProvider.SecondaryDirectory; // delete all previous tgs json files var files = await ioManager.GetFilesWithExtension(basePath, JsonPostfix, cancellationToken).ConfigureAwait(false); await Task.WhenAll(files.Select(x => ioManager.DeleteFile(x, cancellationToken))).ConfigureAwait(false); // i changed this back from guids, hopefully i don't regret that string JsonFile(string name) => String.Format(CultureInfo.InvariantCulture, "{0}.{1}", name, JsonPostfix); var securityLevelToUse = launchParameters.SecurityLevel.Value; switch (dmbProvider.CompileJob.MinimumSecurityLevel) { case DreamDaemonSecurity.Ultrasafe: break; case DreamDaemonSecurity.Safe: if (securityLevelToUse == DreamDaemonSecurity.Ultrasafe) { securityLevelToUse = DreamDaemonSecurity.Safe; } break; case DreamDaemonSecurity.Trusted: securityLevelToUse = DreamDaemonSecurity.Trusted; break; default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid DreamDaemonSecurity value: {0}", dmbProvider.CompileJob.MinimumSecurityLevel)); } // setup interop files var interopInfo = new JsonFile { AccessIdentifier = accessIdentifier, ApiValidateOnly = apiValidate, ChatChannelsJson = JsonFile("chat_channels"), ChatCommandsJson = JsonFile("chat_commands"), ServerCommandsJson = JsonFile("server_commands"), InstanceName = instance.Name, SecurityLevel = securityLevelToUse, Revision = new Api.Models.Internal.RevisionInformation { CommitSha = dmbProvider.CompileJob.RevisionInformation.CommitSha, OriginCommitSha = dmbProvider.CompileJob.RevisionInformation.OriginCommitSha } }; interopInfo.TestMerges.AddRange(dmbProvider.CompileJob.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x => new Interop.TestMerge(x, interopInfo.Revision))); var interopJsonFile = JsonFile("interop"); var interopJson = JsonConvert.SerializeObject(interopInfo, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); var chatJsonTrackingTask = chat.TrackJsons(basePath, interopInfo.ChatChannelsJson, interopInfo.ChatCommandsJson, cancellationToken); await ioManager.WriteAllBytes(ioManager.ConcatPath(basePath, interopJsonFile), Encoding.UTF8.GetBytes(interopJson), cancellationToken).ConfigureAwait(false); var chatJsonTrackingContext = await chatJsonTrackingTask.ConfigureAwait(false); try { // get the byond lock var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false); try { // create interop context var context = new CommContext(ioManager, loggerFactory.CreateLogger <CommContext>(), basePath, interopInfo.ServerCommandsJson); try { // set command line options // more sanitization here cause it uses the same scheme var parameters = String.Format(CultureInfo.InvariantCulture, "{2}={0}&{3}={1}", byondTopicSender.SanitizeString(application.Version.ToString()), byondTopicSender.SanitizeString(interopJsonFile), byondTopicSender.SanitizeString(Constants.DMParamHostVersion), byondTopicSender.SanitizeString(Constants.DMParamInfoJson)); var visibility = apiValidate ? "invisible" : "public"; // important to run on all ports to allow port changing var arguments = String.Format(CultureInfo.InvariantCulture, "{0} -port {1} -ports 1-65535 {2}-close -{3} -{5} -public -params \"{4}\"", dmbProvider.DmbName, primaryPort ? launchParameters.PrimaryPort : launchParameters.SecondaryPort, launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, SecurityWord(securityLevelToUse), parameters, visibility); // See https://github.com/tgstation/tgstation-server/issues/719 var noShellExecute = !platformIdentifier.IsWindows; // launch dd var process = processExecutor.LaunchProcess(byondLock.DreamDaemonPath, basePath, arguments, noShellExecute: noShellExecute); try { networkPromptReaper.RegisterProcess(process); // return the session controller for it var result = new SessionController(new ReattachInformation { AccessIdentifier = accessIdentifier, Dmb = dmbProvider, IsPrimary = primaryDirectory, Port = portToUse.Value, ProcessId = process.Id, ChatChannelsJson = interopInfo.ChatChannelsJson, ChatCommandsJson = interopInfo.ChatCommandsJson, ServerCommandsJson = interopInfo.ServerCommandsJson, }, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger <SessionController>(), launchParameters.SecurityLevel, launchParameters.StartupTimeout); // writeback launch parameter's fixed security level launchParameters.SecurityLevel = securityLevelToUse; return(result); } catch { process.Dispose(); throw; } } catch { context.Dispose(); throw; } } catch { if (currentByondLock == null) { byondLock.Dispose(); } throw; } } catch { chatJsonTrackingContext.Dispose(); throw; } }
async Task <WatchdogLaunchResult> LaunchNoLock(bool startMonitor, bool announce, bool doReattach, CancellationToken cancellationToken) { logger.LogTrace("Begin LaunchNoLock"); using (var alphaStartCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) { if (Running) { logger.LogTrace("Aborted due to already running!"); return(null); } Task chatTask; //this is necessary, the monitor could be in it's sleep loop trying to restart if (startMonitor && await StopMonitor().ConfigureAwait(false)) { chatTask = chat.SendWatchdogMessage("Automatic retry sequence cancelled by manual launch. Restarting...", cancellationToken); } else if (announce) { chatTask = chat.SendWatchdogMessage("Starting...", cancellationToken); } else { chatTask = Task.CompletedTask; } //start both servers LastLaunchParameters = ActiveLaunchParameters; try { //good ole sanity if (alphaServer != null || bravoServer != null) { throw new InvalidOperationException("Entered LaunchNoLock with one or more of the servers not being null!"); } var reattachInfo = doReattach ? await reattachInfoHandler.Load(cancellationToken).ConfigureAwait(false) : null; var doesntNeedNewDmb = doReattach && reattachInfo.Alpha != null && reattachInfo.Bravo != null; var dmbToUse = doesntNeedNewDmb ? null : dmbFactory.LockNextDmb(2); try { Task <ISessionController> alphaServerTask; if (!doesntNeedNewDmb) { alphaServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, true, true, false, alphaStartCts.Token); } else { alphaServerTask = sessionControllerFactory.Reattach(reattachInfo.Alpha, cancellationToken); } //wait until this boy officially starts so as not to confuse the servers as to who came first var startTime = DateTimeOffset.Now; alphaServer = await alphaServerTask.ConfigureAwait(false); alphaServer.SetHighPriority(); //extra delay for total ordering var now = DateTimeOffset.Now; var delay = now - startTime; if (delay.TotalSeconds < AlphaBravoStartupSeperationInterval) { await Task.Delay(startTime.AddSeconds(AlphaBravoStartupSeperationInterval) - now, cancellationToken).ConfigureAwait(false); } Task <ISessionController> bravoServerTask; if (!doesntNeedNewDmb) { bravoServerTask = sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, false, false, false, cancellationToken); } else { bravoServerTask = sessionControllerFactory.Reattach(reattachInfo.Bravo, cancellationToken); } bravoServer = await bravoServerTask.ConfigureAwait(false); bravoServer.SetHighPriority(); async Task <LaunchResult> CheckLaunch(ISessionController controller, string serverName) { var launch = await controller.LaunchResult.ConfigureAwait(false); if (launch.ExitCode.HasValue) { //you killed us ray... throw new JobException(String.Format(CultureInfo.InvariantCulture, "{1} server failed to start: {0}", launch.ToString(), serverName)); } if (!launch.StartupTime.HasValue) { throw new JobException(String.Format(CultureInfo.InvariantCulture, "{1} server timed out on startup: {0}s", launch.ToString(), ActiveLaunchParameters.StartupTimeout.Value)); } return(launch); } var alphaLrt = CheckLaunch(alphaServer, "Alpha"); var bravoLrt = CheckLaunch(bravoServer, "Bravo"); //now we have two booting servers, get them up and running var allTask = Task.WhenAll(alphaLrt, bravoLrt); //don't forget about the cancelationToken var cancelTcs = new TaskCompletionSource <object>(); using (cancellationToken.Register(() => cancelTcs.SetCanceled())) await Task.WhenAny(allTask, cancelTcs.Task).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); //both servers are now running, alpha is the active server, huzzah AlphaIsActive = doReattach ? reattachInfo.AlphaIsActive : true; LastLaunchResult = alphaLrt.Result; logger.LogInformation("Launched servers successfully"); Running = true; if (startMonitor) { await StopMonitor().ConfigureAwait(false); monitorCts = new CancellationTokenSource(); monitorTask = MonitorLifetimes(monitorCts.Token); } return(new WatchdogLaunchResult { Alpha = alphaLrt.Result, Bravo = bravoLrt.Result }); } catch { if (!doesntNeedNewDmb && (alphaServer == null && bravoServer == null)) { dmbToUse.Dispose(); //guaranteed to not be null here dmbToUse.Dispose(); //yes, dispose it twice. See the definition of IDmbFactory.LockNextDmb(), we called it with 2 locks } DisposeAndNullControllers(); throw; } } catch (Exception e) { logger.LogWarning("Failed to start watchdog: {0}", e.ToString()); throw; } finally { try { await chatTask.ConfigureAwait(false); } catch (OperationCanceledException) { } } } }
/// <summary> /// Handles the actions to take when the monitor has to "wake up" /// </summary> /// <param name="activationReason">The <see cref="MonitorActivationReason"/> that caused the invocation</param> /// <param name="monitorState">The current <see cref="MonitorState"/>. Will be modified upon retrn</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param> /// <returns>A <see cref="Task"/> representing the running operation</returns> async Task HandlerMonitorWakeup(MonitorActivationReason activationReason, MonitorState monitorState, CancellationToken cancellationToken) { logger.LogDebug("Monitor activation. Reason: {0}", activationReason); //returns true if the inactive server can't be used immediately bool FullRestartDeadInactive() { if (monitorState.RebootingInactiveServer || monitorState.InactiveServerCritFail) { logger.LogInformation("Inactive server is {0}! Restarting monitor...", monitorState.InactiveServerCritFail ? "critically failed" : "still rebooting"); monitorState.NextAction = MonitorAction.Restart; //will dispose server return(true); } return(false); }; //trys to set inactive server's port to the public port //doesn't handle closing active server's port async Task <bool> MakeInactiveActive() { logger.LogDebug("Setting inactive server to port {0}...", ActiveLaunchParameters.PrimaryPort.Value); var result = await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.PrimaryPort.Value, cancellationToken).ConfigureAwait(false); if (!result) { logger.LogWarning("Failed to activate inactive server! Restarting monitor..."); monitorState.NextAction = MonitorAction.Restart; //will dispose server return(false); } //inactive server should always be using active launch parameters LastLaunchParameters = ActiveLaunchParameters; var tmp = monitorState.ActiveServer; monitorState.ActiveServer = monitorState.InactiveServer; monitorState.InactiveServer = tmp; AlphaIsActive = !AlphaIsActive; return(true); } // Tries to load inactive server with latest dmb, falling back to current dmb on failure. Requires a lock on <see cref="semaphore"/> async Task <bool> RestartInactiveServer() { logger.LogInformation("Rebooting inactive server..."); var newDmb = dmbFactory.LockNextDmb(1); bool usedMostRecentDmb; try { monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, newDmb, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); usedMostRecentDmb = true; } catch (OperationCanceledException) { throw; } catch (Exception e) { logger.LogError("Error occurred while recreating server! Attempting backup strategy of running DMB of running server! Exception: {0}", e.ToString()); //ahh jeez, what do we do here? //this is our fault, so it should never happen but //idk maybe a database error while handling the newest dmb? //either way try to start it using the active server's dmb as a backup try { var dmbBackup = await dmbFactory.FromCompileJob(monitorState.ActiveServer.Dmb.CompileJob, cancellationToken).ConfigureAwait(false); if (dmbBackup == null) //NANI!? //just give up, if THAT compile job is failing then the ActiveServer is gonna crash soon too or already has { throw new JobException("Creating backup DMB provider failed!"); } monitorState.InactiveServer = await sessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbBackup, null, false, !monitorState.ActiveServer.IsPrimary, false, cancellationToken).ConfigureAwait(false); usedMostRecentDmb = false; await chat.SendWatchdogMessage("Staging newest DMB on inactive server failed: {0} Falling back to previous dmb...", cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { throw; } catch (Exception e2) { //fuuuuucckkk logger.LogError("Backup strategy failed! Monitor will restart when active server reboots! Exception: {0}", e2.ToString()); monitorState.InactiveServerCritFail = true; await chat.SendWatchdogMessage("Attempted reboot of inactive server failed. Watchdog will reset when active server fails or exits", cancellationToken).ConfigureAwait(false); return(true); //we didn't use the old dmb } } logger.LogInformation("Successfully relaunched inactive server!"); monitorState.RebootingInactiveServer = true; return(usedMostRecentDmb); } async Task UpdateAndRestartInactiveServer(bool breakAfter) { //replace the notification tcs here so that the next loop will read a fresh one activeParametersUpdated = new TaskCompletionSource <object>(); monitorState.InactiveServer.Dispose(); //kill or recycle it monitorState.NextAction = breakAfter ? MonitorAction.Break : MonitorAction.Continue; var usedLatestDmb = await RestartInactiveServer().ConfigureAwait(false); if (monitorState.NextAction == (breakAfter ? MonitorAction.Break : MonitorAction.Continue)) { monitorState.ActiveServer.ClosePortOnReboot = false; if (monitorState.InactiveServerHasStagedDmb && !usedLatestDmb) { monitorState.InactiveServerHasStagedDmb = false; //don't try to load it again though } } }; string ExitWord(ISessionController controller) => controller.TerminationWasRequested ? "exited" : "crashed"; //reason handling switch (activationReason) { case MonitorActivationReason.ActiveServerCrashed: if (monitorState.ActiveServer.RebootState == Components.Watchdog.RebootState.Shutdown) { await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server {0}! Exiting due to graceful termination request...", ExitWord(monitorState.ActiveServer)), cancellationToken).ConfigureAwait(false); monitorState.NextAction = MonitorAction.Exit; break; } if (FullRestartDeadInactive()) { await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server {0}! Inactive server unable to online!", ExitWord(monitorState.ActiveServer)), cancellationToken).ConfigureAwait(false); break; } await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Active server {0}! Onlining inactive server...", ExitWord(monitorState.ActiveServer)), cancellationToken).ConfigureAwait(false); if (!await MakeInactiveActive().ConfigureAwait(false)) { break; } await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); break; case MonitorActivationReason.InactiveServerCrashed: await chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Inactive server {0}! Rebooting...", ExitWord(monitorState.InactiveServer)), cancellationToken).ConfigureAwait(false); await UpdateAndRestartInactiveServer(false).ConfigureAwait(false); break; case MonitorActivationReason.ActiveServerRebooted: if (FullRestartDeadInactive()) { break; } //what matters here is the RebootState bool restartOnceSwapped = false; var rebootState = monitorState.ActiveServer.RebootState; monitorState.ActiveServer.ResetRebootState(); //the DMAPI has already done this internally switch (rebootState) { case Components.Watchdog.RebootState.Normal: break; case Components.Watchdog.RebootState.Restart: restartOnceSwapped = true; break; case Components.Watchdog.RebootState.Shutdown: await chat.SendWatchdogMessage("Active server rebooted! Exiting due to graceful termination request...", cancellationToken).ConfigureAwait(false); DisposeAndNullControllers(); monitorState.NextAction = MonitorAction.Exit; return; } var sameCompileJob = monitorState.InactiveServer.Dmb.CompileJob.Id == monitorState.ActiveServer.Dmb.CompileJob.Id; if (sameCompileJob && monitorState.InactiveServerHasStagedDmb) { //both servers up to date monitorState.InactiveServerHasStagedDmb = false; } if (!sameCompileJob || ActiveLaunchParameters != LastLaunchParameters) { //need a new launch in ActiveServer restartOnceSwapped = true; } if (restartOnceSwapped && !monitorState.ActiveServer.ClosePortOnReboot) { //we need to manually restart active server //it won't listen to us right now so just kill it monitorState.ActiveServer.Dispose(); } if ((!restartOnceSwapped && !monitorState.ActiveServer.ClosePortOnReboot) || !await MakeInactiveActive().ConfigureAwait(false)) { break; } monitorState.ActiveServer.ClosePortOnReboot = true; if (!restartOnceSwapped) { monitorState.InactiveServer.ClosePortOnReboot = false; //try to reopen inactive server on the private port so it's not pinging all the time //failing that, just reboot it restartOnceSwapped = !await monitorState.InactiveServer.SetPort(ActiveLaunchParameters.SecondaryPort.Value, cancellationToken).ConfigureAwait(false); } if (restartOnceSwapped) //for one reason or another { await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); //break because worse case, active server is still booting } else { monitorState.InactiveServer.ClosePortOnReboot = false; monitorState.NextAction = MonitorAction.Break; } break; case MonitorActivationReason.InactiveServerRebooted: monitorState.RebootingInactiveServer = true; monitorState.InactiveServer.ResetRebootState(); monitorState.ActiveServer.ClosePortOnReboot = false; monitorState.NextAction = MonitorAction.Continue; break; case MonitorActivationReason.InactiveServerStartupComplete: //eziest case of my life monitorState.RebootingInactiveServer = false; monitorState.ActiveServer.ClosePortOnReboot = true; monitorState.NextAction = MonitorAction.Continue; break; case MonitorActivationReason.NewDmbAvailable: monitorState.InactiveServerHasStagedDmb = true; await UpdateAndRestartInactiveServer(true).ConfigureAwait(false); //next case does same thing break; case MonitorActivationReason.ActiveLaunchParametersUpdated: await UpdateAndRestartInactiveServer(false).ConfigureAwait(false); break; } }
public async Task TestSuccessfulLaunchAndShutdown() { var mockChat = new Mock <IChat>(); mockChat.Setup(x => x.RegisterCommandHandler(It.IsNotNull <ICustomCommandHandler>())).Verifiable(); var mockSessionControllerFactory = new Mock <ISessionControllerFactory>(); var mockDmbFactory = new Mock <IDmbFactory>(); var mockLogger = new Mock <ILogger <ExperimentalWatchdog> >(); var mockReattachInfoHandler = new Mock <IReattachInfoHandler>(); var mockDatabaseContextFactory = new Mock <IDatabaseContextFactory>(); var mockByondTopicSender = new Mock <IByondTopicSender>(); var mockEventConsumer = new Mock <IEventConsumer>(); var mockJobManager = new Mock <IJobManager>(); var mockRestartRegistration = new Mock <IRestartRegistration>(); mockRestartRegistration.Setup(x => x.Dispose()).Verifiable(); var mockServerControl = new Mock <IServerControl>(); mockServerControl.Setup(x => x.RegisterForRestart(It.IsNotNull <IRestartHandler>())).Returns(mockRestartRegistration.Object).Verifiable(); var mockLaunchParameters = new DreamDaemonLaunchParameters(); var mockInstance = new Models.Instance(); var mockAsyncDelayer = new Mock <IAsyncDelayer>(); using (var wd = new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, mockLogger.Object, mockLaunchParameters, mockInstance, default)) using (var cts = new CancellationTokenSource()) { var mockCompileJob = new Models.CompileJob(); var mockDmbProvider = new Mock <IDmbProvider>(); mockDmbProvider.SetupGet(x => x.CompileJob).Returns(mockCompileJob).Verifiable(); var mDmbP = mockDmbProvider.Object; var infiniteTask = new TaskCompletionSource <int>().Task; mockDmbFactory.SetupGet(x => x.OnNewerDmb).Returns(infiniteTask); mockDmbFactory.SetupGet(x => x.DmbAvailable).Returns(true).Verifiable(); mockDmbFactory.Setup(x => x.LockNextDmb(2)).Returns(mDmbP).Verifiable(); var sessionsToVerify = new List <Mock <ISessionController> >(); var cancellationToken = cts.Token; mockSessionControllerFactory.Setup(x => x.LaunchNew(mockLaunchParameters, mDmbP, null, It.IsAny <bool>(), It.IsAny <bool>(), false, cancellationToken)).Returns(() => { var mockSession = new Mock <ISessionController>(); mockSession.SetupGet(x => x.Lifetime).Returns(infiniteTask).Verifiable(); mockSession.SetupGet(x => x.OnReboot).Returns(infiniteTask).Verifiable(); mockSession.SetupGet(x => x.Dmb).Returns(mDmbP).Verifiable(); mockSession.SetupGet(x => x.LaunchResult).Returns(Task.FromResult(new LaunchResult { StartupTime = TimeSpan.FromSeconds(1) })).Verifiable(); sessionsToVerify.Add(mockSession); return(Task.FromResult(mockSession.Object)); }).Verifiable(); mockAsyncDelayer.Setup(x => x.Delay(It.IsAny <TimeSpan>(), cancellationToken)).Returns(Task.CompletedTask).Verifiable(); cts.CancelAfter(TimeSpan.FromSeconds(15)); try { await wd.Launch(cancellationToken).ConfigureAwait(false); await wd.Terminate(false, cancellationToken).ConfigureAwait(false); } finally { cts.Cancel(); } Assert.AreEqual(2, sessionsToVerify.Count); foreach (var I in sessionsToVerify) { I.VerifyAll(); } mockDmbProvider.VerifyAll(); } mockSessionControllerFactory.VerifyAll(); mockDmbFactory.VerifyAll(); mockRestartRegistration.VerifyAll(); mockServerControl.VerifyAll(); mockChat.VerifyAll(); mockAsyncDelayer.VerifyAll(); }
public void TestConstruction() { Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(null, null, null, null, null, null, null, null, null, null, null, null, null, default)); var mockChat = new Mock <IChat>(); mockChat.Setup(x => x.RegisterCommandHandler(It.IsNotNull <ICustomCommandHandler>())).Verifiable(); Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(mockChat.Object, null, null, null, null, null, null, null, null, null, null, null, null, default)); var mockSessionControllerFactory = new Mock <ISessionControllerFactory>(); Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, null, null, null, null, null, null, null, null, null, null, null, default)); var mockDmbFactory = new Mock <IDmbFactory>(); Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, null, null, null, null, null, null, null, null, null, null, default)); var mockReattachInfoHandler = new Mock <IReattachInfoHandler>(); Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, null, null, null, null, null, null, null, null, null, default)); var mockDatabaseContextFactory = new Mock <IDatabaseContextFactory>(); Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, null, null, null, null, null, null, null, null, default)); var mockByondTopicSender = new Mock <IByondTopicSender>(); Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, null, null, null, null, null, null, null, default)); var mockEventConsumer = new Mock <IEventConsumer>(); Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, null, null, null, null, null, null, default)); var mockJobManager = new Mock <IJobManager>(); Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, null, null, null, null, null, default)); var mockRestartRegistration = new Mock <IRestartRegistration>(); mockRestartRegistration.Setup(x => x.Dispose()).Verifiable(); var mockServerControl = new Mock <IServerControl>(); mockServerControl.Setup(x => x.RegisterForRestart(It.IsNotNull <IRestartHandler>())).Returns(mockRestartRegistration.Object).Verifiable(); Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, null, null, null, null, default)); var mockAsyncDelayer = new Mock <IAsyncDelayer>(); Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, null, null, null, default)); var mockLogger = new Mock <ILogger <ExperimentalWatchdog> >(); Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, mockLogger.Object, null, null, default)); var mockLaunchParameters = new DreamDaemonLaunchParameters(); Assert.ThrowsException <ArgumentNullException>(() => new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, mockLogger.Object, mockLaunchParameters, null, default)); var mockInstance = new Models.Instance(); new ExperimentalWatchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, mockLogger.Object, mockLaunchParameters, mockInstance, default).Dispose(); mockRestartRegistration.VerifyAll(); mockServerControl.VerifyAll(); mockChat.VerifyAll(); }
/// <summary> /// Run a quick DD instance to test the DMAPI is installed on the target code /// </summary> /// <param name="timeout">The timeout in seconds for validation</param> /// <param name="securityLevel">The <see cref="DreamDaemonSecurity"/> level to use to validate the API</param> /// <param name="job">The <see cref="CompileJob"/> for the operation</param> /// <param name="byondLock">The current <see cref="IByondExecutableLock"/></param> /// <param name="portToUse">The port to use for API validation</param> /// <param name="requireValidate">If the API validation is required to complete the deployment.</param> /// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param> /// <returns>A <see cref="Task"/> representing the running operation</returns> async Task VerifyApi( uint timeout, DreamDaemonSecurity securityLevel, Models.CompileJob job, IByondExecutableLock byondLock, ushort portToUse, bool requireValidate, CancellationToken cancellationToken) { logger.LogTrace("Verifying {0}DMAPI...", requireValidate ? "required " : String.Empty); var launchParameters = new DreamDaemonLaunchParameters { AllowWebClient = false, Port = portToUse, SecurityLevel = securityLevel, StartupTimeout = timeout, TopicRequestTimeout = 0, // not used HeartbeatSeconds = 0 // not used }; job.MinimumSecurityLevel = securityLevel; // needed for the TempDmbProvider ApiValidationStatus validationStatus; using (var provider = new TemporaryDmbProvider(ioManager.ResolvePath(job.DirectoryName.ToString()), String.Concat(job.DmeName, DmbExtension), job)) await using (var controller = await sessionControllerFactory.LaunchNew(provider, byondLock, launchParameters, true, cancellationToken).ConfigureAwait(false)) { var launchResult = await controller.LaunchResult.ConfigureAwait(false); if (launchResult.StartupTime.HasValue) { await controller.Lifetime.WithToken(cancellationToken).ConfigureAwait(false); } if (!controller.Lifetime.IsCompleted) { await controller.DisposeAsync().ConfigureAwait(false); } validationStatus = controller.ApiValidationStatus; if (requireValidate && validationStatus == ApiValidationStatus.NeverValidated) { throw new JobException(ErrorCode.DreamMakerNeverValidated); } logger.LogTrace("API validation status: {0}", validationStatus); job.DMApiVersion = controller.DMApiVersion; } switch (validationStatus) { case ApiValidationStatus.RequiresUltrasafe: job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe; return; case ApiValidationStatus.RequiresSafe: job.MinimumSecurityLevel = DreamDaemonSecurity.Safe; return; case ApiValidationStatus.RequiresTrusted: job.MinimumSecurityLevel = DreamDaemonSecurity.Trusted; return; case ApiValidationStatus.NeverValidated: if (requireValidate) { throw new JobException(ErrorCode.DreamMakerNeverValidated); } job.MinimumSecurityLevel = DreamDaemonSecurity.Ultrasafe; break; case ApiValidationStatus.BadValidationRequest: case ApiValidationStatus.Incompatible: throw new JobException(ErrorCode.DreamMakerInvalidValidation); case ApiValidationStatus.UnaskedValidationRequest: default: throw new InvalidOperationException( $"Session controller returned unexpected ApiValidationStatus: {validationStatus}"); } }
/// <summary> /// Initializes a new instance of the <see cref="ExperimentalWatchdog"/> <see langword="class"/>. /// </summary> /// <param name="chat">The <see cref="IChat"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="reattachInfoHandler">The <see cref="IReattachInfoHandler"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="byondTopicSender">The <see cref="IByondTopicSender"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="eventConsumer">The <see cref="IEventConsumer"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="logger">The <see cref="ILogger"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="initialLaunchParameters">The <see cref="DreamDaemonLaunchParameters"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="instance">The <see cref="Api.Models.Instance"/> for the <see cref="WatchdogBase"/>.</param> /// <param name="autoStart">The autostart value for the <see cref="WatchdogBase"/>.</param> public ExperimentalWatchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILogger <ExperimentalWatchdog> logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) : base( chat, sessionControllerFactory, dmbFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, serverControl, asyncDelayer, logger, initialLaunchParameters, instance, autoStart) { alphaIsActive = true; }