Skip to content

Commit a9ec685

Browse files
authored
Messaging service extension for health check service (#63)
* Add health check for RabbitMQ Signed-off-by: Victor Chang <[email protected]>
1 parent 64222e2 commit a9ec685

28 files changed

+498
-185
lines changed

.licenserc.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ header:
1313
- '**/*.ruleset'
1414
- 'src/.sonarlint/**'
1515
- 'src/coverlet.runsettings'
16+
- 'src/Monai.Deploy.Messaging.sln'
1617

1718
comment: on-failure
1819

@@ -28,4 +29,4 @@ header:
2829
Config:
2930
extensions:
3031
- ".conf"
31-
comment_style_id: Hashtag
32+
comment_style_id: Hashtag

doc/dependency_decisions.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@
2727
:versions:
2828
- 17.3.0
2929
:when: 2022-08-16 21:39:37.382080790 Z
30+
- - :approve
31+
- Microsoft.Extensions.Diagnostics.HealthChecks
32+
- :who: mocsharp
33+
:why: MIT (https://github.com/dotnet/aspnetcore/raw/main/LICENSE.txt)
34+
:versions:
35+
- 6.0.8
36+
:when: 2022-08-29 18:11:22.090772006 Z
37+
- - :approve
38+
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions
39+
- :who: mocsharp
40+
:why: MIT (https://github.com/dotnet/aspnetcore/raw/main/LICENSE.txt)
41+
:versions:
42+
- 6.0.8
43+
:when: 2022-08-29 18:11:22.090772006 Z
3044
- - :approve
3145
- Microsoft.Extensions.Configuration
3246
- :who: mocsharp
@@ -144,7 +158,7 @@
144158
- :who: mocsharp
145159
:why: MIT (https://github.com/dotnet/runtime/raw/main/LICENSE.TXT)
146160
:versions:
147-
- 6.0.0
161+
- 6.0.1
148162
:when: 2022-08-16 21:39:44.471693654 Z
149163
- - :approve
150164
- Microsoft.Extensions.Logging.Configuration

src/Messaging/Configuration/ConfigurationException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
16+
1717
using System.Runtime.Serialization;
1818

1919
namespace Monai.Deploy.Messaging.Configuration

src/Messaging/Events/ExportCompleteEvent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
16+
1717
using System.ComponentModel.DataAnnotations;
1818
using Ardalis.GuardClauses;
1919
using Newtonsoft.Json;

src/Messaging/Events/ExportRequestEvent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
16+
1717
using System.ComponentModel.DataAnnotations;
1818
using Newtonsoft.Json;
1919

src/Messaging/Events/ExportStatus.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
* limitations under the License.
1515
*/
1616

17-
1817
namespace Monai.Deploy.Messaging.Events
1918
{
2019
public enum ExportStatus

src/Messaging/Events/FailureReason.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
* limitations under the License.
1515
*/
1616

17-
1817
namespace Monai.Deploy.Messaging.Events
1918
{
2019
public enum FailureReason

src/Messaging/Events/WorkflowRequestEvent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
16+
1717
using System.ComponentModel.DataAnnotations;
1818
using Monai.Deploy.Messaging.Common;
1919
using Newtonsoft.Json;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2022 MONAI Consortium
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using Microsoft.Extensions.DependencyInjection;
18+
using Microsoft.Extensions.Diagnostics.HealthChecks;
19+
20+
namespace Monai.Deploy.Messaging
21+
{
22+
public abstract class SubscriberServiceHealthCheckRegistrationBase : HealthCheckRegistrationBase
23+
{
24+
}
25+
26+
public abstract class PublisherServiceHealthCheckRegistrationBase : HealthCheckRegistrationBase
27+
{
28+
}
29+
30+
public abstract class HealthCheckRegistrationBase
31+
{
32+
public abstract IHealthChecksBuilder Configure(
33+
IHealthChecksBuilder builder,
34+
HealthStatus? failureStatus = null,
35+
IEnumerable<string>? tags = null,
36+
TimeSpan? timeout = null);
37+
}
38+
}

src/Messaging/IServiceCollectionExtension.cs

Lines changed: 105 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,29 @@
1818
using System.Reflection;
1919
using Ardalis.GuardClauses;
2020
using Microsoft.Extensions.DependencyInjection;
21+
using Microsoft.Extensions.Diagnostics.HealthChecks;
2122
using Monai.Deploy.Messaging.API;
2223
using Monai.Deploy.Messaging.Configuration;
2324

2425
namespace Monai.Deploy.Messaging
2526
{
2627
public static class IServiceCollectionExtensions
2728
{
28-
private static IFileSystem? s_fileSystem;
29-
3029
/// <summary>
3130
/// Configures all dependencies required for the MONAI Deploy Message Broker Subscriber Service.
3231
/// </summary>
3332
/// <param name="services">Instance of <see cref="IServiceCollection"/>.</param>
3433
/// <param name="fullyQualifiedTypeName">Fully qualified type name of the service to use.</param>
3534
/// <returns>Instance of <see cref="IServiceCollection"/>.</returns>
3635
/// <exception cref="ConfigurationException"></exception>
37-
public static IServiceCollection AddMonaiDeployMessageBrokerSubscriberService(this IServiceCollection services, string fullyQualifiedTypeName)
38-
=> AddMonaiDeployMessageBrokerSubscriberService(services, fullyQualifiedTypeName, new FileSystem());
36+
public static IServiceCollection AddMonaiDeployMessageBrokerSubscriberService(
37+
this IServiceCollection services,
38+
string fullyQualifiedTypeName,
39+
bool registerHealthCheck = true,
40+
HealthStatus? failureStatus = null,
41+
IEnumerable<string>? tags = null,
42+
TimeSpan? timeout = null)
43+
=> AddMonaiDeployMessageBrokerSubscriberService(services, fullyQualifiedTypeName, new FileSystem(), registerHealthCheck, failureStatus, tags, timeout);
3944

4045
/// <summary>
4146
/// Configures all dependencies required for the MONAI Deploy Message Broker Subscriber Service.
@@ -45,8 +50,15 @@ public static IServiceCollection AddMonaiDeployMessageBrokerSubscriberService(th
4550
/// <param name="fileSystem">Instance of <see cref="IFileSystem"/>.</param>
4651
/// <returns>Instance of <see cref="IServiceCollection"/>.</returns>
4752
/// <exception cref="ConfigurationException"></exception>
48-
public static IServiceCollection AddMonaiDeployMessageBrokerSubscriberService(this IServiceCollection services, string fullyQualifiedTypeName, IFileSystem fileSystem)
49-
=> Add<IMessageBrokerSubscriberService, SubscriberServiceRegistrationBase>(services, fullyQualifiedTypeName, fileSystem);
53+
public static IServiceCollection AddMonaiDeployMessageBrokerSubscriberService(
54+
this IServiceCollection services,
55+
string fullyQualifiedTypeName,
56+
IFileSystem fileSystem,
57+
bool registerHealthCheck = true,
58+
HealthStatus? failureStatus = null,
59+
IEnumerable<string>? tags = null,
60+
TimeSpan? timeout = null)
61+
=> Add<IMessageBrokerSubscriberService, SubscriberServiceRegistrationBase, SubscriberServiceHealthCheckRegistrationBase>(services, fullyQualifiedTypeName, fileSystem, registerHealthCheck, failureStatus, tags, timeout);
5062

5163
/// <summary>
5264
/// Configures all dependencies required for the MONAI Deploy Message Broker Publisher Service.
@@ -55,8 +67,14 @@ public static IServiceCollection AddMonaiDeployMessageBrokerSubscriberService(th
5567
/// <param name="fullyQualifiedTypeName">Fully qualified type name of the service to use.</param>
5668
/// <returns>Instance of <see cref="IServiceCollection"/>.</returns>
5769
/// <exception cref="ConfigurationException"></exception>
58-
public static IServiceCollection AddMonaiDeployMessageBrokerPublisherService(this IServiceCollection services, string fullyQualifiedTypeName)
59-
=> AddMonaiDeployMessageBrokerPublisherService(services, fullyQualifiedTypeName, new FileSystem());
70+
public static IServiceCollection AddMonaiDeployMessageBrokerPublisherService(
71+
this IServiceCollection services,
72+
string fullyQualifiedTypeName,
73+
bool registerHealthCheck = true,
74+
HealthStatus? failureStatus = null,
75+
IEnumerable<string>? tags = null,
76+
TimeSpan? timeout = null)
77+
=> AddMonaiDeployMessageBrokerPublisherService(services, fullyQualifiedTypeName, new FileSystem(), registerHealthCheck, failureStatus, tags, timeout);
6078

6179
/// <summary>
6280
/// Configures all dependencies required for the MONAI Deploy Message Broker Publisher Service.
@@ -66,42 +84,96 @@ public static IServiceCollection AddMonaiDeployMessageBrokerPublisherService(thi
6684
/// <param name="fileSystem">Instance of <see cref="IFileSystem"/>.</param>
6785
/// <returns>Instance of <see cref="IServiceCollection"/>.</returns>
6886
/// <exception cref="ConfigurationException"></exception>
69-
public static IServiceCollection AddMonaiDeployMessageBrokerPublisherService(this IServiceCollection services, string fullyQualifiedTypeName, IFileSystem fileSystem)
70-
=> Add<IMessageBrokerPublisherService, PublisherServiceRegistrationBase>(services, fullyQualifiedTypeName, fileSystem);
71-
72-
private static IServiceCollection Add<T, U>(this IServiceCollection services, string fullyQualifiedTypeName, IFileSystem fileSystem) where U : ServiceRegistrationBase
87+
public static IServiceCollection AddMonaiDeployMessageBrokerPublisherService(
88+
this IServiceCollection services,
89+
string fullyQualifiedTypeName,
90+
IFileSystem fileSystem,
91+
bool registerHealthCheck = true,
92+
HealthStatus? failureStatus = null,
93+
IEnumerable<string>? tags = null,
94+
TimeSpan? timeout = null)
95+
=> Add<IMessageBrokerPublisherService, PublisherServiceRegistrationBase, PublisherServiceHealthCheckRegistrationBase>(services, fullyQualifiedTypeName, fileSystem, registerHealthCheck, failureStatus, tags, timeout);
96+
97+
private static IServiceCollection Add<T, U, V>(
98+
this IServiceCollection services,
99+
string fullyQualifiedTypeName,
100+
IFileSystem fileSystem,
101+
bool registerHealthCheck = true,
102+
HealthStatus? failureStatus = null,
103+
IEnumerable<string>? tags = null,
104+
TimeSpan? timeout = null)
105+
where U : ServiceRegistrationBase
106+
where V : HealthCheckRegistrationBase
73107
{
74108
Guard.Against.NullOrWhiteSpace(fullyQualifiedTypeName, nameof(fullyQualifiedTypeName));
75109
Guard.Against.Null(fileSystem, nameof(fileSystem));
76110

77-
s_fileSystem = fileSystem;
111+
ResolveEventHandler resolveEventHandler = (sender, args) =>
112+
{
113+
return CurrentDomain_AssemblyResolve(args, fileSystem);
114+
};
78115

79-
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
116+
AppDomain.CurrentDomain.AssemblyResolve += resolveEventHandler;
80117

81118
try
82119
{
83-
var serviceAssembly = LoadAssemblyFromDisk(GetAssemblyName(fullyQualifiedTypeName));
84-
var serviceRegistrationType = serviceAssembly.GetTypes().FirstOrDefault(p => p.BaseType == typeof(U));
120+
var serviceAssembly = LoadAssemblyFromDisk(GetAssemblyName(fullyQualifiedTypeName), fileSystem);
85121

86-
if (serviceRegistrationType is null || Activator.CreateInstance(serviceRegistrationType, fullyQualifiedTypeName) is not U serviceRegistrar)
122+
if (!IsSupportedType<T>(fullyQualifiedTypeName, serviceAssembly))
87123
{
88-
throw new ConfigurationException($"Service registrar cannot be found for the configured plug-in '{fullyQualifiedTypeName}'.");
124+
throw new ConfigurationException($"The configured type '{fullyQualifiedTypeName}' does not implement the {typeof(T).Name} interface.");
89125
}
90126

91-
if (!IsSupportedType<T>(fullyQualifiedTypeName, serviceAssembly))
127+
RegisterServices<U>(services, fullyQualifiedTypeName, serviceAssembly);
128+
129+
if (registerHealthCheck)
92130
{
93-
throw new ConfigurationException($"The configured type '{fullyQualifiedTypeName}' does not implement the {typeof(T).Name} interface.");
131+
RegisterHealtChecks<V>(services, fullyQualifiedTypeName, serviceAssembly, failureStatus, tags, timeout);
94132
}
95133

96-
return serviceRegistrar.Configure(services);
134+
return services;
97135
}
98136
finally
99137
{
100-
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
138+
AppDomain.CurrentDomain.AssemblyResolve -= resolveEventHandler;
139+
}
140+
}
141+
142+
private static void RegisterHealtChecks<V>(
143+
IServiceCollection services,
144+
string fullyQualifiedTypeName,
145+
Assembly serviceAssembly,
146+
HealthStatus? failureStatus,
147+
IEnumerable<string>? tags,
148+
TimeSpan? timeout) where V : HealthCheckRegistrationBase
149+
{
150+
var healthCheckBaseType = serviceAssembly.GetTypes().FirstOrDefault(p => p.BaseType == typeof(V));
151+
152+
if (healthCheckBaseType is null || Activator.CreateInstance(healthCheckBaseType) is not V healthCheckBuilderBase)
153+
{
154+
throw new ConfigurationException($"Health check registrar cannot be found for the configured plug-in '{fullyQualifiedTypeName}'.");
155+
}
156+
157+
var healthCheckBuilder = services.AddHealthChecks();
158+
healthCheckBuilderBase.Configure(healthCheckBuilder, failureStatus, tags, timeout);
159+
}
160+
161+
private static void RegisterServices<U>(
162+
IServiceCollection services,
163+
string fullyQualifiedTypeName,
164+
Assembly serviceAssembly) where U : ServiceRegistrationBase
165+
{
166+
var serviceRegistrationType = serviceAssembly.GetTypes().FirstOrDefault(p => p.BaseType == typeof(U));
167+
168+
if (serviceRegistrationType is null || Activator.CreateInstance(serviceRegistrationType) is not U serviceRegistrar)
169+
{
170+
throw new ConfigurationException($"Service registrar cannot be found for the configured plug-in '{fullyQualifiedTypeName}'.");
101171
}
172+
173+
serviceRegistrar.Configure(services);
102174
}
103175

104-
private static bool IsSupportedType<T>(string fullyQualifiedTypeName, Assembly storageServiceAssembly)
176+
internal static bool IsSupportedType<T>(string fullyQualifiedTypeName, Assembly storageServiceAssembly)
105177
{
106178
Guard.Against.NullOrWhiteSpace(fullyQualifiedTypeName, nameof(fullyQualifiedTypeName));
107179
Guard.Against.Null(storageServiceAssembly, nameof(storageServiceAssembly));
@@ -112,7 +184,7 @@ private static bool IsSupportedType<T>(string fullyQualifiedTypeName, Assembly s
112184
storageServiceType.GetInterfaces().Contains(typeof(T));
113185
}
114186

115-
private static string GetAssemblyName(string fullyQualifiedTypeName)
187+
internal static string GetAssemblyName(string fullyQualifiedTypeName)
116188
{
117189
var assemblyNameParts = fullyQualifiedTypeName.Split(',', StringSplitOptions.None);
118190
if (assemblyNameParts.Length < 2 || string.IsNullOrWhiteSpace(assemblyNameParts[1]))
@@ -126,31 +198,32 @@ private static string GetAssemblyName(string fullyQualifiedTypeName)
126198
return assemblyNameParts[1].Trim();
127199
}
128200

129-
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
201+
internal static Assembly CurrentDomain_AssemblyResolve(ResolveEventArgs args, IFileSystem fileSystem)
130202
{
131203
Guard.Against.Null(args, nameof(args));
204+
Guard.Against.Null(fileSystem, nameof(fileSystem));
132205

133206
var requestedAssemblyName = new AssemblyName(args.Name);
134-
return LoadAssemblyFromDisk(requestedAssemblyName.Name);
207+
return LoadAssemblyFromDisk(requestedAssemblyName.Name!, fileSystem);
135208
}
136209

137-
private static Assembly LoadAssemblyFromDisk(string assemblyName)
210+
internal static Assembly LoadAssemblyFromDisk(string assemblyName, IFileSystem fileSystem)
138211
{
139212
Guard.Against.NullOrWhiteSpace(assemblyName, nameof(assemblyName));
140-
Guard.Against.Null(s_fileSystem, nameof(s_fileSystem));
213+
Guard.Against.Null(fileSystem, nameof(fileSystem));
141214

142-
if (!s_fileSystem.Directory.Exists(SR.PlugInDirectoryPath))
215+
if (!fileSystem.Directory.Exists(SR.PlugInDirectoryPath))
143216
{
144217
throw new ConfigurationException($"Plug-in directory '{SR.PlugInDirectoryPath}' cannot be found.");
145218
}
146219

147-
var assemblyFilePath = s_fileSystem.Path.Combine(SR.PlugInDirectoryPath, $"{assemblyName}.dll");
148-
if (!s_fileSystem.File.Exists(assemblyFilePath))
220+
var assemblyFilePath = fileSystem.Path.Combine(SR.PlugInDirectoryPath, $"{assemblyName}.dll");
221+
if (!fileSystem.File.Exists(assemblyFilePath))
149222
{
150223
throw new ConfigurationException($"The configured plug-in '{assemblyFilePath}' cannot be found.");
151224
}
152225

153-
var asesmblyeData = s_fileSystem.File.ReadAllBytes(assemblyFilePath);
226+
var asesmblyeData = fileSystem.File.ReadAllBytes(assemblyFilePath);
154227
return Assembly.Load(asesmblyeData);
155228
}
156229
}

src/Messaging/Monai.Deploy.Messaging.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
<ItemGroup>
7777
<PackageReference Include="Ardalis.GuardClauses" Version="4.0.1" />
7878
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
79+
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.8" />
7980
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
8081
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
8182
<PackageReference Include="RabbitMQ.Client" Version="6.4.0" />

src/Messaging/SR.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
* limitations under the License.
1515
*/
1616

17-
1817
namespace Monai.Deploy.Messaging
1918
{
2019
internal static class SR

0 commit comments

Comments
 (0)