mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 07:04:57 +01:00
@@ -0,0 +1,8 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
interface IInstance : IHostedService
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
interface IInstanceFactory
|
||||
{
|
||||
IInstance CreateInstance(Instance metadata);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing <see cref="IInstance"/>s
|
||||
/// </summary>
|
||||
interface IInstanceManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="IInstance"/> associated with given <paramref name="metadata"/>
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="Models.Instance"/> of the desired <see cref="IInstance"/></param>
|
||||
/// <returns>The <see cref="IInstance"/> associated with the given <paramref name="metadata"/></returns>
|
||||
IInstance GetInstance(Models.Instance metadata);
|
||||
|
||||
/// <summary>
|
||||
/// Online an <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="Models.Instance"/> of the desired <see cref="IInstance"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Offline an <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="Models.Instance"/> of the desired <see cref="IInstance"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task OfflineInstance(Models.Instance metadata, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Move an <see cref="IInstance"/>
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="Models.Instance"/> of the desired <see cref="IInstance"/></param>
|
||||
/// <param name="newPath">The new path of the <see cref="IInstance"/>. <paramref name="metadata"/> will have this set on <see cref="Api.Models.Instance.Path"/> if the operation completes successfully</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task MoveInstance(Models.Instance metadata, string newPath, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class InstanceManager : IInstanceManager, IHostedService
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IInstanceFactory"/> for the <see cref="IInstanceManager"/>
|
||||
/// </summary>
|
||||
readonly IInstanceFactory instanceFactory;
|
||||
/// <summary>
|
||||
/// The <see cref="IServiceProvider"/> for the <see cref="IInstanceManager"/>
|
||||
/// </summary>
|
||||
readonly IServiceProvider serviceProvider;
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="IInstanceManager"/>
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
/// <summary>
|
||||
/// Map of <see cref="Api.Models.Instance.Id"/>s to respective <see cref="IInstance"/>s
|
||||
/// </summary>
|
||||
readonly Dictionary<long, IInstance> instances;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InstanceManager"/>
|
||||
/// </summary>
|
||||
/// <param name="instanceFactory">The value of <see cref="instanceFactory"/></param>
|
||||
/// <param name="serviceProvider">The value of <paramref name="serviceProvider"/></param>
|
||||
/// <param name="ioManager">The value of <paramref name="ioManager"/></param>
|
||||
public InstanceManager(IInstanceFactory instanceFactory, IServiceProvider serviceProvider, IIOManager ioManager)
|
||||
{
|
||||
this.instanceFactory = instanceFactory ?? throw new ArgumentNullException(nameof(instanceFactory));
|
||||
this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
instances = new Dictionary<long, IInstance>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IInstance GetInstance(Instance metadata)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (!instances.TryGetValue(metadata.Id, out IInstance instance))
|
||||
throw new InvalidOperationException("Instance not online!");
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task MoveInstance(Instance instance, string newPath, CancellationToken cancellationToken)
|
||||
{
|
||||
if (newPath == null)
|
||||
throw new ArgumentNullException(nameof(newPath));
|
||||
if (instance.Online)
|
||||
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)
|
||||
if (instanceOnlineTask == null)
|
||||
await OnlineInstance(instance, default).ConfigureAwait(false);
|
||||
else
|
||||
await instanceOnlineTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task OfflineInstance(Instance metadata, CancellationToken cancellationToken)
|
||||
{
|
||||
IInstance instance;
|
||||
lock (this)
|
||||
{
|
||||
if (!instances.TryGetValue(metadata.Id, out instance))
|
||||
throw new InvalidOperationException("Instance not online!");
|
||||
instances.Remove(metadata.Id);
|
||||
}
|
||||
await instance.StopAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task OnlineInstance(Instance metadata, CancellationToken cancellationToken)
|
||||
{
|
||||
var instance = instanceFactory.CreateInstance(metadata);
|
||||
lock (this)
|
||||
{
|
||||
if (instances.ContainsKey(metadata.Id))
|
||||
throw new InvalidOperationException("Instance already online!");
|
||||
instances.Add(metadata.Id, instance);
|
||||
}
|
||||
await instance.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using(var scope = serviceProvider.CreateScope())
|
||||
{
|
||||
var databaseContext = scope.ServiceProvider.GetRequiredService<IDatabaseContext>();
|
||||
await databaseContext.Initialize(cancellationToken).ConfigureAwait(false);
|
||||
var dbInstances = databaseContext.Instances.Where(x => x.Online).Include(x => x.RepositorySettings).Include(x => x.ChatSettings).Include(x => x.DreamDaemonSettings).ToAsyncEnumerable();
|
||||
var tasks = new List<Task>();
|
||||
await dbInstances.ForEachAsync(metadata => tasks.Add(OnlineInstance(metadata, cancellationToken)), cancellationToken).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(false);
|
||||
instances.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using System;
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Reflection;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Controllers;
|
||||
using Tgstation.Server.Host.Models;
|
||||
@@ -132,6 +133,10 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<ITokenFactory, TokenFactory>();
|
||||
services.AddSingleton<ISystemIdentityFactory, SystemIdentityFactory>();
|
||||
|
||||
services.AddSingleton<InstanceManager>();
|
||||
services.AddSingleton<IInstanceManager>(x => x.GetRequiredService<InstanceManager>());
|
||||
services.AddSingleton<IHostedService>(x => x.GetRequiredService<InstanceManager>());
|
||||
|
||||
services.AddSingleton<JobManager>();
|
||||
services.AddSingleton<IJobManager>(x => x.GetRequiredService<JobManager>());
|
||||
services.AddSingleton<IHostedService>(x => x.GetRequiredService<JobManager>());
|
||||
|
||||
Reference in New Issue
Block a user