Use a filewatcher on the config files (#11)

Add a filewatcher to the directory containing the config files
Try to automatically reload config files if they have changed and emit an event if successful
Add tests for updating config files
This commit is contained in:
Jonathan Tripp 2021-05-12 16:12:22 +01:00 коммит произвёл GitHub
Родитель 638e65f0a1
Коммит db25f6d1bd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 727 добавлений и 410 удалений

Просмотреть файл

@ -56,6 +56,11 @@
/// </summary>
NewConfigurationError,
/// <summary>
/// Configuration files have changed.
/// </summary>
NewConfigurationDetetected,
/// <summary>
/// Error in ping.
/// </summary>

Просмотреть файл

@ -6,17 +6,17 @@
using Microsoft.Extensions.Logging;
/// <summary>
/// Monitor a JSON file containing a list of AETConfigModels.
/// Monitor a JSON file or folder containing a list of <see cref="AETConfigModel"/>.
/// </summary>
public class AETConfigProvider : BaseConfigProvider<List<AETConfigModel>>
public class AETConfigProvider : BaseConfigProvider<IEnumerable<AETConfigModel>>
{
/// <summary>
/// File name for JSON file containing a list of AETConfigModels.
/// File name for JSON file containing a list of <see cref="AETConfigModel"/>.
/// </summary>
public static readonly string AETConfigFileName = "GatewayModelRulesConfig.json";
/// <summary>
/// Folder name for folder containing JSON files, each containing a list of AETConfigModels.
/// Folder name for folder containing JSON files, each containing a list of <see cref="AETConfigModel"/>.
/// </summary>
public static readonly string AETConfigFolderName = "GatewayModelRulesConfig";
@ -30,25 +30,18 @@
ILogger logger,
string configurationsPathRoot,
bool useFile = false) : base(logger,
Path.Combine(configurationsPathRoot, useFile ? AETConfigFileName : AETConfigFolderName))
Path.Combine(configurationsPathRoot, useFile ? string.Empty : AETConfigFolderName),
useFile ? AETConfigFileName : string.Empty,
MergeModels)
{
}
/// <summary>
/// Lookup list of AETConfigModels from a JSON file.
/// Helper to create a <see cref="Func{TResult}"/> for returning list of <see cref="AETConfigModel"/> from cache.
/// </summary>
/// <returns>List of AETConfigModels.</returns>
public IEnumerable<AETConfigModel> GetAETConfigs()
{
Load();
_t = _ts != null ? MergeModels(_ts) : _t;
// no need to keep two copies of all the config data.
_ts = null;
return _t;
}
/// <returns>Cached list of <see cref="AETConfigModel"/>.</returns>
public IEnumerable<AETConfigModel> AETConfigModels() =>
Config;
/// <summary>
/// Merge a list of lists of AET config models into one list.
@ -64,7 +57,7 @@
/// </remarks>
/// <param name="modelLists">List of lists of AET config models.</param>
/// <returns>List of AET config models.</returns>
private static List<AETConfigModel> MergeModels(IEnumerable<List<AETConfigModel>> modelLists)
private static List<AETConfigModel> MergeModels(IEnumerable<IEnumerable<AETConfigModel>> modelLists)
{
var mergedModels = new List<AETConfigModel>();

Просмотреть файл

@ -12,8 +12,50 @@
/// Class that monitors a JSON settings file or folder.
/// </summary>
/// <typeparam name="T">Data type underlying the JSON settings.</typeparam>
public class BaseConfigProvider<T>
public class BaseConfigProvider<T> : IDisposable
{
/// <summary>
/// Class to hold results from trying to open and parse a possible JSON file.
/// </summary>
private class LoadJsonResult
{
/// <summary>
/// True if the file has loaded, false otherwise.
/// </summary>
/// <remarks>
/// The FileSystemWatcher can report file changed events whilst another process is saving
/// a file. In this case there may be a System.IO.IOException (“File used by another process”).
/// </remarks>
public bool Loaded { get; }
/// <summary>
/// True if the file has been parsed, false otherwise.
/// </summary>
/// <remarks>
/// In the case of loading from a folder, all files are loaded and parsed. There may be other files
/// there and they are to be ignored.
/// </remarks>
public bool Parsed { get; }
/// <summary>
/// An instance of type T if the file has been loaded and parsed correctly. Default(T) otherwise.
/// </summary>
public T Result { get; }
/// <summary>
/// Initialize a new instance of the <see cref="LoadJsonResult"/> class.
/// </summary>
/// <param name="loaded">True if file loaded.</param>
/// <param name="parsed">True if file parsed.</param>
/// <param name="result">Instance of type T if loaded and parsed.</param>
public LoadJsonResult(bool loaded, bool parsed, T result)
{
Loaded = loaded;
Parsed = parsed;
Result = result;
}
}
/// <summary>
/// Logger for errors loading or parsing JSON.
/// </summary>
@ -25,60 +67,139 @@
private readonly string _settingsFileOrFolderName;
/// <summary>
/// Cached copy of data as last loaded from JSON file.
/// Optional flat map to handle folders.
/// </summary>
protected T _t;
private readonly Func<IEnumerable<T>, T> _flatMap;
/// <summary>
/// Cached copy of data as last loaded from folder of JSON files.
/// File system watcher to monitor changes to file or folder.
/// </summary>
protected IEnumerable<T> _ts;
private readonly FileSystemWatcher _fileSystemWatcher;
/// <summary>
/// Disposed flag for IDisposable.
/// </summary>
private bool disposedValue;
/// <summary>
/// Config as last loaded from file or folder.
/// </summary>
public T Config { get; private set; }
/// <summary>
/// Called when the config has changed.
/// </summary>
public event EventHandler ConfigChanged;
/// <summary>
/// Initialize a new instance of the <see cref="BaseConfigProvider"/> class.
/// </summary>
/// <param name="logger">Logger.</param>
/// <param name="settingsFileOrFolderName">JSON settings file or folder.</param>
/// <param name="folderName">Settings folder name.</param>
/// <param name="settingsFile">Optional settings file, use String.Empty to monitor a folder.</param>
/// <param name="flatMap">Optional flat map to handle folders. This should merge a T from each file in the folder
/// into a single new T. This is required if monitoring a folder.</param>
public BaseConfigProvider(
ILogger logger,
string settingsFileOrFolderName)
string folderName,
string settingsFile,
Func<IEnumerable<T>, T> flatMap = null)
{
_logger = logger;
_settingsFileOrFolderName = settingsFileOrFolderName;
_settingsFileOrFolderName = Path.Combine(folderName, settingsFile);
_flatMap = flatMap;
if (string.IsNullOrWhiteSpace(settingsFile) && flatMap == null)
{
throw new ArgumentNullException(nameof(flatMap), "If monitoring a folder, flatMap must be supplied");
}
_fileSystemWatcher = new FileSystemWatcher(folderName)
{
Filter = !string.IsNullOrWhiteSpace(settingsFile) ? settingsFile : "*.json",
NotifyFilter = NotifyFilters.LastWrite,
};
_fileSystemWatcher.Changed += OnChanged;
_fileSystemWatcher.EnableRaisingEvents = true;
Load();
}
/// <summary>
/// Load T or Ts from a JSON file or folder.
/// File watcher Changed event handler. Filter the events, reload the config and if successful invoke ConfigChanged.
/// </summary>
protected void Load()
/// <param name="sender">Sender.</param>
/// <param name="e">File system event args.</param>
private void OnChanged(object sender, FileSystemEventArgs e)
{
if (e.ChangeType != WatcherChangeTypes.Changed)
{
return;
}
var logEntry = LogEntry.Create(ServiceStatus.NewConfigurationDetetected,
string.Format("Settings have changed: {0}", e.FullPath));
logEntry.Log(_logger, LogLevel.Information);
if (!Load())
{
return;
}
ConfigChanged?.Invoke(this, new EventArgs());
}
/// <summary>
/// Load T from a JSON file or folder.
/// </summary>
/// <returns>True if new config has been loaded, false otherwise.</returns>
private bool Load()
{
if (File.Exists(_settingsFileOrFolderName))
{
_ts = null;
var loadJsonResult = LoadFile(_settingsFileOrFolderName);
(_t, _) = LoadFile(_settingsFileOrFolderName);
if (!loadJsonResult.Loaded || !loadJsonResult.Parsed)
{
return false;
}
Config = loadJsonResult.Result;
return true;
}
else if (Directory.Exists(_settingsFileOrFolderName))
{
_t = default(T);
var ts = new List<T>();
foreach (var file in Directory.EnumerateFiles(_settingsFileOrFolderName, "*.json"))
{
var (t, loaded) = LoadFile(file);
if (loaded)
var loadJsonResult = LoadFile(file);
if (!loadJsonResult.Loaded)
{
ts.Add(t);
// File still in use, FileWatcher has reported file changed but
// the other process has not finished yet.
return false;
}
if (loadJsonResult.Parsed)
{
ts.Add(loadJsonResult.Result);
}
}
_ts = ts.ToArray();
Config = _flatMap(ts);
return true;
}
else
{
var logEntry = LogEntry.Create(ServiceStatus.NewConfigurationError,
string.Format("Settings is neither a file nor a folder: {0}", _settingsFileOrFolderName));
logEntry.Log(_logger, LogLevel.Error);
return false;
}
}
@ -86,49 +207,62 @@
/// Update settings file, according to an update callback function.
/// </summary>
/// <param name="updater">Callback to update the settings. Return new settings for update, or the same object to not update.</param>
/// <param name="equalityComparer">How to compare objects.</param>
protected void UpdateFile(Func<T, T> updater, IEqualityComparer<T> equalityComparer)
/// <param name="equalityComparer">Optional, how to compare objects.</param>
public (T, bool) Update(Func<T, T> updater, IEqualityComparer<T> equalityComparer = null)
{
if (!File.Exists(_settingsFileOrFolderName))
{
throw new NotImplementedException(string.Format("Can only update single settings files: {0}", _settingsFileOrFolderName));
}
var (t, loaded) = LoadFile(_settingsFileOrFolderName);
if (!loaded)
var loadJsonResult = LoadFile(_settingsFileOrFolderName);
if (!loadJsonResult.Loaded || !loadJsonResult.Parsed)
{
return;
return (default(T), false);
}
var newt = updater.Invoke(t);
if (equalityComparer.Equals(newt, t))
var newt = updater(loadJsonResult.Result);
equalityComparer = equalityComparer ?? EqualityComparer<T>.Default;
if (equalityComparer.Equals(newt, loadJsonResult.Result))
{
return;
return (default(T), false);
}
SaveFile(newt, _settingsFileOrFolderName);
return (newt, true);
}
/// <summary>
/// Load T from a JSON file.
/// </summary>
/// <param name="path">Path to file.</param>
/// <returns>Pair of T and true if file loaded correctly, false otherwise.</returns>
private (T, bool) LoadFile(string path)
/// <returns>New <see cref="LoadJsonResult"/>.</returns>
private LoadJsonResult LoadFile(string path)
{
try
{
var jsonText = File.ReadAllText(path);
return (JsonConvert.DeserializeObject<T>(jsonText), true);
return new LoadJsonResult(true, true, JsonConvert.DeserializeObject<T>(jsonText));
}
catch (Exception e)
catch (JsonSerializationException e)
{
var logEntry = LogEntry.Create(ServiceStatus.NewConfigurationError,
string.Format("Unable to parse settings file {0}", path));
logEntry.Log(_logger, LogLevel.Error, e);
return new LoadJsonResult(true, false, default(T));
}
catch (IOException e)
{
var logEntry = LogEntry.Create(ServiceStatus.NewConfigurationError,
string.Format("Unable to load settings file {0}", path));
logEntry.Log(_logger, LogLevel.Error, e);
return (default(T), false);
return new LoadJsonResult(false, false, default(T));
}
}
@ -149,5 +283,33 @@
var jsonText = JsonConvert.SerializeObject(t, serializerSettings);
File.WriteAllText(path, jsonText);
}
/// <summary>
/// Disposes of all managed resources.
/// </summary>
/// <param name="disposing">If we are disposing.</param>
protected virtual void Dispose(bool disposing)
{
if (disposedValue)
{
return;
}
if (disposing)
{
_fileSystemWatcher.Dispose();
}
disposedValue = true;
}
/// <summary>
/// Implements the disposable pattern.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

