1 using Microsoft.EntityFrameworkCore;
2 using Microsoft.Extensions.Hosting;
3 using Microsoft.Extensions.Logging;
4 using Microsoft.Extensions.Options;
6 using System.Collections.Generic;
9 using System.Threading.Tasks;
27 public Task Ready => readyTcs.Task;
77 readonly ILogger<InstanceManager>
logger;
97 readonly TaskCompletionSource<object>
readyTcs;
133 IOptions<GeneralConfiguration> generalConfigurationOptions,
134 ILogger<InstanceManager> logger)
136 this.instanceFactory = instanceFactory ??
throw new ArgumentNullException(nameof(instanceFactory));
137 this.ioManager = ioManager ??
throw new ArgumentNullException(nameof(ioManager));
138 this.databaseContextFactory = databaseContextFactory ??
throw new ArgumentNullException(nameof(databaseContextFactory));
139 this.assemblyInformationProvider = assemblyInformationProvider ??
throw new ArgumentNullException(nameof(assemblyInformationProvider));
140 this.jobManager = jobManager ??
throw new ArgumentNullException(nameof(jobManager));
141 this.serverControl = serverControl ??
throw new ArgumentNullException(nameof(serverControl));
142 this.systemIdentityFactory = systemIdentityFactory ??
throw new ArgumentNullException(nameof(systemIdentityFactory));
143 this.asyncDelayer = asyncDelayer ??
throw new ArgumentNullException(nameof(asyncDelayer));
144 this.databaseSeeder = databaseSeeder ??
throw new ArgumentNullException(nameof(databaseSeeder));
145 generalConfiguration = generalConfigurationOptions?.Value ??
throw new ArgumentNullException(nameof(generalConfigurationOptions));
146 this.logger = logger ??
throw new ArgumentNullException(nameof(logger));
150 instances =
new Dictionary<long, IInstance>();
151 bridgeHandlers =
new Dictionary<string, IBridgeHandler>();
152 readyTcs =
new TaskCompletionSource<object>();
165 foreach (var I
in instances)
172 if (metadata == null)
173 throw new ArgumentNullException(nameof(metadata));
176 if (!instances.TryGetValue(metadata.Id, out
IInstance instance))
177 throw new InvalidOperationException(
"Instance not online!");
183 public async Task
MoveInstance(Models.Instance instance,
string newPath, CancellationToken cancellationToken)
186 throw new ArgumentNullException(nameof(newPath));
187 if (instance.Online.Value)
188 throw new InvalidOperationException(
"Cannot move an online instance!");
189 var oldPath = instance.Path;
190 await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(
false);
191 await databaseContextFactory.UseContext(db =>
193 var targetInstance =
new Models.Instance
197 db.Instances.Attach(targetInstance);
198 targetInstance.Path = newPath;
199 return db.Save(cancellationToken);
200 }).ConfigureAwait(
false);
201 await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(
false);
205 public async Task
OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)
207 if (metadata == null)
208 throw new ArgumentNullException(nameof(metadata));
209 logger.LogInformation(
"Offlining instance ID {0}", metadata.Id);
213 if (!instances.TryGetValue(metadata.Id, out instance))
214 throw new InvalidOperationException(
"Instance not online!");
215 instances.Remove(metadata.Id);
221 var tasks =
new List<Task>();
222 await databaseContextFactory.UseContext(async db =>
227 .Where(x => x.Instance.Id == metadata.Id)
228 .Select(x =>
new Models.Job
232 await jobs.ForEachAsync(job =>
235 tasks.Add(jobManager.CancelJob(job, user,
true, cancellationToken));
236 }, cancellationToken).ConfigureAwait(
false);
237 }).ConfigureAwait(
false);
239 await Task.WhenAll(tasks).ConfigureAwait(
false);
241 await instance.StopAsync(cancellationToken).ConfigureAwait(
false);
250 public async Task
OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
252 if (metadata == null)
253 throw new ArgumentNullException(nameof(metadata));
254 logger.LogInformation(
"Onlining instance ID {0} ({1}) at {2}", metadata.Id, metadata.Name, metadata.Path);
255 var instance = instanceFactory.CreateInstance(
this, metadata);
260 if (instances.ContainsKey(metadata.Id))
261 throw new InvalidOperationException(
"Instance already online!");
262 instances.Add(metadata.Id, instance);
271 await instance.StartAsync(cancellationToken).ConfigureAwait(
false);
275 #pragma warning disable CA1506 // TODO: Decomplexify 276 public Task StartAsync(CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext =>
278 logger.LogInformation(assemblyInformationProvider.VersionString);
282 generalConfiguration.CheckCompatibility(logger);
284 CheckSystemCompatibility();
285 var factoryStartup = instanceFactory.StartAsync(cancellationToken);
286 await databaseSeeder.Initialize(databaseContext, cancellationToken).ConfigureAwait(
false);
287 await jobManager.StartAsync(cancellationToken).ConfigureAwait(
false);
288 var dbInstances = databaseContext
291 .Where(x => x.Online.Value)
292 .Include(x => x.RepositorySettings)
293 .Include(x => x.ChatSettings)
294 .ThenInclude(x => x.Channels)
295 .Include(x => x.DreamDaemonSettings)
296 .ToAsyncEnumerable();
297 var tasks =
new List<Task>();
298 await factoryStartup.ConfigureAwait(
false);
299 await dbInstances.ForEachAsync(metadata => tasks.Add(metadata.Online.Value ? OnlineInstance(metadata, cancellationToken) : Task.CompletedTask), cancellationToken).ConfigureAwait(
false);
300 await Task.WhenAll(tasks).ConfigureAwait(
false);
301 logger.LogInformation(
"Server ready!");
302 readyTcs.SetResult(null);
304 catch (OperationCanceledException)
306 logger.LogInformation(
"Cancelled instance manager initialization!");
310 logger.LogCritical(
"Instance manager startup error! Exception: {0}", e);
313 await serverControl.Die(e).ConfigureAwait(
false);
318 logger.LogCritical(
"Failed to kill server! Exception: {0}", e2);
324 #pragma warning restore CA1506 // TODO: Decomplexify 327 public async Task
StopAsync(CancellationToken cancellationToken)
329 await jobManager.StopAsync(cancellationToken).ConfigureAwait(
false);
330 await Task.WhenAll(instances.Select(x => x.Value.StopAsync(cancellationToken))).ConfigureAwait(
false);
331 await instanceFactory.StopAsync(cancellationToken).ConfigureAwait(
false);
334 if (downgradeVersion != null)
335 await databaseContextFactory.UseContext(db => databaseSeeder.Downgrade(db, downgradeVersion, cancellationToken)).ConfigureAwait(
false);
339 public Task
HandleRestart(Version updateVersion, CancellationToken cancellationToken)
341 downgradeVersion = updateVersion != null && updateVersion < assemblyInformationProvider.Version ? updateVersion : null;
342 return Task.CompletedTask;
350 using var systemIdentity = systemIdentityFactory.GetCurrent();
351 if (!systemIdentity.CanCreateSymlinks)
352 throw new InvalidOperationException(
"The user running tgstation-server cannot create symlinks! Please try running as an administrative user!");
358 if (parameters == null)
359 throw new ArgumentNullException(nameof(parameters));
362 for (var i = 0; bridgeHandler == null && i < 30; ++i)
366 Task delayTask = Task.CompletedTask;
367 lock (bridgeHandlers)
368 if (!bridgeHandlers.TryGetValue(parameters.
AccessIdentifier, out bridgeHandler))
369 delayTask = asyncDelayer.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
371 await delayTask.ConfigureAwait(
false);
374 if (bridgeHandler == null)
375 lock (bridgeHandlers)
376 if (!bridgeHandlers.TryGetValue(parameters.
AccessIdentifier, out bridgeHandler))
378 logger.LogWarning(
"Recieved invalid bridge request with accees identifier: {0}", parameters.
AccessIdentifier);
382 return await bridgeHandler.
ProcessBridgeRequest(parameters, cancellationToken).ConfigureAwait(
false);
388 if (bridgeHandler == null)
389 throw new ArgumentNullException(nameof(bridgeHandler));
392 lock (bridgeHandlers)
394 bridgeHandlers.Add(accessIdentifier, bridgeHandler);
395 logger.LogTrace(
"Registered bridge handler: {0}", accessIdentifier);
400 lock (bridgeHandlers)
402 bridgeHandlers.Remove(accessIdentifier);
403 logger.LogTrace(
"Unregistered bridge handler: {0}", accessIdentifier);
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the InstanceManager
readonly IServerControl serverControl
The IServerControl for the InstanceManager
IInstance GetInstance(Models.Instance metadata)
Get the IInstance associated with given metadata
readonly IDatabaseSeeder databaseSeeder
The IDatabaseSeeder for the InstanceManager
readonly IDictionary< string, IBridgeHandler > bridgeHandlers
Map of DMApiParameters.AccessIdentifiers to their respective IBridgeHandlers.
Use server authentication
Factory for scoping usage of IDatabaseContexts. Meant for use by Components
async Task OnlineInstance(Models.Instance metadata, CancellationToken cancellationToken)
Online an IInstance
For initially setting up a database.
readonly ISystemIdentityFactory systemIdentityFactory
The ISystemIdentityFactory for the InstanceManager
bool disposed
If the InstanceManager has been Disposed
Task HandleRestart(Version updateVersion, CancellationToken cancellationToken)
Handle a restart of the server
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the InstanceManager
For waiting asynchronously
async Task OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)
IBridgeRegistration RegisterHandler(IBridgeHandler bridgeHandler)
Register a given bridgeHandler .
async Task< BridgeResponse > ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters .
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the InstanceManager.
Factory for creating IInstances
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the InstanceManager
readonly IJobManager jobManager
The IJobManager for the InstanceManager
Handler for server restarts
readonly IDictionary< long, IInstance > instances
Map of instance EntityId.Ids to respective IInstances. Also used as a object.
Factory for ISystemIdentitys
InstanceManager(IInstanceFactory instanceFactory, IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IAssemblyInformationProvider assemblyInformationProvider, IJobManager jobManager, IServerControl serverControl, ISystemIdentityFactory systemIdentityFactory, IAsyncDelayer asyncDelayer, IDatabaseSeeder databaseSeeder, IOptions< GeneralConfiguration > generalConfigurationOptions, ILogger< InstanceManager > logger)
Construct an InstanceManager
readonly TaskCompletionSource< object > readyTcs
The TaskCompletionSource<TResult> for Ready.
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)
string AccessIdentifier
Used to identify and authenticate the DreamDaemon instance
readonly ILogger< InstanceManager > logger
The ILogger for the InstanceManager
Parameters for a bridge request.
readonly IInstanceFactory instanceFactory
The IInstanceFactory for the InstanceManager
Task< BridgeResponse > ProcessBridgeRequest(BridgeParameters parameters, CancellationToken cancellationToken)
Handle a set of bridge parameters .
General configuration options
Manages the runtime of Jobs
readonly IIOManager ioManager
The IIOManager for the InstanceManager
Interface for using filesystems
Registers IBridgeHandlers.
void CheckSystemCompatibility()
Check we have a valid system identity.
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
Represents a registration of an interop session.
DMApiParameters DMApiParameters
The DMApiParameters for the IBridgeHandler.
Represents a service that may take an updated Host assembly and run it, stopping the current assembly...