diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/RepositoryBasedServerUpdate.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/RepositoryBasedServerUpdate.graphql new file mode 100644 index 0000000000..4f8a3bdbcc --- /dev/null +++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/RepositoryBasedServerUpdate.graphql @@ -0,0 +1,11 @@ +mutation RepositoryBasedServerUpdate($targetVersion: Semver!) { + changeServerVersionViaTrackedRepository(input: { targetVersion: $targetVersion }) { + errors { + ... on ErrorMessageError { + additionalData + errorCode + message + } + } + } +} diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/RestartServer.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/RestartServer.graphql new file mode 100644 index 0000000000..c9abfea9ef --- /dev/null +++ b/src/Tgstation.Server.Client.GraphQL/GQL/Mutations/RestartServer.graphql @@ -0,0 +1,11 @@ +mutation RestartServer() { + restartServer() { + errors { + ... on ErrorMessageError { + additionalData + errorCode + message + } + } + } +} diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetUpdateInformation.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetUpdateInformation.graphql new file mode 100644 index 0000000000..dfa3498216 --- /dev/null +++ b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/GetUpdateInformation.graphql @@ -0,0 +1,10 @@ +query GetUpdateInformation($forceFresh: Boolean!) { + swarm { + updateInformation { + generatedAt + latestVersion(forceFresh: $forceFresh) + updateInProgress + trackedRepositoryUrl(forceFresh: $forceFresh) + } + } +} diff --git a/src/Tgstation.Server.Host/GraphQL/Mutations/AdministrationMutations.cs b/src/Tgstation.Server.Host/GraphQL/Mutations/AdministrationMutations.cs new file mode 100644 index 0000000000..6214227d80 --- /dev/null +++ b/src/Tgstation.Server.Host/GraphQL/Mutations/AdministrationMutations.cs @@ -0,0 +1,84 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +using HotChocolate; +using HotChocolate.Types; + +using Tgstation.Server.Api.Models.Response; +using Tgstation.Server.Api.Rights; +using Tgstation.Server.Host.Authority; +using Tgstation.Server.Host.GraphQL.Scalars; +using Tgstation.Server.Host.Security; + +namespace Tgstation.Server.Host.GraphQL.Mutations +{ + /// + /// related s. + /// + [ExtendObjectType(typeof(Mutation))] + [GraphQLDescription(Mutation.GraphQLDescription)] + public sealed class AdministrationMutations + { + /// + /// Restarts the server node without terminating running game instances. + /// + /// The for the . + /// A representing the running operation. + [TgsGraphQLAuthorize(nameof(IAdministrationAuthority.TriggerServerRestart))] + [Error(typeof(ErrorMessageException))] + public async ValueTask RestartServer( + [Service] IGraphQLAuthorityInvoker administrationAuthority) + { + ArgumentNullException.ThrowIfNull(administrationAuthority); + await administrationAuthority.Invoke( + authority => authority.TriggerServerRestart()); + + return new Query(); + } + + /// + /// Restarts the server node without terminating running game instances and changes its . + /// + /// The semver of the server available in the tracked repository to switch to. + /// The for the . + /// The for the operation. + /// A representing the running operation. + [TgsGraphQLAuthorize(AdministrationRights.ChangeVersion)] + [Error(typeof(ErrorMessageException))] + public async ValueTask ChangeServerVersionViaTrackedRepository( + Version targetVersion, + [Service] IGraphQLAuthorityInvoker administrationAuthority, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(targetVersion); + ArgumentNullException.ThrowIfNull(administrationAuthority); + await administrationAuthority.Invoke( + authority => authority.TriggerServerVersionChange(targetVersion, false, cancellationToken)); + return new Query(); + } + + /// + /// Restarts the server node without terminating running game instances and changes its . + /// + /// The semver of the server available in the tracked repository to switch to. + /// The for the . + /// The for the operation. + /// A FileTicket that should be used to upload a zip containing the update data to the file transfer service. + [TgsGraphQLAuthorize(AdministrationRights.UploadVersion)] + [Error(typeof(ErrorMessageException))] + [GraphQLType] + public async ValueTask ChangeServerVersionViaUpload( + Version targetVersion, + [Service] IGraphQLAuthorityInvoker administrationAuthority, + CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(targetVersion); + ArgumentNullException.ThrowIfNull(administrationAuthority); + var response = await administrationAuthority.Invoke( + authority => authority.TriggerServerVersionChange(targetVersion, true, cancellationToken)); + + return response.FileTicket ?? throw new InvalidOperationException("Administration authority did not generate a FileUploadTicket!"); + } + } +} diff --git a/src/Tgstation.Server.Host/GraphQL/Types/ServerSwarm.cs b/src/Tgstation.Server.Host/GraphQL/Types/ServerSwarm.cs index 286aad2adf..35d82985dd 100644 --- a/src/Tgstation.Server.Host/GraphQL/Types/ServerSwarm.cs +++ b/src/Tgstation.Server.Host/GraphQL/Types/ServerSwarm.cs @@ -7,7 +7,6 @@ using HotChocolate; using Microsoft.Extensions.Options; using Tgstation.Server.Host.Configuration; -using Tgstation.Server.Host.Core; using Tgstation.Server.Host.GraphQL.Interfaces; using Tgstation.Server.Host.Properties; using Tgstation.Server.Host.Security; @@ -20,19 +19,6 @@ namespace Tgstation.Server.Host.GraphQL.Types /// public sealed class ServerSwarm { - /// - /// If there is a swarm update in progress. - /// - /// The to use. - /// if there is an update in progress, otherwise. - [TgsGraphQLAuthorize] - public bool UpdateInProgress( - [Service] IServerControl serverControl) - { - ArgumentNullException.ThrowIfNull(serverControl); - return serverControl.UpdateInProgress; - } - /// /// Gets the swarm protocol major version in use. /// @@ -78,5 +64,12 @@ namespace Tgstation.Server.Host.GraphQL.Types ArgumentNullException.ThrowIfNull(swarmService); return swarmService.GetSwarmServers()?.Select(x => new SwarmNode(x)).ToList(); } + + /// + /// Gets the for the swarm. + /// + /// A new . + [TgsGraphQLAuthorize] + public UpdateInformation UpdateInformation() => new(); } } diff --git a/src/Tgstation.Server.Host/GraphQL/Types/UpdateInformation.cs b/src/Tgstation.Server.Host/GraphQL/Types/UpdateInformation.cs new file mode 100644 index 0000000000..27e758bd79 --- /dev/null +++ b/src/Tgstation.Server.Host/GraphQL/Types/UpdateInformation.cs @@ -0,0 +1,117 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +using HotChocolate; + +using Tgstation.Server.Api.Models.Response; +using Tgstation.Server.Host.Authority; +using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Utils; + +namespace Tgstation.Server.Host.GraphQL.Types +{ + /// + /// Gets information about updates for the . + /// + public sealed class UpdateInformation : IDisposable + { + /// + /// to prevent duplicate cache generations in one query. + /// + readonly SemaphoreSlim cacheReadSemaphore; + + /// + /// If the cache was already force generated this query. + /// + bool cacheForceGenerated; + + /// + /// Initializes a new instance of the class. + /// + public UpdateInformation() + { + cacheReadSemaphore = new SemaphoreSlim(1, 1); + } + + /// + public void Dispose() + => cacheReadSemaphore.Dispose(); + + /// + /// If there is a swarm update in progress. This is not affected by . + /// + /// The to use. + /// if there is an update in progress, otherwise. + public bool UpdateInProgress( + [Service] IServerControl serverControl) + { + ArgumentNullException.ThrowIfNull(serverControl); + return serverControl.UpdateInProgress; + } + + /// + /// Gets the of the GitHub repository updates are sourced from. + /// + /// If the local cache TGS keeps of this data will be bypassed. + /// The for the to use. + /// The for the operation. + /// The of the GitHub repository updates are sourced from on success. if a GitHub API error occurred. + public async ValueTask TrackedRepositoryUrl( + bool forceFresh, + [Service] IGraphQLAuthorityInvoker administrationAuthority, + CancellationToken cancellationToken) + => (await GetAdministrationResponseSafe(forceFresh, administrationAuthority, cancellationToken)).TrackedRepositoryUrl; + + /// + /// Gets the time the was generated. + /// + /// The for the to use. + /// The for the operation. + /// The time the was generated on success. if a GitHub API error occurred. + public async ValueTask GeneratedAt( + [Service] IGraphQLAuthorityInvoker administrationAuthority, + CancellationToken cancellationToken) + => (await GetAdministrationResponseSafe(false, administrationAuthority, cancellationToken)).GeneratedAt; + + /// + /// Gets the latest of tgstation-server available on the GitHub repository updates are sourced from. + /// + /// If the local cache TGS keeps of this data will be bypassed. + /// The for the to use. + /// The for the operation. + /// The of the latest TGS version on success. if a GitHub API error occurred. + public async ValueTask LatestVersion( + bool forceFresh, + [Service] IGraphQLAuthorityInvoker administrationAuthority, + CancellationToken cancellationToken) + => (await GetAdministrationResponseSafe(forceFresh, administrationAuthority, cancellationToken)).LatestVersion; + + /// + /// Safely retrieve the from a given without generating the cache multiple times in one query. + /// + /// If the local cache TGS keeps of this data will be bypassed. + /// The for the to use. + /// The for the operation. + /// A resulting in the from the . + async ValueTask GetAdministrationResponseSafe( + bool forceFresh, + IGraphQLAuthorityInvoker administrationAuthority, + CancellationToken cancellationToken) + { + using (await SemaphoreSlimContext.Lock(cacheReadSemaphore, cancellationToken)) + { + if (cacheForceGenerated) + forceFresh = false; + else + cacheForceGenerated |= forceFresh; + + ArgumentNullException.ThrowIfNull(administrationAuthority); + var response = await administrationAuthority.Invoke( + authority => authority.GetUpdateInformation(forceFresh, cancellationToken)); + + return response; + } + } + } +} diff --git a/tests/Tgstation.Server.Tests/Live/AdministrationTest.cs b/tests/Tgstation.Server.Tests/Live/AdministrationTest.cs index afeabef6f5..d50205da90 100644 --- a/tests/Tgstation.Server.Tests/Live/AdministrationTest.cs +++ b/tests/Tgstation.Server.Tests/Live/AdministrationTest.cs @@ -13,11 +13,13 @@ namespace Tgstation.Server.Tests.Live { sealed class AdministrationTest { - readonly IAdministrationClient client; + readonly IMultiServerClient client; + readonly IAdministrationClient restClient; - public AdministrationTest(IAdministrationClient client) + public AdministrationTest(MultiServerClient client) { this.client = client ?? throw new ArgumentNullException(nameof(client)); + this.restClient = client.RestClient.Administration; } public async Task Run(CancellationToken cancellationToken) @@ -29,24 +31,24 @@ namespace Tgstation.Server.Tests.Live async Task TestLogs(CancellationToken cancellationToken) { - var logs = await client.ListLogs(null, cancellationToken); + var logs = await restClient.ListLogs(null, cancellationToken); Assert.AreNotEqual(0, logs.Count); var logFile = logs[0]; Assert.IsNotNull(logFile); Assert.IsFalse(string.IsNullOrWhiteSpace(logFile.Name)); Assert.IsNull(logFile.FileTicket); - var downloadedTuple = await client.GetLog(logFile, cancellationToken); + var downloadedTuple = await restClient.GetLog(logFile, cancellationToken); Assert.AreEqual(logFile.Name, downloadedTuple.Item1.Name); Assert.IsTrue(logFile.LastModified <= downloadedTuple.Item1.LastModified); Assert.IsNull(logFile.FileTicket); - await ApiAssert.ThrowsException>(() => client.GetLog(new LogFileResponse + await ApiAssert.ThrowsException>(() => restClient.GetLog(new LogFileResponse { Name = "very_fake_path.log" }, cancellationToken), ErrorCode.IOError); - await ApiAssert.ThrowsException>(() => client.GetLog(new LogFileResponse + await ApiAssert.ThrowsException>(() => restClient.GetLog(new LogFileResponse { Name = "../out_of_bounds.file" }, cancellationToken)); @@ -54,24 +56,59 @@ namespace Tgstation.Server.Tests.Live async Task TestRead(CancellationToken cancellationToken) { - var model = await client.Read(false, cancellationToken); + await client.Execute( + async restServerClient => + { + var restClient = restServerClient.Administration; - //we've released a few 5.x versions now, check the release checker is at least somewhat functional - Assert.IsTrue(4 < model.LatestVersion.Major); - Assert.IsNotNull(model.TrackedRepositoryUrl); - Assert.IsTrue(model.GeneratedAt.HasValue); - Assert.IsTrue(model.GeneratedAt.Value <= DateTimeOffset.UtcNow); + var model = await restClient.Read(false, cancellationToken); - // test the cache - var newerModel = await client.Read(false, cancellationToken); - Assert.AreEqual(model.GeneratedAt, newerModel.GeneratedAt); + //we've released a few 5.x versions now, check the release checker is at least somewhat functional + Assert.IsNotNull(model.LatestVersion); + Assert.IsTrue(4 < model.LatestVersion.Major); + Assert.IsNotNull(model.TrackedRepositoryUrl); + Assert.IsTrue(model.GeneratedAt.HasValue); + Assert.IsTrue(model.GeneratedAt.Value <= DateTimeOffset.UtcNow); - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + // test the cache + var newerModel = await restClient.Read(false, cancellationToken); + Assert.AreEqual(model.GeneratedAt, newerModel.GeneratedAt); - var newestModel = await client.Read(true, cancellationToken); - Assert.AreNotEqual(model.GeneratedAt, newestModel.GeneratedAt); - Assert.IsNotNull(newestModel.GeneratedAt); - Assert.IsTrue(model.GeneratedAt < newestModel.GeneratedAt); + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + + var newestModel = await restClient.Read(true, cancellationToken); + Assert.AreNotEqual(model.GeneratedAt, newestModel.GeneratedAt); + Assert.IsNotNull(newestModel.GeneratedAt); + Assert.IsTrue(model.GeneratedAt < newestModel.GeneratedAt); + }, + async gqlClient => + { + var queryResult = await gqlClient.RunQueryEnsureNoErrors( + gql => gql.GetUpdateInformation.ExecuteAsync(false, cancellationToken), + cancellationToken); + + // we've released a few 5.x versions now, check the release checker is at least somewhat functional + Assert.IsNotNull(queryResult.Swarm.UpdateInformation.LatestVersion); + Assert.IsTrue(4 < queryResult.Swarm.UpdateInformation.LatestVersion.Major); + Assert.IsNotNull(queryResult.Swarm.UpdateInformation.TrackedRepositoryUrl); + Assert.IsTrue(queryResult.Swarm.UpdateInformation.GeneratedAt.HasValue); + Assert.IsTrue(queryResult.Swarm.UpdateInformation.GeneratedAt.Value <= DateTimeOffset.UtcNow); + + // test the cache + var queryResult2 = await gqlClient.RunQueryEnsureNoErrors( + gql => gql.GetUpdateInformation.ExecuteAsync(false, cancellationToken), + cancellationToken); + Assert.AreEqual(queryResult.Swarm.UpdateInformation.GeneratedAt, queryResult2.Swarm.UpdateInformation.GeneratedAt); + + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + var queryResult3 = await gqlClient.RunQueryEnsureNoErrors( + gql => gql.GetUpdateInformation.ExecuteAsync(true, cancellationToken), + cancellationToken); + + Assert.AreNotEqual(queryResult.Swarm.UpdateInformation.GeneratedAt, queryResult3.Swarm.UpdateInformation.GeneratedAt); + Assert.IsNotNull(queryResult3.Swarm.UpdateInformation.GeneratedAt); + Assert.IsTrue(queryResult.Swarm.UpdateInformation.GeneratedAt.Value < queryResult3.Swarm.UpdateInformation.GeneratedAt.Value); + }); } } } diff --git a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs index 9140a29df3..5df923a85c 100644 --- a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs +++ b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs @@ -340,21 +340,29 @@ namespace Tgstation.Server.Tests.Live } //attempt to update to stable - var responseModel = await TestWithoutAndWithPermission( - () => adminClient.RestClient.Administration.Update( - new ServerUpdateRequest - { - NewVersion = TestUpdateVersion, - UploadZip = false, - }, - null, - cancellationToken), - adminClient.RestClient, - AdministrationRights.ChangeVersion); + await adminClient.Execute( + async restClient => + { + var responseModel = await TestWithoutAndWithPermission( + () => restClient.Administration.Update( + new ServerUpdateRequest + { + NewVersion = TestUpdateVersion, + UploadZip = false, + }, + null, + cancellationToken), + adminClient.RestClient, + AdministrationRights.ChangeVersion); - Assert.IsNotNull(responseModel); - Assert.IsNull(responseModel.FileTicket); - Assert.AreEqual(TestUpdateVersion, responseModel.NewVersion); + Assert.IsNotNull(responseModel); + Assert.IsNull(responseModel.FileTicket); + Assert.AreEqual(TestUpdateVersion, responseModel.NewVersion); + }, + async gqlClient => await gqlClient.RunMutationEnsureNoErrors( + gql => gql.RepositoryBasedServerUpdate.ExecuteAsync(TestUpdateVersion, cancellationToken), + result => result, + cancellationToken)); try { @@ -524,17 +532,25 @@ namespace Tgstation.Server.Tests.Live CheckInfo(controllerInfo); // test update - var responseModel = await controllerClient.RestClient.Administration.Update( - new ServerUpdateRequest + await controllerClient.Execute( + async restClient => { - NewVersion = TestUpdateVersion - }, - null, - cancellationToken); + var responseModel = await restClient.Administration.Update( + new ServerUpdateRequest + { + NewVersion = TestUpdateVersion + }, + null, + cancellationToken); - Assert.IsNotNull(responseModel); - Assert.IsNull(responseModel.FileTicket); - Assert.AreEqual(TestUpdateVersion, responseModel.NewVersion); + Assert.IsNotNull(responseModel); + Assert.IsNull(responseModel.FileTicket); + Assert.AreEqual(TestUpdateVersion, responseModel.NewVersion); + }, + async gqlClient => await gqlClient.RunMutationEnsureNoErrors( + gql => gql.RepositoryBasedServerUpdate.ExecuteAsync(TestUpdateVersion, cancellationToken), + result => result, + cancellationToken)); await Task.WhenAny(Task.Delay(TimeSpan.FromMinutes(2)), serverTask); Assert.IsTrue(serverTask.IsCompleted); @@ -711,13 +727,18 @@ namespace Tgstation.Server.Tests.Live await ApiAssert.ThrowsException(() => node1Client.RestClient.Instances.GetId(controllerInstance, cancellationToken), Api.Models.ErrorCode.ResourceNotPresent); // test update - await node1Client.RestClient.Administration.Update( - new ServerUpdateRequest - { - NewVersion = TestUpdateVersion - }, - null, - cancellationToken); + await node1Client.Execute( + async restClient => await restClient.Administration.Update( + new ServerUpdateRequest + { + NewVersion = TestUpdateVersion + }, + null, + cancellationToken), + async gqlClient => await gqlClient.RunMutationEnsureNoErrors( + gql => gql.RepositoryBasedServerUpdate.ExecuteAsync(TestUpdateVersion, cancellationToken), + result => result, + cancellationToken)); await Task.WhenAny(Task.Delay(TimeSpan.FromMinutes(2)), serverTask); Assert.IsTrue(serverTask.IsCompleted); @@ -752,13 +773,22 @@ namespace Tgstation.Server.Tests.Live await using var controllerClient2 = await CreateAdminClient(controller.ApiUrl, cancellationToken); await using var node1Client2 = await CreateAdminClient(node1.ApiUrl, cancellationToken); - await ApiAssert.ThrowsException(() => controllerClient2.RestClient.Administration.Update( - new ServerUpdateRequest - { - NewVersion = TestUpdateVersion - }, - null, - cancellationToken), Api.Models.ErrorCode.SwarmIntegrityCheckFailed); + await controllerClient2.Execute( + async restClient => await ApiAssert.ThrowsException( + () => restClient.Administration.Update( + new ServerUpdateRequest + { + NewVersion = TestUpdateVersion + }, + null, + cancellationToken), + Api.Models.ErrorCode.SwarmIntegrityCheckFailed), + async gqlClient => await ApiAssert.OperationFails( + gqlClient, + gql => gql.RepositoryBasedServerUpdate.ExecuteAsync(TestUpdateVersion, cancellationToken), + result => result, + Client.GraphQL.ErrorCode.SwarmIntegrityCheckFailed, + cancellationToken)); // regression: test updating also works from the controller serverTask = Task.WhenAll( @@ -954,7 +984,13 @@ namespace Tgstation.Server.Tests.Live Assert.IsFalse(node2Info.SwarmServers.Any(x => x.Identifier == "node1")); // restart the controller - await controllerClient.RestClient.Administration.Restart(cancellationToken); + await controllerClient.Execute( + restClient => restClient.Administration.Restart(cancellationToken), + async gqlClient => await gqlClient.RunMutationEnsureNoErrors( + gql => gql.RestartServer.ExecuteAsync(cancellationToken), + result => result, + cancellationToken)); + await Task.WhenAny( controllerTask, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken)); @@ -976,7 +1012,12 @@ namespace Tgstation.Server.Tests.Live await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken); // restart node2 - await node2Client.RestClient.Administration.Restart(cancellationToken); + await node2Client.Execute( + restClient => restClient.Administration.Restart(cancellationToken), + async gqlClient => await gqlClient.RunMutationEnsureNoErrors( + gql => gql.RestartServer.ExecuteAsync(cancellationToken), + result => result, + cancellationToken)); await Task.WhenAny( node2Task, Task.Delay(TimeSpan.FromMinutes(1))); @@ -988,14 +1029,22 @@ namespace Tgstation.Server.Tests.Live Assert.IsNull(controllerInfo.SwarmServers.SingleOrDefault(x => x.Identifier == "node2")); // update should fail - await ApiAssert.ThrowsException( - () => controllerClient2.RestClient.Administration.Update(new ServerUpdateRequest - { - NewVersion = TestUpdateVersion - }, - null, - cancellationToken), - Api.Models.ErrorCode.SwarmIntegrityCheckFailed); + await controllerClient2.Execute( + async restClient => await ApiAssert.ThrowsException( + () => restClient.Administration.Update( + new ServerUpdateRequest + { + NewVersion = TestUpdateVersion + }, + null, + cancellationToken), + Api.Models.ErrorCode.SwarmIntegrityCheckFailed), + async gqlClient => await ApiAssert.OperationFails( + gqlClient, + gql => gql.RepositoryBasedServerUpdate.ExecuteAsync(TestUpdateVersion, cancellationToken), + result => result, + Client.GraphQL.ErrorCode.SwarmIntegrityCheckFailed, + cancellationToken)); node2Task = node2.Run(cancellationToken).AsTask(); await using var node2Client2 = await CreateAdminClient(node2.ApiUrl, cancellationToken); @@ -1486,7 +1535,7 @@ namespace Tgstation.Server.Tests.Live jobsHubTestTask = FailFast(await jobsHubTest.Run(cancellationToken)); // returns Task var rootTest = FailFast(RawRequestTests.Run(restClientFactory, firstAdminRestClient, cancellationToken)); - var adminTest = FailFast(new AdministrationTest(firstAdminRestClient.Administration).Run(cancellationToken)); + var adminTest = FailFast(new AdministrationTest(firstAdminMultiClient).Run(cancellationToken)); var usersTest = FailFast(new UsersTest(firstAdminMultiClient).Run(cancellationToken).AsTask()); var instanceManagerTest = new InstanceManagerTest(firstAdminRestClient, server.Directory); @@ -1632,7 +1681,12 @@ namespace Tgstation.Server.Tests.Live await Task.Delay(1000, cancellationToken); jobsHubTest.ExpectShutdown(); - await firstAdminRestClient.Administration.Restart(cancellationToken); + await firstAdminMultiClient.Execute( + restClient => restClient.Administration.Restart(cancellationToken), + async gqlClient => await gqlClient.RunMutationEnsureNoErrors( + gql => gql.RestartServer.ExecuteAsync(cancellationToken), + result => result, + cancellationToken)); } catch { @@ -1777,7 +1831,12 @@ namespace Tgstation.Server.Tests.Live Assert.AreEqual(WatchdogStatus.Offline, dd.Status); jobsHubTest.ExpectShutdown(); - await adminClient.Administration.Restart(cancellationToken); + await multiClient.Execute( + restClient => restClient.Administration.Restart(cancellationToken), + async gqlClient => await gqlClient.RunMutationEnsureNoErrors( + gql => gql.RestartServer.ExecuteAsync(cancellationToken), + result => result, + cancellationToken)); } await Task.WhenAny(serverTask, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken)); @@ -1844,7 +1903,12 @@ namespace Tgstation.Server.Tests.Live expectedStaged = compileJob.Id.Value; jobsHubTest.ExpectShutdown(); - await restAdminClient.Administration.Restart(cancellationToken); + await adminClient.Execute( + restClient => restClient.Administration.Restart(cancellationToken), + async gqlClient => await gqlClient.RunMutationEnsureNoErrors( + gql => gql.RestartServer.ExecuteAsync(cancellationToken), + result => result, + cancellationToken)); } await Task.WhenAny(serverTask, Task.Delay(TimeSpan.FromMinutes(1), cancellationToken));