Просмотреть файл

@ -1,20 +1,18 @@
namespace Microsoft.InnerEye.Listener.Common.Providers
{
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Logging;
using Microsoft.InnerEye.Azure.Segmentation.Client;
using Microsoft.InnerEye.Gateway.Logging;
using Microsoft.InnerEye.Gateway.Models;
/// <summary>
/// Monitor a JSON file containing a GatewayProcessorConfig.
/// Monitor a JSON file containing a <see cref="GatewayProcessorConfig"/>.
/// </summary>
public class GatewayProcessorConfigProvider : BaseConfigProvider<GatewayProcessorConfig>
{
/// <summary>
/// File name for JSON file containing a GatewayProcessorConfig.
/// File name for JSON file containing a <see cref="GatewayProcessorConfig"/>.
/// </summary>
public static readonly string GatewayProcessorConfigFileName = "GatewayProcessorConfig.json";
@ -26,27 +24,10 @@
public GatewayProcessorConfigProvider(
ILogger logger,
string configurationsPathRoot) : base(logger,
Path.Combine(configurationsPathRoot, GatewayProcessorConfigFileName))
configurationsPathRoot, GatewayProcessorConfigFileName)
{
}
/// <summary>
/// Load GatewayProcessorConfig from a JSON file.
/// </summary>
/// <returns>Loaded GatewayProcessorConfig.</returns>
public GatewayProcessorConfig GatewayProcessorConfig()
{
Load();
return _t;
}
/// <summary>
/// Update GatewayProcessorConfig file, according to an update callback function.
/// </summary>
/// <param name="updater">Callback to update the settings. Return new settings for update, or the same object to not update.</param>
public void Update(Func<GatewayProcessorConfig, GatewayProcessorConfig> updater) =>
UpdateFile(updater, EqualityComparer<GatewayProcessorConfig>.Default);
/// <summary>
/// Set ServiceSettings.RunAsConsole.
/// </summary>
@ -55,7 +36,7 @@
Update(gatewayProcessorConfig => gatewayProcessorConfig.With(new ServiceSettings(runAsConsole)));
/// <summary>
/// Update ProcessorSettings.
/// Update <see cref="ProcessorSettings"/>.
/// </summary>
/// <param name="inferenceUri">Optional new inference API Uri.</param>
/// <param name="licenseKey">Optional new license key.</param>
@ -78,45 +59,45 @@
}
/// <summary>
/// Load ServiceSettings from a JSON file.
/// Helper to create a <see cref="Func{TResult}"/> for returning <see cref="ServiceSettings"/> from cached <see cref="GatewayProcessorConfig"/>.
/// </summary>
/// <returns>Loaded ServiceSettings.</returns>
/// <returns>Cached <see cref="ServiceSettings"/>.</returns>
public ServiceSettings ServiceSettings() =>
GatewayProcessorConfig().ServiceSettings;
Config.ServiceSettings;
/// <summary>
/// Load ProcessorSettings from a JSON file.
/// Helper to create a <see cref="Func{TResult}"/> for returning <see cref="ProcessorSettings"/> from cached <see cref="GatewayProcessorConfig"/>.
/// </summary>
/// <returns>Loaded ProcessorSettings.</returns>
/// <returns>Cached <see cref="ProcessorSettings"/>.</returns>
public ProcessorSettings ProcessorSettings() =>
GatewayProcessorConfig().ProcessorSettings;
Config.ProcessorSettings;
/// <summary>
/// Load DequeueServiceConfig from a JSON file.
/// Helper to create a <see cref="Func{TResult}"/> for returning <see cref="DequeueServiceConfig"/> from cached <see cref="GatewayProcessorConfig"/>.
/// </summary>
/// <returns>Loaded DequeueServiceConfig.</returns>
/// <returns>Cached <see cref="DequeueServiceConfig"/>.</returns>
public DequeueServiceConfig DequeueServiceConfig() =>
GatewayProcessorConfig().DequeueServiceConfig;
Config.DequeueServiceConfig;
/// <summary>
/// Load DownloadServiceConfig from a JSON file.
/// Helper to create a <see cref="Func{TResult}"/> for returning <see cref="DownloadServiceConfig"/> from cached <see cref="GatewayProcessorConfig"/>.
/// </summary>
/// <returns>Loaded DownloadServiceConfig.</returns>
/// <returns>Cached <see cref="DownloadServiceConfig"/>.</returns>
public DownloadServiceConfig DownloadServiceConfig() =>
GatewayProcessorConfig().DownloadServiceConfig;
Config.DownloadServiceConfig;
/// <summary>
/// Load ConfigurationServiceConfig from a JSON file.
/// Helper to create a <see cref="Func{TResult}"/> for returning <see cref="ConfigurationServiceConfig"/> from cached <see cref="GatewayProcessorConfig"/>.
/// </summary>
/// <returns>Loaded ConfigurationServiceConfig.</returns>
/// <returns>Cached <see cref="ConfigurationServiceConfig"/>.</returns>
public ConfigurationServiceConfig ConfigurationServiceConfig() =>
GatewayProcessorConfig().ConfigurationServiceConfig;
Config.ConfigurationServiceConfig;
/// <summary>
/// Create a new segmentation client based on settings in JSON file.
/// Create a new <see cref="IInnerEyeSegmentationClient"/> based on settings in JSON file.
/// </summary>
/// <param name="logger">Optional logger for client.</param>
/// <returns>New IInnerEyeSegmentationClient.</returns>
/// <returns>New <see cref="IInnerEyeSegmentationClient"/>.</returns>
public Func<IInnerEyeSegmentationClient> CreateInnerEyeSegmentationClient(ILogger logger = null) =>
() =>
{

Просмотреть файл

@ -1,18 +1,15 @@
namespace Microsoft.InnerEye.Listener.Common.Providers
{
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Extensions.Logging;
using Microsoft.InnerEye.Gateway.Models;
/// <summary>
/// Monitor a JSON file containing a GatewayReceiveConfig.
/// Monitor a JSON file containing a <see cref="GatewayReceiveConfig"/>.
/// </summary>
public class GatewayReceiveConfigProvider : BaseConfigProvider<GatewayReceiveConfig>
{
/// <summary>
/// File name for JSON file containing a GatewayReceiveConfig.
/// File name for JSON file containing a <see cref="GatewayReceiveConfig"/>.
/// </summary>
public static readonly string GatewayReceiveConfigFileName = "GatewayReceiveConfig.json";
@ -24,27 +21,10 @@
public GatewayReceiveConfigProvider(
ILogger logger,
string configurationsPathRoot) : base(logger,
Path.Combine(configurationsPathRoot, GatewayReceiveConfigFileName))
configurationsPathRoot, GatewayReceiveConfigFileName)
{
}
/// <summary>
/// Load GatewayReceiveConfig from a JSON file.
/// </summary>
/// <returns>Loaded GatewayReceiveConfig.</returns>
public GatewayReceiveConfig GatewayReceiveConfig()
{
Load();
return _t;
}
/// <summary>
/// Update GatewayReceiveConfig file, according to an update callback function.
/// </summary>
/// <param name="updater">Callback to update the settings. Return new settings for update, or the same object to not update.</param>
public void Update(Func<GatewayReceiveConfig, GatewayReceiveConfig> updater) =>
UpdateFile(updater, EqualityComparer<GatewayReceiveConfig>.Default);
/// <summary>
/// Set ServiceSettings.RunAsConsole.
/// </summary>
@ -53,24 +33,24 @@
Update(gatewayReceiveConfig => gatewayReceiveConfig.With(new ServiceSettings(runAsConsole)));
/// <summary>
/// Load ServiceSettings from a JSON file.
/// Helper to create a <see cref="Func{TResult}"/> for returning <see cref="ServiceSettings"/> from cached <see cref="GatewayProcessorConfig"/>.
/// </summary>
/// <returns>Loaded ServiceSettings.</returns>
/// <returns>Cached <see cref="ServiceSettings"/>.</returns>
public ServiceSettings ServiceSettings() =>
GatewayReceiveConfig().ServiceSettings;
Config.ServiceSettings;
/// <summary>
/// Load ConfigurationServiceConfig from a JSON file.
/// Helper to create a <see cref="Func{TResult}"/> for returning <see cref="ConfigurationServiceConfig"/> from cached <see cref="GatewayProcessorConfig"/>.
/// </summary>
/// <returns>Loaded ConfigurationServiceConfig.</returns>
/// <returns>Cached <see cref="ConfigurationServiceConfig"/>.</returns>
public ConfigurationServiceConfig ConfigurationServiceConfig() =>
GatewayReceiveConfig().ConfigurationServiceConfig;
Config.ConfigurationServiceConfig;
/// <summary>
/// Load ReceiveServiceConfig from a JSON file.
/// Helper to create a <see cref="Func{TResult}"/> for returning <see cref="ReceiveServiceConfig"/> from cached <see cref="GatewayProcessorConfig"/>.
/// </summary>
/// <returns>Loaded ReceiveServiceConfig.</returns>
/// <returns>Cached <see cref="ReceiveServiceConfig"/>.</returns>
public ReceiveServiceConfig ReceiveServiceConfig() =>
GatewayReceiveConfig().ReceiveServiceConfig;
Config.ReceiveServiceConfig;
}
}

