Changed logging system to be event based.

Implemented LRUCache for instantiating services without starting.

Service state changed back to enumerator.

Namespace refactoring.
This commit is contained in:
Harrison Deng 2021-04-19 01:34:45 -05:00
parent cac5ca054c
commit 35a2765559
26 changed files with 404 additions and 498 deletions

View File

@ -6,10 +6,12 @@ namespace GameServiceWarden.API.Module
{ {
public interface IService public interface IService
{ {
event EventHandler<bool> StateChangeEvent; event EventHandler<ServiceState> StateChangeEvent;
event EventHandler<string> UpdateLogEvent;
IReadOnlyCollection<IServiceConfigurable> Configurables{ get; } IReadOnlyCollection<IServiceConfigurable> Configurables{ get; }
void InitializeService(Stream stream); void InitializeService();
void ElegantShutdown(); void ElegantShutdown();
byte[] GetLogBuffer();
void ExecuteCommand(string command); void ExecuteCommand(string command);
} }
} }

View File

@ -23,8 +23,7 @@ namespace GameServiceWarden.API.Module
/// Creates an instance of a the service to be used. /// Creates an instance of a the service to be used.
/// </summary> /// </summary>
/// <param name="workspace">The workspace directory. All service required files should be stored here. Expect the directory to be created.</param> /// <param name="workspace">The workspace directory. All service required files should be stored here. Expect the directory to be created.</param>
/// <param name="clean">Whether or not this game service is new. That is, the <code>workspace</code> can be assumed empty.</param>
/// <returns>The <see cref="IService"/> responsible for the instance of the game service.</returns> /// <returns>The <see cref="IService"/> responsible for the instance of the game service.</returns>
IService InstantiateService(string workspace, bool clean); IService InstantiateService(string workspace);
} }
} }

View File

@ -4,5 +4,6 @@ namespace GameServiceWarden.API.Module
{ {
Stopped, Stopped,
Running, Running,
RestartPending
} }
} }

View File

@ -9,7 +9,7 @@ namespace GameServiceWarden.API.Games
public ICollection<string> services; public ICollection<string> services;
public ICollection<string> running; public ICollection<string> running;
public ICollection<string> modules; public ICollection<string> modules;
public IReadOnlyDictionary<string, string> logs; public IReadOnlyDictionary<string, byte[]> logs;
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> serviceOptions; public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> serviceOptions;
} }
} }

View File

