1 using Microsoft.EntityFrameworkCore;
2 using Microsoft.Extensions.Hosting;
3 using Microsoft.Extensions.Logging;
5 using System.Collections.Generic;
8 using System.Threading.Tasks;
50 readonly ILogger<InstanceManager>
logger;
79 this.instanceFactory = instanceFactory ??
throw new ArgumentNullException(nameof(instanceFactory));
80 this.ioManager = ioManager ??
throw new ArgumentNullException(nameof(ioManager));
81 this.databaseContextFactory = databaseContextFactory ??
throw new ArgumentNullException(nameof(databaseContextFactory));
82 this.application = application ??
throw new ArgumentNullException(nameof(application));
83 this.jobManager = jobManager ??
throw new ArgumentNullException(nameof(jobManager));
84 this.serverControl = serverControl ??
throw new ArgumentNullException(nameof(serverControl));
85 this.logger = logger ??
throw new ArgumentNullException(nameof(logger));
89 instances =
new Dictionary<long, IInstance>();
101 foreach (var I
in instances)
108 if (metadata == null)
109 throw new ArgumentNullException(nameof(metadata));
112 if (!instances.TryGetValue(metadata.Id, out
IInstance instance))
113 throw new InvalidOperationException(
"Instance not online!");
119 public async Task
MoveInstance(Models.Instance instance,
string newPath, CancellationToken cancellationToken)
122 throw new ArgumentNullException(nameof(newPath));
123 if (instance.Online.Value)
124 throw new InvalidOperationException(
"Cannot move an online instance!");
125 var oldPath = instance.Path;
126 await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(
false);
127 await databaseContextFactory.UseContext(db =>
129 var targetInstance =
new Models.Instance
133 db.Instances.Attach(targetInstance);
134 targetInstance.Path = newPath;
135 return db.Save(cancellationToken);
136 }).ConfigureAwait(
false);
137 await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(
false);
141 public async Task
OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)
143 if (metadata == null)
144 throw new ArgumentNullException(nameof(metadata));
145 logger.LogInformation(
"Offlining instance ID {0}", metadata.Id);
149 if (!instances.TryGetValue(metadata.Id, out instance))
150 throw new InvalidOperationException(
"Instance not online!");
151 instances.Remove(metadata.Id);
156 var tasks =
new List<Task>();
157 await databaseContextFactory.UseContext(async db =>
159 var jobs = db.Jobs.Where(x => x.Instance.Id == metadata.Id).Select(x =>
new Models.Job
162 }).ToAsyncEnumerable();
163 await jobs.ForEachAsync(job =>
166 tasks.Add(jobManager.CancelJob(job, user,
true, cancellationToken));
167 }, cancellationToken).ConfigureAwait(
false);
168 }).ConfigureAwait(
false);
170 await Task.WhenAll(tasks).ConfigureAwait(
false);
172 await instance.StopAsync(cancellationToken).ConfigureAwait(
false);
181 public async Task
OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
183 if (metadata == null)
184 throw new ArgumentNullException(nameof(metadata));
185 logger.LogInformation(
"Onlining instance ID {0} ({1}) at {2}", metadata.Id, metadata.Name, metadata.Path);
186 var instance = instanceFactory.CreateInstance(metadata);
191 if (instances.ContainsKey(metadata.Id))
192 throw new InvalidOperationException(
"Instance already online!");
193 instances.Add(metadata.Id, instance);
201 await instance.StartAsync(cancellationToken).ConfigureAwait(
false);
205 public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext =>
209 var factoryStartup = instanceFactory.StartAsync(cancellationToken);
210 await databaseContext.Initialize(cancellationToken).ConfigureAwait(
false);
211 await jobManager.StartAsync(cancellationToken).ConfigureAwait(
false);
212 var dbInstances = databaseContext.Instances.Where(x => x.Online.Value)
213 .Include(x => x.RepositorySettings)
214 .Include(x => x.ChatSettings)
215 .ThenInclude(x => x.Channels)
216 .Include(x => x.DreamDaemonSettings)
217 .ToAsyncEnumerable();
218 var tasks =
new List<Task>();
219 await factoryStartup.ConfigureAwait(
false);
220 await dbInstances.ForEachAsync(metadata => tasks.Add(metadata.Online.Value ? OnlineInstance(metadata, cancellationToken) : Task.CompletedTask), cancellationToken).ConfigureAwait(
false);
221 await Task.WhenAll(tasks).ConfigureAwait(
false);
222 logger.LogInformation(
"Server ready!");
223 application.Ready(null);
225 catch (OperationCanceledException)
227 logger.LogInformation(
"Cancelled instance manager initialization!");
231 logger.LogCritical(
"Instance manager startup error! Exception: {0}", e);
232 application.Ready(e);
235 await serverControl.Die(e).ConfigureAwait(
false);
239 logger.LogCritical(
"Failed to kill server! Exception: {0}", e2);
245 public async Task
StopAsync(CancellationToken cancellationToken)
247 await jobManager.StopAsync(cancellationToken).ConfigureAwait(
false);
248 await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(
false);
249 await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(
false);
252 if (downgradeVersion != null)
253 await databaseContextFactory.UseContext(db => db.SchemaDowngradeForServerVersion(downgradeVersion, cancellationToken)).ConfigureAwait(
false);
257 public Task
HandleRestart(Version updateVersion, CancellationToken cancellationToken)
259 downgradeVersion = updateVersion != null && updateVersion < application.Version ? updateVersion : null;
260 return Task.CompletedTask;
Manages the runtime of Jobs
readonly IServerControl serverControl
The IServerControl for the InstanceManager
IInstance GetInstance(Models.Instance metadata)
Get the IInstance associated with given metadata
Configures the ASP.NET Core web application
Use server authentication
async Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
Online an IInstance
Factory for scoping usage of IDatabaseContexts. Meant for use by Components
bool disposed
If the InstanceManager has been Disposed
Task HandleRestart(Version updateVersion, CancellationToken cancellationToken)
Handle a restart of the server
async Task OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)
Factory for creating IInstances
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the InstanceManager
readonly IJobManager jobManager
The IJobManager for the InstanceManager
Handler for server restarts
Version downgradeVersion
Used in StopAsync(CancellationToken) to determine if database downgrades must be made ...
For interacting with the instance services
async Task StopAsync(CancellationToken cancellationToken)
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
readonly IInstanceFactory instanceFactory
The IInstanceFactory for the InstanceManager
readonly Dictionary< long, IInstance > instances
Map of Api.Models.Instance.Ids to respective IInstances
readonly IApplication application
The IApplication for the InstanceManager
readonly IIOManager ioManager
The IIOManager for the InstanceManager
Interface for using filesystems
async Task MoveInstance(Models.Instance instance, string newPath, CancellationToken cancellationToken)
Move an IInstance
IRestartRegistration RegisterForRestart(IRestartHandler handler)
Register a given handler to run before stopping the server for a restart
InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, IJobManager jobManager, IServerControl serverControl, ILogger< InstanceManager > logger)
Construct an InstanceManager
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...