Просмотреть файл

@ -34,55 +34,55 @@
var configurationsPathRoot = ConfigurationService.FindRelativeDirectory(relativePaths, loggerFactory.CreateLogger("Main"));
var aetConfigurationProvider = new AETConfigProvider(
loggerFactory.CreateLogger("ModelSettings"),
configurationsPathRoot);
using (var aetConfigurationProvider = new AETConfigProvider(
loggerFactory.CreateLogger("ModelSettings"),
configurationsPathRoot))
using (var gatewayProcessorConfigProvider = new GatewayProcessorConfigProvider(
loggerFactory.CreateLogger("ProcessorSettings"),
configurationsPathRoot))
{
var segmentationClientLogger = loggerFactory.CreateLogger("SegmentationClient");
var gatewayProcessorConfigProvider = new GatewayProcessorConfigProvider(
loggerFactory.CreateLogger("ProcessorSettings"),
configurationsPathRoot);
var segmentationClientLogger = loggerFactory.CreateLogger("SegmentationClient");
// The ProjectInstaller.cs uses the service name to install the service.
// If you change it please update the ProjectInstaller.cs
ServiceHelpers.RunServices(
ServiceName,
gatewayProcessorConfigProvider.ServiceSettings(),
new ConfigurationService(
gatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(segmentationClientLogger),
gatewayProcessorConfigProvider.ConfigurationServiceConfig,
loggerFactory.CreateLogger("ConfigurationService"),
new UploadService(
// The ProjectInstaller.cs uses the service name to install the service.
// If you change it please update the ProjectInstaller.cs
ServiceHelpers.RunServices(
ServiceName,
gatewayProcessorConfigProvider.ServiceSettings(),
new ConfigurationService(
gatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(segmentationClientLogger),
aetConfigurationProvider.GetAETConfigs,
GatewayMessageQueue.UploadQueuePath,
GatewayMessageQueue.DownloadQueuePath,
GatewayMessageQueue.DeleteQueuePath,
gatewayProcessorConfigProvider.DequeueServiceConfig,
loggerFactory.CreateLogger("UploadService"),
instances: 2),
new DownloadService(
gatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(segmentationClientLogger),
GatewayMessageQueue.DownloadQueuePath,
GatewayMessageQueue.PushQueuePath,
GatewayMessageQueue.DeleteQueuePath,
gatewayProcessorConfigProvider.DownloadServiceConfig,
gatewayProcessorConfigProvider.DequeueServiceConfig,
loggerFactory.CreateLogger("DownloadService"),
instances: 1),
new PushService(
aetConfigurationProvider.GetAETConfigs,
new DicomDataSender(),
GatewayMessageQueue.PushQueuePath,
GatewayMessageQueue.DeleteQueuePath,
gatewayProcessorConfigProvider.DequeueServiceConfig,
loggerFactory.CreateLogger("PushService"),
instances: 1),
new DeleteService(
GatewayMessageQueue.DeleteQueuePath,
gatewayProcessorConfigProvider.DequeueServiceConfig,
loggerFactory.CreateLogger("DeleteService"))));
gatewayProcessorConfigProvider.ConfigurationServiceConfig,
loggerFactory.CreateLogger("ConfigurationService"),
new UploadService(
gatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(segmentationClientLogger),
aetConfigurationProvider.AETConfigModels,
GatewayMessageQueue.UploadQueuePath,
GatewayMessageQueue.DownloadQueuePath,
GatewayMessageQueue.DeleteQueuePath,
gatewayProcessorConfigProvider.DequeueServiceConfig,
loggerFactory.CreateLogger("UploadService"),
instances: 2),
new DownloadService(
gatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(segmentationClientLogger),
GatewayMessageQueue.DownloadQueuePath,
GatewayMessageQueue.PushQueuePath,
GatewayMessageQueue.DeleteQueuePath,
gatewayProcessorConfigProvider.DownloadServiceConfig,
gatewayProcessorConfigProvider.DequeueServiceConfig,
loggerFactory.CreateLogger("DownloadService"),
instances: 1),
new PushService(
aetConfigurationProvider.AETConfigModels,
new DicomDataSender(),
GatewayMessageQueue.PushQueuePath,
GatewayMessageQueue.DeleteQueuePath,
gatewayProcessorConfigProvider.DequeueServiceConfig,
loggerFactory.CreateLogger("PushService"),
instances: 1),
new DeleteService(
GatewayMessageQueue.DeleteQueuePath,
gatewayProcessorConfigProvider.DequeueServiceConfig,
loggerFactory.CreateLogger("DeleteService"))));
}
}
}
}

Просмотреть файл

