diff --git a/AspNetCore.Diagnostics.HealthChecks.sln b/AspNetCore.Diagnostics.HealthChecks.sln index ced291fa9a..aa729bff82 100644 --- a/AspNetCore.Diagnostics.HealthChecks.sln +++ b/AspNetCore.Diagnostics.HealthChecks.sln @@ -108,6 +108,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HealthChecks.UI.Branding", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HealthChecks.Azure.IoTHub", "src\HealthChecks.Azure.IoTHub\HealthChecks.Azure.IoTHub.csproj", "{252BB504-B7CB-4581-8CD8-D7398CAA16F5}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HealthChecks.IbmMQ", "src\HealthChecks.IbmMQ\HealthChecks.IbmMQ.csproj", "{E780FB0C-19B8-432B-A749-B9959F2CCA4A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -282,6 +284,10 @@ Global {252BB504-B7CB-4581-8CD8-D7398CAA16F5}.Debug|Any CPU.Build.0 = Debug|Any CPU {252BB504-B7CB-4581-8CD8-D7398CAA16F5}.Release|Any CPU.ActiveCfg = Release|Any CPU {252BB504-B7CB-4581-8CD8-D7398CAA16F5}.Release|Any CPU.Build.0 = Release|Any CPU + {E780FB0C-19B8-432B-A749-B9959F2CCA4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E780FB0C-19B8-432B-A749-B9959F2CCA4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E780FB0C-19B8-432B-A749-B9959F2CCA4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E780FB0C-19B8-432B-A749-B9959F2CCA4A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -329,6 +335,7 @@ Global {18F9E412-646D-4751-9751-30AA7A0233DF} = {2A3FD988-2BB8-43CF-B3A2-B70E648259D4} {B526834E-9392-4749-BAB2-7DF579F8F418} = {092533AB-7505-4EDC-8932-D40BF575D0D2} {252BB504-B7CB-4581-8CD8-D7398CAA16F5} = {2A3FD988-2BB8-43CF-B3A2-B70E648259D4} + {E780FB0C-19B8-432B-A749-B9959F2CCA4A} = {2A3FD988-2BB8-43CF-B3A2-B70E648259D4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2B8C62A1-11B6-469F-874C-A02443256568} diff --git a/build.ps1 b/build.ps1 index 8fddf81439..6885736a7d 100644 --- a/build.ps1 +++ b/build.ps1 @@ -140,4 +140,5 @@ else { exec { & dotnet pack .\src\HealthChecks.Kubernetes\HealthChecks.Kubernetes.csproj -c Release -o .\artifacts --include-symbols --no-build --version-suffix=$suffix } exec { & dotnet pack .\src\HealthChecks.SignalR\HealthChecks.SignalR.csproj -c Release -o .\artifacts --include-symbols --no-build --version-suffix=$suffix } exec { & dotnet pack .\src\HealthChecks.Gcp.CloudFirestore\HealthChecks.Gcp.CloudFirestore.csproj -c Release -o .\artifacts --include-symbols --no-build --version-suffix=$suffix } + exec { & dotnet pack .\src\HealthChecks.IbmMQ\HealthChecks.IbmMQ.csproj -c Release -o .\artifacts --include-symbols --no-build } } diff --git a/build/dependencies.props b/build/dependencies.props index 104dc74a81..1782226ebe 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -115,5 +115,6 @@ 3.0.0 3.0.0 3.0.0 + 3.0.0 diff --git a/docker-compose.yml b/docker-compose.yml index 04ebdca7a2..60b4fa9c8d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -128,4 +128,15 @@ services: image: consul:latest ports: - "8500:8500" - - "8600:8600" \ No newline at end of file + - "8600:8600" + ibmmq: + image: ibmcom/mq + ports: + - "1414:1414" + - "9157:9157" + environment: + - LICENSE=accept + - MQ_QMGR_NAME=QM.TEST.01 + - MQ_APP_PASSWORD=12345678 + - MQ_ENABLE_METRICS=true + \ No newline at end of file diff --git a/src/HealthChecks.IbmMQ/DependencyInjection/IbmMQHealthCheckBuilderExtensions.cs b/src/HealthChecks.IbmMQ/DependencyInjection/IbmMQHealthCheckBuilderExtensions.cs new file mode 100644 index 0000000000..6bc269f2a3 --- /dev/null +++ b/src/HealthChecks.IbmMQ/DependencyInjection/IbmMQHealthCheckBuilderExtensions.cs @@ -0,0 +1,81 @@ +using HealthChecks.IbmMQ; +using IBM.WMQ; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using System; +using System.Collections; +using System.Collections.Generic; + + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class IbmMQHealthCheckBuilderExtensions + { + const string NAME = "ibmmq"; + + /// + /// Add a health check for IbmMQ services using connection properties. + /// + /// The . + /// The name of the queue manager to use. + /// The list of properties that will be used for connection. + /// The health check name. Optional. If null the type name 'ibmmq' will be used for the name. + /// + /// The that should be reported when the health check fails. Optional. If null then + /// the default status of will be reported. + /// + /// A list of tags that can be used to filter sets of health checks. Optional. + /// An optional System.TimeSpan representing the timeout of the check. + /// The . + public static IHealthChecksBuilder AddIbmMQ(this IHealthChecksBuilder builder, string queueManager, Hashtable connectionProperties, string name = default, HealthStatus? failureStatus = default, IEnumerable tags = default, TimeSpan? timeout = default) + { + return builder.Add(new HealthCheckRegistration( + name ?? NAME, + new IbmMQHealthCheck(queueManager, connectionProperties), + failureStatus, + tags, + timeout)); + } + + /// + /// Add a health check for IbmMQ services using a managed connection. + /// + /// The . + /// The name of the queue manager to use. + /// The name of the channel. + /// The connection information in the following format HOSTNAME(PORT). + /// The user name. Optional. + /// The password. Optional + /// The health check name. Optional. If null the type name 'ibmmq' will be used for the name. + /// + /// The that should be reported when the health check fails. Optional. If null then + /// the default status of will be reported. + /// + /// A list of tags that can be used to filter sets of health checks. Optional. + /// An optional System.TimeSpan representing the timeout of the check. + /// The . + public static IHealthChecksBuilder AddIbmMQManagedConnection(this IHealthChecksBuilder builder, string queueManager, string channel, string connectionInfo, string userName = null, string password = null, string name = default, HealthStatus? failureStatus = default, IEnumerable tags = default, TimeSpan? timeout = default) + { + return builder.Add(new HealthCheckRegistration( + name ?? NAME, + new IbmMQHealthCheck(queueManager, BuildProperties(channel, connectionInfo, userName, password)), + failureStatus, + tags, + timeout)); + } + private static Hashtable BuildProperties(string channel, string connectionInfo, string userName = null, string password = null) + { + Hashtable properties = new Hashtable { + {MQC.CHANNEL_PROPERTY, channel}, + {MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_MANAGED}, + {MQC.CONNECTION_NAME_PROPERTY, connectionInfo} + }; + + if (!string.IsNullOrEmpty(userName)) + properties.Add(MQC.USER_ID_PROPERTY, userName); + if (!string.IsNullOrEmpty(password)) + properties.Add(MQC.PASSWORD_PROPERTY, password); + + return properties; + } + } +} diff --git a/src/HealthChecks.IbmMQ/HealthChecks.IbmMQ.csproj b/src/HealthChecks.IbmMQ/HealthChecks.IbmMQ.csproj new file mode 100644 index 0000000000..8ea53eebc1 --- /dev/null +++ b/src/HealthChecks.IbmMQ/HealthChecks.IbmMQ.csproj @@ -0,0 +1,24 @@ + + + + $(NetStandardTargetVersion) + $(PackageLicenseUrl) + $(PackageProjectUrl) + HealthCheck;IbmMQ + HealthChecks.IbmMQ is the health check package for IbmMQ. + $(HealthCheckIbmMQ) + $(RepositoryUrl) + $(Company) + $(Authors) + latest + AspNetCore.HealthChecks.IbmMQ + $(PublishRepositoryUrl) + $(AllowedOutputExtensionsInPackageBuildOutputFolder) + + + + + + + + diff --git a/src/HealthChecks.IbmMQ/IbmMQHealthCheck.cs b/src/HealthChecks.IbmMQ/IbmMQHealthCheck.cs new file mode 100644 index 0000000000..1b99acb9f2 --- /dev/null +++ b/src/HealthChecks.IbmMQ/IbmMQHealthCheck.cs @@ -0,0 +1,38 @@ +using IBM.WMQ; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using System; +using System.Collections; +using System.Threading; +using System.Threading.Tasks; + +namespace HealthChecks.IbmMQ +{ + public class IbmMQHealthCheck : IHealthCheck + { + private readonly Hashtable _connectionProperties; + private readonly string _queueManager; + + public IbmMQHealthCheck(string queueManager, Hashtable connectionProperties) + { + _queueManager = queueManager; + _connectionProperties = connectionProperties; + } + + public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + try + { + using (var connection = new MQQueueManager(_queueManager, _connectionProperties)) + { + return Task.FromResult( + HealthCheckResult.Healthy()); + } + } + catch (Exception ex) + { + return Task.FromResult( + new HealthCheckResult(context.Registration.FailureStatus, exception: ex)); + } + } + } +} diff --git a/src/HealthChecks.IbmMQ/README.md b/src/HealthChecks.IbmMQ/README.md new file mode 100644 index 0000000000..4208f330fb --- /dev/null +++ b/src/HealthChecks.IbmMQ/README.md @@ -0,0 +1,41 @@ +# IbmMQ Health Check + +This health check verifies the ability to communicate with a IbmMQ 9.0.+ server + +## Example Usage + +With all of the following examples, you can additionally add the following parameters: + +- `name`: The health check name. Default if not specified is `ibmmq`. +- `failureStatus`: The `HealthStatus` that should be reported when the health check fails. Default is `HealthStatus.Unhealthy`. +- `tags`: A list of tags that can be used to filter sets of health checks. +- `timeout`: A `System.TimeSpan` representing the timeout of the check. + +### Basic + +Use the extension method where you provide the queue manager name and the connection properties. + +```cs +public void ConfigureServices(IServiceCollection services) +{ + Hashtable connectionProperties = new Hashtable { + {MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_BINDINGS}, + }; + services + .AddHealthChecks() + .AddIbmMQ("QM.TEST.01", connectionProperties); +} +``` + +### Using Managed Client Connection + +For `MQC.TRANSPORT_MQSERIES_MANAGED` connection you can use the following conveniece extension method where you need to specified the channel and the host(port) information. User and password are optional parameters. + +```cs +public void ConfigureServices(IServiceCollection services) +{ + services + .AddHealthChecks() + .AddIbmMQManagedConnection("QM.TEST.01", "DEV.APP.SVRCONN", "localhost(1417)", userName: "app", password: "xxx"); +} +``` diff --git a/test/FunctionalTests/FunctionalTests.csproj b/test/FunctionalTests/FunctionalTests.csproj index bea5c07a3f..7c88d6a75d 100644 --- a/test/FunctionalTests/FunctionalTests.csproj +++ b/test/FunctionalTests/FunctionalTests.csproj @@ -22,6 +22,7 @@ + diff --git a/test/FunctionalTests/HealthChecks.IbmMQ/IbmMQHealthChecksTests.cs b/test/FunctionalTests/HealthChecks.IbmMQ/IbmMQHealthChecksTests.cs new file mode 100644 index 0000000000..98a5f6d2af --- /dev/null +++ b/test/FunctionalTests/HealthChecks.IbmMQ/IbmMQHealthChecksTests.cs @@ -0,0 +1,170 @@ +using FluentAssertions; +using FunctionalTests.Base; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using System; +using System.Net; +using System.Threading.Tasks; +using Xunit; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using System.Collections; +using IBM.WMQ; + +namespace FunctionalTests.HealthChecks.IbmMQ +{ + + [Collection("execution")] + public class ibmmq_healthcheck_should + { + private readonly ExecutionFixture _fixture; + + // Define the name of the queue manager to use (applies to all connections) + const string qManager = "QM.TEST.01"; + + // Define the name of your host connection (applies to client connections only) + const string hostName = "localhost(1414)"; + + const string wrongHostName = "localhost(1417)"; + + // Define the name of the channel to use (applies to client connections only) + const string channel = "DEV.APP.SVRCONN"; + + // Define the user name. + const string user = "app"; + + // Define the password. + const string password = "12345678"; + + public ibmmq_healthcheck_should(ExecutionFixture fixture) + { + _fixture = fixture ?? throw new ArgumentNullException(nameof(fixture)); + } + + + [SkipOnAppVeyor] + public async Task be_healthy_if_ibmmq_is_available() + { + Hashtable properties = new Hashtable(); + properties.Add(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_MANAGED); + properties.Add(MQC.CHANNEL_PROPERTY, channel); + properties.Add(MQC.CONNECTION_NAME_PROPERTY, hostName); + properties.Add(MQC.USER_ID_PROPERTY, user); + properties.Add(MQC.PASSWORD_PROPERTY, password); + + var webHostBuilder = new WebHostBuilder() + .UseStartup() + .ConfigureServices(services => + { + services.AddHealthChecks(). + AddIbmMQ(qManager, properties , tags: new string[] { "ibmmq" }); + }) + .Configure(app => + { + app.UseHealthChecks("/health", new HealthCheckOptions() + { + Predicate = r => r.Tags.Contains("ibmmq") + }); + }); + + var server = new TestServer(webHostBuilder); + + var response = await server.CreateRequest("/health") + .GetAsync(); + + response.StatusCode + .Should().Be(HttpStatusCode.OK); + } + + [SkipOnAppVeyor] + public async Task be_unhealthy_if_ibmmq_is_unavailable() + { + Hashtable properties = new Hashtable(); + properties.Add(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_MANAGED); + properties.Add(MQC.CHANNEL_PROPERTY, channel); + properties.Add(MQC.CONNECTION_NAME_PROPERTY, wrongHostName); + properties.Add(MQC.USER_ID_PROPERTY, user); + properties.Add(MQC.PASSWORD_PROPERTY, password); + + var webHostBuilder = new WebHostBuilder() + .UseStartup() + .ConfigureServices(services => + { + services.AddHealthChecks() + .AddIbmMQ(qManager, properties, tags: new string[] { "ibmmq" }); + }) + .Configure(app => + { + app.UseHealthChecks("/health", new HealthCheckOptions() + { + Predicate = r => r.Tags.Contains("ibmmq") + }); + }); + + var server = new TestServer(webHostBuilder); + + var response = await server.CreateRequest("/health") + .GetAsync(); + + response.StatusCode + .Should().Be(HttpStatusCode.ServiceUnavailable); + } + + [SkipOnAppVeyor] + public async Task be_unhealthy_if_ibmmq_managed_is_unavailable() + { + + var webHostBuilder = new WebHostBuilder() + .UseStartup() + .ConfigureServices(services => + { + services.AddHealthChecks() + .AddIbmMQManagedConnection(qManager, channel, wrongHostName, user, password, tags: new string[] { "ibmmq" }); + }) + .Configure(app => + { + app.UseHealthChecks("/health", new HealthCheckOptions() + { + Predicate = r => r.Tags.Contains("ibmmq") + }); + }); + + var server = new TestServer(webHostBuilder); + + var response = await server.CreateRequest("/health") + .GetAsync(); + + response.StatusCode + .Should().Be(HttpStatusCode.ServiceUnavailable); + } + + [SkipOnAppVeyor] + public async Task be_healthy_if_ibmmq_managed_is_available() + { + + var webHostBuilder = new WebHostBuilder() + .UseStartup() + .ConfigureServices(services => + { + services.AddHealthChecks(). + AddIbmMQManagedConnection(qManager, channel, hostName, user, password, tags: new string[] { "ibmmq" }); + }) + .Configure(app => + { + app.UseHealthChecks("/health", new HealthCheckOptions() + { + Predicate = r => r.Tags.Contains("ibmmq") + }); + }); + + var server = new TestServer(webHostBuilder); + + var response = await server.CreateRequest("/health") + .GetAsync(); + + response.StatusCode + .Should().Be(HttpStatusCode.OK); + } + } +}