@ -1,264 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using GameServiceWarden.Core.Games.Modules.Exceptions;
using GameServiceWarden.Core.Logging;
using GameServiceWarden.API.Module;
using System.Net.Sockets;
//TODO Update UML
namespace GameServiceWarden.Core.Games
{
public class ServiceDescriptor //entity
{
private const string LOG_DISTRIBUTOR_PREFIX = "log_dist_";
private const int TIMEOUT = 1000;
/// <summary>
/// The name of the service itself, independent of the name of the module this service is using.
/// </summary>
public string ServiceName { get { return serviceName; } }
private readonly string serviceName;
private readonly Guid runningUID;
private volatile bool running;
private readonly IService service;
/// <summary>
/// The services log output pipe name.
/// </summary>
public string ServiceLogPipeName { get { return (runningUID.ToString() + ".pipe"); } }
private string moduleName;
private readonly string assemblyName;
private volatile NamedPipeServerStream logReceiver;
private volatile NamedPipeClientStream logSender;
private ConcurrentStack<NamedPipeServerStream> logStreamListeners;
private Task logUpdateTask;
private Task listenTask;
private volatile CancellationTokenSource stopToken;
private NamedPipeServerStream acceptingPipe;
/// <summary>
/// Name of module this service uses.
/// </summary>
private readonly IReadOnlyDictionary<string, IServiceConfigurable> configurables;
public event EventHandler<bool> ServiceStateChangeEvent;
public ServiceDescriptor(IService service, string serviceName, string moduleName, string assemblyName)
{
this.service = service ?? throw new ArgumentNullException("service");
this.moduleName = moduleName ?? throw new ArgumentNullException("moduleName");
this.assemblyName = assemblyName ?? throw new ArgumentNullException("assemblyName");
this.serviceName = serviceName ?? throw new ArgumentNullException("serviceName");
this.service.StateChangeEvent += OnServiceStateChange;
runningUID = Guid.NewGuid();
Dictionary<string, IServiceConfigurable> tempConfigurables = new Dictionary<string, IServiceConfigurable>();
foreach (IServiceConfigurable configurable in service.Configurables)
{
tempConfigurables.Add(configurable.OptionName, configurable);
}
this.configurables = new ReadOnlyDictionary<string, IServiceConfigurable>(tempConfigurables);
logStreamListeners = new ConcurrentStack<NamedPipeServerStream>();
}
/// <summary>
/// Starts this service.
/// </summary>
/// <exception cref="InvalidOperationException">Is thrown when the service is already running.</exception>
public void Start()
{
Logger.Log($"\"{ServiceName}\" is starting.");
if (running) throw new InvalidOperationException("Service instance already running.");
logReceiver = new NamedPipeServerStream(LOG_DISTRIBUTOR_PREFIX + ServiceLogPipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
Task waitForConnection = logReceiver.WaitForConnectionAsync();
logSender = new NamedPipeClientStream(".", LOG_DISTRIBUTOR_PREFIX + ServiceLogPipeName, PipeDirection.Out);
logSender.Connect();
waitForConnection.Wait();
byte[] idToken = Guid.NewGuid().ToByteArray();
ValueTask sendTokenTask = logSender.WriteAsync(idToken);
byte[] receivedToken = new byte[idToken.Length];
logReceiver.Read(receivedToken);
if (!sendTokenTask.AsTask().Wait(500) || !sendTokenTask.IsCompletedSuccessfully)
{
throw new ServiceInitializationException("Error while sending identification token.");
}
if (!idToken.SequenceEqual(receivedToken))
{
throw new ServiceInitializationException("Wrong distributor identification token.");
}
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TIMEOUT);
Task initializationTask = Task.Run(() => service.InitializeService(logSender), cancellationTokenSource.Token);
initializationTask.Wait();
cancellationTokenSource.Dispose();
stopToken = new CancellationTokenSource();
listenTask = AcceptLogConnections();
logUpdateTask = BroadcastLog();
}
/// <summary>
/// Stops the service.
/// </summary>
/// <exception cref="InvalidOperationException">Is thrown when the is not running.</exception>
public void Stop()
{
if (!running) throw new InvalidOperationException("Service instance not running.");
Logger.Log($"\"{ServiceName}\" is stopping.");
service.ElegantShutdown();
stopToken.Cancel(); // Doesn't work on Linux(?)
acceptingPipe.Dispose(); //Handles Linux case
logSender.Dispose(); //Makes sure logging client is disposed
logReceiver.Dispose(); //Closes receiver (Linux doesn't respond to cancellations, needed to dispose either way).
NamedPipeServerStream terminatingPipe;
while (logStreamListeners.TryPop(out terminatingPipe))
{
terminatingPipe.Dispose(); // Required before waiting since this is under listenTask.
}
try
{
if (!listenTask.Wait(TIMEOUT)) {
throw new TimeoutException($"Could not stop \"{ServiceName}\" accepting task within {TIMEOUT}ms.");
}
}
catch (AggregateException e)
{
e.Handle((exception) => exception is TaskCanceledException || (exception is SocketException && exception.Message.Equals("Operation canceled"))); //Task cancel for Windows, Socket for operation cancellation.
}
try
{
if (!logUpdateTask.Wait(TIMEOUT)) {
throw new TimeoutException($"Could not stop \"{ServiceName}\" broadcast within{TIMEOUT}ms.");
}
}
catch (AggregateException e)
{
e.Handle((exception) => exception is TaskCanceledException || (exception is SocketException && exception.Message.Equals("Operation canceled"))); //Same as above.
}
stopToken.Dispose();
}
/// <summary>
/// Sends a command to this service to execute.
/// </summary>
/// <param name="command">The command to execute.</param>
/// <exception cref="InvalidOperationException">Is thrown when the service is not running.</exception>
public void ExecuteCommand(string command)
{
Logger.Log($"\"{ServiceName}\" is executing command \"{command}\".", LogLevel.DEBUG);
if (!running) throw new InvalidOperationException("Service instance not running.");
service.ExecuteCommand(command);
}
/// <summary>
/// Gets the possible <see cref="IServiceConfigurable"/>'s names for this service.
/// </summary>
/// <returns>A <see cref="ISet{string}"/> returned where the string is the option's name.</returns>
public ISet<string> GetConfigurableOptions()
{
return new HashSet<string>(this.configurables.Keys);
}
public bool SetConfigurableValue(string configurationName, string value)
{
if (!this.configurables.ContainsKey(configurationName)) throw new KeyNotFoundException($"Unable to find option with name \"{configurationName}\".");
return this.configurables[configurationName].SetValue(value);
}
public string GetConfigurableValue(string configurationName)
{
if (!this.configurables.ContainsKey(configurationName)) throw new KeyNotFoundException($"Unable to find option with name \"{configurationName}\".");
return this.configurables[configurationName].GetValue();
}
public bool GetServiceState()
{
return running;
}
public string GetServiceName()
{
return serviceName;
}
public string GetModuleName()
{
return moduleName;
}
/// <returns>The name of assembly this module is contained in.</returns>
public string GetAssemblyName()
{
return assemblyName;
}
private void OnServiceStateChange(object sender, bool running)
{
this.running = running;
Logger.Log($"The service \"{ServiceName}\" is changing states to {(running ? "running" : "stopped")}.", LogLevel.DEBUG);
ServiceStateChangeEvent?.Invoke(this, running);
}
private async Task AcceptLogConnections()
{
Logger.Log($"\"{ServiceName}\" is now accepting log listeners.");
while (running)
{
NamedPipeServerStream pipe = new NamedPipeServerStream(ServiceLogPipeName, PipeDirection.Out, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
acceptingPipe = pipe;
await pipe.WaitForConnectionAsync(stopToken.Token);
Logger.Log($"A log listener has connected. Currently broadcasting to {logStreamListeners.Count + 1} listener(s).", LogLevel.DEBUG);
logStreamListeners.Push(pipe);
}
Logger.Log($"\"{ServiceName}\" stopped accepting log listeners.");
}
private async Task BroadcastLog()
{
Stack<NamedPipeServerStream> completeStack = new Stack<NamedPipeServerStream>();
Stack<(Task, CancellationTokenSource)> writeTasks = new Stack<(Task, CancellationTokenSource)>();
byte[] buffer = new byte[1024 * 8];
int fill;
Logger.Log($"\"{ServiceName}\" is now listening to the service log and broadcasting.");
while (running && (fill = await logReceiver.ReadAsync(buffer, 0, buffer.Length, stopToken.Token)) > 0)
{
Logger.Log($"Broadcasting {fill} bytes.", LogLevel.DEBUG);
NamedPipeServerStream pipe;
while (logStreamListeners.TryPop(out pipe))
{
if (!pipe.IsConnected)
{
pipe.Dispose();
Logger.Log($"\"{ServiceName}\" detected a disconnected log listener. Removing from list of listener(s).", LogLevel.DEBUG);
}
else
{
CancellationTokenSource cancelToken = new CancellationTokenSource(1000);
writeTasks.Push((pipe.WriteAsync(buffer, 0, fill, cancelToken.Token), cancelToken));
completeStack.Push(pipe);
}
}
NamedPipeServerStream completePipe;
while (completeStack.TryPop(out completePipe))
{
logStreamListeners.Push(completePipe);
}
(Task, CancellationTokenSource) taskAndCancel;
while (writeTasks.TryPop(out taskAndCancel))
{
await taskAndCancel.Item1;
taskAndCancel.Item2.Dispose();
}
Logger.Log($"\"{ServiceName}\" broadcasted to {logStreamListeners.Count} listener(s).", LogLevel.DEBUG);
}
Logger.Log($"\"{ServiceName}\" stopped listening to service log.");
}
}
}

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Runtime.Serialization; using System.Runtime.Serialization;
namespace GameServiceWarden.Core.Games.Modules.Exceptions namespace GameServiceWarden.Core.Module.Exceptions
{ {
[System.Serializable] [System.Serializable]
public class ModuleLoadException : Exception public class ModuleLoadException : Exception

View File

@ -1,4 +1,4 @@
namespace GameServiceWarden.Core.Games.Modules.Exceptions namespace GameServiceWarden.Core.Module.Exceptions
{ {
[System.Serializable] [System.Serializable]
public class ServiceInitializationException : System.Exception public class ServiceInitializationException : System.Exception

View File

@ -1,6 +1,6 @@
using GameServiceWarden.API.Games; using GameServiceWarden.API.Games;
namespace GameServiceWarden.Core.Games namespace GameServiceWarden.Core.Module
{ {
public interface IServiceManagerActionExecuter public interface IServiceManagerActionExecuter
{ {

View File

@ -1,6 +1,6 @@
using GameServiceWarden.API.Games; using GameServiceWarden.API.Games;
namespace GameServiceWarden.Core.Games namespace GameServiceWarden.Core.Module
{ {
public interface IServiceManagerMonitor public interface IServiceManagerMonitor
{ {

View File

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
namespace GameServiceWarden.Core.Module
{
public class LRUCache<K, V>
{
private class Node
{
public K key;
public V value;
public Node front;
public Node back;
}
public int Size {get { return size; } }
public int Length {get { return valueDictionary.Count; } }
private readonly int size;
private Node top;
private Node bottom;
private Dictionary<K, Node> valueDictionary;
private Action<V> cleanupAction;
public LRUCache(int size = 100, Action<V> cleanup = null)
{
this.size = size;
valueDictionary = new Dictionary<K, Node>(size);
this.cleanupAction = cleanup;
}
private void MoveToTop(K key) {
Node node = valueDictionary[key];
if (node != null && top != node) {
node.front.back = node.back;
node.back = top;
node.front = null;
top = node;
}
}
private Node AddToTop(K key, V value) {
Node node = new Node();
node.front = null;
node.back = top;
node.value = value;
node.key = key;
top = node;
if (bottom == null) {
bottom = node;
} else if (valueDictionary.Count == Size) {
valueDictionary.Remove(bottom.key);
cleanupAction(bottom.value);
bottom = bottom.front;
}
valueDictionary[key] = node;
return node;
}
public V Use(K key, Func<V> generate) {
if (generate == null) throw new ArgumentNullException("generate");
Node value = null;
if (valueDictionary.ContainsKey(key)) {
value = valueDictionary[key];
MoveToTop(key);
} else {
value = AddToTop(key, generate());
}
return value.value;
}
public void Clear() {
top = null;
bottom = null;
valueDictionary.Clear();
}
}
}

View File

@ -2,7 +2,7 @@ using System;
using System.Reflection; using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
namespace GameServiceWarden.Core.Games.Modules namespace GameServiceWarden.Core.Module
{ {
class ModuleLoadContext : AssemblyLoadContext class ModuleLoadContext : AssemblyLoadContext
{ {

View File

@ -1,12 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using GameServiceWarden.Core.Games.Modules.Exceptions; using GameServiceWarden.Core.Module.Exceptions;
using GameServiceWarden.API.Module; using GameServiceWarden.API.Module;
namespace GameServiceWarden.Core.Games.Modules namespace GameServiceWarden.Core.Module
{ {
public class ServiceModuleLoader //Gateway public class ModuleLoader //Gateway
{ {
/// <summary> /// <summary>
/// Loads an extension module. /// Loads an extension module.

View File

@ -0,0 +1,156 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using GameServiceWarden.Core.Module.Exceptions;
using GameServiceWarden.Core.Logging;
using GameServiceWarden.API.Module;
using System.Net.Sockets;
//TODO Update UML
namespace GameServiceWarden.Core.Module
{
public class ServiceDescriptor //entity
{
public const string MODULE_PROPERTY = "SERVICE_MODULE";
public const string ASSEMBLY_PROPERTY = "SERVICE_ASSEMBLY";
private const int TIMEOUT = 1000;
/// <summary>
/// The name of the service itself, independent of the name of the module this service is using.
/// </summary>
public string ServiceName { get { return serviceName; } }
private readonly string serviceName;
private volatile ServiceState state;
private readonly IService service;
private string moduleName;
private readonly string assemblyName;
/// <summary>
/// Name of module this service uses.
/// </summary>
private readonly IReadOnlyDictionary<string, IServiceConfigurable> configurables;
public event EventHandler<ServiceState> ServiceStateChangeEvent;
public event EventHandler<string> LogUpdateEvent;
public ServiceDescriptor(IService service, string serviceName, string moduleName, string assemblyName)
{
this.service = service ?? throw new ArgumentNullException("service");
this.moduleName = moduleName ?? throw new ArgumentNullException("moduleName");
this.assemblyName = assemblyName ?? throw new ArgumentNullException("assemblyName");
this.serviceName = serviceName ?? throw new ArgumentNullException("serviceName");
this.service.StateChangeEvent += OnServiceStateChange;
this.service.UpdateLogEvent += OnLogUpdate;
Dictionary<string, IServiceConfigurable> tempConfigurables = new Dictionary<string, IServiceConfigurable>();
foreach (IServiceConfigurable configurable in service.Configurables)
{
tempConfigurables.Add(configurable.OptionName, configurable);
}
this.configurables = new ReadOnlyDictionary<string, IServiceConfigurable>(tempConfigurables);
}
/// <summary>
/// Starts this service.
/// </summary>
/// <exception cref="InvalidOperationException">Is thrown when the service is already running.</exception>
public void Start()
{
Logger.Log($"\"{ServiceName}\" is starting.");
if (state == ServiceState.Running) throw new InvalidOperationException("Service instance already running.");
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TIMEOUT);
Task initializationTask = Task.Run(() => service.InitializeService(), cancellationTokenSource.Token);
try {
initializationTask.Wait();
} catch (AggregateException a) {
a.Handle((e) => e is TaskCanceledException);
}
cancellationTokenSource.Dispose();
}
/// <summary>
/// Stops the service.
/// </summary>
/// <exception cref="InvalidOperationException">Is thrown when the is not running.</exception>
public void Stop()
{
if (state != ServiceState.Running) throw new InvalidOperationException("Service instance not running.");
Logger.Log($"\"{ServiceName}\" is stopping.");
service.ElegantShutdown();
}
/// <summary>
/// Sends a command to this service to execute.
/// </summary>
/// <param name="command">The command to execute.</param>
/// <exception cref="InvalidOperationException">Is thrown when the service is not running.</exception>
public void ExecuteCommand(string command)
{
Logger.Log($"\"{ServiceName}\" is executing command \"{command}\".", LogLevel.DEBUG);
if (state != ServiceState.Running) throw new InvalidOperationException("Service instance not running.");
service.ExecuteCommand(command);
}
/// <summary>
/// Gets the possible <see cref="IServiceConfigurable"/>'s names for this service.
/// </summary>
/// <returns>A <see cref="ISet{string}"/> returned where the string is the option's name.</returns>
public ISet<string> GetConfigurableOptions()
{
return new HashSet<string>(this.configurables.Keys);
}
public bool SetConfigurableValue(string configurationName, string value)
{
if (!this.configurables.ContainsKey(configurationName)) throw new KeyNotFoundException($"Unable to find option with name \"{configurationName}\".");
return this.configurables[configurationName].SetValue(value);
}
public string GetConfigurableValue(string configurationName)
{
if (!this.configurables.ContainsKey(configurationName)) throw new KeyNotFoundException($"Unable to find option with name \"{configurationName}\".");
return this.configurables[configurationName].GetValue();
}
public ServiceState GetServiceState()
{
return state;
}
public string GetServiceName()
{
return serviceName;
}
public string GetModuleName()
{
return moduleName;
}
/// <returns>The name of assembly this module is contained in.</returns>
public string GetAssemblyName()
{
return assemblyName;
}
public byte[] GetLogBuffer() {
if (state == ServiceState.Stopped) throw new InvalidOperationException("Cannot get log of service that is not running.");
return service.GetLogBuffer();
}
private void OnServiceStateChange(object sender, ServiceState state)
{
this.state = state;
Logger.Log($"The service \"{ServiceName}\" is changing states to {this.state}.", LogLevel.DEBUG);
ServiceStateChangeEvent?.Invoke(this, this.state);
}
private void OnLogUpdate(object sender, string log) {
LogUpdateEvent?.Invoke(this, log);
}
}
}

View File

@ -9,22 +9,25 @@ using System.Linq;
using GameServiceWarden.API.Games; using GameServiceWarden.API.Games;
using GameServiceWarden.Core.Persistence; using GameServiceWarden.Core.Persistence;
using GameServiceWarden.API.Module; using GameServiceWarden.API.Module;
using System.Text;
namespace GameServiceWarden.Core.Games namespace GameServiceWarden.Core.Module
{ {
public class ServiceManager : IServiceManagerActionExecuter public class ServiceManager : IServiceManagerActionExecuter
{ {
private IServiceManagerMonitor managerMonitor; private IServiceManagerMonitor managerMonitor;
private readonly ConcurrentDictionary<string, ServiceDescriptor> running; private readonly ConcurrentDictionary<string, ServiceDescriptor> running;
private readonly IPersistent<ServiceDescriptor> services; private readonly IPersistent<IReadOnlyDictionary<string, string>> services;
private readonly LRUCache<string, ServiceDescriptor> descriptorCache;
private readonly IReadOnlyPersistent<IReadOnlyDictionary<string, IServiceModule>> modules; private readonly IReadOnlyPersistent<IReadOnlyDictionary<string, IServiceModule>> modules;
public ServiceManager(IServiceManagerMonitor actionMonitor, IPersistent<ServiceDescriptor> services, IReadOnlyPersistent<IReadOnlyDictionary<string, IServiceModule>> modules) public ServiceManager(IServiceManagerMonitor actionMonitor, IPersistent<IReadOnlyDictionary<string, string>> services, IReadOnlyPersistent<IReadOnlyDictionary<string, IServiceModule>> modules)
{ {
this.services = services ?? throw new ArgumentNullException("services"); this.services = services ?? throw new ArgumentNullException("services");
this.modules = modules ?? throw new ArgumentNullException("modules"); this.modules = modules ?? throw new ArgumentNullException("modules");
this.managerMonitor = actionMonitor ?? throw new ArgumentNullException("actionMonitor"); this.managerMonitor = actionMonitor ?? throw new ArgumentNullException("actionMonitor");
this.running = new ConcurrentDictionary<string, ServiceDescriptor>(); this.running = new ConcurrentDictionary<string, ServiceDescriptor>();
this.descriptorCache = new LRUCache<string, ServiceDescriptor>(100);
} }
public void CreateService(string serviceName, string assemblyName, string moduleName) public void CreateService(string serviceName, string assemblyName, string moduleName)
@ -32,8 +35,10 @@ namespace GameServiceWarden.Core.Games
if (!this.modules.ContainsKey(assemblyName)) throw new KeyNotFoundException($"No file \"{assemblyName}\" found."); if (!this.modules.ContainsKey(assemblyName)) throw new KeyNotFoundException($"No file \"{assemblyName}\" found.");
IReadOnlyDictionary<string, IServiceModule> assemblyModules = this.modules[assemblyName]; IReadOnlyDictionary<string, IServiceModule> assemblyModules = this.modules[assemblyName];
if (services.ContainsKey(serviceName)) throw new ArgumentException($"Service of Name \"{serviceName}\" already exists."); if (services.ContainsKey(serviceName)) throw new ArgumentException($"Service of Name \"{serviceName}\" already exists.");
Dictionary<string, string> data = new Dictionary<string, string>();
services.AddToPersistence(serviceName, new ServiceDescriptor(assemblyModules[moduleName].InstantiateService(services.GetPathForKey(serviceName), true), serviceName, moduleName, assemblyName)); data[ServiceDescriptor.ASSEMBLY_PROPERTY] = assemblyName;
data[ServiceDescriptor.MODULE_PROPERTY] = moduleName;
services.AddToPersistence(serviceName, data);
ServiceManagerState managerState = new ServiceManagerState(); ServiceManagerState managerState = new ServiceManagerState();
managerState.delta = true; managerState.delta = true;
managerState.subtract = false; managerState.subtract = false;
@ -45,7 +50,7 @@ namespace GameServiceWarden.Core.Games
public void DeleteService(string serviceName) public void DeleteService(string serviceName)
{ {
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found."); if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
if (services[serviceName].GetServiceState()) services[serviceName].Stop(); if (running.ContainsKey(serviceName)) running[serviceName].Stop();
services.Delete(serviceName); services.Delete(serviceName);
ServiceManagerState managerState = new ServiceManagerState(); ServiceManagerState managerState = new ServiceManagerState();
managerState.delta = true; managerState.delta = true;
@ -81,15 +86,18 @@ namespace GameServiceWarden.Core.Games
private IEnumerable<string> GetServiceOptions(string serviceName) private IEnumerable<string> GetServiceOptions(string serviceName)
{ {
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found."); if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
ServiceDescriptor serviceInfo = services[serviceName]; IReadOnlyDictionary<string, string> info = services[serviceName];
return serviceInfo.GetConfigurableOptions(); ServiceDescriptor service = descriptorCache.Use(serviceName, () => GenerateDescriptor(serviceName, info[ServiceDescriptor.ASSEMBLY_PROPERTY], info[ServiceDescriptor.MODULE_PROPERTY]));
return service.GetConfigurableOptions();
} }
private string GetServiceOptionValue(string serviceName, string optionName) private string GetServiceOptionValue(string serviceName, string optionName)
{ {
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found."); if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
if (!services[serviceName].GetConfigurableOptions().Contains(optionName)) throw new KeyNotFoundException($"Option \"{optionName}\" for service \"{serviceName}\" not found."); IReadOnlyDictionary<string, string> info = services[serviceName];
return services[serviceName].GetConfigurableValue(optionName); ServiceDescriptor service = descriptorCache.Use(serviceName, () => GenerateDescriptor(serviceName, info[ServiceDescriptor.ASSEMBLY_PROPERTY], info[ServiceDescriptor.MODULE_PROPERTY]));
if (!service.GetConfigurableOptions().Contains(optionName)) throw new KeyNotFoundException($"Option \"{optionName}\" for service \"{serviceName}\" not found.");
return service.GetConfigurableValue(optionName);
} }
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> GetOptions() { public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> GetOptions() {
@ -112,9 +120,11 @@ namespace GameServiceWarden.Core.Games
public bool SetServiceOptionValue(string serviceName, string optionName, string value) public bool SetServiceOptionValue(string serviceName, string optionName, string value)
{ {
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found."); if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
if (!services[serviceName].GetConfigurableOptions().Contains(optionName)) throw new KeyNotFoundException($"Option \"{optionName}\" for service \"{serviceName}\" not found."); IReadOnlyDictionary<string, string> info = services[serviceName];
ServiceDescriptor service = descriptorCache.Use(serviceName, () => GenerateDescriptor(serviceName, info[ServiceDescriptor.ASSEMBLY_PROPERTY], info[ServiceDescriptor.MODULE_PROPERTY]));
if (!service.GetConfigurableOptions().Contains(optionName)) throw new KeyNotFoundException($"Option \"{optionName}\" for service \"{serviceName}\" not found.");
ServiceManagerState managerState = new ServiceManagerState(); ServiceManagerState managerState = new ServiceManagerState();
if (services[serviceName].SetConfigurableValue(optionName, value)) { if (service.SetConfigurableValue(optionName, value)) {
managerState.delta = true; managerState.delta = true;
Dictionary<string, IReadOnlyDictionary<string, string>> changedOption = new Dictionary<string, IReadOnlyDictionary<string, string>>(); Dictionary<string, IReadOnlyDictionary<string, string>> changedOption = new Dictionary<string, IReadOnlyDictionary<string, string>>();
Dictionary<string, string> options = new Dictionary<string, string>(); Dictionary<string, string> options = new Dictionary<string, string>();
@ -130,9 +140,11 @@ namespace GameServiceWarden.Core.Games
{ {
if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found."); if (!services.ContainsKey(serviceName)) throw new KeyNotFoundException($"Service under name \"{serviceName}\" not found.");
if (running.ContainsKey(serviceName)) throw new InvalidOperationException($"Service under name \"{serviceName}\" is already running."); if (running.ContainsKey(serviceName)) throw new InvalidOperationException($"Service under name \"{serviceName}\" is already running.");
ServiceDescriptor info = services[serviceName]; IReadOnlyDictionary<string, string> info = services[serviceName];
info.ServiceStateChangeEvent += OnServiceStateChange; ServiceDescriptor service = descriptorCache.Use(serviceName, () => GenerateDescriptor(serviceName, info[ServiceDescriptor.ASSEMBLY_PROPERTY], info[ServiceDescriptor.MODULE_PROPERTY]));
info.Start(); service.ServiceStateChangeEvent += OnServiceStateChange;
service.LogUpdateEvent += OnLogUpdated;
service.Start();
} }
public void StopService(string serviceName) public void StopService(string serviceName)
@ -147,52 +159,55 @@ namespace GameServiceWarden.Core.Games
running[serviceName].ExecuteCommand(command); running[serviceName].ExecuteCommand(command);
} }
private string GetServiceLogPipeName(string serviceName) private byte[] GetServiceLogBuffer(string serviceName) {
{ if (!running.ContainsKey(serviceName)) throw new InvalidOperationException($"Service under name \"{serviceName}\" is not running and therefore, does not have a log.");
if (!running.ContainsKey(serviceName)) throw new InvalidOperationException($"Service under name \"{serviceName}\" is not running."); IReadOnlyDictionary<string, string> info = services[serviceName];
return running[serviceName].ServiceLogPipeName; ServiceDescriptor service = descriptorCache.Use(serviceName, () => GenerateDescriptor(serviceName, info[ServiceDescriptor.ASSEMBLY_PROPERTY], info[ServiceDescriptor.MODULE_PROPERTY]));
return service.GetLogBuffer();
} }
public IReadOnlyDictionary<string, string> GetLogPipeNames() { public Dictionary<string, byte[]> GetLogBuffer() {
ServiceManagerState managerState = new ServiceManagerState(); Dictionary<string, byte[]> logs = new Dictionary<string, byte[]>();
Dictionary<string, string> logPipeNames = new Dictionary<string, string>(); foreach (string service in running.Keys)
foreach (string service in GetRunningServiceNames())
{ {
logPipeNames.Add(service, GetServiceLogPipeName(service)); logs.Add(service, running[service].GetLogBuffer());
} }
managerState.logs = logPipeNames; ServiceManagerState managerState = new ServiceManagerState();
managerState.logs = logs;
managerMonitor.Present(managerState); managerMonitor.Present(managerState);
return logPipeNames; return logs;
} }
private void OnServiceStateChange(object sender, bool state) { private ServiceDescriptor GenerateDescriptor(string name, string assembly, string module) {
return new ServiceDescriptor(modules[assembly][module].InstantiateService(services.GetPathForKey(name)), name, module, assembly);
}
private void OnServiceStateChange(object sender, ServiceState state) {
ServiceDescriptor serviceInfo = (ServiceDescriptor)sender; ServiceDescriptor serviceInfo = (ServiceDescriptor)sender;
ServiceManagerState managerChange = new ServiceManagerState(); ServiceManagerState managerChange = new ServiceManagerState();
switch (state) switch (state)
{ {
case true: case ServiceState.Running:
if (running.TryAdd(serviceInfo.ServiceName, serviceInfo)) { if (running.TryAdd(serviceInfo.ServiceName, serviceInfo)) {
managerChange.delta = true; managerChange.delta = true;
managerChange.running = new List<string>(); managerChange.running = new List<string>();
managerChange.running.Add(serviceInfo.ServiceName); managerChange.running.Add(serviceInfo.ServiceName);
Dictionary<string, string> logAdded = new Dictionary<string, string>();
logAdded.Add(serviceInfo.ServiceName, GetServiceLogPipeName(serviceInfo.ServiceName));
managerChange.logs = logAdded;
} }
break; break;
case false: case ServiceState.Stopped:
ServiceDescriptor removed; ServiceDescriptor removed;
if (running.TryRemove(serviceInfo.ServiceName, out removed)) { if (running.TryRemove(serviceInfo.ServiceName, out removed)) {
removed.ServiceStateChangeEvent -= OnServiceStateChange; removed.ServiceStateChangeEvent -= OnServiceStateChange;
services[serviceInfo.ServiceName] = removed; removed.LogUpdateEvent -= OnLogUpdated;
Dictionary<string, string> removedInfo = new Dictionary<string, string>();
removedInfo[ServiceDescriptor.ASSEMBLY_PROPERTY] = removed.GetAssemblyName();
removedInfo[ServiceDescriptor.MODULE_PROPERTY] = removed.GetModuleName();
services[serviceInfo.ServiceName] = removedInfo;
managerChange.delta = true; managerChange.delta = true;
managerChange.subtract = true; managerChange.subtract = true;
managerChange.running = new List<string>(); managerChange.running = new List<string>();
managerChange.running.Add(serviceInfo.ServiceName); managerChange.running.Add(serviceInfo.ServiceName);
Dictionary<string, string> logRemoved = new Dictionary<string, string>();
logRemoved.Add(serviceInfo.ServiceName, null);
managerChange.logs = logRemoved;
} }
break; break;
} }
@ -200,6 +215,15 @@ namespace GameServiceWarden.Core.Games
managerMonitor.Present(managerChange); managerMonitor.Present(managerChange);
} }
void OnLogUpdated(object sender, string update) {
ServiceDescriptor service = (ServiceDescriptor)sender;
ServiceManagerState state = new ServiceManagerState();
state.delta = true;
Dictionary<string, string> logUpdate = new Dictionary<string, string>();
logUpdate[service.ServiceName] = update;
managerMonitor.Present(state);
}
public void ExecuteAction(ServiceManagerAction action) public void ExecuteAction(ServiceManagerAction action)
{ {
switch (action.action) switch (action.action)
@ -208,7 +232,7 @@ namespace GameServiceWarden.Core.Games
GetServiceNames(); GetServiceNames();
GetRunningServiceNames(); GetRunningServiceNames();
GetModuleNames(); GetModuleNames();
GetLogPipeNames(); GetLogBuffer();
GetOptions(); GetOptions();
break; break;

View File

@ -1,5 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using GameServiceWarden.Core.Games; using GameServiceWarden.Core.Module;
namespace GameServiceWarden.Core.Persistence namespace GameServiceWarden.Core.Persistence
{ {

View File

@ -3,39 +3,36 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using GameServiceWarden.Core.Games; using GameServiceWarden.Core.Module;
using GameServiceWarden.API.Module; using GameServiceWarden.API.Module;
using System.Collections.ObjectModel;
namespace GameServiceWarden.Core.Persistence namespace GameServiceWarden.Core.Persistence
{ {
public class ServiceDescriptorPersistence : IPersistent<ServiceDescriptor> public class ServiceDescriptorPersistence : IPersistent<IReadOnlyDictionary<string, string>>
{ {
private readonly IReadOnlyPersistent<IReadOnlyDictionary<string, IServiceModule>> modules;
private readonly string mapDirectory; private readonly string mapDirectory;
private const string ASSEMBLY_NAME = "Assembly Name";
private const string MODULE_NAME = "Module Name";
private const string EXTENSION = ".sin"; private const string EXTENSION = ".sin";
public ServiceDescriptorPersistence(string mapDirectory, IReadOnlyPersistent<IReadOnlyDictionary<string, IServiceModule>> modules) public ServiceDescriptorPersistence(string mapDirectory)
{ {
this.mapDirectory = mapDirectory; this.mapDirectory = mapDirectory;
this.modules = modules;
} }
public ServiceDescriptor this[string key] public IReadOnlyDictionary<string, string> this[string key]
{ {
set set
{ {
SaveService(key, value.GetAssemblyName(), value.GetModuleName()); SaveService(key, value[ServiceDescriptor.ASSEMBLY_PROPERTY], value[ServiceDescriptor.MODULE_PROPERTY]);
} }
get get
{ {
if (!ContainsKey(key)) throw new KeyNotFoundException(); if (!ContainsKey(key)) throw new KeyNotFoundException();
string assemblyName = GetServiceInfoValue(key, ASSEMBLY_NAME); Dictionary<string, string> info = new Dictionary<string, string>();
string moduleName = GetServiceInfoValue(key, MODULE_NAME); info[ServiceDescriptor.ASSEMBLY_PROPERTY] = GetServiceInfoValue(key, ServiceDescriptor.ASSEMBLY_PROPERTY);
IService service = modules[assemblyName][moduleName].InstantiateService(GetPathForKey(key), false); info[ServiceDescriptor.MODULE_PROPERTY] = GetServiceInfoValue(key, ServiceDescriptor.MODULE_PROPERTY);
return new ServiceDescriptor(service, key, moduleName, assemblyName); return new ReadOnlyDictionary<string, string>(info);
} }
} }
@ -51,10 +48,10 @@ namespace GameServiceWarden.Core.Persistence
} }
} }
public IEnumerable<ServiceDescriptor> Values { public IEnumerable<IReadOnlyDictionary<string,string>> Values {
get { get {
IEnumerable<string> keys = Keys; IEnumerable<string> keys = Keys;
List<ServiceDescriptor> res = new List<ServiceDescriptor>(); List<IReadOnlyDictionary<string,string>> res = new List<IReadOnlyDictionary<string,string>>();
foreach (string key in keys) foreach (string key in keys)
{ {
res.Add(this[key]); res.Add(this[key]);
@ -75,7 +72,7 @@ namespace GameServiceWarden.Core.Persistence
} }
} }
public void AddToPersistence(string key, ServiceDescriptor value) public void AddToPersistence(string key, IReadOnlyDictionary<string,string> value)
{ {
if (key == null) throw new ArgumentNullException(); if (key == null) throw new ArgumentNullException();
if (ContainsKey(key)) throw new ArgumentException(); if (ContainsKey(key)) throw new ArgumentException();
@ -96,13 +93,13 @@ namespace GameServiceWarden.Core.Persistence
return Directory.Exists(GetPathForKey(key)); return Directory.Exists(GetPathForKey(key));
} }
public IEnumerator<KeyValuePair<string, ServiceDescriptor>> GetEnumerator() public IEnumerator<KeyValuePair<string, IReadOnlyDictionary<string,string>>> GetEnumerator()
{ {
IEnumerable<string> keys = Keys; IEnumerable<string> keys = Keys;
List<KeyValuePair<string, ServiceDescriptor>> result = new List<KeyValuePair<string, ServiceDescriptor>>(); List<KeyValuePair<string, IReadOnlyDictionary<string,string>>> result = new List<KeyValuePair<string, IReadOnlyDictionary<string,string>>>();
foreach (string key in keys) foreach (string key in keys)
{ {
result.Add(new KeyValuePair<string, ServiceDescriptor>(key, this[key])); result.Add(new KeyValuePair<string, IReadOnlyDictionary<string,string>>(key, this[key]));
} }
return result.GetEnumerator(); return result.GetEnumerator();
} }
@ -127,7 +124,7 @@ namespace GameServiceWarden.Core.Persistence
return true; return true;
} }
public bool TryLoadValue(string key, [MaybeNullWhen(false)] out ServiceDescriptor value) public bool TryLoadValue(string key, [MaybeNullWhen(false)] out IReadOnlyDictionary<string,string> value)
{ {
try { try {
value = this[key]; value = this[key];
@ -152,8 +149,8 @@ namespace GameServiceWarden.Core.Persistence
Directory.CreateDirectory(serviceInfoPath); Directory.CreateDirectory(serviceInfoPath);
using (StreamWriter writer = File.CreateText(Path.Combine(serviceInfoPath, key + EXTENSION))) using (StreamWriter writer = File.CreateText(Path.Combine(serviceInfoPath, key + EXTENSION)))
{ {
writer.WriteLine($"{ASSEMBLY_NAME}: {assemblyName}"); writer.WriteLine($"{ServiceDescriptor.ASSEMBLY_PROPERTY}: {assemblyName}");
writer.WriteLine($"{MODULE_NAME}: {moduleName}"); writer.WriteLine($"{ServiceDescriptor.MODULE_PROPERTY}: {moduleName}");
} }
} }

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using GameServiceWarden.Core.Games.Modules; using GameServiceWarden.Core.Module;
using GameServiceWarden.API.Module; using GameServiceWarden.API.Module;
namespace GameServiceWarden.Core.Persistence namespace GameServiceWarden.Core.Persistence
@ -12,7 +12,7 @@ namespace GameServiceWarden.Core.Persistence
{ {
private readonly string mapDirectory; private readonly string mapDirectory;
private readonly ServiceModuleLoader loader = new ServiceModuleLoader(); private readonly ModuleLoader loader = new ModuleLoader();
public ServiceModules(string mapDirectory) public ServiceModules(string mapDirectory)
{ {

View File

@ -2,7 +2,7 @@ using System.Diagnostics;
using System.Text.Json; using System.Text.Json;
using GameServiceWarden.API.Communicable; using GameServiceWarden.API.Communicable;
using GameServiceWarden.API.Communicable.Requests; using GameServiceWarden.API.Communicable.Requests;
using GameServiceWarden.Core.Games; using GameServiceWarden.Core.Module;
using GameServiceWarden.Core.Logging; using GameServiceWarden.Core.Logging;
namespace GameServiceWarden.Core.UI namespace GameServiceWarden.Core.UI

View File

@ -2,7 +2,7 @@ using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using GameServiceWarden.API.Communicable; using GameServiceWarden.API.Communicable;
using GameServiceWarden.API.Games; using GameServiceWarden.API.Games;
using GameServiceWarden.Core.Games; using GameServiceWarden.Core.Module;
namespace GameServiceWarden.Core.UI namespace GameServiceWarden.Core.UI
{ {

View File

@ -2,7 +2,7 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using GameServiceWarden.Core.Games; using GameServiceWarden.Core.Module;
using GameServiceWarden.Core.Persistence; using GameServiceWarden.Core.Persistence;
namespace GameServiceWarden.Core.Tests.Modules namespace GameServiceWarden.Core.Tests.Modules

View File

@ -10,9 +10,11 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
{ {
public IReadOnlyCollection<IServiceConfigurable> Configurables { get; set; } public IReadOnlyCollection<IServiceConfigurable> Configurables { get; set; }
public event EventHandler<bool> StateChangeEvent; public event EventHandler<ServiceState> StateChangeEvent;
public ServiceState CurrentState { get; private set; } = ServiceState.Stopped; public event EventHandler<string> UpdateLogEvent;
public ServiceState CurrentState { get; private set; } = ServiceState.Stopped;
private MemoryStream memoryStream;
private StreamWriter consoleWriter; private StreamWriter consoleWriter;
private Stack<Task> taskStack = new Stack<Task>(); private Stack<Task> taskStack = new Stack<Task>();
@ -29,7 +31,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
public void ElegantShutdown() public void ElegantShutdown()
{ {
CurrentState = ServiceState.Stopped; CurrentState = ServiceState.Stopped;
StateChangeEvent?.Invoke(this, false); StateChangeEvent?.Invoke(this, ServiceState.Stopped);
Task task; Task task;
while(taskStack.TryPop(out task)) { while(taskStack.TryPop(out task)) {
if (task.IsCompleted) { if (task.IsCompleted) {
@ -44,13 +46,20 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
{ {
taskStack.Push(consoleWriter.WriteLineAsync(command)); taskStack.Push(consoleWriter.WriteLineAsync(command));
taskStack.Push(consoleWriter.FlushAsync()); taskStack.Push(consoleWriter.FlushAsync());
UpdateLogEvent?.Invoke(this, command);
} }
public void InitializeService(Stream stream) public void InitializeService()
{ {
CurrentState = ServiceState.Running; CurrentState = ServiceState.Running;
this.consoleWriter = new StreamWriter(stream); memoryStream = new MemoryStream();
StateChangeEvent?.Invoke(this, true); this.consoleWriter = new StreamWriter(memoryStream);
StateChangeEvent?.Invoke(this, ServiceState.Running);
}
public byte[] GetLogBuffer()
{
return memoryStream.ToArray();
} }
} }
} }

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using GameServiceWarden.API.Games; using GameServiceWarden.API.Games;
using GameServiceWarden.Core.Games; using GameServiceWarden.Core.Module;
namespace GameServiceWarden.Core.Tests.Modules.Games namespace GameServiceWarden.Core.Tests.Modules.Games
{ {

View File

@ -17,7 +17,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
public IEnumerable<string> Authors { get; private set; } = new string[] { "FakeAuthor", "FakeAuthor2" }; public IEnumerable<string> Authors { get; private set; } = new string[] { "FakeAuthor", "FakeAuthor2" };
public IService InstantiateService(string workspace, bool clean) public IService InstantiateService(string workspace)
{ {
return new FakeService(configurables); return new FakeService(configurables);
} }

View File

@ -1,10 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using GameServiceWarden.Core.Games; using GameServiceWarden.Core.Module;
using GameServiceWarden.Core.Logging; using GameServiceWarden.Core.Logging;
using GameServiceWarden.API.Module; using GameServiceWarden.API.Module;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
using System.Text;
namespace GameServiceWarden.Core.Tests.Modules.Games namespace GameServiceWarden.Core.Tests.Modules.Games
{ {
@ -34,7 +35,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
IService stubService = new FakeService(); IService stubService = new FakeService();
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly"); ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly");
serviceInfo.Start(); serviceInfo.Start();
Assert.True(serviceInfo.GetServiceState()); Assert.Equal(ServiceState.Running, serviceInfo.GetServiceState());
serviceInfo.Stop(); serviceInfo.Stop();
} }
@ -46,7 +47,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly"); ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly");
serviceInfo.Start(); serviceInfo.Start();
serviceInfo.Stop(); serviceInfo.Stop();
Assert.False(serviceInfo.GetServiceState()); Assert.Equal(ServiceState.Stopped, serviceInfo.GetServiceState());
} }
[Fact] [Fact]
@ -89,7 +90,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
IService stubService = new FakeService(); IService stubService = new FakeService();
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly"); ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly");
//Then //Then
Assert.False(serviceInfo.GetServiceState()); Assert.Equal(ServiceState.Stopped, serviceInfo.GetServiceState());
} }
[Fact] [Fact]
@ -102,7 +103,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
//When //When
serviceInfo.Start(); serviceInfo.Start();
//Then //Then
Assert.True(serviceInfo.GetServiceState()); Assert.Equal(ServiceState.Running, serviceInfo.GetServiceState());
serviceInfo.Stop(); serviceInfo.Stop();
} }
@ -143,28 +144,24 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
} }
[Fact] [Fact]
public void ServiceLogPipeName_ServiceNotStarted_PipeNameReturned() public void GetLogBuffer_CommandWritten_CommandLogged()
{ {
//Given //Given
const string SERVICE_NAME = "ServiceLogPipeName_ServiceNotStarted_PipeNameReturned"; const string CMD = "hello";
const string SERVICE_NAME = "GetLogBuffer_CommandWritten_CommandLogged";
IService stubService = new FakeService(); IService stubService = new FakeService();
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly"); ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly");
//Then
Assert.NotNull(serviceInfo.ServiceLogPipeName);
}
[Fact]
public void ServiceLogPipeName_ServiceStarted_StreamReturned()
{
//Given
const string SERVICE_NAME = "ServiceLogPipeName_ServiceStarted_StreamReturned";
IService stubService = new FakeService();
ServiceDescriptor serviceInfo = new ServiceDescriptor(stubService, SERVICE_NAME, "FakeModule", "FakeAssembly");
//When
serviceInfo.Start(); serviceInfo.Start();
//When
serviceInfo.ExecuteCommand(CMD);
//Then //Then
Assert.NotNull(serviceInfo.ServiceLogPipeName); using (MemoryStream mem = new MemoryStream(serviceInfo.GetLogBuffer()))
serviceInfo.Stop(); {
using (StreamReader reader = new StreamReader(mem))
{
Assert.Equal(CMD, reader.ReadLine());
}
}
} }
} }
} }

View File

@ -4,11 +4,12 @@ using System.IO;
using System.IO.Pipes; using System.IO.Pipes;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using GameServiceWarden.Core.Games; using GameServiceWarden.Core.Module;
using GameServiceWarden.Core.Logging; using GameServiceWarden.Core.Logging;
using GameServiceWarden.API.Module; using GameServiceWarden.API.Module;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
using System.Text;
[assembly: CollectionBehavior(DisableTestParallelization = true)] [assembly: CollectionBehavior(DisableTestParallelization = true)]
namespace GameServiceWarden.Core.Tests.Modules.Games namespace GameServiceWarden.Core.Tests.Modules.Games
@ -28,7 +29,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
const string ASSEMBLY_NAME = "FakeAssembly"; const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "CreateService_NewManager_NewServiceCreated"; const string FAKE_SERVICE_NAME = "CreateService_NewManager_NewServiceCreated";
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>(); FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
FakePersistence<ServiceDescriptor> stubPersistentServiceDictionary = new FakePersistence<ServiceDescriptor>(); FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor(); FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary); ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>(); Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
@ -48,7 +49,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
const string ASSEMBLY_NAME = "FakeAssembly"; const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "CreateService_OneService_ServiceDeleted"; const string FAKE_SERVICE_NAME = "CreateService_OneService_ServiceDeleted";
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>(); FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
FakePersistence<ServiceDescriptor> stubPersistentServiceDictionary = new FakePersistence<ServiceDescriptor>(); FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor(); FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary); ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>(); Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
@ -71,7 +72,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
const string ASSEMBLY_NAME = "FakeAssembly"; const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_PREFIX = "GetServiceNames_MultipleServices_AllCorrectNames_"; const string FAKE_SERVICE_PREFIX = "GetServiceNames_MultipleServices_AllCorrectNames_";
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>(); FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
FakePersistence<ServiceDescriptor> stubPersistentServiceDictionary = new FakePersistence<ServiceDescriptor>(); FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor(); FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary); ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>(); Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
@ -97,7 +98,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
const string ASSEMBLY_NAME = "FakeAssembly"; const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "GetServiceOptions_ThreeOptionService_CorrectOptions"; const string FAKE_SERVICE_NAME = "GetServiceOptions_ThreeOptionService_CorrectOptions";
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>(); FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
FakePersistence<ServiceDescriptor> stubPersistentServiceDictionary = new FakePersistence<ServiceDescriptor>(); FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor(); FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary); ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>(); Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
@ -122,7 +123,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
const string ASSEMBLY_NAME = "FakeAssembly"; const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "SetandGetServiceOptionValue_OneOption_OptionChanged"; const string FAKE_SERVICE_NAME = "SetandGetServiceOptionValue_OneOption_OptionChanged";
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>(); FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
FakePersistence<ServiceDescriptor> stubPersistentServiceDictionary = new FakePersistence<ServiceDescriptor>(); FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor(); FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary); ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>(); Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
@ -145,7 +146,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
const string ASSEMBLY_NAME = "FakeAssembly"; const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "GetServiceState_NotRunning_ReturnsNotRunningState"; const string FAKE_SERVICE_NAME = "GetServiceState_NotRunning_ReturnsNotRunningState";
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>(); FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
FakePersistence<ServiceDescriptor> stubPersistentServiceDictionary = new FakePersistence<ServiceDescriptor>(); FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor(); FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary); ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>(); Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
@ -165,7 +166,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
const string ASSEMBLY_NAME = "FakeAssembly"; const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "StartService_NotStarted_SuccessfulStart"; const string FAKE_SERVICE_NAME = "StartService_NotStarted_SuccessfulStart";
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>(); FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
FakePersistence<ServiceDescriptor> stubPersistentServiceDictionary = new FakePersistence<ServiceDescriptor>(); FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor(); FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary); ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>(); Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
@ -187,7 +188,7 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
const string ASSEMBLY_NAME = "FakeAssembly"; const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "StopService_ServiceStartedThenStopped_StateUpdated"; const string FAKE_SERVICE_NAME = "StopService_ServiceStartedThenStopped_StateUpdated";
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>(); FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
FakePersistence<ServiceDescriptor> stubPersistentServiceDictionary = new FakePersistence<ServiceDescriptor>(); FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor(); FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary); ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>(); Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
@ -203,14 +204,14 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
} }
[Fact] [Fact]
public void ExecuteCommand_CommandExecutedBeforeConnected_CommandLogged() public void ExecuteCommand_ServiceStarted_CommandLogged()
{ {
//Given //Given
const string ASSEMBLY_NAME = "FakeAssembly"; const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "ExecuteCommand_CommandExecutedBeforeConnected_CommandLogged"; const string FAKE_SERVICE_NAME = "ExecuteCommand_ServiceStarted_CommandLogged";
const string COMMAND = "TEST"; const string COMMAND = "TEST";
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>(); FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
FakePersistence<ServiceDescriptor> stubPersistentServiceDictionary = new FakePersistence<ServiceDescriptor>(); FakePersistence<IReadOnlyDictionary<string, string>> stubPersistentServiceDictionary = new FakePersistence<IReadOnlyDictionary<string, string>>();
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor(); FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary); ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>(); Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
@ -220,116 +221,16 @@ namespace GameServiceWarden.Core.Tests.Modules.Games
//When //When
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name); serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name);
serviceManager.StartService(FAKE_SERVICE_NAME); serviceManager.StartService(FAKE_SERVICE_NAME);
string pipeName = serviceManager.GetLogPipeNames()[FAKE_SERVICE_NAME];
NamedPipeClientStream clientStream = new NamedPipeClientStream(".", pipeName, PipeDirection.In);
serviceManager.ExecuteCommand(FAKE_SERVICE_NAME, COMMAND);
clientStream.Connect(1000);
Thread.Sleep(1000);
//Then
byte[] buffer = new byte[1024 * 8];
CancellationTokenSource cancelToken = new CancellationTokenSource(2000);
ValueTask<int> task = clientStream.ReadAsync(buffer, cancelToken.Token);
Assert.False(task.AsTask().Wait(1000));
serviceManager.StopService(FAKE_SERVICE_NAME);
cancelToken.Dispose();
}
[Fact]
public void ExecuteCommand_CommandExecutedAfterConnected_CommandLogged()
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "ExecuteCommand_CommandExecutedAfterConnected_CommandLogged";
const string COMMAND = "TEST";
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
FakePersistence<ServiceDescriptor> stubPersistentServiceDictionary = new FakePersistence<ServiceDescriptor>();
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
IServiceModule stubServiceModule = new FakeServiceModule();
stubAssemblyModulesDictionary.Add(stubServiceModule.Name, stubServiceModule);
stubPersistentModuleDictionary.AddToPersistence(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name);
serviceManager.StartService(FAKE_SERVICE_NAME);
string pipeName = serviceManager.GetLogPipeNames()[FAKE_SERVICE_NAME];
NamedPipeClientStream clientStream = new NamedPipeClientStream(".", pipeName, PipeDirection.In);
clientStream.Connect(1000);
Thread.Sleep(1000);
serviceManager.ExecuteCommand(FAKE_SERVICE_NAME, COMMAND); serviceManager.ExecuteCommand(FAKE_SERVICE_NAME, COMMAND);
//Then //Then
using (StreamReader reader = new StreamReader(clientStream)) using (MemoryStream mem = new MemoryStream(serviceManager.GetLogBuffer()[FAKE_SERVICE_NAME]))
{ {
CancellationTokenSource cancelToken = new CancellationTokenSource(2000); using (StreamReader reader = new StreamReader(mem))
string message = null;
Task task = Task.Run(() => message = reader.ReadLine(), cancelToken.Token);
Assert.True(task.Wait(1000));
Assert.True(COMMAND.Equals(message), $"Received message \"{message}\" when expecting \"{COMMAND}\"");
cancelToken.Dispose();
}
serviceManager.StopService(FAKE_SERVICE_NAME);
}
[Fact]
public void ExecuteCommand_CommandExecutedAfterMultipleLogListenersConnected_CommandLogged()
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "ExecuteCommand_CommandExecutedAfterMultipleLogListenersConnected_CommandLogged";
const string COMMAND = "TEST";
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
FakePersistence<ServiceDescriptor> stubPersistentServiceDictionary = new FakePersistence<ServiceDescriptor>();
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
IServiceModule stubServiceModule = new FakeServiceModule();
stubAssemblyModulesDictionary.Add(stubServiceModule.Name, stubServiceModule);
stubPersistentModuleDictionary.AddToPersistence(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name);
serviceManager.StartService(FAKE_SERVICE_NAME);
string pipeName = serviceManager.GetLogPipeNames()[FAKE_SERVICE_NAME];
NamedPipeClientStream[] clientStreams = new NamedPipeClientStream[5];
for (int i = 0; i < clientStreams.Length; i++)
{
clientStreams[i] = new NamedPipeClientStream(".", pipeName, PipeDirection.In);
clientStreams[i].Connect(1000);
}
Thread.Sleep(1000);
serviceManager.ExecuteCommand(FAKE_SERVICE_NAME, COMMAND);
//Then
for (int i = 0; i < clientStreams.Length; i++)
{
using (StreamReader reader = new StreamReader(clientStreams[i]))
{ {
string message = null; Assert.Equal(COMMAND, reader.ReadLine());
Task clientTask = Task.Run(() => message = reader.ReadLine());
Assert.True(clientTask.Wait(1000));
Assert.True(COMMAND.Equals(message), $"Received message \"{message}\" when expecting \"{COMMAND}\"");
} }
} }
Task task = Task.Run(() => serviceManager.StopService(FAKE_SERVICE_NAME)); serviceManager.StopService(FAKE_SERVICE_NAME);
Assert.True(task.Wait(5000)); //TODO FIX WHY THIS IS HAPPENING!!!!!
}
[Fact]
public void GetServiceConsoleStream_ServiceStopped_ExceptionThrown()
{
//Given
const string ASSEMBLY_NAME = "FakeAssembly";
const string FAKE_SERVICE_NAME = "GetServiceConsoleStream_ServiceStopped_ExceptionThrown";
FakePersistence<IReadOnlyDictionary<string, IServiceModule>> stubPersistentModuleDictionary = new FakePersistence<IReadOnlyDictionary<string, IServiceModule>>();
FakePersistence<ServiceDescriptor> stubPersistentServiceDictionary = new FakePersistence<ServiceDescriptor>();
FakeServiceManagerMonitor stubMonitor = new FakeServiceManagerMonitor();
ServiceManager serviceManager = new ServiceManager(stubMonitor, stubPersistentServiceDictionary, stubPersistentModuleDictionary);
Dictionary<string, IServiceModule> stubAssemblyModulesDictionary = new Dictionary<string, IServiceModule>();
IServiceModule stubServiceModule = new FakeServiceModule();
stubAssemblyModulesDictionary.Add(stubServiceModule.Name, stubServiceModule);
stubPersistentModuleDictionary.AddToPersistence(ASSEMBLY_NAME, stubAssemblyModulesDictionary);
//When
serviceManager.CreateService(FAKE_SERVICE_NAME, ASSEMBLY_NAME, stubServiceModule.Name);
//Then
Assert.Throws<KeyNotFoundException>(() => serviceManager.GetLogPipeNames()[FAKE_SERVICE_NAME]);
} }
} }
} }

View File

@ -1,7 +1,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using GameServiceWarden.Core.Games; using GameServiceWarden.Core.Module;
using GameServiceWarden.Core.Persistence; using GameServiceWarden.Core.Persistence;
using GameServiceWarden.Core.Tests.Modules; using GameServiceWarden.Core.Tests.Modules;
using GameServiceWarden.Core.Tests.Modules.Games; using GameServiceWarden.Core.Tests.Modules.Games;
@ -28,7 +28,7 @@ namespace GameServiceWarden.Core.Tests.Persistence
stubAssemblyDict[MODULE_NAME] = stubServiceModule; stubAssemblyDict[MODULE_NAME] = stubServiceModule;
stubModulesPersistence[ASSEMBLY_NAME] = stubAssemblyDict; stubModulesPersistence[ASSEMBLY_NAME] = stubAssemblyDict;
ServiceDescriptorPersistence persistedServices = new ServiceDescriptorPersistence(TEST_DIR, stubModulesPersistence); ServiceDescriptorPersistence persistedServices = new ServiceDescriptorPersistence(TEST_DIR);
//Then //Then
Assert.True(persistedServices.GetPathForKey(SERVICE_NAME).Equals(Path.Combine(TEST_DIR, SERVICE_NAME))); Assert.True(persistedServices.GetPathForKey(SERVICE_NAME).Equals(Path.Combine(TEST_DIR, SERVICE_NAME)));
@ -49,11 +49,14 @@ namespace GameServiceWarden.Core.Tests.Persistence
stubAssemblyDict[MODULE_NAME] = stubServiceModule; stubAssemblyDict[MODULE_NAME] = stubServiceModule;
stubModulesPersistence[ASSEMBLY_NAME] = stubAssemblyDict; stubModulesPersistence[ASSEMBLY_NAME] = stubAssemblyDict;
ServiceDescriptorPersistence persistedServiceInfos = new ServiceDescriptorPersistence(TEST_DIR, stubModulesPersistence); ServiceDescriptorPersistence persistedServiceInfos = new ServiceDescriptorPersistence(TEST_DIR);
ServiceDescriptor stubServiceInfo = new ServiceDescriptor(stubModulesPersistence[ASSEMBLY_NAME][MODULE_NAME].InstantiateService(persistedServiceInfos.GetPathForKey(SERVICE_NAME), true), SERVICE_NAME, MODULE_NAME, ASSEMBLY_NAME); ServiceDescriptor stubServiceInfo = new ServiceDescriptor(stubModulesPersistence[ASSEMBLY_NAME][MODULE_NAME].InstantiateService(persistedServiceInfos.GetPathForKey(SERVICE_NAME)), SERVICE_NAME, MODULE_NAME, ASSEMBLY_NAME);
//When //When
persistedServiceInfos[SERVICE_NAME] = stubServiceInfo; Dictionary<string, string> info = new Dictionary<string, string>();
info[ServiceDescriptor.MODULE_PROPERTY] = MODULE_NAME;
info[ServiceDescriptor.ASSEMBLY_PROPERTY] = ASSEMBLY_NAME;
persistedServiceInfos[SERVICE_NAME] = info;
//Then //Then
Assert.True(Directory.Exists(TEST_DIR)); Assert.True(Directory.Exists(TEST_DIR));
Assert.True(Directory.Exists(persistedServiceInfos.GetPathForKey(SERVICE_NAME))); Assert.True(Directory.Exists(persistedServiceInfos.GetPathForKey(SERVICE_NAME)));
@ -79,15 +82,19 @@ namespace GameServiceWarden.Core.Tests.Persistence
stubAssemblyDict[MODULE_NAME] = stubServiceModule; stubAssemblyDict[MODULE_NAME] = stubServiceModule;
stubModulesPersistence[ASSEMBLY_NAME] = stubAssemblyDict; stubModulesPersistence[ASSEMBLY_NAME] = stubAssemblyDict;
ServiceDescriptorPersistence persistedServices = new ServiceDescriptorPersistence(TEST_DIR, stubModulesPersistence); ServiceDescriptorPersistence persistedServices = new ServiceDescriptorPersistence(TEST_DIR);
ServiceDescriptor stubServiceInfo = new ServiceDescriptor(stubModulesPersistence[ASSEMBLY_NAME][MODULE_NAME].InstantiateService(persistedServices.GetPathForKey(SERVICE_NAME), true), SERVICE_NAME, MODULE_NAME, ASSEMBLY_NAME); Dictionary<string, string> info = new Dictionary<string, string>();
persistedServices[SERVICE_NAME] = stubServiceInfo; info[ServiceDescriptor.MODULE_PROPERTY] = MODULE_NAME;
info[ServiceDescriptor.ASSEMBLY_PROPERTY] = ASSEMBLY_NAME;
persistedServices[SERVICE_NAME] = info;
//When //When
ServiceDescriptor loadedService = persistedServices[SERVICE_NAME]; IReadOnlyDictionary<string, string> loadedServiceInfo = persistedServices[SERVICE_NAME];
string loadedAssemblyName = loadedServiceInfo[ServiceDescriptor.ASSEMBLY_PROPERTY];
string loadedModuleName = loadedServiceInfo[ServiceDescriptor.MODULE_PROPERTY];
//Then //Then
Assert.True(loadedService.GetModuleName().Equals(MODULE_NAME)); Assert.True(loadedModuleName.Equals(MODULE_NAME));
Assert.True(loadedService.GetAssemblyName().Equals(ASSEMBLY_NAME)); Assert.True(loadedAssemblyName.Equals(ASSEMBLY_NAME));
Directory.Delete(TEST_DIR, true); Directory.Delete(TEST_DIR, true);
} }