@ -33,23 +33,24 @@
var configurationsPathRoot = ConfigurationService.FindRelativeDirectory(relativePaths, loggerFactory.CreateLogger("Main"));
var gatewayReceiveConfigProvider = new GatewayReceiveConfigProvider(
using (var gatewayReceiveConfigProvider = new GatewayReceiveConfigProvider(
loggerFactory.CreateLogger("ProcessorSettings"),
configurationsPathRoot);
// The ProjectInstaller.cs uses the service name to install the service.
// If you change it please update the ProjectInstaller.cs
ServiceHelpers.RunServices(
ServiceName,
gatewayReceiveConfigProvider.ServiceSettings(),
new ConfigurationService(
null,
gatewayReceiveConfigProvider.ConfigurationServiceConfig,
loggerFactory.CreateLogger("ConfigurationService"),
new ReceiveService(
gatewayReceiveConfigProvider.ReceiveServiceConfig,
GatewayMessageQueue.UploadQueuePath,
loggerFactory.CreateLogger("ReceiveService"))));
configurationsPathRoot))
{
// The ProjectInstaller.cs uses the service name to install the service.
// If you change it please update the ProjectInstaller.cs
ServiceHelpers.RunServices(
ServiceName,
gatewayReceiveConfigProvider.ServiceSettings(),
new ConfigurationService(
null,
gatewayReceiveConfigProvider.ConfigurationServiceConfig,
loggerFactory.CreateLogger("ConfigurationService"),
new ReceiveService(
gatewayReceiveConfigProvider.ReceiveServiceConfig,
GatewayMessageQueue.UploadQueuePath,
loggerFactory.CreateLogger("ReceiveService"))));
}
}
}
}

Просмотреть файл

