From 8ef63a29e75a3082a5fbe21b9796bbe081b72175 Mon Sep 17 00:00:00 2001 From: Neil South Date: Thu, 25 Jul 2024 16:53:41 +0100 Subject: [PATCH] fixing heath checks Signed-off-by: Neil South --- .github/.gitversion.yml | 12 +- .github/workflows/ci.yml | 11 +- doc/dependency_decisions.yml | 1 + src/Api/Storage/Payload.cs | 2 + src/Api/packages.lock.json | 6 +- src/CLI/packages.lock.json | 6 +- src/Client.Common/packages.lock.json | 6 +- src/Common/packages.lock.json | 6 +- src/Configuration/packages.lock.json | 6 +- src/DicomWebClient/CLI/packages.lock.json | 6 +- src/DicomWebClient/packages.lock.json | 6 +- .../Logging/Log.0.General.cs | 6 + src/InformaticsGateway/Program.cs | 4 +- .../Repositories/MonaiServiceLocator.cs | 12 +- .../Services/Connectors/PayloadAssembler.cs | 19 +- .../Services/Export/Hl7ExportService.cs | 10 +- .../Services/HealthLevel7/MllpService.cs | 252 +--------------- .../Services/HealthLevel7/MllpServiceHost.cs | 269 ++++++++++++++++++ .../Services/Http/MonaiHealthCheck.cs | 10 +- .../Repositories/MonaiServiceLocatorTest.cs | 30 +- .../Services/Export/ExportHl7ServiceTests.cs | 19 +- .../Services/HealthLevel7/MllpServiceTest.cs | 28 +- .../Services/Http/MonaiHealthCheckTest.cs | 9 +- .../Drivers/EfDataProvider.cs | 9 +- tests/Integration.Test/appsettings.json | 4 +- 25 files changed, 425 insertions(+), 324 deletions(-) mode change 100644 => 100755 .github/.gitversion.yml mode change 100644 => 100755 src/Api/packages.lock.json mode change 100644 => 100755 src/CLI/packages.lock.json mode change 100644 => 100755 src/Client.Common/packages.lock.json mode change 100644 => 100755 src/Common/packages.lock.json mode change 100644 => 100755 src/Configuration/packages.lock.json mode change 100644 => 100755 src/DicomWebClient/CLI/packages.lock.json mode change 100644 => 100755 src/DicomWebClient/packages.lock.json mode change 100644 => 100755 src/InformaticsGateway/Logging/Log.0.General.cs mode change 100644 => 100755 src/InformaticsGateway/Repositories/MonaiServiceLocator.cs create mode 100755 src/InformaticsGateway/Services/HealthLevel7/MllpServiceHost.cs mode change 100644 => 100755 src/InformaticsGateway/Services/Http/MonaiHealthCheck.cs mode change 100644 => 100755 src/InformaticsGateway/Test/Services/Http/MonaiHealthCheckTest.cs mode change 100644 => 100755 tests/Integration.Test/Drivers/EfDataProvider.cs diff --git a/.github/.gitversion.yml b/.github/.gitversion.yml old mode 100644 new mode 100755 index 6486d5775..b7af40385 --- a/.github/.gitversion.yml +++ b/.github/.gitversion.yml @@ -16,15 +16,17 @@ assembly-versioning-scheme: MajorMinorPatchTag mode: ContinuousDelivery branches: main: - tag: '' + label: '' release: - tag: rc + label: rc develop: - tag: beta + label: beta + increment: Patch feature: - tag: alpha.{BranchName} + label: alpha.{BranchName} pull-request: - tag: pr + label: pr + increment: Patch ignore: sha: [] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47ed9b252..cc1f0af52 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: semVer: ${{ steps.gitversion.outputs.semVer }} preReleaseLabel: ${{ steps.gitversion.outputs.preReleaseLabel }} majorMinorPatch: ${{ steps.gitversion.outputs.majorMinorPatch }} - nuGetVersionV2: ${{ steps.gitversion.outputs.nuGetVersionV2 }} + nuGetVersionV2: ${{ steps.gitversion.outputs.MajorMinorPatch }} steps: - uses: actions/checkout@v4 @@ -50,12 +50,17 @@ jobs: with: dotnet-version: "8.0.x" + # - name: Install GitVersion + # run: dotnet tool install --global GitVersion.Tool + - name: Install GitVersion - run: dotnet tool install --global GitVersion.Tool + uses: gittools/actions/gitversion/setup@v2.0.1 + with: + versionSpec: '6.x' - name: Determine Version id: gitversion - uses: gittools/actions/gitversion/execute@v0.10.2 + uses: gittools/actions/gitversion/execute@v2.0.1 with: useConfigFile: true updateAssemblyInfo: true diff --git a/doc/dependency_decisions.yml b/doc/dependency_decisions.yml index d9608f850..bdfc6e842 100755 --- a/doc/dependency_decisions.yml +++ b/doc/dependency_decisions.yml @@ -588,6 +588,7 @@ - 8.0.2 - 8.0.3 - 8.0.6 + - 8.0.7 :when: 2022-10-14T23:37:16.793Z :who: mocsharp :why: MIT (https://github.com/dotnet/runtime/raw/main/LICENSE.TXT) diff --git a/src/Api/Storage/Payload.cs b/src/Api/Storage/Payload.cs index 46115f63e..cccb148ec 100755 --- a/src/Api/Storage/Payload.cs +++ b/src/Api/Storage/Payload.cs @@ -86,6 +86,8 @@ public TimeSpan Elapsed public int FilesFailedToUpload { get => Files.Count(p => p.IsUploadFailed); } + public Payload() { } + public Payload(string key, string correlationId, string? workflowInstanceId, string? taskId, DataOrigin dataTrigger, uint timeout) { Guard.Against.NullOrWhiteSpace(key, nameof(key)); diff --git a/src/Api/packages.lock.json b/src/Api/packages.lock.json old mode 100644 new mode 100755 index ae1920d9a..ff01254b3 --- a/src/Api/packages.lock.json +++ b/src/Api/packages.lock.json @@ -41,9 +41,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.6, )", - "resolved": "8.0.6", - "contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ==" + "requested": "[8.0.7, )", + "resolved": "8.0.7", + "contentHash": "iI52ptEKby2ymQ6B7h4TWbFmm85T4VvLgc/HvS45Yr3lgi4IIFbQtjON3bQbX/Vc94jXNSLvrDOp5Kh7SJyFYQ==" }, "Monai.Deploy.Messaging": { "type": "Direct", diff --git a/src/CLI/packages.lock.json b/src/CLI/packages.lock.json old mode 100644 new mode 100755 index d530a862d..31a286b83 --- a/src/CLI/packages.lock.json +++ b/src/CLI/packages.lock.json @@ -35,9 +35,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.6, )", - "resolved": "8.0.6", - "contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ==" + "requested": "[8.0.7, )", + "resolved": "8.0.7", + "contentHash": "iI52ptEKby2ymQ6B7h4TWbFmm85T4VvLgc/HvS45Yr3lgi4IIFbQtjON3bQbX/Vc94jXNSLvrDOp5Kh7SJyFYQ==" }, "System.CommandLine.Hosting": { "type": "Direct", diff --git a/src/Client.Common/packages.lock.json b/src/Client.Common/packages.lock.json old mode 100644 new mode 100755 index e8e4fa01e..da9a2e2d7 --- a/src/Client.Common/packages.lock.json +++ b/src/Client.Common/packages.lock.json @@ -10,9 +10,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.6, )", - "resolved": "8.0.6", - "contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ==" + "requested": "[8.0.7, )", + "resolved": "8.0.7", + "contentHash": "iI52ptEKby2ymQ6B7h4TWbFmm85T4VvLgc/HvS45Yr3lgi4IIFbQtjON3bQbX/Vc94jXNSLvrDOp5Kh7SJyFYQ==" } } } diff --git a/src/Common/packages.lock.json b/src/Common/packages.lock.json old mode 100644 new mode 100755 index d43b35d29..4abc07400 --- a/src/Common/packages.lock.json +++ b/src/Common/packages.lock.json @@ -10,9 +10,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.6, )", - "resolved": "8.0.6", - "contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ==" + "requested": "[8.0.7, )", + "resolved": "8.0.7", + "contentHash": "iI52ptEKby2ymQ6B7h4TWbFmm85T4VvLgc/HvS45Yr3lgi4IIFbQtjON3bQbX/Vc94jXNSLvrDOp5Kh7SJyFYQ==" }, "System.IO.Abstractions": { "type": "Direct", diff --git a/src/Configuration/packages.lock.json b/src/Configuration/packages.lock.json old mode 100644 new mode 100755 index bdbcbfb47..9f4ff7802 --- a/src/Configuration/packages.lock.json +++ b/src/Configuration/packages.lock.json @@ -4,9 +4,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.6, )", - "resolved": "8.0.6", - "contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ==" + "requested": "[8.0.7, )", + "resolved": "8.0.7", + "contentHash": "iI52ptEKby2ymQ6B7h4TWbFmm85T4VvLgc/HvS45Yr3lgi4IIFbQtjON3bQbX/Vc94jXNSLvrDOp5Kh7SJyFYQ==" }, "Ardalis.GuardClauses": { "type": "Transitive", diff --git a/src/DicomWebClient/CLI/packages.lock.json b/src/DicomWebClient/CLI/packages.lock.json old mode 100644 new mode 100755 index bba51e306..8f7ad6849 --- a/src/DicomWebClient/CLI/packages.lock.json +++ b/src/DicomWebClient/CLI/packages.lock.json @@ -14,9 +14,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.6, )", - "resolved": "8.0.6", - "contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ==" + "requested": "[8.0.7, )", + "resolved": "8.0.7", + "contentHash": "iI52ptEKby2ymQ6B7h4TWbFmm85T4VvLgc/HvS45Yr3lgi4IIFbQtjON3bQbX/Vc94jXNSLvrDOp5Kh7SJyFYQ==" }, "Ardalis.GuardClauses": { "type": "Transitive", diff --git a/src/DicomWebClient/packages.lock.json b/src/DicomWebClient/packages.lock.json old mode 100644 new mode 100755 index d0e54b405..b24bb65e3 --- a/src/DicomWebClient/packages.lock.json +++ b/src/DicomWebClient/packages.lock.json @@ -23,9 +23,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.6, )", - "resolved": "8.0.6", - "contentHash": "E+lDylsTeP4ZiDmnEkiJ5wobnGaIJzFhOgZppznJCb69UZgbh6G3cfv1pnLDLLBx6JAgl0kAlnINDeT3uCuczQ==" + "requested": "[8.0.7, )", + "resolved": "8.0.7", + "contentHash": "iI52ptEKby2ymQ6B7h4TWbFmm85T4VvLgc/HvS45Yr3lgi4IIFbQtjON3bQbX/Vc94jXNSLvrDOp5Kh7SJyFYQ==" }, "Ardalis.GuardClauses": { "type": "Transitive", diff --git a/src/InformaticsGateway/Logging/Log.0.General.cs b/src/InformaticsGateway/Logging/Log.0.General.cs old mode 100644 new mode 100755 index fa610c15e..9fba72e7e --- a/src/InformaticsGateway/Logging/Log.0.General.cs +++ b/src/InformaticsGateway/Logging/Log.0.General.cs @@ -59,5 +59,11 @@ public static partial class Log [LoggerMessage(EventId = 13, Level = LogLevel.Critical, Message = "Failed to start {ServiceName}.")] public static partial void ServiceFailedToStart(this ILogger logger, string serviceName, Exception ex); + + [LoggerMessage(EventId = 14, Level = LogLevel.Critical, Message = "All services are unhealthy")] + public static partial void AllServiceUnheathly(this ILogger logger); + + [LoggerMessage(EventId = 15, Level = LogLevel.Error, Message = "Some services are unhealthy {list}")] + public static partial void SomeServiceUnheathly(this ILogger logger, string list); } } diff --git a/src/InformaticsGateway/Program.cs b/src/InformaticsGateway/Program.cs index a3352862a..c74ffb94b 100755 --- a/src/InformaticsGateway/Program.cs +++ b/src/InformaticsGateway/Program.cs @@ -36,6 +36,7 @@ using Monai.Deploy.InformaticsGateway.Services.DicomWeb; using Monai.Deploy.InformaticsGateway.Services.Export; using Monai.Deploy.InformaticsGateway.Services.Fhir; +using Monai.Deploy.InformaticsGateway.Services.HealthLevel7; using Monai.Deploy.InformaticsGateway.Services.Http; using Monai.Deploy.InformaticsGateway.Services.Scp; using Monai.Deploy.InformaticsGateway.Services.Scu; @@ -138,7 +139,6 @@ internal static IHostBuilder CreateHostBuilder(string[] args) => services.AddSingleton(); services.AddSingleton(); - var timeout = TimeSpan.FromSeconds(hostContext.Configuration.GetValue("InformaticsGateway:dicomWeb:clientTimeout", DicomWebConfiguration.DefaultClientTimeout)); services .AddHttpClient("dicomweb", configure => configure.Timeout = timeout) @@ -163,7 +163,7 @@ internal static IHostBuilder CreateHostBuilder(string[] args) => services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); - services.AddHostedService(); + services.AddHostedService(); services.AddHostedService(); }) diff --git a/src/InformaticsGateway/Repositories/MonaiServiceLocator.cs b/src/InformaticsGateway/Repositories/MonaiServiceLocator.cs old mode 100644 new mode 100755 index 07a063760..3fd78bfc2 --- a/src/InformaticsGateway/Repositories/MonaiServiceLocator.cs +++ b/src/InformaticsGateway/Repositories/MonaiServiceLocator.cs @@ -19,6 +19,8 @@ using System.Linq; using System.Reflection; using Ardalis.GuardClauses; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Monai.Deploy.InformaticsGateway.Api.Rest; using Monai.Deploy.InformaticsGateway.Services.Common; @@ -51,7 +53,13 @@ public Dictionary GetServiceStatus() { Guard.Against.Null(type, nameof(type)); - return (_serviceProvider.GetService(type) as IMonaiService); + var TypeInterface = type.GetInterfaces().FirstOrDefault(i => i.Name == $"I{type.Name}"); + if (TypeInterface is null) + { + var service = _serviceProvider.GetServices()?.FirstOrDefault(i => i.GetType().Name == type.Name); + return service as IMonaiService; + } + return (_serviceProvider.GetService(TypeInterface) as IMonaiService); } @@ -67,7 +75,7 @@ private static List LocateTypes() return services.Distinct().ToList(); } - private IList LocateServices() + private List LocateServices() { var list = new List(); foreach (var t in _types) diff --git a/src/InformaticsGateway/Services/Connectors/PayloadAssembler.cs b/src/InformaticsGateway/Services/Connectors/PayloadAssembler.cs index 5891fef24..ee668a9ed 100755 --- a/src/InformaticsGateway/Services/Connectors/PayloadAssembler.cs +++ b/src/InformaticsGateway/Services/Connectors/PayloadAssembler.cs @@ -25,9 +25,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Monai.Deploy.InformaticsGateway.Api; +using Monai.Deploy.InformaticsGateway.Api.Rest; using Monai.Deploy.InformaticsGateway.Api.Storage; using Monai.Deploy.InformaticsGateway.Database.Api.Repositories; using Monai.Deploy.InformaticsGateway.Logging; +using Monai.Deploy.InformaticsGateway.Services.Common; using Monai.Deploy.Messaging.Events; #nullable enable @@ -38,7 +40,7 @@ namespace Monai.Deploy.InformaticsGateway.Services.Connectors /// An in-memory queue for providing any files/DICOM instances received by the Informatics Gateway to /// other internal services. /// - internal sealed partial class PayloadAssembler : IPayloadAssembler, IDisposable + internal sealed partial class PayloadAssembler : IPayloadAssembler, IDisposable, IMonaiService { internal const int DEFAULT_TIMEOUT = 5; private readonly ILogger _logger; @@ -56,6 +58,8 @@ public PayloadAssembler( { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory)); + var scope = _serviceScopeFactory.CreateScope(); + var repository = scope.ServiceProvider.GetRequiredService(); // done here to ensure connection on startup _workItems = []; _tokenSource = new CancellationTokenSource(); @@ -69,8 +73,14 @@ public PayloadAssembler( }; _timer.Elapsed += OnTimedEvent; _timer.Enabled = true; + + Status = ServiceStatus.Running; } + public string ServiceName { get => nameof(PayloadAssembler); } + + public ServiceStatus Status { get; set; } = ServiceStatus.Unknown; + private async Task RemovePendingPayloads() { _logger.RemovingPendingPayloads(); @@ -193,6 +203,8 @@ private async Task QueueBucketForNotification(string key, Payload payload) payload.State = Payload.PayloadState.Move; var scope = _serviceScopeFactory.CreateScope(); var repository = scope.ServiceProvider.GetRequiredService(); + + await repository.UpdateAsync(payload).ConfigureAwait(false); _logger.PayloadSaved(payload.PayloadId); _workItems.Add(payload); @@ -219,9 +231,11 @@ private async Task CreateOrGetPayload(string key, string correlationId, private async Task PayloadFactory(string key, string correlationId, string? workflowInstanceId, string? taskId, Messaging.Events.DataOrigin dataOrigin, uint timeout, CancellationToken cancellationToken) { + var newPayload = new Payload(key, correlationId, workflowInstanceId, taskId, dataOrigin, timeout, null); var scope = _serviceScopeFactory.CreateScope(); var repository = scope.ServiceProvider.GetRequiredService(); - var newPayload = new Payload(key, correlationId, workflowInstanceId, taskId, dataOrigin, timeout, null); + + await repository.AddAsync(newPayload, cancellationToken).ConfigureAwait(false); _logger.BucketCreated(key, timeout); return newPayload; @@ -232,6 +246,7 @@ public void Dispose() _tokenSource.Cancel(); _payloads.Clear(); _timer.Stop(); + Status = ServiceStatus.Stopped; } } } diff --git a/src/InformaticsGateway/Services/Export/Hl7ExportService.cs b/src/InformaticsGateway/Services/Export/Hl7ExportService.cs index 4e6e7ba47..0c6abf91d 100755 --- a/src/InformaticsGateway/Services/Export/Hl7ExportService.cs +++ b/src/InformaticsGateway/Services/Export/Hl7ExportService.cs @@ -28,9 +28,9 @@ using Monai.Deploy.InformaticsGateway.Configuration; using Monai.Deploy.InformaticsGateway.Database.Api.Repositories; using Monai.Deploy.InformaticsGateway.Logging; -using Monai.Deploy.InformaticsGateway.Api.Mllp; using Monai.Deploy.Messaging.Common; using Polly; +using Monai.Deploy.InformaticsGateway.Api.Mllp; namespace Monai.Deploy.InformaticsGateway.Services.Export { @@ -38,27 +38,28 @@ internal class Hl7ExportService : ExportServiceBase { private readonly ILogger _logger; private readonly InformaticsGatewayConfiguration _configuration; - private readonly IMllpService _mllpService; protected override ushort Concurrency { get; } public override string RoutingKey { get; } public override string ServiceName => "DICOM Export HL7 Service"; + private readonly IMllpService _mllpService; public Hl7ExportService( ILogger logger, IServiceScopeFactory serviceScopeFactory, IOptions configuration, - IDicomToolkit dicomToolkit) + IDicomToolkit dicomToolkit, + IMllpService mllpService) : base(logger, configuration, serviceScopeFactory, dicomToolkit) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _configuration = configuration.Value ?? throw new ArgumentNullException(nameof(configuration)); - _mllpService = serviceScopeFactory.CreateScope().ServiceProvider.GetRequiredService(); RoutingKey = $"{configuration.Value.Messaging.Topics.ExportHL7}"; ExportCompleteTopic = $"{configuration.Value.Messaging.Topics.ExportHl7Complete}"; Concurrency = _configuration.Dicom.Scu.MaximumNumberOfAssociations; + _mllpService = mllpService ?? throw new ArgumentNullException(nameof(mllpService)); } @@ -159,6 +160,5 @@ protected override Task ExecuteOutputDataEngineCallbac { return Task.FromResult(exportDataRequest); } - } } diff --git a/src/InformaticsGateway/Services/HealthLevel7/MllpService.cs b/src/InformaticsGateway/Services/HealthLevel7/MllpService.cs index c8163d6dc..4d0bb9fef 100755 --- a/src/InformaticsGateway/Services/HealthLevel7/MllpService.cs +++ b/src/InformaticsGateway/Services/HealthLevel7/MllpService.cs @@ -1,5 +1,5 @@ -/* - * Copyright 2022-2023 MONAI Consortium +/* + * Copyright 2023 MONAI Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,253 +15,30 @@ */ using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO.Abstractions; -using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; -using Ardalis.GuardClauses; using HL7.Dotnetcore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Monai.Deploy.InformaticsGateway.Api.PlugIns; -using Monai.Deploy.InformaticsGateway.Api.Rest; -using Monai.Deploy.InformaticsGateway.Api.Storage; -using Monai.Deploy.InformaticsGateway.Common; +using Monai.Deploy.InformaticsGateway.Api.Mllp; using Monai.Deploy.InformaticsGateway.Configuration; -using Monai.Deploy.InformaticsGateway.Database.Api.Repositories; using Monai.Deploy.InformaticsGateway.Logging; -using Monai.Deploy.InformaticsGateway.Services.Common; -using Monai.Deploy.InformaticsGateway.Services.Connectors; -using Monai.Deploy.InformaticsGateway.Services.HealthLevel7; -using Monai.Deploy.InformaticsGateway.Services.Storage; -using Monai.Deploy.Messaging.Events; -namespace Monai.Deploy.InformaticsGateway.Api.Mllp +namespace Monai.Deploy.InformaticsGateway.Services.HealthLevel7 { - internal sealed class MllpService : IMllpService, IHostedService, IDisposable, IMonaiService + internal class MllpService : IMllpService { - private const int SOCKET_OPERATION_CANCELLED = 125; - private bool _disposedValue; - private readonly ITcpListener _tcpListener; - private readonly IMllpClientFactory _mllpClientFactory; - private readonly IObjectUploadQueue _uploadQueue; - private readonly IPayloadAssembler _payloadAssembler; - private readonly IFileSystem _fileSystem; - private readonly ILoggerFactory _logginFactory; - private readonly ILogger _logger; - private readonly IOptions _configuration; - private readonly IStorageInfoProvider _storageInfoProvider; - private readonly ConcurrentDictionary _activeTasks; - private readonly IMllpExtract _mIIpExtract; - private readonly IInputHL7DataPlugInEngine _inputHL7DataPlugInEngine; - private readonly IHl7ApplicationConfigRepository _hl7ApplicationConfigRepository; - private DateTime _lastConfigRead = new(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - public int ActiveConnections - { - get - { - return _activeTasks.Count; - } - } - - public ServiceStatus Status { get; set; } = ServiceStatus.Unknown; - - public string ServiceName => "HL7 Service"; - - public MllpService(IServiceScopeFactory serviceScopeFactory, IOptions configuration) - { - ArgumentNullException.ThrowIfNull(serviceScopeFactory, nameof(serviceScopeFactory)); - - _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); - - var serviceScope = serviceScopeFactory.CreateScope(); - _logginFactory = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(ILoggerFactory)); - _logger = _logginFactory.CreateLogger(); - var tcpListenerFactory = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(ITcpListenerFactory)); - _tcpListener = tcpListenerFactory.CreateTcpListener(System.Net.IPAddress.Any, _configuration.Value.Hl7.Port); - _mllpClientFactory = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IMllpClientFactory)); - _uploadQueue = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IObjectUploadQueue)); - _payloadAssembler = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IPayloadAssembler)); - _fileSystem = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IFileSystem)); - _storageInfoProvider = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IStorageInfoProvider)); - _mIIpExtract = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IMllpExtract)); - _activeTasks = new ConcurrentDictionary(); - _inputHL7DataPlugInEngine = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IInputHL7DataPlugInEngine)); - _hl7ApplicationConfigRepository = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IHl7ApplicationConfigRepository)); - } - - public Task StartAsync(CancellationToken cancellationToken) - { - var task = Task.Run(async () => - { - _tcpListener.Start(); - await BackgroundProcessing(cancellationToken).ConfigureAwait(true); - }, CancellationToken.None); - - Status = ServiceStatus.Running; - _logger.ServiceRunning(ServiceName); - _logger.Hl7ListeningOnPort(_configuration.Value.Hl7.Port); - - if (task.IsCompleted) - return task; - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) - { - _logger.ServiceStopping(ServiceName); - _tcpListener.Stop(); - Status = ServiceStatus.Stopped; - return Task.CompletedTask; - } - - private async Task BackgroundProcessing(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - IMllpClient? mllpClient = null; - try - { - WaitUntilAvailable(_configuration.Value.Hl7.MaximumNumberOfConnections); - var client = await _tcpListener.AcceptTcpClientAsync(cancellationToken).ConfigureAwait(false); - _logger.ClientConnected(); - - if (!_storageInfoProvider.HasSpaceAvailableToStore) - { - _logger.Hl7DisconnectedDueToLowStorageSpace(_storageInfoProvider.AvailableFreeSpace); - client.Close(); - await Task.Delay(5000, cancellationToken).ConfigureAwait(false); - continue; - } - - mllpClient = _mllpClientFactory.CreateClient(client, _configuration.Value.Hl7, _logginFactory.CreateLogger()); - _ = mllpClient.Start(OnDisconnect, cancellationToken); - _activeTasks.TryAdd(mllpClient.ClientId, mllpClient); - } - catch (System.Net.Sockets.SocketException ex) - { - _logger.Hl7SocketException(ex.Message); - - if (mllpClient is not null) - { - mllpClient.Dispose(); - _activeTasks.Remove(mllpClient.ClientId, out _); - } - - if (ex.ErrorCode == SOCKET_OPERATION_CANCELLED) - { - break; - } - } - catch (Exception ex) - { - _logger.ServiceInvalidOrCancelled(ServiceName, ex); - } - } - Status = ServiceStatus.Cancelled; - _logger.ServiceCancelled(ServiceName); - } - - private async Task OnDisconnect(IMllpClient client, MllpClientResult result) - { - Guard.Against.Null(client, nameof(client)); - Guard.Against.Null(result, nameof(result)); - await ConfigurePlugInEngine().ConfigureAwait(false); - - try - { - foreach (var message in result.Messages) - { - var newMessage = message; - var hl7Filemetadata = new Hl7FileStorageMetadata(client.ClientId.ToString(), DataService.HL7, client.ClientIp); - var configItem = await _mIIpExtract.GetConfigItem(message).ConfigureAwait(false); - if (configItem is not null) - { - await _inputHL7DataPlugInEngine.ExecutePlugInsAsync(message, hl7Filemetadata, configItem).ConfigureAwait(false); - newMessage = await _mIIpExtract.ExtractInfo(hl7Filemetadata, message, configItem).ConfigureAwait(false); - - _logger.HL7MessageAfterPluginProcessing(newMessage.HL7Message, hl7Filemetadata.CorrelationId); - } - _logger.Hl7MessageReceieved(newMessage.HL7Message); - await hl7Filemetadata.SetDataStream(newMessage.HL7Message, _configuration.Value.Storage.TemporaryDataStorage, _fileSystem, _configuration.Value.Storage.LocalTemporaryStoragePath).ConfigureAwait(false); - var payloadId = await _payloadAssembler.Queue(client.ClientId.ToString(), hl7Filemetadata, new DataOrigin { DataService = DataService.HL7, Source = client.ClientIp, Destination = FileStorageMetadata.IpAddress() }).ConfigureAwait(false); - hl7Filemetadata.PayloadId ??= payloadId.ToString(); - _uploadQueue.Queue(hl7Filemetadata); - } - } - catch (Exception ex) - { - _logger.ErrorHandlingHl7Results(ex); - } - finally - { - _activeTasks.Remove(client.ClientId, out _); - _logger.Hl7ClientRemoved(client.ClientId); - client.Dispose(); - } - } - - private async Task ConfigurePlugInEngine() - { - var configs = await _hl7ApplicationConfigRepository.GetAllAsync().ConfigureAwait(false); - if (configs is not null && configs.Count is not 0 && configs.Max(c => c.LastModified) > _lastConfigRead) - { - var pluginAssemblies = new List(); - foreach (var config in configs.Where(p => p.PlugInAssemblies?.Count > 0)) - { - try - { - pluginAssemblies.AddRange(config.PlugInAssemblies.Where(p => string.IsNullOrWhiteSpace(p) is false && pluginAssemblies.Contains(p) is false)); - } - catch (Exception ex) - { - _logger.HL7PluginLoadingExceptions(ex); - } - } - if (pluginAssemblies.Count is not 0) - { - _inputHL7DataPlugInEngine.Configure(pluginAssemblies); - } - } - _lastConfigRead = DateTime.UtcNow; - } - - private void WaitUntilAvailable(int maximumNumberOfConnections) - { - var count = 0; - while (ActiveConnections >= maximumNumberOfConnections) - { - if (++count % 25 == 1) - { - _logger.MaxedOutHl7Connections(maximumNumberOfConnections); - } - Thread.Sleep(100); - } - } + private readonly ILogger _logger; + private readonly InformaticsGatewayConfiguration _configuration; - private void Dispose(bool disposing) + public MllpService(ILogger logger, IOptions configuration) { - if (!_disposedValue) - { - if (disposing) - { - foreach (var client in _activeTasks.Values) - { - client.Dispose(); - } - } - - _disposedValue = true; - } + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _configuration = configuration?.Value ?? throw new ArgumentNullException(nameof(configuration)); ; } public async Task SendMllp(IPAddress address, int port, string hl7Message, CancellationToken cancellationToken) @@ -312,7 +89,7 @@ private async Task WriteMessage(byte[] sendMessageByteBuffer, IPAddress address, private async Task EnsureAck(NetworkStream networkStream) { using var s_cts = new CancellationTokenSource(); - s_cts.CancelAfter(_configuration.Value.Hl7.ClientTimeoutMilliseconds); + s_cts.CancelAfter(_configuration.Hl7.ClientTimeoutMilliseconds); var buffer = new byte[2048]; // get the SentHl7Message @@ -344,12 +121,5 @@ private async Task EnsureAck(NetworkStream networkStream) } throw new Hl7SendException("ACK message contains no ACK!"); } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/src/InformaticsGateway/Services/HealthLevel7/MllpServiceHost.cs b/src/InformaticsGateway/Services/HealthLevel7/MllpServiceHost.cs new file mode 100755 index 000000000..4b90666c5 --- /dev/null +++ b/src/InformaticsGateway/Services/HealthLevel7/MllpServiceHost.cs @@ -0,0 +1,269 @@ +/* + * Copyright 2022-2023 MONAI Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO.Abstractions; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Ardalis.GuardClauses; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Monai.Deploy.InformaticsGateway.Api.PlugIns; +using Monai.Deploy.InformaticsGateway.Api.Rest; +using Monai.Deploy.InformaticsGateway.Api.Storage; +using Monai.Deploy.InformaticsGateway.Common; +using Monai.Deploy.InformaticsGateway.Configuration; +using Monai.Deploy.InformaticsGateway.Database.Api.Repositories; +using Monai.Deploy.InformaticsGateway.Logging; +using Monai.Deploy.InformaticsGateway.Services.Common; +using Monai.Deploy.InformaticsGateway.Services.Connectors; +using Monai.Deploy.InformaticsGateway.Services.Storage; +using Monai.Deploy.Messaging.Events; + +namespace Monai.Deploy.InformaticsGateway.Api.Mllp +{ + internal sealed class MllpServiceHost : IHostedService, IDisposable, IMonaiService + { + private const int SOCKET_OPERATION_CANCELLED = 125; + private bool _disposedValue; + private readonly ITcpListener _tcpListener; + private readonly IMllpClientFactory _mllpClientFactory; + private readonly IObjectUploadQueue _uploadQueue; + private readonly IPayloadAssembler _payloadAssembler; + private readonly IFileSystem _fileSystem; + private readonly ILoggerFactory _logginFactory; + private readonly ILogger _logger; + private readonly IOptions _configuration; + private readonly IStorageInfoProvider _storageInfoProvider; + private readonly ConcurrentDictionary _activeTasks; + private readonly IMllpExtract _mIIpExtract; + private readonly IInputHL7DataPlugInEngine _inputHL7DataPlugInEngine; + private readonly IHl7ApplicationConfigRepository _hl7ApplicationConfigRepository; + private DateTime _lastConfigRead = new(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public int ActiveConnections + { + get + { + return _activeTasks.Count; + } + } + + public ServiceStatus Status { get; set; } = ServiceStatus.Unknown; + + public string ServiceName => "HL7 Service"; + + public MllpServiceHost(IServiceScopeFactory serviceScopeFactory, IOptions configuration) + { + ArgumentNullException.ThrowIfNull(serviceScopeFactory, nameof(serviceScopeFactory)); + + _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + + var serviceScope = serviceScopeFactory.CreateScope(); + _logginFactory = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(ILoggerFactory)); + _logger = _logginFactory.CreateLogger(); + var tcpListenerFactory = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(ITcpListenerFactory)); + _tcpListener = tcpListenerFactory.CreateTcpListener(System.Net.IPAddress.Any, _configuration.Value.Hl7.Port); + _mllpClientFactory = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IMllpClientFactory)); + _uploadQueue = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IObjectUploadQueue)); + _payloadAssembler = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IPayloadAssembler)); + _fileSystem = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IFileSystem)); + _storageInfoProvider = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IStorageInfoProvider)); + _mIIpExtract = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IMllpExtract)); + _activeTasks = new ConcurrentDictionary(); + _inputHL7DataPlugInEngine = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IInputHL7DataPlugInEngine)); + _hl7ApplicationConfigRepository = serviceScope.ServiceProvider.GetService() ?? throw new ServiceNotFoundException(nameof(IHl7ApplicationConfigRepository)); + } + + public Task StartAsync(CancellationToken cancellationToken) + { + var task = Task.Run(async () => + { + _tcpListener.Start(); + await BackgroundProcessing(cancellationToken).ConfigureAwait(true); + }, CancellationToken.None); + + Status = ServiceStatus.Running; + _logger.ServiceRunning(ServiceName); + _logger.Hl7ListeningOnPort(_configuration.Value.Hl7.Port); + + if (task.IsCompleted) + return task; + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _logger.ServiceStopping(ServiceName); + _tcpListener.Stop(); + Status = ServiceStatus.Stopped; + return Task.CompletedTask; + } + + private async Task BackgroundProcessing(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + IMllpClient? mllpClient = null; + try + { + WaitUntilAvailable(_configuration.Value.Hl7.MaximumNumberOfConnections); + var client = await _tcpListener.AcceptTcpClientAsync(cancellationToken).ConfigureAwait(false); + _logger.ClientConnected(); + + if (!_storageInfoProvider.HasSpaceAvailableToStore) + { + _logger.Hl7DisconnectedDueToLowStorageSpace(_storageInfoProvider.AvailableFreeSpace); + client.Close(); + await Task.Delay(5000, cancellationToken).ConfigureAwait(false); + continue; + } + + mllpClient = _mllpClientFactory.CreateClient(client, _configuration.Value.Hl7, _logginFactory.CreateLogger()); + _ = mllpClient.Start(OnDisconnect, cancellationToken); + _activeTasks.TryAdd(mllpClient.ClientId, mllpClient); + } + catch (System.Net.Sockets.SocketException ex) + { + _logger.Hl7SocketException(ex.Message); + + if (mllpClient is not null) + { + mllpClient.Dispose(); + _activeTasks.Remove(mllpClient.ClientId, out _); + } + + if (ex.ErrorCode == SOCKET_OPERATION_CANCELLED) + { + break; + } + } + catch (Exception ex) + { + _logger.ServiceInvalidOrCancelled(ServiceName, ex); + } + } + Status = ServiceStatus.Cancelled; + _logger.ServiceCancelled(ServiceName); + } + + private async Task OnDisconnect(IMllpClient client, MllpClientResult result) + { + Guard.Against.Null(client, nameof(client)); + Guard.Against.Null(result, nameof(result)); + + await ConfigurePlugInEngine().ConfigureAwait(false); + + try + { + foreach (var message in result.Messages) + { + var newMessage = message; + var hl7Filemetadata = new Hl7FileStorageMetadata(client.ClientId.ToString(), DataService.HL7, client.ClientIp); + var configItem = await _mIIpExtract.GetConfigItem(message).ConfigureAwait(false); + if (configItem is not null) + { + await _inputHL7DataPlugInEngine.ExecutePlugInsAsync(message, hl7Filemetadata, configItem).ConfigureAwait(false); + newMessage = await _mIIpExtract.ExtractInfo(hl7Filemetadata, message, configItem).ConfigureAwait(false); + + _logger.HL7MessageAfterPluginProcessing(newMessage.HL7Message, hl7Filemetadata.CorrelationId); + } + _logger.Hl7MessageReceieved(newMessage.HL7Message); + await hl7Filemetadata.SetDataStream(newMessage.HL7Message, _configuration.Value.Storage.TemporaryDataStorage, _fileSystem, _configuration.Value.Storage.LocalTemporaryStoragePath).ConfigureAwait(false); + var payloadId = await _payloadAssembler.Queue(client.ClientId.ToString(), hl7Filemetadata, new DataOrigin { DataService = DataService.HL7, Source = client.ClientIp, Destination = FileStorageMetadata.IpAddress() }).ConfigureAwait(false); + hl7Filemetadata.PayloadId ??= payloadId.ToString(); + _uploadQueue.Queue(hl7Filemetadata); + } + } + catch (Exception ex) + { + _logger.ErrorHandlingHl7Results(ex); + } + finally + { + _activeTasks.Remove(client.ClientId, out _); + _logger.Hl7ClientRemoved(client.ClientId); + client.Dispose(); + } + } + + private async Task ConfigurePlugInEngine() + { + var configs = await _hl7ApplicationConfigRepository.GetAllAsync().ConfigureAwait(false); + if (configs is not null && configs.Count is not 0 && configs.Max(c => c.LastModified) > _lastConfigRead) + { + var pluginAssemblies = new List(); + foreach (var config in configs.Where(p => p.PlugInAssemblies?.Count > 0)) + { + try + { + pluginAssemblies.AddRange(config.PlugInAssemblies.Where(p => string.IsNullOrWhiteSpace(p) is false && pluginAssemblies.Contains(p) is false)); + } + catch (Exception ex) + { + _logger.HL7PluginLoadingExceptions(ex); + } + } + if (pluginAssemblies.Count is not 0) + { + _inputHL7DataPlugInEngine.Configure(pluginAssemblies); + } + } + _lastConfigRead = DateTime.UtcNow; + } + + private void WaitUntilAvailable(int maximumNumberOfConnections) + { + var count = 0; + while (ActiveConnections >= maximumNumberOfConnections) + { + if (++count % 25 == 1) + { + _logger.MaxedOutHl7Connections(maximumNumberOfConnections); + } + Thread.Sleep(100); + } + } + + private void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + foreach (var client in _activeTasks.Values) + { + client.Dispose(); + } + } + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/InformaticsGateway/Services/Http/MonaiHealthCheck.cs b/src/InformaticsGateway/Services/Http/MonaiHealthCheck.cs old mode 100644 new mode 100755 index 007995a2f..771c229de --- a/src/InformaticsGateway/Services/Http/MonaiHealthCheck.cs +++ b/src/InformaticsGateway/Services/Http/MonaiHealthCheck.cs @@ -18,7 +18,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; + using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Monai.Deploy.InformaticsGateway.Logging; using Monai.Deploy.InformaticsGateway.Repositories; namespace Monai.Deploy.InformaticsGateway.Services.Http @@ -26,10 +29,12 @@ namespace Monai.Deploy.InformaticsGateway.Services.Http public class MonaiHealthCheck : IHealthCheck { private readonly IMonaiServiceLocator _monaiServiceLocator; + private readonly ILogger _logger; - public MonaiHealthCheck(IMonaiServiceLocator monaiServiceLocator) + public MonaiHealthCheck(IMonaiServiceLocator monaiServiceLocator, ILogger logger) { _monaiServiceLocator = monaiServiceLocator ?? throw new ArgumentNullException(nameof(monaiServiceLocator)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) @@ -44,9 +49,10 @@ public Task CheckHealthAsync(HealthCheckContext context, Canc if (unhealthyServices.Count == services.Count) { + _logger.AllServiceUnheathly(); return Task.FromResult(HealthCheckResult.Unhealthy(data: unhealthyServices)); } - + _logger.SomeServiceUnheathly(string.Join(",", unhealthyServices.Keys)); return Task.FromResult(HealthCheckResult.Degraded(data: unhealthyServices)); } } diff --git a/src/InformaticsGateway/Test/Repositories/MonaiServiceLocatorTest.cs b/src/InformaticsGateway/Test/Repositories/MonaiServiceLocatorTest.cs index d7a3dc915..0202492e3 100755 --- a/src/InformaticsGateway/Test/Repositories/MonaiServiceLocatorTest.cs +++ b/src/InformaticsGateway/Test/Repositories/MonaiServiceLocatorTest.cs @@ -15,6 +15,8 @@ */ using System; +using System.Collections.Generic; +using Microsoft.Extensions.Hosting; using Monai.Deploy.InformaticsGateway.Api.Rest; using Monai.Deploy.InformaticsGateway.Repositories; using Monai.Deploy.InformaticsGateway.Services.Common; @@ -37,26 +39,30 @@ public MonaiServiceLocatorTest() mock.SetupGet(p => p.ServiceName).Returns(type.Name); return mock.Object; }); + + _serviceProvider.Setup(sp => sp.GetService(typeof(IEnumerable))) + .Returns((Type type) => + { + var mock = new Mock(); + return new List { mock.Object }; + }); } [Fact(DisplayName = "GetMonaiServices")] public void GetMonaiServices() { + var hosted = new List() + { + new Mock().Object + }; + + + var serviceLocator = new MonaiServiceLocator(_serviceProvider.Object); var result = serviceLocator.GetMonaiServices(); Assert.Collection(result, - items => items.ServiceName.Equals("DataRetrievalService"), - items => items.ServiceName.Equals("ScpService"), - items => items.ServiceName.Equals("ScuService"), - items => items.ServiceName.Equals("ExtAppScuService"), - items => items.ServiceName.Equals("SpaceReclaimerService"), - items => items.ServiceName.Equals("DicomWebExportService"), - items => items.ServiceName.Equals("ScuExportService"), - items => items.ServiceName.Equals("PayloadNotificationService"), - items => items.ServiceName.Equals("HL7 Service"), - items => items.ServiceName.Equals("ExtAppScuExportService"), - items => items.ServiceName.Equals("Hl7ExportService")); + items => items.ServiceName.Equals("IPayloadService")); } [Fact(DisplayName = "GetServiceStatus")] @@ -65,7 +71,7 @@ public void GetServiceStatus() var serviceLocator = new MonaiServiceLocator(_serviceProvider.Object); var result = serviceLocator.GetServiceStatus(); - Assert.Equal(11, result.Count); + Assert.Equal(1, result.Count); foreach (var svc in result.Keys) { Assert.Equal(ServiceStatus.Running, result[svc]); diff --git a/src/InformaticsGateway/Test/Services/Export/ExportHl7ServiceTests.cs b/src/InformaticsGateway/Test/Services/Export/ExportHl7ServiceTests.cs index 0897a68a2..27677cce7 100755 --- a/src/InformaticsGateway/Test/Services/Export/ExportHl7ServiceTests.cs +++ b/src/InformaticsGateway/Test/Services/Export/ExportHl7ServiceTests.cs @@ -97,10 +97,11 @@ public ExportHl7ServiceTests() [RetryFact(1, 250, DisplayName = "Constructor - throws on null params")] public void Constructor_ThrowsOnNullParams() { - Assert.Throws(() => new Hl7ExportService(null, null, null, null)); - Assert.Throws(() => new Hl7ExportService(_logger.Object, null, null, null)); - Assert.Throws(() => new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, null, null)); - Assert.Throws(() => new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, null)); + Assert.Throws(() => new Hl7ExportService(null, null, null, null, null)); + Assert.Throws(() => new Hl7ExportService(_logger.Object, null, null, null, null)); + Assert.Throws(() => new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, null, null, null)); + Assert.Throws(() => new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, null, null)); + Assert.Throws(() => new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, _dicomToolkit.Object, null)); } @@ -123,7 +124,7 @@ public async Task ShallFailWhenNoDestinationIsDefined() _storageService.Setup(p => p.GetObjectAsync(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(new MemoryStream(Encoding.UTF8.GetBytes("test"))); - var service = new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, _dicomToolkit.Object); + var service = new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, _dicomToolkit.Object, _mllpService.Object); var dataflowCompleted = new ManualResetEvent(false); service.ReportActionCompleted += (sender, args) => @@ -168,7 +169,7 @@ public async Task ShallFailWhenDestinationIsNotConfigured() _repository.Setup(p => p.FindByNameAsync(It.IsAny(), It.IsAny())).ReturnsAsync(default(HL7DestinationEntity)); - var service = new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, _dicomToolkit.Object); + var service = new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, _dicomToolkit.Object, _mllpService.Object); var dataflowCompleted = new ManualResetEvent(false); service.ReportActionCompleted += (sender, args) => @@ -221,7 +222,7 @@ public async Task No_Ack_Sent() _repository.Setup(p => p.FindByNameAsync(It.IsAny(), It.IsAny())).ReturnsAsync(destination); _dicomToolkit.Setup(p => p.Load(It.IsAny())).Returns(InstanceGenerator.GenerateDicomFile(sopInstanceUid: sopInstanceUid)); - var service = new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, _dicomToolkit.Object); + var service = new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, _dicomToolkit.Object, _mllpService.Object); var dataflowCompleted = new ManualResetEvent(false); service.ReportActionCompleted += (sender, args) => @@ -258,7 +259,7 @@ public async Task Error_Loading_HL7_Content() _extAppScpLogger.Invocations.Clear(); var sopInstanceUid = DicomUIDGenerator.GenerateDerivedFromUUID().UID; var destination = new HL7DestinationEntity { HostIp = "192.168.0.0", Port = _port }; - var service = new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, _dicomToolkit.Object); + var service = new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, _dicomToolkit.Object, _mllpService.Object); _messagePublisherService.Setup(p => p.Publish(It.IsAny(), It.IsAny())); _messageSubscriberService.Setup(p => p.Acknowledge(It.IsAny())); @@ -310,7 +311,7 @@ public async Task Success_After_Hl7Send() _extAppScpLogger.Invocations.Clear(); var sopInstanceUid = DicomUIDGenerator.GenerateDerivedFromUUID().UID; var destination = new HL7DestinationEntity { HostIp = "192.168.0.0", Port = _port }; - var service = new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, _dicomToolkit.Object); + var service = new Hl7ExportService(_logger.Object, _serviceScopeFactory.Object, _configuration, _dicomToolkit.Object, _mllpService.Object); _messagePublisherService.Setup(p => p.Publish(It.IsAny(), It.IsAny())); _messageSubscriberService.Setup(p => p.Acknowledge(It.IsAny())); diff --git a/src/InformaticsGateway/Test/Services/HealthLevel7/MllpServiceTest.cs b/src/InformaticsGateway/Test/Services/HealthLevel7/MllpServiceTest.cs index 371e7ab69..363161662 100755 --- a/src/InformaticsGateway/Test/Services/HealthLevel7/MllpServiceTest.cs +++ b/src/InformaticsGateway/Test/Services/HealthLevel7/MllpServiceTest.cs @@ -56,7 +56,7 @@ public class MllpServiceTest private readonly Mock _fileSystem; private readonly CancellationTokenSource _cancellationTokenSource; private readonly Mock _serviceScope; - private readonly Mock> _logger; + private readonly Mock> _logger; private readonly IServiceProvider _serviceProvider; private readonly Mock _storageInfoProvider; private readonly Mock _mIIpExtract = new Mock(); @@ -79,7 +79,7 @@ public MllpServiceTest() _cancellationTokenSource = new CancellationTokenSource(); _serviceScope = new Mock(); - _logger = new Mock>(); + _logger = new Mock>(); _serviceScopeFactory.Setup(p => p.CreateScope()).Returns(_serviceScope.Object); @@ -109,16 +109,16 @@ public MllpServiceTest() [RetryFact(10, 250)] public void GivenAMllpService_WhenInitialized_ExpectParametersToBeValidated() { - Assert.Throws(() => new MllpService(null, null)); - Assert.Throws(() => new MllpService(_serviceScopeFactory.Object, null)); + Assert.Throws(() => new MllpServiceHost(null, null)); + Assert.Throws(() => new MllpServiceHost(_serviceScopeFactory.Object, null)); - new MllpService(_serviceScopeFactory.Object, _options); + new MllpServiceHost(_serviceScopeFactory.Object, _options); } [RetryFact(5, 250)] public void GivenAMllpService_WhenStartAsyncIsCalled_ExpectServiceStartupNormally() { - var service = new MllpService(_serviceScopeFactory.Object, _options); + var service = new MllpServiceHost(_serviceScopeFactory.Object, _options); var task = service.StartAsync(_cancellationTokenSource.Token); Assert.NotNull(task); @@ -129,7 +129,7 @@ public void GivenAMllpService_WhenStartAsyncIsCalled_ExpectServiceStartupNormall public void GivenAMllpService_WhenStopAsyncIsCalled_ExpectServiceStopsNormally() { _tcpListener.Setup(p => p.Stop()); - var service = new MllpService(_serviceScopeFactory.Object, _options); + var service = new MllpServiceHost(_serviceScopeFactory.Object, _options); var task = service.StopAsync(_cancellationTokenSource.Token); Assert.NotNull(task); @@ -172,7 +172,7 @@ public async Task GivenTcpConnections_WhenConnectsAndDisconnectsFromMllpService_ } }); - var service = new MllpService(_serviceScopeFactory.Object, _options); + var service = new MllpServiceHost(_serviceScopeFactory.Object, _options); _ = service.StartAsync(_cancellationTokenSource.Token); Assert.True(checkEvent.Wait(3000)); @@ -208,7 +208,7 @@ public async Task GivenAMllpService_WhenMaximumConnectionLimitIsConfigure_Expect _tcpListener.Setup(p => p.AcceptTcpClientAsync(It.IsAny())) .Returns(ValueTask.FromResult((new Mock()).Object)); - var service = new MllpService(_serviceScopeFactory.Object, _options); + var service = new MllpServiceHost(_serviceScopeFactory.Object, _options); _ = service.StartAsync(_cancellationTokenSource.Token); checkEvent.Wait(); @@ -242,7 +242,7 @@ public async Task GivenConnectedTcpClients_WhenDisconnects_ExpectServiceToDispos _tcpListener.Setup(p => p.AcceptTcpClientAsync(It.IsAny())) .Returns(ValueTask.FromResult((new Mock()).Object)); - var service = new MllpService(_serviceScopeFactory.Object, _options); + var service = new MllpServiceHost(_serviceScopeFactory.Object, _options); _ = service.StartAsync(_cancellationTokenSource.Token); Assert.True(checkEvent.Wait(3000)); @@ -267,7 +267,7 @@ public async Task GivenATcpClientWithHl7Messages_WhenStorageSpaceIsLow_ExpectToD _tcpListener.Setup(p => p.AcceptTcpClientAsync(It.IsAny())) .Returns(ValueTask.FromResult(clientAdapter.Object)); - var service = new MllpService(_serviceScopeFactory.Object, _options); + var service = new MllpServiceHost(_serviceScopeFactory.Object, _options); _ = service.StartAsync(_cancellationTokenSource.Token); _cancellationTokenSource.CancelAfter(400); @@ -311,7 +311,7 @@ public async Task GivenATcpClientWithHl7Messages_WhenDisconnected_ExpectMessageT _tcpListener.Setup(p => p.AcceptTcpClientAsync(It.IsAny())) .Returns(ValueTask.FromResult((new Mock()).Object)); - var service = new MllpService(_serviceScopeFactory.Object, _options); + var service = new MllpServiceHost(_serviceScopeFactory.Object, _options); _ = service.StartAsync(_cancellationTokenSource.Token); Assert.True(checkEvent.Wait(3000)); @@ -358,7 +358,7 @@ public async Task GivenATcpClientWithHl7Messages_WhenDisconnected_ExpectMessageT _tcpListener.Setup(p => p.AcceptTcpClientAsync(It.IsAny())) .Returns(ValueTask.FromResult((new Mock()).Object)); - var service = new MllpService(_serviceScopeFactory.Object, _options); + var service = new MllpServiceHost(_serviceScopeFactory.Object, _options); _ = service.StartAsync(_cancellationTokenSource.Token); Assert.True(checkEvent.Wait(3000)); @@ -403,7 +403,7 @@ public async Task GivenATcpClientWithHl7Messages_ShouldntAdddBlankPlugin() _tcpListener.Setup(p => p.AcceptTcpClientAsync(It.IsAny())) .Returns(ValueTask.FromResult((new Mock()).Object)); - var service = new MllpService(_serviceScopeFactory.Object, _options); + var service = new MllpServiceHost(_serviceScopeFactory.Object, _options); _ = service.StartAsync(_cancellationTokenSource.Token); Assert.True(checkEvent.Wait(3000)); diff --git a/src/InformaticsGateway/Test/Services/Http/MonaiHealthCheckTest.cs b/src/InformaticsGateway/Test/Services/Http/MonaiHealthCheckTest.cs old mode 100644 new mode 100755 index d3c63a9b2..f2548c1c7 --- a/src/InformaticsGateway/Test/Services/Http/MonaiHealthCheckTest.cs +++ b/src/InformaticsGateway/Test/Services/Http/MonaiHealthCheckTest.cs @@ -17,6 +17,8 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Monai.Deploy.InformaticsGateway.Repositories; using Monai.Deploy.InformaticsGateway.Services.Http; using Moq; @@ -27,6 +29,7 @@ namespace Monai.Deploy.InformaticsGateway.Test.Services.Http public class MonaiHealthCheckTest { private readonly Mock _monaiServiceLocator; + private readonly ILogger _logger = new NullLogger(); public MonaiHealthCheckTest() { @@ -43,7 +46,7 @@ public async Task GivenAllServicesRunning_WhenCheckHealthAsyncIsCalled_ReturnsHe { "C", Api.Rest.ServiceStatus.Running }, }); - var svc = new MonaiHealthCheck(_monaiServiceLocator.Object); + var svc = new MonaiHealthCheck(_monaiServiceLocator.Object, _logger); var result = await svc.CheckHealthAsync(null); Assert.Equal(HealthStatus.Healthy, result.Status); } @@ -58,7 +61,7 @@ public async Task GivenSomeServicesNotRunning_WhenCheckHealthAsyncIsCalled_Retur { "C", Api.Rest.ServiceStatus.Stopped }, }); - var svc = new MonaiHealthCheck(_monaiServiceLocator.Object); + var svc = new MonaiHealthCheck(_monaiServiceLocator.Object, _logger); var result = await svc.CheckHealthAsync(null); Assert.Equal(HealthStatus.Degraded, result.Status); Assert.Equal(Api.Rest.ServiceStatus.Cancelled, result.Data["B"]); @@ -75,7 +78,7 @@ public async Task GivenAllServicesNotRunning_WhenCheckHealthAsyncIsCalled_Return { "C", Api.Rest.ServiceStatus.Stopped }, }); - var svc = new MonaiHealthCheck(_monaiServiceLocator.Object); + var svc = new MonaiHealthCheck(_monaiServiceLocator.Object, _logger); var result = await svc.CheckHealthAsync(null); Assert.Equal(HealthStatus.Unhealthy, result.Status); diff --git a/tests/Integration.Test/Drivers/EfDataProvider.cs b/tests/Integration.Test/Drivers/EfDataProvider.cs old mode 100644 new mode 100755 index cda1b3b47..63b0bbb15 --- a/tests/Integration.Test/Drivers/EfDataProvider.cs +++ b/tests/Integration.Test/Drivers/EfDataProvider.cs @@ -62,7 +62,14 @@ public void ClearAllData() DumpAndClear("SourceApplicationEntities", _dbContext.SourceApplicationEntities.ToList()); DumpAndClear("MonaiApplicationEntities", _dbContext.MonaiApplicationEntities.ToList()); DumpAndClear("VirtualApplicationEntities", _dbContext.VirtualApplicationEntities.ToList()); - DumpAndClear("Payloads", _dbContext.Payloads.ToList()); + try + { + DumpAndClear("Payloads", _dbContext.Payloads.ToList()); + } + catch (Exception) + { + } + DumpAndClear("InferenceRequests", _dbContext.InferenceRequests.ToList()); DumpAndClear("StorageMetadataWrapperEntities", _dbContext.StorageMetadataWrapperEntities.ToList()); _dbContext.SaveChanges(); diff --git a/tests/Integration.Test/appsettings.json b/tests/Integration.Test/appsettings.json index 8e3d304b6..2a702e5ea 100755 --- a/tests/Integration.Test/appsettings.json +++ b/tests/Integration.Test/appsettings.json @@ -27,8 +27,8 @@ } }, "ConnectionStrings": { - "Type": "mongodb", - "InformaticsGatewayDatabase": "mongodb://root:rootpassword@localhost:27017", + "Type": "sqlite", + "InformaticsGatewayDatabase": "Data Source=./mig.db", "DatabaseOptions": { "DatabaseName": "InformaticsGateway", "retries": {