From 63053f67e20f31628ecf0f3983f6fa1e89ee8c06 Mon Sep 17 00:00:00 2001 From: Dennis van der Stelt Date: Tue, 10 Mar 2026 10:32:49 +0000 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20Allow=20disabling=20RabbitMQ=20?= =?UTF-8?q?broker=20requirement=20checks=20via=20connection=20string?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For multi-tenant RabbitMQ deployments where tenants cannot be granted management API access, operators can now add DisableBrokerRequirementChecks=true to the connection string. --- .../RabbitMQTransportExtensions.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs b/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs index ebf285839c..1e47dbcf48 100644 --- a/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs +++ b/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs @@ -42,5 +42,13 @@ public static void ApplySettingsFromConnectionString(this RabbitMQTransport tran _ = bool.TryParse(useExternalAuthMechanismString, out var useExternalAuthMechanism); transport.UseExternalAuthMechanism = useExternalAuthMechanism; } + + if (dictionary.TryGetValue("DisableBrokerRequirementChecks", out var disableBrokerRequirementChecksString) + && bool.TryParse(disableBrokerRequirementChecksString, out var disableBrokerRequirementChecks) + && disableBrokerRequirementChecks) + { + transport.DisabledBrokerRequirementChecks = + BrokerRequirementChecks.Version310OrNewer | BrokerRequirementChecks.StreamsEnabled; + } } } From ebf0853e96fff865fb8fcdfb411baa512abe7576 Mon Sep 17 00:00:00 2001 From: Dennis van der Stelt Date: Thu, 12 Mar 2026 12:26:59 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=A8=20Disable=20delivery=20limit=20va?= =?UTF-8?q?lidation=20when=20broker=20checks=20are=20disabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delivery limit validation also requires management API access, so it must be disabled together with broker requirement checks. --- .../RabbitMQTransportExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs b/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs index 1e47dbcf48..1f3f38400c 100644 --- a/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs +++ b/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs @@ -49,6 +49,7 @@ public static void ApplySettingsFromConnectionString(this RabbitMQTransport tran { transport.DisabledBrokerRequirementChecks = BrokerRequirementChecks.Version310OrNewer | BrokerRequirementChecks.StreamsEnabled; + transport.ValidateDeliveryLimits = false; } } } From 67a56ca5da9ac21bc7e0239ba13e288e8ec929f6 Mon Sep 17 00:00:00 2001 From: Dennis van der Stelt Date: Thu, 12 Mar 2026 12:36:30 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=A8=20Skip=20management=20API=20featu?= =?UTF-8?q?res=20when=20broker=20checks=20are=20disabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When DisableBrokerRequirementChecks=true, do not register RabbitMQQuery (broker throughput) or QueueLengthProvider, since both require management API access that the user has indicated is unavailable. A NoOpQueueLengthProvider is registered instead to satisfy the DI requirement. --- .../NoOpQueueLengthProvider.cs | 16 ++++++++++++++++ ...ConventionalRoutingTransportCustomization.cs | 17 +++++++++++++++-- ...bbitMQDirectRoutingTransportCustomization.cs | 17 +++++++++++++++-- .../RabbitMQTransportExtensions.cs | 16 ++++++++++++++++ 4 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 src/ServiceControl.Transports.RabbitMQ/NoOpQueueLengthProvider.cs diff --git a/src/ServiceControl.Transports.RabbitMQ/NoOpQueueLengthProvider.cs b/src/ServiceControl.Transports.RabbitMQ/NoOpQueueLengthProvider.cs new file mode 100644 index 0000000000..09d272617a --- /dev/null +++ b/src/ServiceControl.Transports.RabbitMQ/NoOpQueueLengthProvider.cs @@ -0,0 +1,16 @@ +namespace ServiceControl.Transports.RabbitMQ +{ + using System.Threading; + using System.Threading.Tasks; + + class NoOpQueueLengthProvider : IProvideQueueLength + { + public void TrackEndpointInputQueue(EndpointToQueueMapping queueToTrack) + { + } + + public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } +} diff --git a/src/ServiceControl.Transports.RabbitMQ/RabbitMQConventionalRoutingTransportCustomization.cs b/src/ServiceControl.Transports.RabbitMQ/RabbitMQConventionalRoutingTransportCustomization.cs index 0a096c5222..8338c5d04a 100644 --- a/src/ServiceControl.Transports.RabbitMQ/RabbitMQConventionalRoutingTransportCustomization.cs +++ b/src/ServiceControl.Transports.RabbitMQ/RabbitMQConventionalRoutingTransportCustomization.cs @@ -53,11 +53,24 @@ protected override RabbitMQTransport CreateTransport(TransportSettings transport } protected override void AddTransportForPrimaryCore(IServiceCollection services, TransportSettings transportSettings) - => services.AddSingleton(); + { + if (!RabbitMQTransportExtensions.HasBrokerRequirementChecksDisabled(transportSettings.ConnectionString)) + { + services.AddSingleton(); + } + } protected sealed override void AddTransportForMonitoringCore(IServiceCollection services, TransportSettings transportSettings) { - services.AddSingleton(); + if (RabbitMQTransportExtensions.HasBrokerRequirementChecksDisabled(transportSettings.ConnectionString)) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } + services.AddHostedService(provider => provider.GetRequiredService()); } } diff --git a/src/ServiceControl.Transports.RabbitMQ/RabbitMQDirectRoutingTransportCustomization.cs b/src/ServiceControl.Transports.RabbitMQ/RabbitMQDirectRoutingTransportCustomization.cs index 2d497877d0..6ec3e8bb9e 100644 --- a/src/ServiceControl.Transports.RabbitMQ/RabbitMQDirectRoutingTransportCustomization.cs +++ b/src/ServiceControl.Transports.RabbitMQ/RabbitMQDirectRoutingTransportCustomization.cs @@ -53,11 +53,24 @@ protected override RabbitMQTransport CreateTransport(TransportSettings transport } protected override void AddTransportForPrimaryCore(IServiceCollection services, TransportSettings transportSettings) - => services.AddSingleton(); + { + if (!RabbitMQTransportExtensions.HasBrokerRequirementChecksDisabled(transportSettings.ConnectionString)) + { + services.AddSingleton(); + } + } protected sealed override void AddTransportForMonitoringCore(IServiceCollection services, TransportSettings transportSettings) { - services.AddSingleton(); + if (RabbitMQTransportExtensions.HasBrokerRequirementChecksDisabled(transportSettings.ConnectionString)) + { + services.AddSingleton(); + } + else + { + services.AddSingleton(); + } + services.AddHostedService(provider => provider.GetRequiredService()); } } diff --git a/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs b/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs index 1f3f38400c..f1e00cbd38 100644 --- a/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs +++ b/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs @@ -8,6 +8,22 @@ static class RabbitMQTransportExtensions { + public static bool HasBrokerRequirementChecksDisabled(string connectionString) + { + if (connectionString.StartsWith("amqp", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + var dictionary = new DbConnectionStringBuilder { ConnectionString = connectionString } + .OfType>() + .ToDictionary(pair => pair.Key, pair => pair.Value.ToString(), StringComparer.OrdinalIgnoreCase); + + return dictionary.TryGetValue("DisableBrokerRequirementChecks", out var value) + && bool.TryParse(value, out var disabled) + && disabled; + } + public static void ApplySettingsFromConnectionString(this RabbitMQTransport transport, string connectionString) { if (connectionString.StartsWith("amqp", StringComparison.OrdinalIgnoreCase)) From c475506c947b44dc79056122deabf215c8004cc3 Mon Sep 17 00:00:00 2001 From: Dennis van der Stelt Date: Thu, 12 Mar 2026 13:20:35 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=E2=9A=9C=EF=B8=8F=20Extract=20shared=20con?= =?UTF-8?q?nection=20string=20parsing,=20add=20startup=20logging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract ParseConnectionString helper to eliminate duplicated DbConnectionStringBuilder logic. Add warning log on startup when queue length monitoring is disabled. Mark NoOpQueueLengthProvider as sealed with explanatory comment. --- .../NoOpQueueLengthProvider.cs | 10 +++++-- .../RabbitMQTransportExtensions.cs | 29 ++++++++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/ServiceControl.Transports.RabbitMQ/NoOpQueueLengthProvider.cs b/src/ServiceControl.Transports.RabbitMQ/NoOpQueueLengthProvider.cs index 09d272617a..a9fd004863 100644 --- a/src/ServiceControl.Transports.RabbitMQ/NoOpQueueLengthProvider.cs +++ b/src/ServiceControl.Transports.RabbitMQ/NoOpQueueLengthProvider.cs @@ -2,14 +2,20 @@ namespace ServiceControl.Transports.RabbitMQ { using System.Threading; using System.Threading.Tasks; + using Microsoft.Extensions.Logging; - class NoOpQueueLengthProvider : IProvideQueueLength + // Used when DisableBrokerRequirementChecks=true and the RabbitMQ Management API is not available + sealed class NoOpQueueLengthProvider(ILogger logger) : IProvideQueueLength { public void TrackEndpointInputQueue(EndpointToQueueMapping queueToTrack) { } - public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; + public Task StartAsync(CancellationToken cancellationToken) + { + logger.LogWarning("Queue length monitoring is disabled because RabbitMQ broker requirement checks are disabled via the connection string. Queue length data will not be available."); + return Task.CompletedTask; + } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } diff --git a/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs b/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs index f1e00cbd38..f6786e2513 100644 --- a/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs +++ b/src/ServiceControl.Transports.RabbitMQ/RabbitMQTransportExtensions.cs @@ -1,4 +1,5 @@ -namespace ServiceControl.Transports.RabbitMQ; +#nullable enable +namespace ServiceControl.Transports.RabbitMQ; using System; using System.Collections.Generic; @@ -10,15 +11,12 @@ static class RabbitMQTransportExtensions { public static bool HasBrokerRequirementChecksDisabled(string connectionString) { - if (connectionString.StartsWith("amqp", StringComparison.OrdinalIgnoreCase)) + var dictionary = ParseConnectionString(connectionString); + if (dictionary is null) { return false; } - var dictionary = new DbConnectionStringBuilder { ConnectionString = connectionString } - .OfType>() - .ToDictionary(pair => pair.Key, pair => pair.Value.ToString(), StringComparer.OrdinalIgnoreCase); - return dictionary.TryGetValue("DisableBrokerRequirementChecks", out var value) && bool.TryParse(value, out var disabled) && disabled; @@ -26,15 +24,12 @@ public static bool HasBrokerRequirementChecksDisabled(string connectionString) public static void ApplySettingsFromConnectionString(this RabbitMQTransport transport, string connectionString) { - if (connectionString.StartsWith("amqp", StringComparison.OrdinalIgnoreCase)) + var dictionary = ParseConnectionString(connectionString); + if (dictionary is null) { return; } - var dictionary = new DbConnectionStringBuilder { ConnectionString = connectionString } - .OfType>() - .ToDictionary(pair => pair.Key, pair => pair.Value.ToString(), StringComparer.OrdinalIgnoreCase); - if (dictionary.TryGetValue("ValidateDeliveryLimits", out var validateDeliveryLimitsString)) { _ = bool.TryParse(validateDeliveryLimitsString, out var validateDeliveryLimits); @@ -68,4 +63,16 @@ public static void ApplySettingsFromConnectionString(this RabbitMQTransport tran transport.ValidateDeliveryLimits = false; } } + + static Dictionary? ParseConnectionString(string connectionString) + { + if (connectionString.StartsWith("amqp", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + return new DbConnectionStringBuilder { ConnectionString = connectionString } + .OfType>() + .ToDictionary(pair => pair.Key, pair => pair.Value.ToString(), StringComparer.OrdinalIgnoreCase); + } }