@ -34,7 +34,7 @@
/// The base test class.
/// </summary>
[TestClass]
public class BaseTestClass
public class BaseTestClass : IDisposable
{
/// <summary>
/// List of chars to use for random string generation.
@ -44,12 +44,12 @@
/// <summary>
/// LoggerFactory for creating more ILoggers.
/// </summary>
protected readonly Microsoft.Extensions.Logging.ILoggerFactory _loggerFactory;
private readonly Microsoft.Extensions.Logging.ILoggerFactory _loggerFactory;
/// <summary>
/// Logger for common use.
/// </summary>
protected readonly ILogger _baseTestLogger;
private readonly ILogger _baseTestLogger;
/// <summary>
/// Gets or sets the test context.
@ -110,7 +110,12 @@
/// <summary>
/// GatewayReceiveConfigProvider as loaded from _basePathConfigs.
/// </summary>
private GatewayReceiveConfigProvider _testGatewayReceiveConfigProvider;
protected GatewayReceiveConfigProvider TestGatewayReceiveConfigProvider { get; }
/// <summary>
/// Disposed flag for IDisposable.
/// </summary>
private bool disposedValue;
/// <summary>
/// Initializes a new instance of the <see cref="BaseTestClass"/> class.
@ -125,9 +130,9 @@
// Set a logger for fo-dicom network operations so that they show up in VS output when debugging
Dicom.Log.LogManager.SetImplementation(new Dicom.Log.TextWriterLogManager(new DataProviderTests.DebugTextWriter()));
_testAETConfigProvider = new AETConfigProvider(_loggerFactory.CreateLogger("ModelSettings"), _basePathConfigs);
TestGatewayProcessorConfigProvider = new GatewayProcessorConfigProvider(_loggerFactory.CreateLogger("ProcessorSettings"), _basePathConfigs);
_testGatewayReceiveConfigProvider = new GatewayReceiveConfigProvider(_loggerFactory.CreateLogger("ProcessorSettings"), _basePathConfigs);
_testAETConfigProvider = CreateAETConfigProvider(_basePathConfigs);
TestGatewayProcessorConfigProvider = CreateGatewayProcessorConfigProvider(_basePathConfigs);
TestGatewayReceiveConfigProvider = CreateGatewayReceiveConfigProvider(_basePathConfigs);
}
[TestInitialize]
@ -170,6 +175,35 @@
TryKillAnyZombieProcesses();
}
/// <summary>
/// Create a new <see cref="AETConfigProvider"/> based on the given folder.
/// </summary>
/// <param name="configurationsPathRoot">Path to folder containing config folder.</param>
/// <param name="useFile">True to use config file, false to use folder of files.</param>
/// <returns>New <see cref="AETConfigProvider"/>.</returns>
protected AETConfigProvider CreateAETConfigProvider(
string configurationsPathRoot,
bool useFile = false) =>
new AETConfigProvider(_loggerFactory.CreateLogger("ModelSettings"), configurationsPathRoot, useFile);
/// <summary>
/// Create a new <see cref="GatewayProcessorConfigProvider"/> based on the given folder.
/// </summary>
/// <param name="configurationsPathRoot">Path to folder containing config file.</param>
/// <returns>New <see cref="GatewayProcessorConfigProvider"/>.</returns>
protected GatewayProcessorConfigProvider CreateGatewayProcessorConfigProvider(
string configurationsPathRoot) =>
new GatewayProcessorConfigProvider(_loggerFactory.CreateLogger("ProcessorSettings"), configurationsPathRoot);
/// <summary>
/// Create a new <see cref="GatewayReceiveConfigProvider"/> based on the given folder.
/// </summary>
/// <param name="configurationsPathRoot">Path to folder containing config file.</param>
/// <returns>New <see cref="GatewayReceiveConfigProvider"/>.</returns>
protected GatewayReceiveConfigProvider CreateGatewayReceiveConfigProvider(
string configurationsPathRoot) =>
new GatewayReceiveConfigProvider(_loggerFactory.CreateLogger("ReceiveSettings"), configurationsPathRoot);
protected void WriteDicomFileForBuildPackage(string fileName, DicomFile dicomFile)
{
var path = GetBuildPackageResultPath(fileName, "AnonymisationProtocols");
@ -312,7 +346,7 @@
}
protected AETConfigModel GetTestAETConfigModel() =>
_testAETConfigProvider.GetAETConfigs().First();
_testAETConfigProvider.Config.First();
/// <summary>
/// Create ReceiveServiceConfig from test files, but overwrite the port and rootDicomFolder.
@ -324,7 +358,7 @@
int port,
DirectoryInfo rootDicomFolder = null)
{
var gatewayConfig = _testGatewayReceiveConfigProvider.GatewayReceiveConfig().ReceiveServiceConfig;
var gatewayConfig = TestGatewayReceiveConfigProvider.Config.ReceiveServiceConfig;
return gatewayConfig.With(
new DicomEndPoint(gatewayConfig.GatewayDicomEndPoint.Title, port, gatewayConfig.GatewayDicomEndPoint.Ip),
@ -522,7 +556,7 @@
protected PushService CreatePushService(
Func<IEnumerable<AETConfigModel>> aetConfigProvider = null) =>
new PushService(
aetConfigProvider ?? _testAETConfigProvider.GetAETConfigs,
aetConfigProvider ?? _testAETConfigProvider.AETConfigModels,
new DicomDataSender(),
TestPushQueuePath,
TestDeleteQueuePath,
@ -560,7 +594,7 @@
int instances = 1) =>
new UploadService(
innerEyeSegmentationClient != null ? () => innerEyeSegmentationClient : TestGatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(),
aetConfigProvider ?? _testAETConfigProvider.GetAETConfigs,
aetConfigProvider ?? _testAETConfigProvider.AETConfigModels,
TestUploadQueuePath,
TestDownloadQueuePath,
TestDeleteQueuePath,
@ -824,5 +858,35 @@
/// </summary>
public static readonly Func<DicomTag, Random, DicomItem> RandomDicomTime = (tag, random) =>
new DicomTime(tag, DateTime.UtcNow.AddSeconds(random.NextDouble() * 1000.0));
/// <summary>
/// Disposes of all managed resources.
/// </summary>
/// <param name="disposing">If we are disposing.</param>
protected virtual void Dispose(bool disposing)
{
if (disposedValue)
{
return;
}
if (disposing)
{
_testAETConfigProvider.Dispose();
TestGatewayProcessorConfigProvider.Dispose();
TestGatewayReceiveConfigProvider.Dispose();
}
disposedValue = true;
}
/// <summary>
/// Implements the disposable pattern.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

Просмотреть файл

@ -1,18 +1,17 @@
namespace Microsoft.InnerEye.Listener.Tests.Models
{
using System;
using System.Collections.Generic;
/// <summary>
/// Mock implementation of IConfigurationProvider<T>.
/// Mock returning configuration, but sometimes throwing an exception.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="T">Configuration type.</typeparam>
public class MockConfigurationProvider<T>
{
/// <summary>
/// Previous configuration, if there was a queue.
/// Configuration.
/// </summary>
private T _previousConfiguration;
private T _configuration;
/// <summary>
/// TestException to throw on GetConfiguration to mock failure.
@ -20,11 +19,18 @@
public Exception TestException { get; set; }
/// <summary>
/// Mock queue of configurations.
/// Initialize a new instance of the.
/// </summary>
public Queue<T> ConfigurationQueue { get; } = new Queue<T>();
/// <param name="configuration">Configuration.</param>
public MockConfigurationProvider(T configuration)
{
_configuration = configuration;
}
/// <inheritdoc/>
/// <summary>
/// Return configuration or throw an exception.
/// </summary>
/// <returns>Configuration.</returns>
public T GetConfiguration()
{
if (TestException != null)
@ -32,12 +38,7 @@
throw TestException;
}
if (ConfigurationQueue.Count > 0)
{
_previousConfiguration = ConfigurationQueue.Dequeue();
}
return _previousConfiguration;
return _configuration;
}
}
}

Просмотреть файл

@ -5,6 +5,7 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.InnerEye.Azure.Segmentation.API.Common;
using Microsoft.InnerEye.DicomConstraints;
using Microsoft.InnerEye.Gateway.Models;
@ -503,8 +504,10 @@
[TestCategory("ConfigurationProvider")]
[Description("Creates a random gateway receive config, saves it, and checks it loads correctly.")]
[DataRow(false, DisplayName = "Load GatewayReceiveConfig")]
[DataRow(true, DisplayName = "Reload GatewayReceiveConfig")]
[TestMethod]
public void TestLoadGatewayReceiveConfig()
public void TestLoadGatewayReceiveConfig(bool reload)
{
var configurationDirectory = CreateTemporaryDirectory().FullName;
var random = new Random();
@ -512,16 +515,68 @@
var expectedGatewayReceiveConfig = RandomGatewayReceiveConfig(random);
Serialise(expectedGatewayReceiveConfig, configurationDirectory, GatewayReceiveConfigProvider.GatewayReceiveConfigFileName);
var gatewayReceiveConfigProvider = new GatewayReceiveConfigProvider(_baseTestLogger, configurationDirectory);
var actualGatewayReceiveConfig = gatewayReceiveConfigProvider.GatewayReceiveConfig();
using (var gatewayReceiveConfigProvider = CreateGatewayReceiveConfigProvider(configurationDirectory))
{
Assert.AreEqual(expectedGatewayReceiveConfig, gatewayReceiveConfigProvider.Config);
Assert.AreEqual(expectedGatewayReceiveConfig, actualGatewayReceiveConfig);
if (reload)
{
var configReloadedCount = 0;
gatewayReceiveConfigProvider.ConfigChanged += (s, e) =>
{
Interlocked.Increment(ref configReloadedCount);
};
var expectedGatewayReceiveConfig2 = RandomGatewayReceiveConfig(random);
Serialise(expectedGatewayReceiveConfig2, configurationDirectory, GatewayReceiveConfigProvider.GatewayReceiveConfigFileName);
SpinWait.SpinUntil(() => configReloadedCount > 0, TimeSpan.FromSeconds(10));
Assert.AreEqual(expectedGatewayReceiveConfig2, gatewayReceiveConfigProvider.Config);
}
}
}
[TestCategory("ConfigurationProvider")]
[Description("Creates a random gateway receive config, saves it, and checks it loads correctly. Then toggles runAsConsole.")]
[TestMethod]
public void TestUpdateGatewayReceiveConfigRunAsConsole()
{
var configurationDirectory = CreateTemporaryDirectory().FullName;
var random = new Random();
var expectedGatewayReceiveConfig = RandomGatewayReceiveConfig(random);
Serialise(expectedGatewayReceiveConfig, configurationDirectory, GatewayReceiveConfigProvider.GatewayReceiveConfigFileName);
using (var gatewayReceiveConfigProvider = CreateGatewayReceiveConfigProvider(configurationDirectory))
{
Assert.AreEqual(expectedGatewayReceiveConfig, gatewayReceiveConfigProvider.Config);
var configReloadedCount = 0;
gatewayReceiveConfigProvider.ConfigChanged += (s, e) =>
{
Interlocked.Increment(ref configReloadedCount);
};
var runAsConsole = gatewayReceiveConfigProvider.Config.ServiceSettings.RunAsConsole;
gatewayReceiveConfigProvider.SetRunAsConsole(!runAsConsole);
SpinWait.SpinUntil(() => configReloadedCount > 0, TimeSpan.FromSeconds(10));
// RunAsConsole should have now toggled.
Assert.AreEqual(!runAsConsole, gatewayReceiveConfigProvider.Config.ServiceSettings.RunAsConsole);
}
}
[TestCategory("ConfigurationProvider")]
[Description("Creates a random gateway processor config, saves it, and checks it loads correctly.")]
[DataRow(false, DisplayName = "Load GatewayProcessorConfig")]
[DataRow(true, DisplayName = "Reload GatewayProcessorConfig")]
[TestMethod]
public void TestLoadGatewayProcessorConfig()
public void TestLoadGatewayProcessorConfig(bool reload)
{
var configurationDirectory = CreateTemporaryDirectory().FullName;
var random = new Random();
@ -529,112 +584,191 @@
var expectedGatewayProcessorConfig = RandomGatewayProcessorConfig(random);
Serialise(expectedGatewayProcessorConfig, configurationDirectory, GatewayProcessorConfigProvider.GatewayProcessorConfigFileName);
var gatewayProcessorConfigProvider = new GatewayProcessorConfigProvider(_baseTestLogger, configurationDirectory);
var actualGatewayProcessorConfig = gatewayProcessorConfigProvider.GatewayProcessorConfig();
using (var gatewayProcessorConfigProvider = CreateGatewayProcessorConfigProvider(configurationDirectory))
{
Assert.AreEqual(expectedGatewayProcessorConfig, gatewayProcessorConfigProvider.Config);
Assert.AreEqual(expectedGatewayProcessorConfig, actualGatewayProcessorConfig);
if (reload)
{
var configReloadedCount = 0;
gatewayProcessorConfigProvider.ConfigChanged += (s, e) =>
{
Interlocked.Increment(ref configReloadedCount);
};
var expectedGatewayProcessorConfig2 = RandomGatewayProcessorConfig(random);
Serialise(expectedGatewayProcessorConfig2, configurationDirectory, GatewayProcessorConfigProvider.GatewayProcessorConfigFileName);
SpinWait.SpinUntil(() => configReloadedCount > 0, TimeSpan.FromSeconds(10));
Assert.AreEqual(expectedGatewayProcessorConfig2, gatewayProcessorConfigProvider.Config);
}
}
}
[TestCategory("ConfigurationProvider")]
[Description("Creates a random gateway processor config, saves it, and checks it loads correctly. Then toggles runAsConsole.")]
[TestMethod]
public void TestUpdateGatewayProcessorConfigRunAsConsole()
{
var configurationDirectory = CreateTemporaryDirectory().FullName;
var random = new Random();
var expectedGatewayProcessorConfig = RandomGatewayProcessorConfig(random);
Serialise(expectedGatewayProcessorConfig, configurationDirectory, GatewayProcessorConfigProvider.GatewayProcessorConfigFileName);
using (var gatewayProcessorConfigProvider = CreateGatewayProcessorConfigProvider(configurationDirectory))
{
Assert.AreEqual(expectedGatewayProcessorConfig, gatewayProcessorConfigProvider.Config);
var configReloadedCount = 0;
gatewayProcessorConfigProvider.ConfigChanged += (s, e) =>
{
Interlocked.Increment(ref configReloadedCount);
};
var runAsConsole = gatewayProcessorConfigProvider.Config.ServiceSettings.RunAsConsole;
gatewayProcessorConfigProvider.SetRunAsConsole(!runAsConsole);
SpinWait.SpinUntil(() => configReloadedCount > 0, TimeSpan.FromSeconds(10));
// RunAsConsole should have now toggled.
Assert.AreEqual(!runAsConsole, gatewayProcessorConfigProvider.Config.ServiceSettings.RunAsConsole);
}
}
[TestCategory("ConfigurationProvider")]
[Description("Creates a random gateway processor config, saves it, and checks it loads correctly. Then toggles updates processor settings.")]
[TestMethod]
public void TestUpdateGatewayProcessorConfigProcessorSettings()
{
var configurationDirectory = CreateTemporaryDirectory().FullName;
var random = new Random();
var expectedGatewayProcessorConfig = RandomGatewayProcessorConfig(random);
Serialise(expectedGatewayProcessorConfig, configurationDirectory, GatewayProcessorConfigProvider.GatewayProcessorConfigFileName);
using (var gatewayProcessorConfigProvider = CreateGatewayProcessorConfigProvider(configurationDirectory))
{
Assert.AreEqual(expectedGatewayProcessorConfig, gatewayProcessorConfigProvider.Config);
var configReloadedCount = 0;
gatewayProcessorConfigProvider.ConfigChanged += (s, e) =>
{
Interlocked.Increment(ref configReloadedCount);
};
var processorSettings = RandomProcessorSettings(random);
gatewayProcessorConfigProvider.SetProcessorSettings(processorSettings.InferenceUri);
SpinWait.SpinUntil(() => configReloadedCount > 0, TimeSpan.FromSeconds(10));
// InferenceUri should have now changed.
Assert.AreEqual(processorSettings.InferenceUri, gatewayProcessorConfigProvider.Config.ProcessorSettings.InferenceUri);
}
}
/// <summary>
/// Create a list of random AET config models, save them to a single file, and check they load correctly.
/// Create an Action for saving a set of AETConfigModels to a single file.
/// </summary>
/// <param name="useFile">True to use AETConfigProvider in single file mode, false to use it in folder mode.</param>
public void TestLoadAETConfigCommon(bool useFile)
{
var configurationDirectory = CreateTemporaryDirectory().FullName;
var random = new Random();
/// <param name="configurationDirectory">Folder to store file in.</param>
/// <returns>Action.</returns>
public static Action<Random, AETConfigModel[]> SaveFileAETConfigModels(string configurationDirectory) =>
(random, aetConfigModels) => Serialise(aetConfigModels, configurationDirectory, AETConfigProvider.AETConfigFileName);
var expectedAETConfigModels = RandomArray(random, 2, 10, RandomAETConfigModel);
var folder = string.Empty;
var filename = string.Empty;
if (useFile)
/// <summary>
/// Create an Action for saving a set of AETConfigModels to a folder.
/// </summary>
/// <param name="configurationDirectory">Folder to store folder in.</param>
/// <param name="addJunk">True to add junk files that should be ignored.</param>
/// <param name="multipleFiles">True to split config across multiple files.</param>
/// <returns>Action.</returns>
public static Action<Random, AETConfigModel[]> SaveFolderAETConfigModels(string configurationDirectory, bool addJunk, bool multipleFiles) =>
(random, aetConfigModels) =>
{
folder = configurationDirectory;
filename = AETConfigProvider.AETConfigFileName;
}
else
{
folder = Path.Combine(configurationDirectory, AETConfigProvider.AETConfigFolderName);
var folder = Path.Combine(configurationDirectory, AETConfigProvider.AETConfigFolderName);
Directory.CreateDirectory(folder);
filename = "test1.json";
}
Serialise(expectedAETConfigModels, folder, filename);
// Add in two extra files that should be ignored.
if (addJunk)
{
// Write a random GatewayProcessorConfig
var gatewayProcessorConfig = RandomGatewayProcessorConfig(random);
Serialise(gatewayProcessorConfig, folder, GatewayProcessorConfigProvider.GatewayProcessorConfigFileName);
var aetConfigProvider = new AETConfigProvider(_baseTestLogger, configurationDirectory, useFile);
var actualAETConfigModels = aetConfigProvider.GetAETConfigs().ToArray();
// Write a random GatewayReceiverConfig
var gatewayReceiveConfig = RandomGatewayReceiveConfig(random);
Serialise(gatewayReceiveConfig, folder, GatewayReceiveConfigProvider.GatewayReceiveConfigFileName);
}
Assert.IsTrue(expectedAETConfigModels.SequenceEqual(actualAETConfigModels));
}
if (multipleFiles)
{
for (var i = 0; i < aetConfigModels.Length; i++)
{
var expectedAETConfig = new[] { aetConfigModels[i] };
Serialise(expectedAETConfig, folder, string.Format("test{0}.json", i + 1));
}
}
else
{
Serialise(aetConfigModels, folder, "test1.json");
}
};
public static AETConfigModel[] OrderAETConfigModels(IEnumerable<AETConfigModel> aetConfigModels) =>
aetConfigModels.OrderBy(m => m.CalledAET).ThenBy(m => m.CallingAET).ToArray();
public static void AssertAETConfigModelsEqual(IEnumerable<AETConfigModel> expectedAETConfigModels, IEnumerable<AETConfigModel> actualAETConfigModels) =>
Assert.IsTrue(OrderAETConfigModels(expectedAETConfigModels).SequenceEqual(OrderAETConfigModels(actualAETConfigModels)));
[TestCategory("ConfigurationProvider")]
[Description("Creates a list of AET config models, saves it to a file, and checks it loads correctly.")]
[Description("Creates a list of AET config models, saves it to a file or folder, and checks it loads correctly.")]
[DataRow(false, false, false, false, DisplayName = "Load folder AETConfigModels")]
[DataRow(false, false, false, true, DisplayName = "Load folder AETConfigModels, split files")]
[DataRow(false, false, true, false, DisplayName = "Load folder AETConfigModels, ignore junk")]
[DataRow(false, true, false, false, DisplayName = "Reload folder AETConfigModels")]
[DataRow(false, true, true, false, DisplayName = "Reload folder AETConfigModels, ignore junk")]
[DataRow(true, false, false, false, DisplayName = "Load file AETConfigModels")]
[DataRow(true, true, false, false, DisplayName = "Reload file AETConfigModels")]
[TestMethod]
public void TestLoadAETConfigFile()
{
TestLoadAETConfigCommon(true);
}
[TestCategory("ConfigurationProvider")]
[Description("Creates a list of AET config models, saves it to a file in a folder, and checks it loads correctly.")]
[TestMethod]
public void TestLoadAETConfigFolder()
{
TestLoadAETConfigCommon(false);
}
[TestCategory("ConfigurationProvider")]
[Description("Creates a list of AET config models, saves them along with two other config files, and checks the models load correctly" +
"and the other configs are ignored.")]
[TestMethod]
public void TestLoadAETConfigInvalidFiles()
public void TestLoadAETConfigModels(bool useFile, bool reload, bool addJunk, bool multipleFiles)
{
var configurationDirectory = CreateTemporaryDirectory().FullName;
var random = new Random();
var aetConfigFolder = Path.Combine(configurationDirectory, AETConfigProvider.AETConfigFolderName);
Directory.CreateDirectory(aetConfigFolder);
var saveAETConfigModels = useFile ?
SaveFileAETConfigModels(configurationDirectory) :
SaveFolderAETConfigModels(configurationDirectory, addJunk, multipleFiles);
var expectedAETConfigModels = RandomArray(random, 3, 10, RandomAETConfigModel);
Serialise(expectedAETConfigModels, aetConfigFolder, "test1.json");
saveAETConfigModels.Invoke(random, expectedAETConfigModels);
// Write a random GatewayProcessorConfig
var gatewayProcessorConfig = RandomGatewayProcessorConfig(random);
Serialise(gatewayProcessorConfig, aetConfigFolder, "test2.json");
// Write a random GatewayReceiverConfig
var gatewayReceiveConfig = RandomGatewayReceiveConfig(random);
Serialise(gatewayReceiveConfig, aetConfigFolder, "test3.json");
var aetConfigProvider = new AETConfigProvider(_baseTestLogger, configurationDirectory);
var actualAETConfigModels = aetConfigProvider.GetAETConfigs().ToArray();
Assert.IsTrue(expectedAETConfigModels.SequenceEqual(actualAETConfigModels));
}
[TestCategory("ConfigurationProvider")]
[Description("Creates a list of AET config models, saves them to one file per called/calling pair, and checks they all load correctly.")]
[TestMethod]
public void TestLoadAETConfigConcatenate()
{
var configurationDirectory = CreateTemporaryDirectory().FullName;
var random = new Random();
var aetConfigFolder = Path.Combine(configurationDirectory, AETConfigProvider.AETConfigFolderName);
Directory.CreateDirectory(aetConfigFolder);
var expectedAETConfigModels = RandomArray(random, 3, 10, RandomAETConfigModel);
for (var i = 0; i < expectedAETConfigModels.Length; i++)
using (var aetConfigProvider = CreateAETConfigProvider(configurationDirectory, useFile))
{
var expectedAETConfig = new[] { expectedAETConfigModels[i] };
Serialise(expectedAETConfig, aetConfigFolder, string.Format("GatewayModelRulesConfig{0}.json", i));
AssertAETConfigModelsEqual(expectedAETConfigModels, aetConfigProvider.Config);
if (reload)
{
var configReloadedCount = 0;
aetConfigProvider.ConfigChanged += (s, e) =>
{
Interlocked.Increment(ref configReloadedCount);
};
var expectedAETConfigModels2 = RandomArray(random, 3, 10, RandomAETConfigModel);
saveAETConfigModels.Invoke(random, expectedAETConfigModels2);
SpinWait.SpinUntil(() => configReloadedCount > (addJunk ? 4 : 0), TimeSpan.FromSeconds(10));
AssertAETConfigModelsEqual(expectedAETConfigModels2, aetConfigProvider.Config);
}
}
var aetConfigProvider = new AETConfigProvider(_baseTestLogger, configurationDirectory);
var actualAETConfigModels = aetConfigProvider.GetAETConfigs().ToArray();
Assert.IsTrue(expectedAETConfigModels.SequenceEqual(actualAETConfigModels));
}
[TestCategory("ConfigurationProvider")]
@ -669,10 +803,10 @@
Serialise(expectedAETConfig, aetConfigFolder, string.Format("GatewayModelRulesConfig{0}.json", i), true);
}
var aetConfigProvider = new AETConfigProvider(_baseTestLogger, configurationDirectory);
var actualAETConfigModels = aetConfigProvider.GetAETConfigs().ToArray();
Assert.IsTrue(expectedAETConfigModels.SequenceEqual(actualAETConfigModels));
using (var aetConfigProvider = CreateAETConfigProvider(configurationDirectory))
{
AssertAETConfigModelsEqual(expectedAETConfigModels, aetConfigProvider.Config);
}
}
[TestCategory("ConfigurationProvider")]
@ -714,10 +848,10 @@
}
}
var aetConfigProvider = new AETConfigProvider(_baseTestLogger, configurationDirectory);
var actualAETConfigModels = aetConfigProvider.GetAETConfigs().ToArray();
Assert.IsTrue(expectedAETConfigModels.SequenceEqual(actualAETConfigModels));
using (var aetConfigProvider = CreateAETConfigProvider(configurationDirectory))
{
AssertAETConfigModelsEqual(expectedAETConfigModels, aetConfigProvider.Config);
}
}
}
}

