From 133ff159c1bdc76d66d74bd590ab82fb9c4674bd Mon Sep 17 00:00:00 2001 From: Bart Gevaert <36637783+brattpurrie@users.noreply.github.com> Date: Fri, 13 Dec 2024 11:59:54 +0100 Subject: [PATCH] Re-design Azure IoTHub health checks to allow for managed identity and other features (#2216) Co-authored-by: Adam Sitnik --- .../IoTHubHealthChecksBuilderExtensions.cs | 101 +++++++++++++++--- .../IoTHubHealthCheck.cs | 74 ------------- .../IoTHubOptions.cs | 41 ------- .../IoTHubRegistryManagerHealthCheck.cs | 71 ++++++++++++ .../IoTHubServiceClientHealthCheck.cs | 29 +++++ src/HealthChecks.Azure.IoTHub/README.md | 50 ++++++--- .../DependencyInjection/RegistrationTests.cs | 19 ++-- .../HealthChecks.Azure.IoTHub.Tests.csproj | 1 + .../HealthChecks.Azure.IoTHub.approved.txt | 17 ++- .../IoTHubRegistryManagerConformanceTests.cs | 44 ++++++++ .../IoTHubServiceClientConformanceTests.cs | 23 ++++ 11 files changed, 312 insertions(+), 158 deletions(-) delete mode 100644 src/HealthChecks.Azure.IoTHub/IoTHubHealthCheck.cs delete mode 100644 src/HealthChecks.Azure.IoTHub/IoTHubOptions.cs create mode 100644 src/HealthChecks.Azure.IoTHub/IoTHubRegistryManagerHealthCheck.cs create mode 100644 src/HealthChecks.Azure.IoTHub/IoTHubServiceClientHealthCheck.cs create mode 100644 test/HealthChecks.Azure.IoTHub.Tests/IoTHubRegistryManagerConformanceTests.cs create mode 100644 test/HealthChecks.Azure.IoTHub.Tests/IoTHubServiceClientConformanceTests.cs diff --git a/src/HealthChecks.Azure.IoTHub/DependencyInjection/IoTHubHealthChecksBuilderExtensions.cs b/src/HealthChecks.Azure.IoTHub/DependencyInjection/IoTHubHealthChecksBuilderExtensions.cs index 8533f9f07f..aa23204301 100644 --- a/src/HealthChecks.Azure.IoTHub/DependencyInjection/IoTHubHealthChecksBuilderExtensions.cs +++ b/src/HealthChecks.Azure.IoTHub/DependencyInjection/IoTHubHealthChecksBuilderExtensions.cs @@ -1,20 +1,27 @@ using HealthChecks.Azure.IoTHub; +using Microsoft.Azure.Devices; using Microsoft.Extensions.Diagnostics.HealthChecks; namespace Microsoft.Extensions.DependencyInjection; /// -/// Extension methods to configure . +/// Extension methods to configure . /// public static class IoTHubHealthChecksBuilderExtensions { - private const string NAME = "iothub"; + private const string NAME_REGISTRY_MANAGER_READ = "iothub_registrymanager_read"; + private const string NAME_REGISTRY_MANAGER_WRITE = "iothub_registrymanager_write"; + private const string NAME_SERVICE_CLIENT = "iothub_serviceclient"; /// - /// Add a health check for Azure IoT Hub. + /// Adds a read health check for Azure IoT Hub registry manager. /// /// The . - /// A action to configure the Azure IoT Hub connection to use. + /// + /// An optional factory to obtain instance. + /// When not provided, is simply resolved from . + /// + /// The query to perform. /// The health check name. Optional. If null the type name 'iothub' will be used for the name. /// /// The that should be reported when the health check fails. Optional. If null then @@ -23,22 +30,92 @@ public static class IoTHubHealthChecksBuilderExtensions /// A list of tags that can be used to filter sets of health checks. Optional. /// An optional representing the timeout of the check. /// The specified . - public static IHealthChecksBuilder AddAzureIoTHub( + public static IHealthChecksBuilder AddAzureIoTHubRegistryReadCheck( this IHealthChecksBuilder builder, - Action? optionsFactory, - string? name = default, + Func? registryManagerFactory = default, + string query = "SELECT deviceId FROM devices", + string? name = NAME_REGISTRY_MANAGER_READ, HealthStatus? failureStatus = default, IEnumerable? tags = default, TimeSpan? timeout = default) { - var options = new IoTHubOptions(); - optionsFactory?.Invoke(options); + Guard.ThrowIfNull(query); + + return builder.Add(new HealthCheckRegistration( + name ?? NAME_REGISTRY_MANAGER_READ, + sp => new IoTHubRegistryManagerHealthCheck( + registryManager: registryManagerFactory?.Invoke(sp) ?? sp.GetRequiredService(), + readQuery: query), + failureStatus, + tags, + timeout)); + } - var registrationName = name ?? NAME; + /// + /// Add a write health check for Azure IoT Hub registry manager. + /// + /// The . + /// + /// An optional factory to obtain instance. + /// When not provided, is simply resolved from . + /// + /// The id of the device to add and remove. + /// The health check name. Optional. If null the type name 'iothub' 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 representing the timeout of the check. + /// The specified . + public static IHealthChecksBuilder AddAzureIoTHubRegistryWriteCheck( + this IHealthChecksBuilder builder, + Func? registryManagerFactory = default, + string deviceId = "health-check-registry-write-device-id", + string? name = NAME_REGISTRY_MANAGER_WRITE, + HealthStatus? failureStatus = default, + IEnumerable? tags = default, + TimeSpan? timeout = default) + { + Guard.ThrowIfNull(deviceId); return builder.Add(new HealthCheckRegistration( - registrationName, - sp => new IoTHubHealthCheck(options), + name ?? NAME_REGISTRY_MANAGER_WRITE, + sp => new IoTHubRegistryManagerHealthCheck( + registryManager: registryManagerFactory?.Invoke(sp) ?? sp.GetRequiredService(), + writeDeviceId: deviceId), + failureStatus, + tags, + timeout)); + } + + /// + /// Add a health check for Azure IoT Hub service client. + /// + /// The . + /// + /// An optional factory to obtain instance. + /// When not provided, is simply resolved from . + /// + /// The health check name. Optional. If null the type name 'iothub' 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 representing the timeout of the check. + /// The specified . + public static IHealthChecksBuilder AddAzureIoTHubServiceClient( + this IHealthChecksBuilder builder, + Func? serviceClientFactory = default, + string? name = NAME_SERVICE_CLIENT, + HealthStatus? failureStatus = default, + IEnumerable? tags = default, + TimeSpan? timeout = default) + { + return builder.Add(new HealthCheckRegistration( + name ?? NAME_SERVICE_CLIENT, + sp => new IoTHubServiceClientHealthCheck(serviceClient: serviceClientFactory?.Invoke(sp) ?? sp.GetRequiredService()), failureStatus, tags, timeout)); diff --git a/src/HealthChecks.Azure.IoTHub/IoTHubHealthCheck.cs b/src/HealthChecks.Azure.IoTHub/IoTHubHealthCheck.cs deleted file mode 100644 index 4b88715b6e..0000000000 --- a/src/HealthChecks.Azure.IoTHub/IoTHubHealthCheck.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Microsoft.Azure.Devices; -using Microsoft.Extensions.Diagnostics.HealthChecks; - -namespace HealthChecks.Azure.IoTHub; - -public class IoTHubHealthCheck : IHealthCheck -{ - private readonly IoTHubOptions _options; - - public IoTHubHealthCheck(IoTHubOptions options) - { - _options = Guard.ThrowIfNull(options); - } - - /// - public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) - { - try - { - if (_options.RegistryWriteCheck) - { - await ExecuteRegistryWriteCheckAsync(cancellationToken).ConfigureAwait(false); - } - else if (_options.RegistryReadCheck) - { - await ExecuteRegistryReadCheckAsync().ConfigureAwait(false); - } - if (_options.ServiceConnectionCheck) - { - await ExecuteServiceConnectionCheckAsync(cancellationToken).ConfigureAwait(false); - } - - return HealthCheckResult.Healthy(); - } - catch (Exception ex) - { - return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); - } - } - - private async Task ExecuteServiceConnectionCheckAsync(CancellationToken cancellationToken) - { - using var client = ServiceClient.CreateFromConnectionString(_options.ConnectionString, _options.ServiceConnectionTransport); - await client.GetServiceStatisticsAsync(cancellationToken).ConfigureAwait(false); - } - - private async Task ExecuteRegistryReadCheckAsync() - { - using var client = RegistryManager.CreateFromConnectionString(_options.ConnectionString); - var query = client.CreateQuery(_options.RegistryReadQuery, 1); - await query.GetNextAsJsonAsync().ConfigureAwait(false); - } - - private async Task ExecuteRegistryWriteCheckAsync(CancellationToken cancellationToken) - { - using var client = RegistryManager.CreateFromConnectionString(_options.ConnectionString); - - var deviceId = _options.RegistryWriteDeviceIdFactory(); - var device = await client.GetDeviceAsync(deviceId, cancellationToken).ConfigureAwait(false); - - // in default implementation of configuration deviceId equals "health-check-registry-write-device-id" - // if in previous health check device were not removed -- try remove it - // if in previous health check device were added and removed -- try create and remove it - if (device != null) - { - await client.RemoveDeviceAsync(deviceId, cancellationToken).ConfigureAwait(false); - } - else - { - await client.AddDeviceAsync(new Device(deviceId), cancellationToken).ConfigureAwait(false); - await client.RemoveDeviceAsync(deviceId, cancellationToken).ConfigureAwait(false); - } - } -} diff --git a/src/HealthChecks.Azure.IoTHub/IoTHubOptions.cs b/src/HealthChecks.Azure.IoTHub/IoTHubOptions.cs deleted file mode 100644 index 9bd2df9f8d..0000000000 --- a/src/HealthChecks.Azure.IoTHub/IoTHubOptions.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Microsoft.Azure.Devices; - -namespace HealthChecks.Azure.IoTHub; - -public class IoTHubOptions -{ - internal string ConnectionString { get; private set; } = null!; - internal bool RegistryReadCheck { get; private set; } - internal bool RegistryWriteCheck { get; private set; } - internal bool ServiceConnectionCheck { get; private set; } - internal string RegistryReadQuery { get; private set; } = null!; - internal Func RegistryWriteDeviceIdFactory { get; private set; } = null!; - internal TransportType ServiceConnectionTransport { get; private set; } - - public IoTHubOptions AddConnectionString(string connectionString) - { - ConnectionString = Guard.ThrowIfNull(connectionString); - return this; - } - - public IoTHubOptions AddRegistryReadCheck(string query = "SELECT deviceId FROM devices") - { - RegistryReadCheck = true; - RegistryReadQuery = query; - return this; - } - - public IoTHubOptions AddRegistryWriteCheck(Func? deviceIdFactory = null) - { - RegistryWriteCheck = true; - RegistryWriteDeviceIdFactory = deviceIdFactory ?? (() => "health-check-registry-write-device-id"); - return this; - } - - public IoTHubOptions AddServiceConnectionCheck(TransportType transport = TransportType.Amqp) - { - ServiceConnectionCheck = true; - ServiceConnectionTransport = transport; - return this; - } -} diff --git a/src/HealthChecks.Azure.IoTHub/IoTHubRegistryManagerHealthCheck.cs b/src/HealthChecks.Azure.IoTHub/IoTHubRegistryManagerHealthCheck.cs new file mode 100644 index 0000000000..4431af4af4 --- /dev/null +++ b/src/HealthChecks.Azure.IoTHub/IoTHubRegistryManagerHealthCheck.cs @@ -0,0 +1,71 @@ +using Microsoft.Azure.Devices; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace HealthChecks.Azure.IoTHub; + +public sealed class IoTHubRegistryManagerHealthCheck : IHealthCheck +{ + private readonly RegistryManager _registryManager; + private readonly string? _readQuery; + private readonly string? _writeDeviceId; + + public IoTHubRegistryManagerHealthCheck(RegistryManager registryManager, string? readQuery = default, string? writeDeviceId = default) + { + _registryManager = Guard.ThrowIfNull(registryManager); + + if (string.IsNullOrEmpty(readQuery) && string.IsNullOrEmpty(writeDeviceId)) + { + throw new ArgumentException("Either readQuery or writeDeviceId has to be provided"); + } + + _readQuery = readQuery; + _writeDeviceId = writeDeviceId; + } + + /// + public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + try + { + if (!string.IsNullOrEmpty(_writeDeviceId)) + { + await ExecuteRegistryWriteCheckAsync(cancellationToken).ConfigureAwait(false); + } + else + { + await ExecuteRegistryReadCheckAsync().ConfigureAwait(false); + } + + return HealthCheckResult.Healthy(); + } + catch (Exception ex) + { + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + } + } + + private async Task ExecuteRegistryReadCheckAsync() + { + var query = _registryManager.CreateQuery(_readQuery!, 1); + await query.GetNextAsJsonAsync().ConfigureAwait(false); + } + + private async Task ExecuteRegistryWriteCheckAsync(CancellationToken cancellationToken) + { + string deviceId = _writeDeviceId!; + var device = await _registryManager.GetDeviceAsync(deviceId, cancellationToken).ConfigureAwait(false); + + // in default implementation of configuration deviceId equals "health-check-registry-write-device-id" + // if in previous health check device were not removed -- try remove it + // if in previous health check device were added and removed -- try create and remove it + if (device != null) + { + await _registryManager.RemoveDeviceAsync(deviceId, cancellationToken).ConfigureAwait(false); + } + else + { + await _registryManager.AddDeviceAsync(new Device(deviceId), cancellationToken).ConfigureAwait(false); + await _registryManager.RemoveDeviceAsync(deviceId, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/HealthChecks.Azure.IoTHub/IoTHubServiceClientHealthCheck.cs b/src/HealthChecks.Azure.IoTHub/IoTHubServiceClientHealthCheck.cs new file mode 100644 index 0000000000..60216fcbb8 --- /dev/null +++ b/src/HealthChecks.Azure.IoTHub/IoTHubServiceClientHealthCheck.cs @@ -0,0 +1,29 @@ +using Microsoft.Azure.Devices; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace HealthChecks.Azure.IoTHub; + +public sealed class IoTHubServiceClientHealthCheck : IHealthCheck +{ + private readonly ServiceClient _serviceClient; + + public IoTHubServiceClientHealthCheck(ServiceClient serviceClient) + { + _serviceClient = Guard.ThrowIfNull(serviceClient); + } + + /// + public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) + { + try + { + await _serviceClient.GetServiceStatisticsAsync(cancellationToken).ConfigureAwait(false); + + return HealthCheckResult.Healthy(); + } + catch (Exception ex) + { + return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); + } + } +} diff --git a/src/HealthChecks.Azure.IoTHub/README.md b/src/HealthChecks.Azure.IoTHub/README.md index e09c14476e..3a4549b6e2 100644 --- a/src/HealthChecks.Azure.IoTHub/README.md +++ b/src/HealthChecks.Azure.IoTHub/README.md @@ -1,27 +1,45 @@ -# Azure IoT Hub Health Check +## Azure IoT Hub Health Check -This health check verifies the ability to communicate with Azure IoT Hub. For more information about Azure IoT Hub please check and .NET please check the [Azure IoT Hub Microsoft Site](https://azure.microsoft.com/en-us/services/iot-hub/) +This health check verifies the ability to communicate with Azure IoT Hub. For more information about Azure IoT Hub please check and .NET please check the [Azure IoT Hub Microsoft Site](https://azure.microsoft.com/services/iot-hub/) -## Example Usage +### Defaults -With all of the following examples, you can additionally add the following parameters: - -- `name`: The health check name. Default if not specified is `iothub`. -- `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 +You can use `RegistryManager` or `ServiceClient` or both. It's recommended to have a single instance per application, so prefer the type you already use. ```csharp public void ConfigureServices(IServiceCollection services) { services + .AddSingleton(sp => ServiceClient.Create("iot-hub-hostname", new DefaultAzureCredential())) .AddHealthChecks() - .AddAzureIoTHub(options => - { - options.AddConnectionString("iot-hub-connectionstring") - .AddServiceConnectionCheck(); - }); + .AddAzureIoTHubServiceClient(); + + // or + + services + .AddSingleton(sp => RegistryManager.Create("iot-hub-hostname", new DefaultAzureCredential())) + .AddHealthChecks() + .AddAzureIoTHubRegistryReadCheck(); } ``` + + +### Customization + +With all of the following examples, you can additionally add the following parameters: + +AddAzureIoTHubServiceClient +- `serviceClientFactory`: An optional factory method to provide `ServiceClient` instance. +- `name`: The health check name. +- `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. + +AddAzureIoTHubRegistryManager +- `registryManagerFactory`: An optional factory method to provide `RegistryManager` instance. +- `query`: A query to perform by the read health check. +- `deviceId`: The id of the device to add and remove. +- `name`: The health check name. +- `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. diff --git a/test/HealthChecks.Azure.IoTHub.Tests/DependencyInjection/RegistrationTests.cs b/test/HealthChecks.Azure.IoTHub.Tests/DependencyInjection/RegistrationTests.cs index 48dc1df25e..93d79dceab 100644 --- a/test/HealthChecks.Azure.IoTHub.Tests/DependencyInjection/RegistrationTests.cs +++ b/test/HealthChecks.Azure.IoTHub.Tests/DependencyInjection/RegistrationTests.cs @@ -1,3 +1,6 @@ +using Azure.Identity; +using Microsoft.Azure.Devices; + namespace HealthChecks.Azure.IoTHub.Tests.DependencyInjection; public class azure_iothub_registration_should @@ -6,8 +9,10 @@ public class azure_iothub_registration_should public void add_health_check_when_properly_configured() { var services = new ServiceCollection(); - services.AddHealthChecks() - .AddAzureIoTHub(options => options.AddConnectionString("the-iot-connection-string")); + services + .AddSingleton(sp => ServiceClient.Create("iot-hub-hostname", new DefaultAzureCredential())) + .AddHealthChecks() + .AddAzureIoTHubServiceClient(name: "iothub"); using var serviceProvider = services.BuildServiceProvider(); var options = serviceProvider.GetRequiredService>(); @@ -16,15 +21,17 @@ public void add_health_check_when_properly_configured() var check = registration.Factory(serviceProvider); registration.Name.ShouldBe("iothub"); - check.ShouldBeOfType(); + check.ShouldBeOfType(); } [Fact] public void add_named_health_check_when_properly_configured() { var services = new ServiceCollection(); - services.AddHealthChecks() - .AddAzureIoTHub(options => options.AddConnectionString("the-iot-connection-string"), name: "iothubcheck"); + services + .AddSingleton(sp => RegistryManager.Create("iot-hub-hostname", new DefaultAzureCredential())) + .AddHealthChecks() + .AddAzureIoTHubRegistryReadCheck(name: "iothubcheck"); using var serviceProvider = services.BuildServiceProvider(); var options = serviceProvider.GetRequiredService>(); @@ -33,6 +40,6 @@ public void add_named_health_check_when_properly_configured() var check = registration.Factory(serviceProvider); registration.Name.ShouldBe("iothubcheck"); - check.ShouldBeOfType(); + check.ShouldBeOfType(); } } diff --git a/test/HealthChecks.Azure.IoTHub.Tests/HealthChecks.Azure.IoTHub.Tests.csproj b/test/HealthChecks.Azure.IoTHub.Tests/HealthChecks.Azure.IoTHub.Tests.csproj index 69836530cc..a3ab1f865d 100644 --- a/test/HealthChecks.Azure.IoTHub.Tests/HealthChecks.Azure.IoTHub.Tests.csproj +++ b/test/HealthChecks.Azure.IoTHub.Tests/HealthChecks.Azure.IoTHub.Tests.csproj @@ -1,6 +1,7 @@ + diff --git a/test/HealthChecks.Azure.IoTHub.Tests/HealthChecks.Azure.IoTHub.approved.txt b/test/HealthChecks.Azure.IoTHub.Tests/HealthChecks.Azure.IoTHub.approved.txt index 62ba4e23d0..eb92a0fe8e 100644 --- a/test/HealthChecks.Azure.IoTHub.Tests/HealthChecks.Azure.IoTHub.approved.txt +++ b/test/HealthChecks.Azure.IoTHub.Tests/HealthChecks.Azure.IoTHub.approved.txt @@ -1,23 +1,22 @@ namespace HealthChecks.Azure.IoTHub { - public class IoTHubHealthCheck : Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck + public sealed class IoTHubRegistryManagerHealthCheck : Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { - public IoTHubHealthCheck(HealthChecks.Azure.IoTHub.IoTHubOptions options) { } + public IoTHubRegistryManagerHealthCheck(Microsoft.Azure.Devices.RegistryManager registryManager, string? readQuery = null, string? writeDeviceId = null) { } public System.Threading.Tasks.Task CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext context, System.Threading.CancellationToken cancellationToken = default) { } } - public class IoTHubOptions + public sealed class IoTHubServiceClientHealthCheck : Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck { - public IoTHubOptions() { } - public HealthChecks.Azure.IoTHub.IoTHubOptions AddConnectionString(string connectionString) { } - public HealthChecks.Azure.IoTHub.IoTHubOptions AddRegistryReadCheck(string query = "SELECT deviceId FROM devices") { } - public HealthChecks.Azure.IoTHub.IoTHubOptions AddRegistryWriteCheck(System.Func? deviceIdFactory = null) { } - public HealthChecks.Azure.IoTHub.IoTHubOptions AddServiceConnectionCheck(Microsoft.Azure.Devices.TransportType transport = 0) { } + public IoTHubServiceClientHealthCheck(Microsoft.Azure.Devices.ServiceClient serviceClient) { } + public System.Threading.Tasks.Task CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext context, System.Threading.CancellationToken cancellationToken = default) { } } } namespace Microsoft.Extensions.DependencyInjection { public static class IoTHubHealthChecksBuilderExtensions { - public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAzureIoTHub(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Action? optionsFactory, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAzureIoTHubRegistryReadCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func? registryManagerFactory = null, string query = "SELECT deviceId FROM devices", string? name = "iothub_registrymanager_read", Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAzureIoTHubRegistryWriteCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func? registryManagerFactory = null, string deviceId = "health-check-registry-write-device-id", string? name = "iothub_registrymanager_write", Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } + public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddAzureIoTHubServiceClient(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Func? serviceClientFactory = null, string? name = "iothub_serviceclient", Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { } } } \ No newline at end of file diff --git a/test/HealthChecks.Azure.IoTHub.Tests/IoTHubRegistryManagerConformanceTests.cs b/test/HealthChecks.Azure.IoTHub.Tests/IoTHubRegistryManagerConformanceTests.cs new file mode 100644 index 0000000000..a70e48a0fd --- /dev/null +++ b/test/HealthChecks.Azure.IoTHub.Tests/IoTHubRegistryManagerConformanceTests.cs @@ -0,0 +1,44 @@ +using Azure.Identity; +using Microsoft.Azure.Devices; + +namespace HealthChecks.Azure.IoTHub.Tests; + +public class IoTHubRegistryManagerConformanceTests_Read : ConformanceTests +{ + protected override IHealthChecksBuilder AddHealthCheck(IHealthChecksBuilder builder, Func? clientFactory = null, Func? optionsFactory = null, string? healthCheckName = null, HealthStatus? failureStatus = null, IEnumerable? tags = null, TimeSpan? timeout = null) + => builder.AddAzureIoTHubRegistryReadCheck(clientFactory, name: healthCheckName, failureStatus: failureStatus, tags: tags, timeout: timeout); + + protected override RegistryManager CreateClientForNonExistingEndpoint() + { + HttpTransportSettings settings = new(); + + return RegistryManager.Create("thisisnotarealurl", new DefaultAzureCredential(), settings); + } + + protected override IoTHubRegistryManagerHealthCheck CreateHealthCheck(RegistryManager client, IotHubRegistryManagerOptions? options) + => new(client, readQuery: "SELECT deviceId FROM devices"); + + protected override IotHubRegistryManagerOptions CreateHealthCheckOptions() + => new(); +} + +public class IoTHubRegistryManagerConformanceTests_Write : ConformanceTests +{ + protected override IHealthChecksBuilder AddHealthCheck(IHealthChecksBuilder builder, Func? clientFactory = null, Func? optionsFactory = null, string? healthCheckName = null, HealthStatus? failureStatus = null, IEnumerable? tags = null, TimeSpan? timeout = null) + => builder.AddAzureIoTHubRegistryWriteCheck(clientFactory, name: healthCheckName, failureStatus: failureStatus, tags: tags, timeout: timeout); + + protected override RegistryManager CreateClientForNonExistingEndpoint() + { + HttpTransportSettings settings = new(); + + return RegistryManager.Create("thisisnotarealurl", new DefaultAzureCredential(), settings); + } + + protected override IoTHubRegistryManagerHealthCheck CreateHealthCheck(RegistryManager client, IotHubRegistryManagerOptions? options) + => new(client, writeDeviceId: "some-id"); + + protected override IotHubRegistryManagerOptions CreateHealthCheckOptions() + => new(); +} + +public sealed class IotHubRegistryManagerOptions; diff --git a/test/HealthChecks.Azure.IoTHub.Tests/IoTHubServiceClientConformanceTests.cs b/test/HealthChecks.Azure.IoTHub.Tests/IoTHubServiceClientConformanceTests.cs new file mode 100644 index 0000000000..1993c030d6 --- /dev/null +++ b/test/HealthChecks.Azure.IoTHub.Tests/IoTHubServiceClientConformanceTests.cs @@ -0,0 +1,23 @@ +using Azure.Identity; +using Microsoft.Azure.Devices; + +namespace HealthChecks.Azure.IoTHub.Tests; + +public class IoTHubServiceClientConformanceTests : ConformanceTests +{ + protected override IHealthChecksBuilder AddHealthCheck(IHealthChecksBuilder builder, Func? clientFactory = null, Func? optionsFactory = null, string? healthCheckName = null, HealthStatus? failureStatus = null, IEnumerable? tags = null, TimeSpan? timeout = null) + => builder.AddAzureIoTHubServiceClient(clientFactory, healthCheckName, failureStatus, tags, timeout); + + protected override ServiceClient CreateClientForNonExistingEndpoint() + { + return ServiceClient.Create("thisisnotarealurl", new DefaultAzureCredential()); + } + + protected override IoTHubServiceClientHealthCheck CreateHealthCheck(ServiceClient client, IotHubServiceClientOptions? options) + => new(client); + + protected override IotHubServiceClientOptions CreateHealthCheckOptions() + => new(); +} + +public sealed class IotHubServiceClientOptions;