Undo breaking API 10 changes

- Restore backwards compatible `ByondController`
This commit is contained in:
Jordan Dominion
2023-10-16 02:45:48 -04:00
parent 9a3d02a244
commit 8e5f2dd696
13 changed files with 804 additions and 28 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<PropertyGroup>
<TgsCoreVersion>6.0.0</TgsCoreVersion>
<TgsConfigVersion>5.0.0</TgsConfigVersion>
<TgsApiVersion>10.0.0</TgsApiVersion>
<TgsApiVersion>9.13.0</TgsApiVersion>
<TgsCommonLibraryVersion>6.0.1</TgsCommonLibraryVersion>
<TgsApiLibraryVersion>12.0.0</TgsApiLibraryVersion>
<TgsClientVersion>13.0.0</TgsClientVersion>
@@ -2,6 +2,8 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json;
using Tgstation.Server.Api.Rights;
namespace Tgstation.Server.Api.Models.Internal
@@ -27,7 +29,14 @@ namespace Tgstation.Server.Api.Models.Internal
/// The <see cref="Rights.EngineRights"/> of the <see cref="InstancePermissionSet"/>.
/// </summary>
[NotMapped]
public EngineRights? EngineRights { get; set; }
[JsonIgnore]
public EngineRights? EngineRights
{
#pragma warning disable CS0618 // Type or member is obsolete
get => ByondRights;
set => ByondRights = value;
#pragma warning restore CS0618 // Type or member is obsolete
}
/// <summary>
/// The legacy <see cref="Rights.EngineRights"/> of the <see cref="InstancePermissionSet"/>.
@@ -0,0 +1,345 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Controllers.Legacy.Models;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Transfer;
namespace Tgstation.Server.Host.Controllers.Legacy
{
/// <summary>
/// Controller for managing BYOND installations.
/// </summary>
[Route(Routes.Root + "Byond")]
public sealed class ByondController : InstanceRequiredController
{
/// <summary>
/// The <see cref="IJobManager"/> for the <see cref="ByondController"/>.
/// </summary>
readonly IJobManager jobManager;
/// <summary>
/// The <see cref="IFileTransferTicketProvider"/> for the <see cref="ByondController"/>.
/// </summary>
readonly IFileTransferTicketProvider fileTransferService;
/// <summary>
/// Create an <see cref="EngineVersion"/> for a given legacy formatted BYOND <paramref name="version"/>.
/// </summary>
/// <param name="version">The legacy BYOND <see cref="Version"/>.</param>
/// <returns>A <see cref="EngineType.Byond"/> <see cref="EngineVersion"/> <paramref name="version"/>.</returns>
static EngineVersion CreateEngineVersionFromLegacyByondVersion(Version version) => new EngineVersion
{
Version = new Version(version.Major, version.Minor),
Engine = EngineType.Byond,
CustomIteration = version.Build <= 0 ? null : version.Build,
};
/// <summary>
/// Create a legacy formated BYOND <see cref="Version"/> for a given <paramref name="engineVersion"/>.
/// </summary>
/// <param name="engineVersion">The <see cref="EngineType.Byond"/> <see cref="EngineVersion"/>.</param>
/// <returns>A legacy BYOND <see cref="Version"/>.</returns>
static Version CreateLegacyByondVersionFromEngineVersion(EngineVersion engineVersion)
=> new (engineVersion.Version.Major, engineVersion.Version.Minor, engineVersion.CustomIteration ?? 0);
/// <summary>
/// Initializes a new instance of the <see cref="ByondController"/> class.
/// </summary>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="InstanceRequiredController"/>.</param>
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="InstanceRequiredController"/>.</param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="InstanceRequiredController"/>.</param>
/// <param name="instanceManager">The <see cref="IInstanceManager"/> for the <see cref="InstanceRequiredController"/>.</param>
/// <param name="jobManager">The value of <see cref="jobManager"/>.</param>
/// <param name="fileTransferService">The value of <see cref="fileTransferService"/>.</param>
public ByondController(
IDatabaseContext databaseContext,
IAuthenticationContextFactory authenticationContextFactory,
ILogger<ByondController> logger,
IInstanceManager instanceManager,
IJobManager jobManager,
IFileTransferTicketProvider fileTransferService)
: base(
databaseContext,
authenticationContextFactory,
logger,
instanceManager)
{
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService));
}
/// <summary>
/// Gets the active <see cref="ByondResponse.Version"/>.
/// </summary>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="IActionResult"/> for the operation.</returns>
/// <response code="200">Retrieved version information successfully.</response>
[HttpGet]
[TgsAuthorize(EngineRights.ReadActive)]
[ProducesResponseType(typeof(ByondResponse), 200)]
public ValueTask<IActionResult> Read()
=> WithComponentInstance(instance =>
{
var version = instance.EngineManager.ActiveVersion;
return ValueTask.FromResult<IActionResult>(
Json(new ByondResponse
{
Version = version?.Engine.Value == EngineType.Byond
? CreateLegacyByondVersionFromEngineVersion(version)
: null,
}));
});
/// <summary>
/// Lists installed <see cref="ByondResponse.Version"/>s.
/// </summary>
/// <param name="page">The current page.</param>
/// <param name="pageSize">The page size.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="IActionResult"/> for the operation.</returns>
/// <response code="200">Retrieved version information successfully.</response>
[HttpGet(Routes.List)]
[TgsAuthorize(EngineRights.ListInstalled)]
[ProducesResponseType(typeof(PaginatedResponse<ByondResponse>), 200)]
public ValueTask<IActionResult> List([FromQuery] int? page, [FromQuery] int? pageSize, CancellationToken cancellationToken)
=> WithComponentInstance(
instance => Paginated(
() => ValueTask.FromResult(
new PaginatableResult<ByondResponse>(
instance
.EngineManager
.InstalledVersions
.Where(x => x.Engine.Value == EngineType.Byond)
.Select(x => new ByondResponse
{
Version = CreateLegacyByondVersionFromEngineVersion(x),
})
.AsQueryable()
.OrderBy(x => x.Version))),
null,
page,
pageSize,
cancellationToken));
/// <summary>
/// Changes the active BYOND version to the one specified in a given <paramref name="model"/>.
/// </summary>
/// <param name="model">The <see cref="ByondVersionRequest"/> containing the <see cref="Version"/> to switch to.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="IActionResult"/> for the operation.</returns>
/// <response code="200">Switched active version successfully.</response>
/// <response code="202">Created <see cref="Job"/> to install and switch active version successfully.</response>
[HttpPost]
[TgsAuthorize(EngineRights.InstallOfficialOrChangeActiveByondVersion | EngineRights.InstallCustomByondVersion)]
[ProducesResponseType(typeof(ByondInstallResponse), 200)]
[ProducesResponseType(typeof(ByondInstallResponse), 202)]
#pragma warning disable CA1506 // TODO: Decomplexify
public async ValueTask<IActionResult> Update([FromBody] ByondVersionRequest model, CancellationToken cancellationToken)
#pragma warning restore CA1506
{
ArgumentNullException.ThrowIfNull(model);
var uploadingZip = model.UploadCustomZip == true;
if (model.Version == null
|| model.Version.Revision != -1
|| (uploadingZip && model.Version.Build > 0))
return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure));
var userEngineRights = AuthenticationContext.InstancePermissionSet.EngineRights.Value;
if ((!userEngineRights.HasFlag(EngineRights.InstallOfficialOrChangeActiveByondVersion) && !uploadingZip)
|| (!userEngineRights.HasFlag(EngineRights.InstallCustomByondVersion) && uploadingZip))
return Forbid();
// remove cruff fields
var result = new ByondInstallResponse();
return await WithComponentInstance(
async instance =>
{
var byondManager = instance.EngineManager;
var engineVersion = CreateEngineVersionFromLegacyByondVersion(model.Version);
var versionAlreadyInstalled = !uploadingZip
&& byondManager
.InstalledVersions
.Any(x => x.Equals(engineVersion));
if (versionAlreadyInstalled)
{
Logger.LogInformation(
"User ID {userId} changing instance ID {instanceId} BYOND version to {newByondVersion}",
AuthenticationContext.User.Id,
Instance.Id,
engineVersion);
try
{
await byondManager.ChangeVersion(
null,
engineVersion,
null,
false,
cancellationToken);
}
catch (InvalidOperationException ex)
{
Logger.LogDebug(
ex,
"Race condition: BYOND version {version} uninstalled before we could switch to it. Creating install job instead...",
engineVersion);
versionAlreadyInstalled = false;
}
}
if (!versionAlreadyInstalled)
{
if (engineVersion.CustomIteration.HasValue)
return BadRequest(new ErrorMessageResponse(ErrorCode.EngineNonExistentCustomVersion));
Logger.LogInformation(
"User ID {userId} installing BYOND version to {newByondVersion} on instance ID {instanceId}",
AuthenticationContext.User.Id,
engineVersion,
Instance.Id);
// run the install through the job manager
var job = new Host.Models.Job
{
Description = $"Install {(!uploadingZip ? string.Empty : "custom ")}BYOND version {engineVersion}",
StartedBy = AuthenticationContext.User,
CancelRightsType = RightsType.Engine,
CancelRight = (ulong)EngineRights.CancelInstall,
Instance = Instance,
};
IFileUploadTicket fileUploadTicket = null;
if (uploadingZip)
fileUploadTicket = fileTransferService.CreateUpload(FileUploadStreamKind.None);
try
{
await jobManager.RegisterOperation(
job,
async (core, databaseContextFactory, paramJob, progressHandler, jobCancellationToken) =>
{
Stream zipFileStream = null;
if (fileUploadTicket != null)
await using (fileUploadTicket)
{
var uploadStream = await fileUploadTicket.GetResult(jobCancellationToken) ?? throw new JobException(ErrorCode.FileUploadExpired);
zipFileStream = new MemoryStream();
try
{
await uploadStream.CopyToAsync(zipFileStream, jobCancellationToken);
}
catch
{
await zipFileStream.DisposeAsync();
throw;
}
}
await using (zipFileStream)
await core.EngineManager.ChangeVersion(
progressHandler,
engineVersion,
zipFileStream,
true,
jobCancellationToken);
},
cancellationToken);
result.InstallJob = job.ToApi();
result.FileTicket = fileUploadTicket?.Ticket.FileTicket;
}
catch
{
if (fileUploadTicket != null)
await fileUploadTicket.DisposeAsync();
throw;
}
}
return result.InstallJob != null ? Accepted(result) : Json(result);
});
}
/// <summary>
/// Attempts to delete the BYOND version specified in a given <paramref name="model"/> from the instance.
/// </summary>
/// <param name="model">The <see cref="ByondVersionDeleteRequest"/> containing the <see cref="Version"/> to delete.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the <see cref="IActionResult"/> for the operation.</returns>
/// <response code="202">Created <see cref="Job"/> to delete target version successfully.</response>
/// <response code="409">Attempted to delete the active BYOND <see cref="Version"/>.</response>
/// <response code="410">The <see cref="ByondVersionDeleteRequest.Version"/> specified was not installed.</response>
[HttpDelete]
[TgsAuthorize(EngineRights.DeleteInstall)]
[ProducesResponseType(typeof(JobResponse), 202)]
[ProducesResponseType(typeof(ErrorMessageResponse), 409)]
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
public async ValueTask<IActionResult> Delete([FromBody] ByondVersionDeleteRequest model, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(model);
if (model.Version == null
|| model.Version.Revision != -1)
return BadRequest(new ErrorMessageResponse(ErrorCode.ModelValidationFailure));
var engineVersion = CreateEngineVersionFromLegacyByondVersion(model.Version);
var notInstalledResponse = await WithComponentInstance(
instance =>
{
var engineManager = instance.EngineManager;
if (engineVersion.Equals(engineManager.ActiveVersion))
return ValueTask.FromResult<IActionResult>(
Conflict(new ErrorMessageResponse(ErrorCode.EngineCannotDeleteActiveVersion)));
var versionNotInstalled = !engineManager.InstalledVersions.Any(x => x.Equals(engineVersion));
return ValueTask.FromResult<IActionResult>(
versionNotInstalled
? this.Gone()
: null);
});
if (notInstalledResponse != null)
return notInstalledResponse;
// run the install through the job manager
var job = new Host.Models.Job
{
Description = $"Delete installed BYOND version {engineVersion}",
StartedBy = AuthenticationContext.User,
CancelRightsType = RightsType.Engine,
CancelRight = (ulong)(engineVersion.CustomIteration.HasValue ? EngineRights.InstallOfficialOrChangeActiveByondVersion : EngineRights.InstallCustomByondVersion),
Instance = Instance,
};
await jobManager.RegisterOperation(
job,
(instanceCore, databaseContextFactory, job, progressReporter, jobCancellationToken)
=> instanceCore.EngineManager.DeleteVersion(progressReporter, engineVersion, jobCancellationToken),
cancellationToken);
var apiResponse = job.ToApi();
return Accepted(apiResponse);
}
}
}
@@ -0,0 +1,27 @@
using System;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Response;
namespace Tgstation.Server.Host.Controllers.Legacy.Models
{
/// <summary>
/// Represents a BYOND installation job. <see cref="FileTicketResponse.FileTicket"/> is used to upload custom BYOND version zip files.
/// </summary>
public sealed class ByondInstallResponse : FileTicketResponse
{
/// <summary>
/// The <see cref="JobResponse"/> being used to install a new <see cref="Version"/>.
/// </summary>
[ResponseOptions]
public JobResponse InstallJob { get; set; }
/// <inheritdoc />
[ResponseOptions]
public override string FileTicket
{
get => base.FileTicket;
set => base.FileTicket = value;
}
}
}
@@ -0,0 +1,18 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Controllers.Legacy.Models
{
/// <summary>
/// Represents an installed BYOND <see cref="Version"/>.
/// </summary>
public sealed class ByondResponse
{
/// <summary>
/// The installed BYOND <see cref="global::System.Version"/>. BYOND itself only considers the <see cref="Version.Major"/> and <see cref="Version.Minor"/> numbers. This older API uses the <see cref="Version.Build"/> number to represent installed custom versions.
/// </summary>
[ResponseOptions]
public Version Version { get; set; }
}
}
@@ -0,0 +1,18 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Controllers.Legacy.Models
{
/// <summary>
/// A request to delete a specific <see cref="Version"/>.
/// </summary>
public class ByondVersionDeleteRequest
{
/// <summary>
/// The BYOND version to delete.
/// </summary>
[RequestOptions(FieldPresence.Required)]
public Version Version { get; set; }
}
}
@@ -0,0 +1,23 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Controllers.Legacy.Models
{
/// <summary>
/// A request to install a BYOND <see cref="Version"/>.
/// </summary>
public sealed class ByondVersionRequest
{
/// <summary>
/// The BYOND version to install.
/// </summary>
[RequestOptions(FieldPresence.Required)]
public Version Version { get; set; }
/// <summary>
/// If a custom BYOND version is to be uploaded.
/// </summary>
public bool? UploadCustomZip { get; set; }
}
}
@@ -43,7 +43,7 @@ namespace Tgstation.Server.Tests
var logger = loggerFactory.CreateLogger("CachingFileDownloader");
var cfd = new CachingFileDownloader(loggerFactory.CreateLogger<CachingFileDownloader>());
var edgeVersion = await ByondTest.GetEdgeVersion(Api.Models.EngineType.Byond, cfd, cancellationToken);
var edgeVersion = await EngineTest.GetEdgeVersion(Api.Models.EngineType.Byond, cfd, cancellationToken);
await InitializeByondVersion(logger, edgeVersion.Version, new PlatformIdentifier().IsWindows, cancellationToken);
@@ -26,9 +26,9 @@ using Tgstation.Server.Host.System;
namespace Tgstation.Server.Tests.Live.Instance
{
sealed class ByondTest(IEngineClient byondClient, IJobsClient jobsClient, IFileDownloader fileDownloader, Api.Models.Instance metadata, EngineType engineType) : JobsRequiredTest(jobsClient)
sealed class EngineTest(IEngineClient engineClient, IJobsClient jobsClient, IFileDownloader fileDownloader, Api.Models.Instance metadata, EngineType engineType) : JobsRequiredTest(jobsClient)
{
readonly IEngineClient byondClient = byondClient ?? throw new ArgumentNullException(nameof(byondClient));
readonly IEngineClient engineClient = engineClient ?? throw new ArgumentNullException(nameof(engineClient));
readonly IFileDownloader fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader));
readonly Api.Models.Instance metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
@@ -111,7 +111,7 @@ namespace Tgstation.Server.Tests.Live.Instance
ValueTask TestInstallNullVersion(CancellationToken cancellationToken)
=> ApiAssert.ThrowsException<ApiConflictException, EngineInstallResponse>(
() => byondClient.SetActiveVersion(
() => engineClient.SetActiveVersion(
new EngineVersionRequest
{
EngineVersion = new EngineVersion
@@ -133,7 +133,7 @@ namespace Tgstation.Server.Tests.Live.Instance
async Task TestDeletes(CancellationToken cancellationToken)
{
var deleteThisOneBecauseItWasntPartOfTheOriginalTest = await byondClient.DeleteVersion(new EngineVersionDeleteRequest
var deleteThisOneBecauseItWasntPartOfTheOriginalTest = await engineClient.DeleteVersion(new EngineVersionDeleteRequest
{
EngineVersion = new EngineVersion
{
@@ -144,7 +144,7 @@ namespace Tgstation.Server.Tests.Live.Instance
}, cancellationToken);
await WaitForJob(deleteThisOneBecauseItWasntPartOfTheOriginalTest, 30, false, null, cancellationToken);
var nonExistentUninstallResponseTask = ApiAssert.ThrowsException<ConflictException, JobResponse>(() => byondClient.DeleteVersion(
var nonExistentUninstallResponseTask = ApiAssert.ThrowsException<ConflictException, JobResponse>(() => engineClient.DeleteVersion(
new EngineVersionDeleteRequest
{
EngineVersion = new EngineVersion
@@ -155,7 +155,7 @@ namespace Tgstation.Server.Tests.Live.Instance
},
cancellationToken), ErrorCode.ResourceNotPresent);
var uninstallResponseTask = byondClient.DeleteVersion(
var uninstallResponseTask = engineClient.DeleteVersion(
new EngineVersionDeleteRequest
{
EngineVersion = new EngineVersion
@@ -167,7 +167,7 @@ namespace Tgstation.Server.Tests.Live.Instance
},
cancellationToken);
var badBecauseActiveResponseTask = ApiAssert.ThrowsException<ConflictException, JobResponse>(() => byondClient.DeleteVersion(
var badBecauseActiveResponseTask = ApiAssert.ThrowsException<ConflictException, JobResponse>(() => engineClient.DeleteVersion(
new EngineVersionDeleteRequest
{
EngineVersion = new EngineVersion
@@ -194,7 +194,7 @@ namespace Tgstation.Server.Tests.Live.Instance
var byondDir = Path.Combine(metadata.Path, "Byond", testVersion.ToString());
Assert.IsFalse(Directory.Exists(byondDir));
var newVersions = await byondClient.InstalledVersions(null, cancellationToken);
var newVersions = await engineClient.InstalledVersions(null, cancellationToken);
Assert.IsNotNull(newVersions);
Assert.AreEqual(1, newVersions.Count);
Assert.AreEqual(testVersion.Version.Semver(), newVersions[0].EngineVersion.Version.Semver());
@@ -211,11 +211,11 @@ namespace Tgstation.Server.Tests.Live.Instance
}
};
await ApiAssert.ThrowsException<ApiConflictException, EngineInstallResponse>(() => byondClient.SetActiveVersion(newModel, null, cancellationToken), ErrorCode.ModelValidationFailure);
await ApiAssert.ThrowsException<ApiConflictException, EngineInstallResponse>(() => engineClient.SetActiveVersion(newModel, null, cancellationToken), ErrorCode.ModelValidationFailure);
newModel.EngineVersion.Engine = testEngine;
var test = await byondClient.SetActiveVersion(newModel, null, cancellationToken);
var test = await engineClient.SetActiveVersion(newModel, null, cancellationToken);
Assert.IsNotNull(test.InstallJob);
await WaitForJob(test.InstallJob, 60, true, ErrorCode.EngineDownloadFail, cancellationToken);
}
@@ -231,10 +231,10 @@ namespace Tgstation.Server.Tests.Live.Instance
SourceSHA = testVersion.SourceSHA,
}
};
var test = await byondClient.SetActiveVersion(newModel, null, cancellationToken);
var test = await engineClient.SetActiveVersion(newModel, null, cancellationToken);
Assert.IsNotNull(test.InstallJob);
await WaitForJob(test.InstallJob, 180, false, null, cancellationToken);
var currentShit = await byondClient.ActiveVersion(cancellationToken);
var currentShit = await engineClient.ActiveVersion(cancellationToken);
Assert.AreEqual(newModel.EngineVersion, currentShit.EngineVersion);
Assert.IsFalse(currentShit.EngineVersion.CustomIteration.HasValue);
@@ -253,8 +253,8 @@ namespace Tgstation.Server.Tests.Live.Instance
async Task TestNoVersion(CancellationToken cancellationToken)
{
var allVersionsTask = byondClient.InstalledVersions(null, cancellationToken);
var currentShit = await byondClient.ActiveVersion(cancellationToken);
var allVersionsTask = engineClient.InstalledVersions(null, cancellationToken);
var currentShit = await engineClient.ActiveVersion(cancellationToken);
Assert.IsNotNull(currentShit);
Assert.IsNull(currentShit.EngineVersion);
var otherShit = await allVersionsTask;
@@ -289,7 +289,7 @@ namespace Tgstation.Server.Tests.Live.Instance
// get the bytes for stable
await using var stableBytesMs = await TestingUtils.ExtractMemoryStreamFromInstallationData(await byondInstaller.DownloadVersion(testVersion, null, cancellationToken), cancellationToken);
var test = await byondClient.SetActiveVersion(
var test = await engineClient.SetActiveVersion(
new EngineVersionRequest
{
EngineVersion = new EngineVersion
@@ -308,7 +308,7 @@ namespace Tgstation.Server.Tests.Live.Instance
// do it again. #1501
stableBytesMs.Seek(0, SeekOrigin.Begin);
var test2 = await byondClient.SetActiveVersion(
var test2 = await engineClient.SetActiveVersion(
new EngineVersionRequest
{
EngineVersion = new EngineVersion
@@ -326,12 +326,12 @@ namespace Tgstation.Server.Tests.Live.Instance
Assert.IsNotNull(test2.InstallJob);
await WaitForJob(test2.InstallJob, 30, false, null, cancellationToken);
var newSettings = await byondClient.ActiveVersion(cancellationToken);
var newSettings = await engineClient.ActiveVersion(cancellationToken);
Assert.AreEqual(new Version(testVersion.Version.Major, testVersion.Version.Minor, 0), newSettings.EngineVersion.Version);
Assert.AreEqual(2, newSettings.EngineVersion.CustomIteration);
// test a few switches
var installResponse = await byondClient.SetActiveVersion(new EngineVersionRequest
var installResponse = await engineClient.SetActiveVersion(new EngineVersionRequest
{
EngineVersion = new EngineVersion
{
@@ -341,7 +341,7 @@ namespace Tgstation.Server.Tests.Live.Instance
}
}, null, cancellationToken);
Assert.IsNull(installResponse.InstallJob);
await ApiAssert.ThrowsException<ApiConflictException, EngineInstallResponse>(() => byondClient.SetActiveVersion(new EngineVersionRequest
await ApiAssert.ThrowsException<ApiConflictException, EngineInstallResponse>(() => engineClient.SetActiveVersion(new EngineVersionRequest
{
EngineVersion = new EngineVersion
{
@@ -351,7 +351,7 @@ namespace Tgstation.Server.Tests.Live.Instance
}
}, null, cancellationToken), ErrorCode.EngineNonExistentCustomVersion);
installResponse = await byondClient.SetActiveVersion(new EngineVersionRequest
installResponse = await engineClient.SetActiveVersion(new EngineVersionRequest
{
EngineVersion = new EngineVersion
{
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
@@ -34,6 +35,22 @@ namespace Tgstation.Server.Tests.Live.Instance
readonly InstanceManager instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
readonly ushort serverPort = serverPort;
public async Task RunLegacyByondTest(
IInstanceClient instanceClient,
CancellationToken cancellationToken)
{
var testVersion = await EngineTest.GetEdgeVersion(EngineType.Byond, fileDownloader, cancellationToken);
await new LegacyByondTest(
instanceClient.Jobs,
fileDownloader,
new LegacyByondClient(
(IApiClient)instanceClient.Engine.GetType().GetProperty("ApiClient", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(instanceClient.Engine),
instanceClient.Metadata),
testVersion.Version,
instanceClient.Metadata)
.Run(cancellationToken);
}
public async Task RunTests(
IInstanceClient instanceClient,
ushort dmPort,
@@ -42,14 +59,14 @@ namespace Tgstation.Server.Tests.Live.Instance
bool lowPrioDeployment,
CancellationToken cancellationToken)
{
var testVersion = await ByondTest.GetEdgeVersion(EngineType.Byond, fileDownloader, cancellationToken);
var byondTest = new ByondTest(instanceClient.Engine, instanceClient.Jobs, fileDownloader, instanceClient.Metadata, testVersion.Engine.Value);
var testVersion = await EngineTest.GetEdgeVersion(EngineType.Byond, fileDownloader, cancellationToken);
var engineTest = new EngineTest(instanceClient.Engine, instanceClient.Jobs, fileDownloader, instanceClient.Metadata, testVersion.Engine.Value);
var chatTest = new ChatTest(instanceClient.ChatBots, instanceManagerClient, instanceClient.Jobs, instanceClient.Metadata);
var configTest = new ConfigurationTest(instanceClient.Configuration, instanceClient.Metadata);
var repoTest = new RepositoryTest(instanceClient.Repository, instanceClient.Jobs);
var dmTest = new DeploymentTest(instanceClient, instanceClient.Jobs, dmPort, ddPort, lowPrioDeployment, testVersion.Engine.Value);
var byondTask = byondTest.Run(cancellationToken, out var firstInstall);
var byondTask = engineTest.Run(cancellationToken, out var firstInstall);
var chatTask = chatTest.RunPreWatchdog(cancellationToken);
var repoLongJob = await repoTest.RunLongClone(cancellationToken);
@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Client;
using Tgstation.Server.Host.Controllers.Legacy.Models;
namespace Tgstation.Server.Tests.Live.Instance
{
/// <inheritdoc cref="IByondClient" />
sealed class LegacyByondClient : PaginatedClient
{
const string Route = Routes.Root + "Byond";
/// <summary>
/// The <see cref="Instance"/> for the <see cref="ByondClient"/>.
/// </summary>
readonly Api.Models.Instance instance;
/// <summary>
/// Initializes a new instance of the <see cref="ByondClient"/> class.
/// </summary>
/// <param name="apiClient">The <see cref="IApiClient"/> for the <see cref="PaginatedClient"/>.</param>
/// <param name="instance">The value of <see cref="Instance"/>.</param>
public LegacyByondClient(IApiClient apiClient, Api.Models.Instance instance)
: base(apiClient)
{
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
}
/// <inheritdoc />
public ValueTask<ByondResponse> ActiveVersion(CancellationToken cancellationToken) => ApiClient.Read<ByondResponse>(Route, instance.Id!.Value, cancellationToken);
/// <inheritdoc />
public ValueTask<JobResponse> DeleteVersion(ByondVersionDeleteRequest deleteRequest, CancellationToken cancellationToken)
=> ApiClient.Delete<ByondVersionDeleteRequest, JobResponse>(Route, deleteRequest, instance.Id!.Value, cancellationToken);
/// <inheritdoc />
public ValueTask<List<ByondResponse>> InstalledVersions(PaginationSettings paginationSettings, CancellationToken cancellationToken)
=> ReadPaged<ByondResponse>(paginationSettings, Routes.ListRoute(Route), instance.Id, cancellationToken);
/// <inheritdoc />
public async ValueTask<ByondInstallResponse> SetActiveVersion(ByondVersionRequest installRequest, Stream zipFileStream, CancellationToken cancellationToken)
{
if (installRequest == null)
throw new ArgumentNullException(nameof(installRequest));
if (installRequest.UploadCustomZip == true && zipFileStream == null)
throw new ArgumentNullException(nameof(zipFileStream));
var result = await ApiClient.Update<ByondVersionRequest, ByondInstallResponse>(
Route,
installRequest,
instance.Id!.Value,
cancellationToken)
.ConfigureAwait(false);
if (installRequest.UploadCustomZip == true)
await ApiClient.Upload(result, zipFileStream, cancellationToken).ConfigureAwait(false);
return result;
}
}
}
@@ -0,0 +1,240 @@
using System;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Client;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Host.Components.Engine;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.System;
using Tgstation.Server.Host.Controllers.Legacy.Models;
using Tgstation.Server.Client.Components;
namespace Tgstation.Server.Tests.Live.Instance
{
internal class LegacyByondTest : JobsRequiredTest
{
readonly LegacyByondClient byondClient;
readonly Version testVersion;
readonly Api.Models.Instance metadata;
readonly IFileDownloader fileDownloader;
public LegacyByondTest(IJobsClient jobsClient, IFileDownloader fileDownloader, LegacyByondClient byondClient, Version testVersion, Api.Models.Instance metadata)
: base(jobsClient)
{
this.fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader));
this.byondClient = byondClient ?? throw new ArgumentNullException(nameof(byondClient));
this.testVersion = testVersion ?? throw new ArgumentNullException(nameof(testVersion));
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
}
public async Task Run(CancellationToken cancellationToken)
{
await TestNoVersion(cancellationToken);
await TestInstallNullVersion(cancellationToken);
await TestInstallStable(cancellationToken);
await TestInstallFakeVersion(cancellationToken);
await TestCustomInstalls(cancellationToken);
await TestDeletes(cancellationToken);
}
ValueTask TestInstallNullVersion(CancellationToken cancellationToken)
=> ApiAssert.ThrowsException<ApiConflictException, ByondInstallResponse>(
() => byondClient.SetActiveVersion(
new ByondVersionRequest(),
null,
cancellationToken),
ErrorCode.ModelValidationFailure);
async Task TestDeletes(CancellationToken cancellationToken)
{
var deleteThisOneBecauseItWasntPartOfTheOriginalTest = await byondClient.DeleteVersion(
new ByondVersionDeleteRequest
{
Version = new Version(testVersion.Major, testVersion.Minor, 2),
}, cancellationToken);
await WaitForJob(deleteThisOneBecauseItWasntPartOfTheOriginalTest, 30, false, null, cancellationToken);
var nonExistentUninstallResponseTask = ApiAssert.ThrowsException<ConflictException, JobResponse>(() => byondClient.DeleteVersion(
new ByondVersionDeleteRequest
{
Version = new(509, 1000),
},
cancellationToken), ErrorCode.ResourceNotPresent);
var uninstallResponseTask = byondClient.DeleteVersion(
new ByondVersionDeleteRequest
{
Version = testVersion
},
cancellationToken);
var badBecauseActiveResponseTask = ApiAssert.ThrowsException<ConflictException, JobResponse>(() => byondClient.DeleteVersion(
new ByondVersionDeleteRequest
{
Version = new Version(testVersion.Major, testVersion.Minor, 1),
},
cancellationToken), ErrorCode.EngineCannotDeleteActiveVersion);
await badBecauseActiveResponseTask;
var uninstallJob = await uninstallResponseTask;
Assert.IsNotNull(uninstallJob);
// Has to wait on deployment test possibly
var uninstallTask = WaitForJob(uninstallJob, 120, false, null, cancellationToken);
await nonExistentUninstallResponseTask;
await uninstallTask;
var byondDir = Path.Combine(metadata.Path, "Byond", testVersion.ToString());
Assert.IsFalse(Directory.Exists(byondDir));
var newVersions = await byondClient.InstalledVersions(null, cancellationToken);
Assert.IsNotNull(newVersions);
Assert.AreEqual(1, newVersions.Count);
Assert.AreEqual(new Version(testVersion.Major, testVersion.Minor, 1), newVersions[0].Version);
}
async Task TestInstallFakeVersion(CancellationToken cancellationToken)
{
var newModel = new ByondVersionRequest
{
Version = new Version(5011, 1385),
};
var test = await byondClient.SetActiveVersion(newModel, null, cancellationToken);
Assert.IsNotNull(test.InstallJob);
await WaitForJob(test.InstallJob, 60, true, ErrorCode.EngineDownloadFail, cancellationToken);
}
async Task TestInstallStable(CancellationToken cancellationToken)
{
var newModel = new ByondVersionRequest
{
Version = testVersion,
};
var test = await byondClient.SetActiveVersion(newModel, null, cancellationToken);
Assert.IsNotNull(test.InstallJob);
await WaitForJob(test.InstallJob, 180, false, null, cancellationToken);
var currentShit = await byondClient.ActiveVersion(cancellationToken);
Assert.AreEqual(newModel.Version.Semver(), currentShit.Version.Semver());
var dreamMaker = "DreamMaker";
if (new PlatformIdentifier().IsWindows)
dreamMaker += ".exe";
var dreamMakerDir = Path.Combine(metadata.Path, "Byond", newModel.Version.ToString(), "byond", "bin");
Assert.IsTrue(Directory.Exists(dreamMakerDir), $"Directory {dreamMakerDir} does not exist!");
Assert.IsTrue(
File.Exists(
Path.Combine(dreamMakerDir, dreamMaker)),
$"Missing DreamMaker executable! Dir contents: {string.Join(", ", Directory.GetFileSystemEntries(dreamMakerDir))}");
}
async Task TestNoVersion(CancellationToken cancellationToken)
{
var allVersionsTask = byondClient.InstalledVersions(null, cancellationToken);
var currentShit = await byondClient.ActiveVersion(cancellationToken);
Assert.IsNotNull(currentShit);
Assert.IsNull(currentShit.Version);
var otherShit = await allVersionsTask;
Assert.IsNotNull(otherShit);
Assert.AreEqual(0, otherShit.Count);
}
async Task TestCustomInstalls(CancellationToken cancellationToken)
{
var generalConfigOptionsMock = new Mock<IOptions<GeneralConfiguration>>();
generalConfigOptionsMock.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
var sessionConfigOptionsMock = new Mock<IOptions<SessionConfiguration>>();
sessionConfigOptionsMock.SetupGet(x => x.Value).Returns(new SessionConfiguration());
var assemblyInformationProvider = new AssemblyInformationProvider();
IEngineInstaller byondInstaller = new PlatformIdentifier().IsWindows
? new WindowsByondInstaller(
Mock.Of<IProcessExecutor>(),
Mock.Of<IIOManager>(),
fileDownloader,
generalConfigOptionsMock.Object,
Mock.Of<ILogger<WindowsByondInstaller>>())
: new PosixByondInstaller(
Mock.Of<IPostWriteHandler>(),
Mock.Of<IIOManager>(),
fileDownloader,
Mock.Of<ILogger<PosixByondInstaller>>());
using var windowsByondInstaller = byondInstaller as WindowsByondInstaller;
// get the bytes for stable
await using var stableBytesMs = await TestingUtils.ExtractMemoryStreamFromInstallationData(await byondInstaller.DownloadVersion(new EngineVersion
{
Version = testVersion,
Engine = EngineType.Byond,
}, null, cancellationToken), cancellationToken);
var test = await byondClient.SetActiveVersion(
new ByondVersionRequest
{
Version = testVersion,
UploadCustomZip = true,
},
stableBytesMs,
cancellationToken);
Assert.IsNotNull(test.InstallJob);
await WaitForJob(test.InstallJob, 30, false, null, cancellationToken);
// do it again. #1501
stableBytesMs.Seek(0, SeekOrigin.Begin);
var test2 = await byondClient.SetActiveVersion(
new ByondVersionRequest
{
Version = testVersion,
UploadCustomZip = true,
},
stableBytesMs,
cancellationToken);
Assert.IsNotNull(test2.InstallJob);
await WaitForJob(test2.InstallJob, 30, false, null, cancellationToken);
var newSettings = await byondClient.ActiveVersion(cancellationToken);
Assert.AreEqual(new Version(testVersion.Major, testVersion.Minor, 2), newSettings.Version);
// test a few switches
var installResponse = await byondClient.SetActiveVersion(new ByondVersionRequest
{
Version = testVersion,
}, null, cancellationToken);
Assert.IsNull(installResponse.InstallJob);
await ApiAssert.ThrowsException<ApiConflictException, ByondInstallResponse>(() => byondClient.SetActiveVersion(new ByondVersionRequest
{
Version = new Version(testVersion.Major, testVersion.Minor, 3),
}, null, cancellationToken), ErrorCode.EngineNonExistentCustomVersion);
installResponse = await byondClient.SetActiveVersion(new ByondVersionRequest
{
Version = new Version(testVersion.Major, testVersion.Minor, 1),
}, null, cancellationToken);
Assert.IsNull(installResponse.InstallJob);
}
}
}
@@ -1141,9 +1141,11 @@ namespace Tgstation.Server.Tests.Live
var instanceManagerTest = new InstanceManagerTest(adminClient, server.Directory);
var compatInstanceTask = instanceManagerTest.CreateTestInstance("CompatTestsInstance", cancellationToken);
var odInstanceTask = instanceManagerTest.CreateTestInstance("OdTestsInstance", cancellationToken);
var byondApiCompatInstanceTask = instanceManagerTest.CreateTestInstance("BCAPITestsInstance", cancellationToken);
instance = await instanceManagerTest.CreateTestInstance("LiveTestsInstance", cancellationToken);
var compatInstance = await compatInstanceTask;
var odInstance = await odInstanceTask;
var byondApiCompatInstance = await byondApiCompatInstanceTask;
var instancesTest = FailFast(instanceManagerTest.RunPreTest(cancellationToken));
Assert.IsTrue(Directory.Exists(instance.Path));
var instanceClient = adminClient.Instances.CreateClient(instance);
@@ -1158,10 +1160,19 @@ namespace Tgstation.Server.Tests.Live
async Task RunInstanceTests()
{
var byondApiCompatTests = FailFast(
instanceTest
.RunLegacyByondTest(
adminClient.Instances.CreateClient(byondApiCompatInstance),
cancellationToken));
if (TestingUtils.RunningInGitHubActions) // they only have 2 cores, can't handle intense parallelization
await byondApiCompatTests;
var odCompatTests = FailFast(
instanceTest
.RunCompatTests(
await ByondTest.GetEdgeVersion(EngineType.OpenDream, fileDownloader, cancellationToken),
await EngineTest.GetEdgeVersion(EngineType.OpenDream, fileDownloader, cancellationToken),
adminClient.Instances.CreateClient(odInstance),
odDMPort,
odDDPort,
@@ -1204,6 +1215,7 @@ namespace Tgstation.Server.Tests.Live
await compatTests;
await odCompatTests;
await byondApiCompatTests;
}
var instanceTests = RunInstanceTests();
@@ -1367,7 +1379,7 @@ namespace Tgstation.Server.Tests.Live
preStartupTime = DateTimeOffset.UtcNow;
serverTask = server.Run(cancellationToken).AsTask();
long expectedCompileJobId, expectedStaged;
var edgeVersion = await ByondTest.GetEdgeVersion(EngineType.Byond, fileDownloader, cancellationToken);
var edgeVersion = await EngineTest.GetEdgeVersion(EngineType.Byond, fileDownloader, cancellationToken);
using (var adminClient = await CreateAdminClient(server.Url, cancellationToken))
{
var instanceClient = adminClient.Instances.CreateClient(instance);