Просмотреть файл

@ -6,6 +6,7 @@
using System.Threading.Tasks;
using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions;
using Microsoft.InnerEye.Gateway.Models;
using Microsoft.InnerEye.Listener.Common.Providers;
using Microsoft.InnerEye.Listener.DataProvider.Implementations;
using Microsoft.InnerEye.Listener.Tests.Common.Helpers;
using Microsoft.InnerEye.Listener.Tests.Models;
@ -20,38 +21,25 @@
[TestMethod]
public void ReceiveServiceRestartTest()
{
var callingAet = "ProstateRTMl";
var testAETConfigModel = GetTestAETConfigModel();
var configurationDirectory = CreateTemporaryDirectory().FullName;
var expectedGatewayReceiveConfig1 = TestGatewayReceiveConfigProvider.Config.With(
receiveServiceConfig: GetTestGatewayReceiveServiceConfig(110),
configurationServiceConfig: new ConfigurationServiceConfig(
configurationRefreshDelaySeconds: 1));
ConfigurationProviderTests.Serialise(expectedGatewayReceiveConfig1, configurationDirectory, GatewayReceiveConfigProvider.GatewayReceiveConfigFileName);
var client = GetMockInnerEyeSegmentationClient();
var mockConfigurationServiceConfigProvider = new MockConfigurationProvider<ConfigurationServiceConfig>();
var configurationServiceConfig1 = new ConfigurationServiceConfig(
configurationRefreshDelaySeconds: 1);
var configurationServiceConfig2 = new ConfigurationServiceConfig(
configurationServiceConfig1.ConfigCreationDateTime.AddSeconds(5),
configurationServiceConfig1.ApplyConfigDateTime.AddSeconds(10));
mockConfigurationServiceConfigProvider.ConfigurationQueue.Clear();
mockConfigurationServiceConfigProvider.ConfigurationQueue.Enqueue(configurationServiceConfig1);
mockConfigurationServiceConfigProvider.ConfigurationQueue.Enqueue(configurationServiceConfig2);
var mockReceiverConfigurationProvider2 = new MockConfigurationProvider<ReceiveServiceConfig>();
var testReceiveServiceConfig1 = GetTestGatewayReceiveServiceConfig(110);
var testReceiveServiceConfig2 = GetTestGatewayReceiveServiceConfig(111);
mockReceiverConfigurationProvider2.ConfigurationQueue.Clear();
mockReceiverConfigurationProvider2.ConfigurationQueue.Enqueue(testReceiveServiceConfig1);
mockReceiverConfigurationProvider2.ConfigurationQueue.Enqueue(testReceiveServiceConfig2);
using (var receiveService = CreateReceiveService(mockReceiverConfigurationProvider2.GetConfiguration))
using (var gatewayReceiveConfigProvider = CreateGatewayReceiveConfigProvider(configurationDirectory))
using (var receiveService = CreateReceiveService(gatewayReceiveConfigProvider.ReceiveServiceConfig))
using (var uploadQueue = receiveService.UploadQueue)
using (var configurationService = CreateConfigurationService(
client,
mockConfigurationServiceConfigProvider.GetConfiguration,
gatewayReceiveConfigProvider.ConfigurationServiceConfig,
receiveService))
{
// Start the service
@ -59,28 +47,36 @@
uploadQueue.Clear(); // Clear the message queue
var expectedGatewayReceiveConfig2 = TestGatewayReceiveConfigProvider.Config.With(
receiveServiceConfig: GetTestGatewayReceiveServiceConfig(111),
configurationServiceConfig: new ConfigurationServiceConfig(
expectedGatewayReceiveConfig1.ConfigurationServiceConfig.ConfigCreationDateTime.AddSeconds(5),
expectedGatewayReceiveConfig1.ConfigurationServiceConfig.ApplyConfigDateTime.AddSeconds(10)));
ConfigurationProviderTests.Serialise(expectedGatewayReceiveConfig2, configurationDirectory, GatewayReceiveConfigProvider.GatewayReceiveConfigFileName);
SpinWait.SpinUntil(() => receiveService.StartCount == 2);
// Send on the new config
// Send on the old config
var result = DcmtkHelpers.SendFileUsingDCMTK(
@"Images\1ValidSmall\1.dcm",
testReceiveServiceConfig1.GatewayDicomEndPoint.Port,
expectedGatewayReceiveConfig1.ReceiveServiceConfig.GatewayDicomEndPoint.Port,
ScuProfile.LEExplicitCT,
TestContext,
applicationEntityTitle: callingAet,
calledAETitle: testReceiveServiceConfig1.GatewayDicomEndPoint.Title);
applicationEntityTitle: testAETConfigModel.CallingAET,
calledAETitle: testAETConfigModel.CalledAET);
// Check this did send on the old config
// Check this did not send on the old config
Assert.IsFalse(string.IsNullOrWhiteSpace(result));
// Send on the new config
result = DcmtkHelpers.SendFileUsingDCMTK(
@"Images\1ValidSmall\1.dcm",
testReceiveServiceConfig2.GatewayDicomEndPoint.Port,
expectedGatewayReceiveConfig2.ReceiveServiceConfig.GatewayDicomEndPoint.Port,
ScuProfile.LEExplicitCT,
TestContext,
applicationEntityTitle: callingAet,
calledAETitle: testReceiveServiceConfig2.GatewayDicomEndPoint.Title);
applicationEntityTitle: testAETConfigModel.CallingAET,
calledAETitle: testAETConfigModel.CalledAET);
// Check this did send on the new config
Assert.IsTrue(string.IsNullOrWhiteSpace(result));
@ -88,8 +84,8 @@
var receiveQueueItem = TransactionalDequeue<UploadQueueItem>(uploadQueue);
Assert.IsNotNull(receiveQueueItem);
Assert.AreEqual(callingAet, receiveQueueItem.CallingApplicationEntityTitle);
Assert.AreEqual(testReceiveServiceConfig2.GatewayDicomEndPoint.Title, receiveQueueItem.CalledApplicationEntityTitle);
Assert.AreEqual(testAETConfigModel.CallingAET, receiveQueueItem.CallingApplicationEntityTitle);
Assert.AreEqual(testAETConfigModel.CalledAET, receiveQueueItem.CalledApplicationEntityTitle);
Assert.IsFalse(string.IsNullOrEmpty(receiveQueueItem.AssociationFolderPath));
@ -113,15 +109,12 @@
[TestMethod]
public void ReceiveServiceAPIDownTest()
{
var callingAet = "ProstateRTMl";
var testAETConfigModel = GetTestAETConfigModel();
var mockReceiverConfigurationProvider = new MockConfigurationProvider<ReceiveServiceConfig>();
var client = GetMockInnerEyeSegmentationClient();
var gatewayReceiveConfig = GetTestGatewayReceiveServiceConfig(140);
mockReceiverConfigurationProvider.ConfigurationQueue.Clear();
mockReceiverConfigurationProvider.ConfigurationQueue.Enqueue(gatewayReceiveConfig);
var mockReceiverConfigurationProvider = new MockConfigurationProvider<ReceiveServiceConfig>(gatewayReceiveConfig);
using (var receiveService = CreateReceiveService(mockReceiverConfigurationProvider.GetConfiguration))
using (var uploadQueue = receiveService.UploadQueue)
@ -138,14 +131,14 @@
gatewayReceiveConfig.GatewayDicomEndPoint.Port,
ScuProfile.LEExplicitCT,
TestContext,
applicationEntityTitle: callingAet,
calledAETitle: gatewayReceiveConfig.GatewayDicomEndPoint.Title);
applicationEntityTitle: testAETConfigModel.CallingAET,
calledAETitle: testAETConfigModel.CalledAET);
var receiveQueueItem = TransactionalDequeue<UploadQueueItem>(uploadQueue, 10000);
Assert.IsNotNull(receiveQueueItem);
Assert.AreEqual(callingAet, receiveQueueItem.CallingApplicationEntityTitle);
Assert.AreEqual(gatewayReceiveConfig.GatewayDicomEndPoint.Title, receiveQueueItem.CalledApplicationEntityTitle);
Assert.AreEqual(testAETConfigModel.CallingAET, receiveQueueItem.CallingApplicationEntityTitle);
Assert.AreEqual(testAETConfigModel.CalledAET, receiveQueueItem.CalledApplicationEntityTitle);
Assert.IsFalse(string.IsNullOrEmpty(receiveQueueItem.AssociationFolderPath));
@ -169,11 +162,9 @@
[TestMethod]
public void ReceiveServiceLiveEndToEndTest()
{
var testAETConfigModel = GetTestAETConfigModel();
var receivePort = 160;
var callingAet = "ProstateRTMl";
var calledAet = "testname";
using (var receiveService = CreateReceiveService(receivePort))
using (var uploadQueue = receiveService.UploadQueue)
{
@ -185,14 +176,14 @@
receivePort,
ScuProfile.LEExplicitCT,
TestContext,
applicationEntityTitle: callingAet,
calledAETitle: calledAet);
applicationEntityTitle: testAETConfigModel.CallingAET,
calledAETitle: testAETConfigModel.CalledAET);
var receiveQueueItem = TransactionalDequeue<UploadQueueItem>(uploadQueue, timeoutMs: 20 * 1000);
Assert.IsNotNull(receiveQueueItem);
Assert.AreEqual(callingAet, receiveQueueItem.CallingApplicationEntityTitle);
Assert.AreEqual(calledAet, receiveQueueItem.CalledApplicationEntityTitle);
Assert.AreEqual(testAETConfigModel.CallingAET, receiveQueueItem.CallingApplicationEntityTitle);
Assert.AreEqual(testAETConfigModel.CalledAET, receiveQueueItem.CalledApplicationEntityTitle);
Assert.IsFalse(string.IsNullOrEmpty(receiveQueueItem.AssociationFolderPath));
@ -216,7 +207,7 @@
[TestMethod]
public async Task ReceiveServiceEchoTest()
{
var calledAet = "testname";
var testAETConfigModel = GetTestAETConfigModel();
var receivePort = 180;
using (var receiveService = CreateReceiveService(receivePort))
@ -229,7 +220,7 @@
await sender.DicomEchoAsync(
"Hello",
calledAet,
testAETConfigModel.CalledAET,
receivePort,
"127.0.0.1");
@ -242,15 +233,15 @@
receivePort,
ScuProfile.LEExplicitCT,
TestContext,
applicationEntityTitle: "ProstateRTMl",
calledAETitle: calledAet);
applicationEntityTitle: testAETConfigModel.CallingAET,
calledAETitle: testAETConfigModel.CalledAET);
Assert.IsNotNull(TransactionalDequeue<UploadQueueItem>(uploadQueue, timeoutMs: 1000));
// Now try another Dicom echo
await sender.DicomEchoAsync(
"Hello",
calledAet,
testAETConfigModel.CalledAET,
receivePort,
"127.0.0.1");

