diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 66e5238161..01e41bbca7 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using Tgstation.Server.Api.Rights; namespace Tgstation.Server.Api.Models @@ -48,6 +49,13 @@ namespace Tgstation.Server.Api.Models [Permissions(WriteRight = InstanceManagerRights.SetAutoUpdate)] public int? AutoUpdateInterval { get; set; } + /// + /// The representing a change of + /// + [Permissions(DenyWrite = true)] + [NotMapped] + public Job MoveJob { get; set; } + /// public Instance CloneMetadata() => new Instance { diff --git a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs index 7d0a3c4ace..f8997edce3 100644 --- a/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs +++ b/src/Tgstation.Server.Api/Rights/InstanceManagerRights.cs @@ -47,6 +47,10 @@ namespace Tgstation.Server.Api.Rights /// /// User can change /// - SetAutoUpdate = 256 + SetAutoUpdate = 256, + /// + /// User can cancel move operations + /// + CancelMove = 512, } } diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 44099741d7..323fa7568a 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using System; using System.Collections.Generic; @@ -40,6 +41,11 @@ namespace Tgstation.Server.Host.Components /// readonly IApplication application; + /// + /// The for the + /// + readonly ILogger logger; + /// /// Map of s to respective s /// @@ -56,12 +62,14 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application) + /// The value of + public InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILogger logger) { this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.application = application ?? throw new ArgumentNullException(nameof(application)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); instances = new Dictionary(); interopConsumers = new Dictionary(); @@ -93,24 +101,11 @@ namespace Tgstation.Server.Host.Components if (newPath == null) throw new ArgumentNullException(nameof(newPath)); if (instance.Online.Value) - await OfflineInstance(instance, cancellationToken).ConfigureAwait(false); - Task instanceOnlineTask = null; - try - { - var oldPath = instance.Path; - await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false); - instance.Path = ioManager.ResolvePath(newPath); - instanceOnlineTask = OnlineInstance(instance, default); - await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false); - } - finally - { - if (instance.Online.Value) - if (instanceOnlineTask == null) - await OnlineInstance(instance, default).ConfigureAwait(false); - else - await instanceOnlineTask.ConfigureAwait(false); - } + throw new InvalidOperationException("Cannot move an online instance!"); + var oldPath = instance.Path; + await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false); + instance.Path = ioManager.ResolvePath(newPath); + await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false); } /// @@ -118,6 +113,7 @@ namespace Tgstation.Server.Host.Components { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); + logger.LogInformation("Offlining instance ID {0}", metadata.Id); IInstance instance; lock (this) { @@ -140,6 +136,7 @@ namespace Tgstation.Server.Host.Components { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); + logger.LogInformation("Onlining instance ID {0} ({1}) at {2}", metadata.Id, metadata.Name, metadata.Path); var instance = instanceFactory.CreateInstance(metadata, this); try { diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs index 5d731e6060..a70c5d80a8 100644 --- a/src/Tgstation.Server.Host/Controllers/ByondController.cs +++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs @@ -78,7 +78,7 @@ namespace Tgstation.Server.Host.Controllers var byondManager = instanceManager.GetInstance(Instance).ByondManager; //remove cruff fields - var installingVersion = new Version(model.Version.Major, model.Version.Major); + var installingVersion = new Version(model.Version.Major, model.Version.Minor); var result = new Api.Models.Byond(); diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 40225f90dc..aab876e91b 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -272,27 +272,58 @@ namespace Tgstation.Server.Host.Controllers await DatabaseContext.Save(cancellationToken).ConfigureAwait(false); + var oldAutoStart = originalModel.DreamDaemonSettings.AutoStart; try { if (originalOnline && model.Online.Value == false) await instanceManager.OfflineInstance(originalModel, cancellationToken).ConfigureAwait(false); else if (!originalOnline && model.Online.Value == true) + { + //force autostart false here because we don't want any long running jobs right now + //remember to document this + originalModel.DreamDaemonSettings.AutoStart = false; await instanceManager.OnlineInstance(originalModel, cancellationToken).ConfigureAwait(false); + } } catch (Exception e) { logger.LogError("Error changing instance online state! Exception: {0}", e); originalModel.Online = originalOnline; + originalModel.DreamDaemonSettings.AutoStart = oldAutoStart; if (originalModelPath != null) originalModel.Path = originalModelPath; await DatabaseContext.Save(default).ConfigureAwait(false); throw; } + var api = originalModel.ToApi(); if (originalModelPath != null) - await ioManager.MoveDirectory(originalModelPath, model.Path, cancellationToken).ConfigureAwait(false); + { + var job = new Models.Job + { + Description = "Move instance location", + Instance = Instance, + CancelRightsType = RightsType.InstanceManager, + CancelRight = (int)InstanceManagerRights.CancelMove, + StartedBy = AuthenticationContext.User + }; - return Json(originalModel.ToApi()); + await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, ct) => { + try + { + await instanceManager.MoveInstance(Instance, originalModel.Path, ct).ConfigureAwait(false); + } + catch + { + originalModel.Path = originalModelPath; + await DatabaseContext.Save(default).ConfigureAwait(false); + throw; + } + }, cancellationToken).ConfigureAwait(false); + api.MoveJob = job.ToApi(); + } + + return Json(api); } /// diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index a8d2a20739..79bb25cc15 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -37,18 +37,12 @@ namespace Tgstation.Server.Host.Controllers [TgsAuthorize] public override async Task List(CancellationToken cancellationToken) { - IQueryable query = DatabaseContext.Jobs; - if (Instance != null) + //you KNOW this will need pagination eventually right? + var jobs = await DatabaseContext.Jobs.Where(x => x.Instance.Id == Instance.Id).OrderByDescending(x => x.StartedAt).Select(x => new Api.Models.Job { - if (AuthenticationContext.InstanceUser?.AnyRights != true) - return Forbid(); - query = query.Where(x => x.Instance.Id == Instance.Id); - } - else - query = query.Where(x => x.Instance == null); - - var jobs = await query.Where(x => x.StoppedAt == null).ToListAsync(cancellationToken).ConfigureAwait(false); - return Json(jobs.Select(x => x.ToApi())); + Id = x.Id + }).ToListAsync(cancellationToken).ConfigureAwait(false); + return Json(jobs); } /// @@ -60,12 +54,12 @@ namespace Tgstation.Server.Host.Controllers if (job == default(Job)) return NotFound(); + if (job.StoppedAt != null) + return StatusCode((int)HttpStatusCode.Gone); + if (job.CancelRight.HasValue && job.CancelRightsType.HasValue && (AuthenticationContext.GetRight(job.CancelRightsType.Value) & job.CancelRight.Value) == 0) return Forbid(); - if(job.StoppedAt != null) - return StatusCode((int)HttpStatusCode.Gone); - await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false); return Ok(); } diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index c99672c654..c72590d5c9 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -172,6 +172,8 @@ namespace Tgstation.Server.Host.Core var databaseContext = scope.ServiceProvider.GetRequiredService(); job = new Job { Id = job.Id }; databaseContext.Jobs.Attach(job); + user = new User { Id = user.Id }; + databaseContext.Users.Attach(user); job.CancelledBy = user; await databaseContext.Save(cancellationToken).ConfigureAwait(false); }