diff --git a/.env b/.env
index 21333af5dc..3157b789f2 100644
--- a/.env
+++ b/.env
@@ -12,6 +12,7 @@ MYSQL_PORT=3306
MYSQL_PASSWORD=Password12!
ZOOKEEPER_PORT=3000
KAFKA_PORT=9092
+PULSAR_PORT=6650
RABBITMQ_PORT=5672
IDSVR_PORT=8888
ORACLE_PORT=1521
@@ -20,4 +21,4 @@ FTP_PORT=21
FTP_USER=bob
FTP_PASS=12345
RAVENDB_PORT=9030
-SOLR_PORT=8983
\ No newline at end of file
+SOLR_PORT=8983
diff --git a/.github/codecov.yml b/.github/codecov.yml
index cac878e857..bc143991a2 100644
--- a/.github/codecov.yml
+++ b/.github/codecov.yml
@@ -87,6 +87,8 @@ flags:
carryforward: true
Publisher.Seq:
carryforward: true
+ Pulsar:
+ carryforward: true
RabbitMQ:
carryforward: true
RavenDb:
diff --git a/.github/labeler.yml b/.github/labeler.yml
index fdc5433489..58ae543ae0 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -99,6 +99,9 @@ prometheus:
- src/HealthChecks.Prometheus.Metrics/**/*
- src/HealthChecks.Publisher.Prometheus/**/*
+pulsar:
+ - src/HealthChecks.Pulsar/**/*
+
applicationinsights:
- src/HealthChecks.Publisher.ApplicationInsights/**/*
diff --git a/.github/workflows/healthchecks_pulsar_cd.yml b/.github/workflows/healthchecks_pulsar_cd.yml
new file mode 100644
index 0000000000..096c44f262
--- /dev/null
+++ b/.github/workflows/healthchecks_pulsar_cd.yml
@@ -0,0 +1,16 @@
+name: HealthChecks Pulsar CD
+
+on:
+ push:
+ tags:
+ - release-pulsar-*
+ - release-all-*
+
+jobs:
+ build:
+ uses: ./.github/workflows/reusable_cd_workflow.yml
+ secrets: inherit
+ with:
+ BUILD_CONFIG: Release
+ PROJECT_PATH: ./src/HealthChecks.Pulsar/HealthChecks.Pulsar.csproj
+ PACKAGE_NAME: AspNetCore.HealthChecks.Pulsar
diff --git a/.github/workflows/healthchecks_pulsar_cd_preview.yml b/.github/workflows/healthchecks_pulsar_cd_preview.yml
new file mode 100644
index 0000000000..277705629e
--- /dev/null
+++ b/.github/workflows/healthchecks_pulsar_cd_preview.yml
@@ -0,0 +1,17 @@
+name: HealthChecks Pulsar Preview CD
+
+on:
+ push:
+ tags:
+ - preview-pulsar-*
+ - preview-all-*
+
+jobs:
+ build:
+ uses: ./.github/workflows/reusable_cd_preview_workflow.yml
+ secrets: inherit
+ with:
+ BUILD_CONFIG: Release
+ VERSION_SUFFIX_PREFIX: rc1
+ PROJECT_PATH: ./src/HealthChecks.Pulsar/HealthChecks.Pulsar.csproj
+ PACKAGE_NAME: AspNetCore.HealthChecks.Pulsar
diff --git a/.github/workflows/healthchecks_pulsar_ci.yml b/.github/workflows/healthchecks_pulsar_ci.yml
new file mode 100644
index 0000000000..7109a4611e
--- /dev/null
+++ b/.github/workflows/healthchecks_pulsar_ci.yml
@@ -0,0 +1,75 @@
+name: HealthChecks Pulsar CI
+
+on:
+ workflow_dispatch:
+ push:
+ branches: [ master ]
+ paths:
+ - src/HealthChecks.Pulsar/**
+ - test/HealthChecks.Pulsar.Tests/**
+ - test/_SHARED/**
+ - .github/workflows/healthchecks_pulsar_ci.yml
+ - Directory.Build.props
+ - Directory.Build.targets
+ tags-ignore:
+ - release-*
+ - preview-*
+
+ pull_request:
+ branches: [ master ]
+ paths:
+ - src/HealthChecks.Pulsar/**
+ - test/HealthChecks.Pulsar.Tests/**
+ - test/_SHARED/**
+ - .github/workflows/healthchecks_pulsar_ci.yml
+ - Directory.Build.props
+ - Directory.Build.targets
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ services:
+ pulsar:
+ image: apachepulsar/pulsar:latest
+ env:
+ PULSAR_MEM: " -Xms1g -Xmx1g -XX:MaxDirectMemorySize=2g"
+ ports:
+ - 6650:6650
+ options: >-
+ --entrypoint '/bin/bash -c "bin/apply-config-from-env.py conf/standalone.conf && bin/pulsar standalone --no-functions-worker"'
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: |
+ 6.0.x
+ 7.0.x
+ 8.0.x
+ - name: Restore
+ run: |
+ dotnet restore ./src/HealthChecks.Pulsar/HealthChecks.Pulsar.csproj &&
+ dotnet restore ./test/HealthChecks.Pulsar.Tests/HealthChecks.Pulsar.Tests.csproj
+ - name: Check formatting
+ run: |
+ dotnet format --no-restore --verify-no-changes --severity warn ./src/HealthChecks.Pulsar/HealthChecks.Pulsar.csproj || (echo "Run 'dotnet format' to fix issues" && exit 1) &&
+ dotnet format --no-restore --verify-no-changes --severity warn ./test/HealthChecks.Pulsar.Tests/HealthChecks.Pulsar.Tests.csproj || (echo "Run 'dotnet format' to fix issues" && exit 1)
+ - name: Build
+ run: |
+ dotnet build --no-restore ./src/HealthChecks.Pulsar/HealthChecks.Pulsar.csproj &&
+ dotnet build --no-restore ./test/HealthChecks.Pulsar.Tests/HealthChecks.Pulsar.Tests.csproj
+ - name: Test
+ run: >
+ dotnet test
+ ./test/HealthChecks.Pulsar.Tests/HealthChecks.Pulsar.Tests.csproj
+ --no-restore
+ --no-build
+ --collect "XPlat Code Coverage"
+ --results-directory .coverage
+ --
+ DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
+ - name: Upload Coverage
+ uses: codecov/codecov-action@v3
+ with:
+ flags: Pulsar
+ directory: .coverage
diff --git a/AspNetCore.Diagnostics.HealthChecks.sln b/AspNetCore.Diagnostics.HealthChecks.sln
index 56d827accc..9af82db086 100644
--- a/AspNetCore.Diagnostics.HealthChecks.sln
+++ b/AspNetCore.Diagnostics.HealthChecks.sln
@@ -1,4 +1,4 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
@@ -310,6 +310,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HealthChecks.Milvus", "src\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HealthChecks.Milvus.Tests", "test\HealthChecks.Milvus.Tests\HealthChecks.Milvus.Tests.csproj", "{D49CF52C-9D21-4D98-8A15-A2B259E9C003}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HealthChecks.Pulsar", "src\HealthChecks.Pulsar\HealthChecks.Pulsar.csproj", "{15E844A6-9411-42DF-9862-EBC6A9147D2C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HealthChecks.Pulsar.Tests", "test\HealthChecks.Pulsar.Tests\HealthChecks.Pulsar.Tests.csproj", "{72D9B21B-97A9-4F29-AEBE-A0FC15CC69B4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -868,6 +872,14 @@ Global
{D49CF52C-9D21-4D98-8A15-A2B259E9C003}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D49CF52C-9D21-4D98-8A15-A2B259E9C003}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D49CF52C-9D21-4D98-8A15-A2B259E9C003}.Release|Any CPU.Build.0 = Release|Any CPU
+ {15E844A6-9411-42DF-9862-EBC6A9147D2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {15E844A6-9411-42DF-9862-EBC6A9147D2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {15E844A6-9411-42DF-9862-EBC6A9147D2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {15E844A6-9411-42DF-9862-EBC6A9147D2C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {72D9B21B-97A9-4F29-AEBE-A0FC15CC69B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {72D9B21B-97A9-4F29-AEBE-A0FC15CC69B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72D9B21B-97A9-4F29-AEBE-A0FC15CC69B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {72D9B21B-97A9-4F29-AEBE-A0FC15CC69B4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1011,6 +1023,8 @@ Global
{3B812989-2C4E-4FCE-B3A0-EF9C00A9B3A5} = {FF4414C2-8863-4ADA-8A1D-4B9F25C361FE}
{17913EAF-3B12-495B-80EA-9EB975FBE6BA} = {2A3FD988-2BB8-43CF-B3A2-B70E648259D4}
{D49CF52C-9D21-4D98-8A15-A2B259E9C003} = {FF4414C2-8863-4ADA-8A1D-4B9F25C361FE}
+ {15E844A6-9411-42DF-9862-EBC6A9147D2C} = {2A3FD988-2BB8-43CF-B3A2-B70E648259D4}
+ {72D9B21B-97A9-4F29-AEBE-A0FC15CC69B4} = {FF4414C2-8863-4ADA-8A1D-4B9F25C361FE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2B8C62A1-11B6-469F-874C-A02443256568}
diff --git a/README.md b/README.md
index 95d18c6eb3..a0e6b920cf 100644
--- a/README.md
+++ b/README.md
@@ -105,6 +105,7 @@ HealthChecks packages include health checks for:
| Nats | [![Nuget](https://img.shields.io/nuget/dt/AspNetCore.HealthChecks.Nats)](https://www.nuget.org/packages/AspNetCore.HealthChecks.Nats) | [![Nuget](https://img.shields.io/nuget/v/AspNetCore.HealthChecks.Nats)](https://www.nuget.org/packages/AspNetCore.HealthChecks.Nats) | [![view](https://img.shields.io/github/issues/Xabaril/AspNetCore.Diagnostics.HealthChecks/nats)](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/labels/nats) | NATS, messaging, message-bus, pubsub |
| Network | [![Nuget](https://img.shields.io/nuget/dt/AspNetCore.HealthChecks.Network)](https://www.nuget.org/packages/AspNetCore.HealthChecks.Network) | [![Nuget](https://img.shields.io/nuget/v/AspNetCore.HealthChecks.Network)](https://www.nuget.org/packages/AspNetCore.HealthChecks.Network) | [![view](https://img.shields.io/github/issues/Xabaril/AspNetCore.Diagnostics.HealthChecks/network)](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/labels/network) | Ftp, SFtp, Dns, Tcp port, Smtp, Imap, Ssl |
| Postgres | [![Nuget](https://img.shields.io/nuget/dt/AspNetCore.HealthChecks.NpgSql)](https://www.nuget.org/packages/AspNetCore.HealthChecks.NpgSql) | [![Nuget](https://img.shields.io/nuget/v/AspNetCore.HealthChecks.NpgSql)](https://www.nuget.org/packages/AspNetCore.HealthChecks.NpgSql) | [![view](https://img.shields.io/github/issues/Xabaril/AspNetCore.Diagnostics.HealthChecks/npgsql)](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/labels/npgsql)
+| Pulsar | [![Nuget](https://img.shields.io/nuget/dt/AspNetCore.HealthChecks.Pulsar)](https://www.nuget.org/packages/AspNetCore.HealthChecks.Pulsar) | [![Nuget](https://img.shields.io/nuget/v/AspNetCore.HealthChecks.Pulsar)](https://www.nuget.org/packages/AspNetCore.HealthChecks.Pulsar) | [![view](https://img.shields.io/github/issues/Xabaril/AspNetCore.Diagnostics.HealthChecks/Pulsar)](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/labels/pulsar)
| Identity Server | [![Nuget](https://img.shields.io/nuget/dt/AspNetCore.HealthChecks.OpenIdConnectServer)](https://www.nuget.org/packages/AspNetCore.HealthChecks.OpenIdConnectServer) | [![Nuget](https://img.shields.io/nuget/v/AspNetCore.HealthChecks.OpenIdConnectServer)](https://www.nuget.org/packages/AspNetCore.HealthChecks.OpenIdConnectServer) | [![view](https://img.shields.io/github/issues/Xabaril/AspNetCore.Diagnostics.HealthChecks/openidconnect)](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/labels/openidconnect)
| Oracle | [![Nuget](https://img.shields.io/nuget/dt/AspNetCore.HealthChecks.Oracle)](https://www.nuget.org/packages/AspNetCore.HealthChecks.Oracle) | [![Nuget](https://img.shields.io/nuget/v/AspNetCore.HealthChecks.Oracle)](https://www.nuget.org/packages/AspNetCore.HealthChecks.Oracle) | [![view](https://img.shields.io/github/issues/Xabaril/AspNetCore.Diagnostics.HealthChecks/oracle)](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/labels/oracle)
| RabbitMQ | [![Nuget](https://img.shields.io/nuget/dt/AspNetCore.HealthChecks.RabbitMQ)](https://www.nuget.org/packages/AspNetCore.HealthChecks.RabbitMQ) | [![Nuget](https://img.shields.io/nuget/v/AspNetCore.HealthChecks.RabbitMQ)](https://www.nuget.org/packages/AspNetCore.HealthChecks.RabbitMQ) | [![view](https://img.shields.io/github/issues/Xabaril/AspNetCore.Diagnostics.HealthChecks/rabbitmq)](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/labels/rabbitmq)
@@ -161,6 +162,7 @@ Install-Package AspNetCore.HealthChecks.MySql
Install-Package AspNetCore.HealthChecks.Nats
Install-Package AspNetCore.HealthChecks.Network
Install-Package AspNetCore.HealthChecks.Npgsql
+Install-Package AspNetCore.HealthChecks.Pulsar
Install-Package AspNetCore.HealthChecks.OpenIdConnectServer
Install-Package AspNetCore.HealthChecks.Oracle
Install-Package AspNetCore.HealthChecks.RabbitMQ
@@ -699,4 +701,4 @@ answering [questions](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthCh
2. Follow the code guidelines and conventions.
3. New features are not only code, tests and documentation are also mandatory.
4. PRs with [`Ups for grabs`](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/labels/Ups%20for%20grabs)
-and [help wanted](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/labels/help%20wanted) tags are good candidates to contribute.
+and [help wanted](https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks/labels/help%20wanted) tags are good candidates to contribute.
diff --git a/build/versions.props b/build/versions.props
index e6d83284d6..1eda4ed831 100644
--- a/build/versions.props
+++ b/build/versions.props
@@ -49,6 +49,7 @@
8.0.1
8.0.1
8.0.1
+ 8.0.1
8.0.2
8.0.1
8.0.1
diff --git a/docker-compose.yml b/docker-compose.yml
index ee3a4c86ee..c5ebb28d4a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -68,6 +68,14 @@ services:
- ${KAFKA_PORT}:9092
links:
- zookeeper
+ pulsar:
+ image: apachepulsar/pulsar:latest
+ environment:
+ PULSAR_MEM: " -Xms1g -Xmx1g -XX:MaxDirectMemorySize=2g"
+ ports:
+ - ${PULSAR_PORT}:6650
+ command: |
+ /bin/bash -c "bin/apply-config-from-env.py conf/standalone.conf && bin/pulsar standalone --no-functions-worker"
rabbitmq:
image: rabbitmq
ports:
@@ -165,7 +173,7 @@ services:
image: postgres
ports:
- "8010:5432"
- environment:
+ environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=Password12!
nats:
@@ -180,7 +188,7 @@ services:
ports:
- "8086:8086"
environment:
- DOCKER_INFLUXDB_INIT_MODE: setup
+ DOCKER_INFLUXDB_INIT_MODE: setup
DOCKER_INFLUXDB_INIT_USERNAME: ci_user
DOCKER_INFLUXDB_INIT_PASSWORD: password
DOCKER_INFLUXDB_INIT_ORG: influxdata
diff --git a/src/HealthChecks.Pulsar/DependencyInjection/PulsarHealthCheckBuilderExtensions.cs b/src/HealthChecks.Pulsar/DependencyInjection/PulsarHealthCheckBuilderExtensions.cs
new file mode 100644
index 0000000000..af9b0ece7d
--- /dev/null
+++ b/src/HealthChecks.Pulsar/DependencyInjection/PulsarHealthCheckBuilderExtensions.cs
@@ -0,0 +1,147 @@
+using DotPulsar.Abstractions;
+using HealthChecks.Pulsar;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+
+// ReSharper disable once CheckNamespace
+namespace Microsoft.Extensions.DependencyInjection;
+
+///
+/// Extension methods to configure .
+///
+public static class PulsarHealthCheckBuilderExtensions
+{
+ private const string NAME = "pulsar";
+ internal const string DEFAULT_TOPIC = "non-persistent://public/default/healthchecks-topic";
+
+ private static readonly TimeSpan DEFAULT_TIMEOUT = TimeSpan.FromSeconds(15);
+
+ ///
+ /// Add a health check for Pulsar cluster.
+ ///
+ /// The .
+ /// The topic name to produce Pulsar messages on.
+ /// The health check name. Optional. If null the type name 'kafka' 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 AddPulsar(
+ this IHealthChecksBuilder builder,
+ string topic = DEFAULT_TOPIC,
+ string? name = default,
+ HealthStatus? failureStatus = default,
+ IEnumerable? tags = default,
+ TimeSpan? timeout = default
+ ) => AddPulsar(
+ builder,
+ sp => sp.GetRequiredService(),
+ topic: topic,
+ name: name,
+ failureStatus: failureStatus,
+ tags: tags,
+ timeout: timeout ?? DEFAULT_TIMEOUT);
+
+ ///
+ /// Add a health check for Pulsar cluster.
+ ///
+ /// The .
+ /// The Pulsar client factory.
+ /// The topic name to produce Pulsar messages on.
+ /// The health check name. Optional. If null the type name 'kafka' 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 AddPulsar(
+ this IHealthChecksBuilder builder,
+ Func clientFactory,
+ string topic = DEFAULT_TOPIC,
+ string? name = default,
+ HealthStatus? failureStatus = default,
+ IEnumerable? tags = default,
+ TimeSpan? timeout = default
+ )
+ {
+ builder.Services.TryAddSingleton(sp => new PulsarHealthCheck(clientFactory(sp), new PulsarHealthCheckOptions
+ {
+ Topic = topic
+ }));
+
+ return builder.Add(new HealthCheckRegistration(
+ name ?? NAME,
+ sp => sp.GetRequiredService(),
+ failureStatus,
+ tags,
+ timeout ?? DEFAULT_TIMEOUT));
+ }
+
+ ///
+ /// Add a health check for Pulsar cluster.
+ ///
+ /// The .
+ /// Options to configure Pulsar health check.
+ /// The health check name. Optional. If null the type name 'kafka' 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 AddPulsar(
+ this IHealthChecksBuilder builder,
+ PulsarHealthCheckOptions options,
+ string? name = default,
+ HealthStatus? failureStatus = default,
+ IEnumerable? tags = default,
+ TimeSpan? timeout = default
+ ) => AddPulsar(
+ builder,
+ options,
+ sp => sp.GetRequiredService(),
+ name: name,
+ failureStatus: failureStatus,
+ tags: tags,
+ timeout: timeout ?? DEFAULT_TIMEOUT);
+
+ ///
+ /// Add a health check for Pulsar cluster.
+ ///
+ /// The .
+ /// Options to configure Pulsar health check.
+ /// The Pulsar client factory.
+ /// The health check name. Optional. If null the type name 'kafka' 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 AddPulsar(
+ this IHealthChecksBuilder builder,
+ PulsarHealthCheckOptions options,
+ Func clientFactory,
+ string? name = default,
+ HealthStatus? failureStatus = default,
+ IEnumerable? tags = default,
+ TimeSpan? timeout = default
+ )
+ {
+ builder.Services.TryAddSingleton(sp => new PulsarHealthCheck(clientFactory(sp), options));
+
+ return builder.Add(new HealthCheckRegistration(
+ name ?? NAME,
+ sp => sp.GetRequiredService(),
+ failureStatus,
+ tags,
+ timeout ?? DEFAULT_TIMEOUT));
+ }
+}
diff --git a/src/HealthChecks.Pulsar/HealthChecks.Pulsar.csproj b/src/HealthChecks.Pulsar/HealthChecks.Pulsar.csproj
new file mode 100644
index 0000000000..600288b3fe
--- /dev/null
+++ b/src/HealthChecks.Pulsar/HealthChecks.Pulsar.csproj
@@ -0,0 +1,15 @@
+
+
+
+ $(DefaultLibraryTargetFrameworks)
+ $(PackageTags);Pulsar
+ HealthChecks.Pulsar is the health check package for Pulsar.
+ $(HealthCheckPulsar)
+
+
+
+
+
+
+
+
diff --git a/src/HealthChecks.Pulsar/PulsarHealthCheck.cs b/src/HealthChecks.Pulsar/PulsarHealthCheck.cs
new file mode 100644
index 0000000000..dfdaa867e5
--- /dev/null
+++ b/src/HealthChecks.Pulsar/PulsarHealthCheck.cs
@@ -0,0 +1,66 @@
+using System.Text;
+using DotPulsar.Abstractions;
+using DotPulsar.Extensions;
+using DotPulsar.Schemas;
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+
+namespace HealthChecks.Pulsar;
+
+///
+/// A health check for Pulsar cluster.
+///
+public class PulsarHealthCheck : IHealthCheck, IAsyncDisposable
+{
+ private readonly IPulsarClient client;
+ private readonly PulsarHealthCheckOptions options;
+ private IProducer? producer;
+
+ public PulsarHealthCheck(IPulsarClient client, PulsarHealthCheckOptions? options)
+ {
+ this.client = client ?? throw new ArgumentNullException(nameof(client));
+ this.options = options ?? new PulsarHealthCheckOptions();
+ }
+
+ ///
+ public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ if (producer != null && producer.IsFinalState())
+ {
+ await producer.DisposeAsync().ConfigureAwait(false);
+ producer = null;
+ }
+
+ if (producer == null)
+ {
+ var builder = client.NewProducer(new StringSchema(Encoding.UTF8));
+ builder.Topic(options.Topic);
+
+ options.Configure?.Invoke(builder);
+ producer ??= builder.Create();
+ }
+
+ using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ if (context.Registration.Timeout != Timeout.InfiniteTimeSpan)
+ {
+ cts.CancelAfter(context.Registration.Timeout);
+ }
+
+ var message = producer.NewMessage();
+ await options.MessageSender(options, message, cts.Token).ConfigureAwait(false);
+ return HealthCheckResult.Healthy();
+ }
+ catch (OperationCanceledException)
+ {
+ return new HealthCheckResult(context.Registration.FailureStatus);
+ }
+ catch (Exception ex)
+ {
+ return new HealthCheckResult(context.Registration.FailureStatus, exception: ex);
+ }
+ }
+
+ ///
+ public ValueTask DisposeAsync() => producer?.DisposeAsync() ?? new ValueTask();
+}
diff --git a/src/HealthChecks.Pulsar/PulsarHealthCheckOptions.cs b/src/HealthChecks.Pulsar/PulsarHealthCheckOptions.cs
new file mode 100644
index 0000000000..238b28380e
--- /dev/null
+++ b/src/HealthChecks.Pulsar/PulsarHealthCheckOptions.cs
@@ -0,0 +1,33 @@
+using DotPulsar;
+using DotPulsar.Abstractions;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace HealthChecks.Pulsar;
+
+///
+/// Options for .
+///
+public class PulsarHealthCheckOptions
+{
+ ///
+ /// The topic name to produce Pulsar messages on.
+ ///
+ public string Topic { get; set; } = PulsarHealthCheckBuilderExtensions.DEFAULT_TOPIC;
+
+ ///
+ /// Optional delegate to configure Pulsar producer.
+ ///
+ public Action>? Configure { get; set; }
+
+ ///
+ /// Delegate to build a message being send to Pulsar.
+ ///
+ public Func MessageBuilder { get; set; } = _ => $"Check Pulsar healthy on {DateTime.UtcNow}";
+
+ ///
+ /// Delegate to build a message being send to Pulsar.
+ ///
+ public Func, CancellationToken, ValueTask> MessageSender { get; set; } = (o, builder, ct) => builder
+ .Key("healthcheck-key")
+ .Send(o.MessageBuilder(o), ct);
+}
diff --git a/test/FunctionalTests/FunctionalTests.csproj b/test/FunctionalTests/FunctionalTests.csproj
index 92e268445a..f8e18812ca 100644
--- a/test/FunctionalTests/FunctionalTests.csproj
+++ b/test/FunctionalTests/FunctionalTests.csproj
@@ -36,6 +36,7 @@
+
diff --git a/test/HealthChecks.Pulsar.Tests/DependencyInjection/RegistrationTests.cs b/test/HealthChecks.Pulsar.Tests/DependencyInjection/RegistrationTests.cs
new file mode 100644
index 0000000000..36b286a776
--- /dev/null
+++ b/test/HealthChecks.Pulsar.Tests/DependencyInjection/RegistrationTests.cs
@@ -0,0 +1,48 @@
+using System.Net;
+using DotPulsar;
+
+namespace HealthChecks.Pulsar.Tests.DependencyInjection;
+
+public class pulsar_registration_should
+{
+ [Fact]
+ public async Task add_health_check_when_properly_configured()
+ {
+ await using var client = PulsarClient.Builder()
+ .ServiceUrl(new Uri("pulsar://localhost:1234"))
+ .Build();
+
+ var services = new ServiceCollection();
+ services.AddHealthChecks()
+ .AddPulsar(_ => client);
+
+ await using var serviceProvider = services.BuildServiceProvider();
+ var options = serviceProvider.GetRequiredService>();
+
+ var registration = options.Value.Registrations.First();
+ var check = registration.Factory(serviceProvider);
+
+ registration.Name.ShouldBe("pulsar");
+ check.ShouldBeOfType();
+ }
+ [Fact]
+ public async Task add_named_health_check_when_properly_configured()
+ {
+ await using var client = PulsarClient.Builder()
+ .ServiceUrl(new Uri("pulsar://localhost:1234"))
+ .Build();
+
+ var services = new ServiceCollection();
+ services.AddHealthChecks()
+ .AddPulsar(_ => client, name: "my-pulsar-group");
+
+ await using var serviceProvider = services.BuildServiceProvider();
+ var options = serviceProvider.GetRequiredService>();
+
+ var registration = options.Value.Registrations.First();
+ var check = registration.Factory(serviceProvider);
+
+ registration.Name.ShouldBe("my-pulsar-group");
+ check.ShouldBeOfType();
+ }
+}
diff --git a/test/HealthChecks.Pulsar.Tests/Functional/PulsarHealthCheckTests.cs b/test/HealthChecks.Pulsar.Tests/Functional/PulsarHealthCheckTests.cs
new file mode 100644
index 0000000000..44957fee96
--- /dev/null
+++ b/test/HealthChecks.Pulsar.Tests/Functional/PulsarHealthCheckTests.cs
@@ -0,0 +1,63 @@
+using System.Net;
+using DotPulsar;
+
+namespace HealthChecks.Pulsar.Tests.Functional;
+
+public class pulsar_healthcheck_should
+{
+ [Fact]
+ public async Task be_unhealthy_if_pulsar_is_unavailable()
+ {
+ await using var client = PulsarClient.Builder()
+ .ServiceUrl(new Uri("pulsar://localhost:1234"))
+ .Build();
+
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddPulsar(_ => client, tags: new string[] { "pulsar" });
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("pulsar")
+ });
+ });
+
+ using var server = new TestServer(webHostBuilder);
+
+ using var response = await server.CreateRequest("/health").GetAsync();
+
+ response.StatusCode.ShouldBe(HttpStatusCode.ServiceUnavailable);
+ }
+
+ [Fact]
+ public async Task be_healthy_if_pulsar_is_available()
+ {
+ await using var client = PulsarClient.Builder()
+ .ServiceUrl(new Uri("pulsar://localhost:6650"))
+ .Build();
+
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddPulsar(_ => client, tags: new string[] { "pulsar" });
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("pulsar")
+ });
+ });
+
+ using var server = new TestServer(webHostBuilder);
+
+ using var response = await server.CreateRequest("/health").GetAsync();
+
+ response.StatusCode.ShouldBe(HttpStatusCode.OK);
+ }
+}
diff --git a/test/HealthChecks.Pulsar.Tests/HealthChecks.Pulsar.Tests.csproj b/test/HealthChecks.Pulsar.Tests/HealthChecks.Pulsar.Tests.csproj
new file mode 100644
index 0000000000..78c91a32b0
--- /dev/null
+++ b/test/HealthChecks.Pulsar.Tests/HealthChecks.Pulsar.Tests.csproj
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/test/HealthChecks.Pulsar.Tests/HealthChecks.Pulsar.approved.txt b/test/HealthChecks.Pulsar.Tests/HealthChecks.Pulsar.approved.txt
new file mode 100644
index 0000000000..5f282702bb
--- /dev/null
+++ b/test/HealthChecks.Pulsar.Tests/HealthChecks.Pulsar.approved.txt
@@ -0,0 +1 @@
+
\ No newline at end of file