Просмотреть файл

@ -8,10 +8,10 @@
using Dicom;
using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions;
using Microsoft.InnerEye.Gateway.Models;
using Microsoft.InnerEye.Listener.Common.Providers;
using Microsoft.InnerEye.Listener.DataProvider.Implementations;
using Microsoft.InnerEye.Listener.DataProvider.Models;
using Microsoft.InnerEye.Listener.Tests.Common.Helpers;
using Microsoft.InnerEye.Listener.Tests.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
@ -23,6 +23,14 @@
[TestMethod]
public async Task ProcessorServiceRestartTest()
{
var configurationDirectory = CreateTemporaryDirectory().FullName;
var expectedGatewayProcessorConfig1 = TestGatewayProcessorConfigProvider.Config.With(
configurationServiceConfig: new ConfigurationServiceConfig(
configurationRefreshDelaySeconds: 1));
ConfigurationProviderTests.Serialise(expectedGatewayProcessorConfig1, configurationDirectory, GatewayProcessorConfigProvider.GatewayProcessorConfigFileName);
var tempFolder = CreateTemporaryDirectory();
foreach (var file in new DirectoryInfo(@"Images\1ValidSmall\").GetFiles())
@ -32,21 +40,7 @@
var client = GetMockInnerEyeSegmentationClient();
var mockConfigurationServiceConfigProvider = new MockConfigurationProvider<ConfigurationServiceConfig>();
var configurationServiceConfig1 = new ConfigurationServiceConfig(
configurationRefreshDelaySeconds: 1);
var configurationServiceConfig2 = new ConfigurationServiceConfig(
configurationServiceConfig1.ConfigCreationDateTime.AddSeconds(5),
configurationServiceConfig1.ApplyConfigDateTime.AddSeconds(10));
mockConfigurationServiceConfigProvider.ConfigurationQueue.Enqueue(configurationServiceConfig1);
mockConfigurationServiceConfigProvider.ConfigurationQueue.Enqueue(configurationServiceConfig2);
var resultDirectory = CreateTemporaryDirectory();
using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(resultDirectory.FullName)))
using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(CreateTemporaryDirectory().FullName)))
{
var eventCount = 0;
var folderPath = string.Empty;
@ -65,9 +59,10 @@
using (var uploadService = CreateUploadService(client))
using (var uploadQueue = uploadService.UploadQueue)
using (var downloadService = CreateDownloadService(client))
using (var gatewayProcessorConfigProvider = CreateGatewayProcessorConfigProvider(configurationDirectory))
using (var configurationService = CreateConfigurationService(
client,
mockConfigurationServiceConfigProvider.GetConfiguration,
gatewayProcessorConfigProvider.ConfigurationServiceConfig,
downloadService,
uploadService,
pushService))
@ -77,6 +72,14 @@
uploadQueue.Clear(); // Clear the message queue
// Save a new config, this should be picked up and the services restart in 10 seconds.
var expectedGatewayProcessorConfig2 = TestGatewayProcessorConfigProvider.Config.With(
configurationServiceConfig: new ConfigurationServiceConfig(
expectedGatewayProcessorConfig1.ConfigurationServiceConfig.ConfigCreationDateTime.AddSeconds(5),
expectedGatewayProcessorConfig1.ConfigurationServiceConfig.ApplyConfigDateTime.AddSeconds(10)));
ConfigurationProviderTests.Serialise(expectedGatewayProcessorConfig2, configurationDirectory, GatewayProcessorConfigProvider.GatewayProcessorConfigFileName);
SpinWait.SpinUntil(() => pushService.StartCount == 2);
SpinWait.SpinUntil(() => uploadService.StartCount == 2);
SpinWait.SpinUntil(() => downloadService.StartCount == 2);

Просмотреть файл

@ -53,40 +53,42 @@ namespace Microsoft.InnerEye.Listener.Wix.Actions
#endif
// Make sure that the applications are run as services.
var gatewayProcessorConfigProvider = new GatewayProcessorConfigProvider(
null,
ConfigInstallDirectory);
gatewayProcessorConfigProvider.SetRunAsConsole(false);
var gatewayReceiveConfigProvider = new GatewayReceiveConfigProvider(
null,
ConfigInstallDirectory);
gatewayReceiveConfigProvider.SetRunAsConsole(false);
// Check if the installer is running unattended - lets skip the UI if true
if (session.CustomActionData[UILevelCustomActionKey] == "2")
using (var gatewayProcessorConfigProvider = new GatewayProcessorConfigProvider(null, ConfigInstallDirectory))
{
return ActionResult.Success;
}
gatewayProcessorConfigProvider.SetRunAsConsole(false);
// In the context of the installer, this may have a different SecurityProtocol to the application.
// In testing it was: SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls
// but it may vary. In order to value the uri and license key, we need TLS 1.2
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
// First time install so lets display a form to grab the license key.
using (var form = new LicenseKeyForm(gatewayProcessorConfigProvider))
{
var licenseKeyDialogResult = form.ShowDialog();
switch (licenseKeyDialogResult)
using (var gatewayReceiveConfigProvider = new GatewayReceiveConfigProvider(
null,
ConfigInstallDirectory))
{
case DialogResult.Cancel:
return ActionResult.UserExit;
case DialogResult.No:
return ActionResult.NotExecuted;
default:
return ActionResult.Success;
gatewayReceiveConfigProvider.SetRunAsConsole(false);
}
// Check if the installer is running unattended - lets skip the UI if true
if (session.CustomActionData[UILevelCustomActionKey] == "2")
{
return ActionResult.Success;
}
// In the context of the installer, this may have a different SecurityProtocol to the application.
// In testing it was: SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls
// but it may vary. In order to value the uri and license key, we need TLS 1.2
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
// First time install so lets display a form to grab the license key.
using (var form = new LicenseKeyForm(gatewayProcessorConfigProvider))
{
var licenseKeyDialogResult = form.ShowDialog();
switch (licenseKeyDialogResult)
{
case DialogResult.Cancel:
return ActionResult.UserExit;
case DialogResult.No:
return ActionResult.NotExecuted;
default:
return ActionResult.Success;
}
}
}
}