From c4e288f55fc67b5096e875de0a19f52c11044aec Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Wed, 30 Oct 2024 17:10:39 +1100 Subject: [PATCH 01/46] Add very basic client for Management API --- .../Converters/DeliveryLimitConverter.cs | 34 +++++++++++++ .../ManagementApi/IManagementApi.cs | 12 +++++ .../ManagementApi/ManagementClient.cs | 48 +++++++++++++++++++ .../ManagementApi/Models/Queue.cs | 22 +++++++++ .../RabbitMQTransport.cs | 4 ++ .../RabbitMQTransportInfrastructure.cs | 4 ++ 6 files changed, 124 insertions(+) create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/DeliveryLimitConverter.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/DeliveryLimitConverter.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/DeliveryLimitConverter.cs new file mode 100644 index 000000000..cf37b5c64 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/DeliveryLimitConverter.cs @@ -0,0 +1,34 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +class DeliveryLimitConverter : JsonConverter +{ + public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Number) + { + return reader.GetInt32(); + } + else if (reader.TokenType == JsonTokenType.String) + { + var value = reader.GetString(); + if (string.Equals(value, "unlimited", StringComparison.OrdinalIgnoreCase)) + { + return -1; + } + + throw new JsonException($"Unexpected string value for `delivery-limit` - {value}"); + } + else + { + throw new JsonException($"Expected `delivery-limit` to be either a Number or the String `unlimited`, not a {reader.TokenType}"); + } + } + + public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) => writer.WriteNumberValue(value); +} diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs new file mode 100644 index 000000000..483544fa7 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs @@ -0,0 +1,12 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi; + +using System.Threading; +using System.Threading.Tasks; +using NServiceBus.Transport.RabbitMQ.ManagementApi.Models; + +interface IManagementApi +{ + Task GetQueue(string queueName, CancellationToken cancellationToken = default); +} diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs new file mode 100644 index 000000000..21a313492 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs @@ -0,0 +1,48 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi; + +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NServiceBus.Transport.RabbitMQ.ManagementApi.Models; + +class ManagementClient : IManagementApi +{ + readonly HttpClient httpClient; + readonly string virtualHost; + readonly string escapedVirtualHost; + + public ManagementClient(ConnectionConfiguration connectionConfiguration) + { + virtualHost = connectionConfiguration.VirtualHost; + escapedVirtualHost = Uri.EscapeDataString(virtualHost); + + var uriBuilder = new UriBuilder + { + Scheme = connectionConfiguration.UseTls ? "https" : "http", + Host = connectionConfiguration.Host, + Port = connectionConfiguration.Port + }; + + httpClient = new HttpClient { BaseAddress = uriBuilder.Uri }; + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( + "Basic", + Convert.ToBase64String(Encoding.ASCII.GetBytes($"{connectionConfiguration.UserName}:{connectionConfiguration.Password}"))); + } + + public async Task GetQueue(string queueName, CancellationToken cancellationToken = default) + { + var escapedQueueName = Uri.EscapeDataString(queueName); + var response = await httpClient.GetAsync($"api/queues/{escapedVirtualHost}/{escapedQueueName}", cancellationToken) + .ConfigureAwait(false); + + return await response.EnsureSuccessStatusCode() + .Content.ReadFromJsonAsync(cancellationToken) + .ConfigureAwait(false); + } +} diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs new file mode 100644 index 000000000..2e3237cbd --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs @@ -0,0 +1,22 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; + +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; + +class Queue +{ + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("delivery_limit")] + [JsonConverter(typeof(DeliveryLimitConverter))] + public int DeliveryLimit { get; set; } + + [JsonExtensionData] + public IReadOnlyDictionary ExtraProperties { get; } = new Dictionary(); +} + diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index 501b6e71e..33090cca7 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -5,6 +5,7 @@ using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; + using NServiceBus.Transport.RabbitMQ.ManagementApi; using RabbitMQ.Client; using RabbitMQ.Client.Events; using Transport; @@ -211,6 +212,8 @@ public override async Task Initialize(HostSettings host additionalClusterNodes ); + var managementClient = new ManagementClient(ConnectionConfiguration); + var channelProvider = new ChannelProvider(connectionFactory, NetworkRecoveryInterval, RoutingTopology); await channelProvider.CreateConnection(cancellationToken).ConfigureAwait(false); @@ -223,6 +226,7 @@ public override async Task Initialize(HostSettings host RoutingTopology, channelProvider, converter, + managementClient, OutgoingNativeMessageCustomization, TimeToWaitBeforeTriggeringCircuitBreaker, PrefetchCountCalculation, diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs index a86c7d0ee..90fcba526 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; + using NServiceBus.Transport.RabbitMQ.ManagementApi; using global::RabbitMQ.Client; sealed class RabbitMQTransportInfrastructure : TransportInfrastructure @@ -14,10 +15,12 @@ sealed class RabbitMQTransportInfrastructure : TransportInfrastructure readonly IRoutingTopology routingTopology; readonly TimeSpan networkRecoveryInterval; readonly bool supportsDelayedDelivery; + readonly IManagementApi managementApi; public RabbitMQTransportInfrastructure(HostSettings hostSettings, ReceiveSettings[] receiverSettings, ConnectionFactory connectionFactory, IRoutingTopology routingTopology, ChannelProvider channelProvider, MessageConverter messageConverter, + IManagementApi managementApi, Action messageCustomization, TimeSpan timeToWaitBeforeTriggeringCircuitBreaker, PrefetchCountCalculation prefetchCountCalculation, TimeSpan networkRecoveryInterval, bool supportsDelayedDelivery) @@ -25,6 +28,7 @@ public RabbitMQTransportInfrastructure(HostSettings hostSettings, ReceiveSetting this.connectionFactory = connectionFactory; this.routingTopology = routingTopology; this.channelProvider = channelProvider; + this.managementApi = managementApi; this.networkRecoveryInterval = networkRecoveryInterval; this.supportsDelayedDelivery = supportsDelayedDelivery; From 42588513253bb1711a4e91cecf406edbdfb1b480 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Fri, 1 Nov 2024 00:44:07 +1100 Subject: [PATCH 02/46] Use default port for management API Pass ManagementClient through to MessagePump Fix Queue model definition --- .../ManagementApi/ManagementClient.cs | 2 +- .../ManagementApi/Models/Queue.cs | 4 ++-- .../RabbitMQTransportInfrastructure.cs | 7 +++++-- .../Receiving/MessagePump.cs | 10 ++++++++++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs index 21a313492..687f18330 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs @@ -26,7 +26,7 @@ public ManagementClient(ConnectionConfiguration connectionConfiguration) { Scheme = connectionConfiguration.UseTls ? "https" : "http", Host = connectionConfiguration.Host, - Port = connectionConfiguration.Port + Port = 15672 // TODO: fallback to default only if specific details aren't given in config }; httpClient = new HttpClient { BaseAddress = uriBuilder.Uri }; diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs index 2e3237cbd..c6d509a26 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs @@ -10,13 +10,13 @@ namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; class Queue { [JsonPropertyName("name")] - public string Name { get; set; } + public required string Name { get; set; } [JsonPropertyName("delivery_limit")] [JsonConverter(typeof(DeliveryLimitConverter))] public int DeliveryLimit { get; set; } [JsonExtensionData] - public IReadOnlyDictionary ExtraProperties { get; } = new Dictionary(); + public IDictionary ExtraProperties { get; } = new Dictionary(); } diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs index 90fcba526..34c702d73 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs @@ -40,8 +40,11 @@ public RabbitMQTransportInfrastructure(HostSettings hostSettings, ReceiveSetting IMessageReceiver CreateMessagePump(HostSettings hostSettings, ReceiveSettings settings, MessageConverter messageConverter, TimeSpan timeToWaitBeforeTriggeringCircuitBreaker, PrefetchCountCalculation prefetchCountCalculation) { var consumerTag = $"{hostSettings.HostDisplayName} - {hostSettings.Name}"; - var receiveAddress = ToTransportAddress(settings.ReceiveAddress); - return new MessagePump(settings, connectionFactory, routingTopology, messageConverter, consumerTag, channelProvider, timeToWaitBeforeTriggeringCircuitBreaker, prefetchCountCalculation, hostSettings.CriticalErrorAction, networkRecoveryInterval); + + return new MessagePump( + settings, connectionFactory, routingTopology, messageConverter, + consumerTag, channelProvider, managementApi, + timeToWaitBeforeTriggeringCircuitBreaker, prefetchCountCalculation, hostSettings.CriticalErrorAction, networkRecoveryInterval); } internal async Task SetupInfrastructure(string[] sendingQueues, CancellationToken cancellationToken = default) diff --git a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs index 2893e0a18..282be7678 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs @@ -11,6 +11,7 @@ using global::RabbitMQ.Client.Events; using global::RabbitMQ.Client.Exceptions; using Logging; + using NServiceBus.Transport.RabbitMQ.ManagementApi; sealed partial class MessagePump : IMessageReceiver { @@ -22,6 +23,7 @@ sealed partial class MessagePump : IMessageReceiver readonly MessageConverter messageConverter; readonly string consumerTag; readonly ChannelProvider channelProvider; + readonly IManagementApi managementApi; readonly TimeSpan timeToWaitBeforeTriggeringCircuitBreaker; readonly QueuePurger queuePurger; readonly PrefetchCountCalculation prefetchCountCalculation; @@ -50,6 +52,7 @@ public MessagePump( MessageConverter messageConverter, string consumerTag, ChannelProvider channelProvider, + IManagementApi managementApi, TimeSpan timeToWaitBeforeTriggeringCircuitBreaker, PrefetchCountCalculation prefetchCountCalculation, Action Date: Fri, 1 Nov 2024 00:44:52 +1100 Subject: [PATCH 03/46] Add policy related models for Management API --- .../Converters/PolicyTargetConverter.cs | 41 +++++++++++++++++++ .../ManagementApi/Models/Policy.cs | 35 ++++++++++++++++ .../ManagementApi/Models/PolicyDefinition.cs | 19 +++++++++ .../ManagementApi/Models/PolicyTarget.cs | 11 +++++ .../ManagementApi/Models/Queue.cs | 9 ++++ 5 files changed, 115 insertions(+) create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/PolicyTargetConverter.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Policy.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyDefinition.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyTarget.cs diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/PolicyTargetConverter.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/PolicyTargetConverter.cs new file mode 100644 index 000000000..6d1e893ae --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/PolicyTargetConverter.cs @@ -0,0 +1,41 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using NServiceBus.Transport.RabbitMQ.ManagementApi.Models; + +class PolicyTargetConverter : JsonConverter +{ + public override PolicyTarget Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return value switch + { + "all" => PolicyTarget.All, + "queues" => PolicyTarget.Queues, + "classic_queues" => PolicyTarget.ClassicQueues, + "quorum_queues" => PolicyTarget.QuorumQueues, + "streams" => PolicyTarget.Streams, + "exchanges" => PolicyTarget.Exchanges, + _ => throw new JsonException($"Unknown PolicyTarget: {value}") + }; + } + + public override void Write(Utf8JsonWriter writer, PolicyTarget policyTarget, JsonSerializerOptions options) + { + var value = policyTarget switch + { + PolicyTarget.All => "all", + PolicyTarget.Queues => "queues", + PolicyTarget.ClassicQueues => "classic_queues", + PolicyTarget.QuorumQueues => "quorum_queues", + PolicyTarget.Streams => "streams", + PolicyTarget.Exchanges => "exchanges", + _ => throw new ArgumentOutOfRangeException(nameof(policyTarget), $"PolicyTarget value out of range: {policyTarget}") + }; + writer.WriteStringValue(value); + } +} diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Policy.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Policy.cs new file mode 100644 index 000000000..cfee275c3 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Policy.cs @@ -0,0 +1,35 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; + +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; +using NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; + +class Policy +{ + [JsonPropertyName("vhost")] + public required string VirtualHost { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("pattern")] + public required Regex Pattern { get; set; } + + [JsonConverter(typeof(PolicyTargetConverter))] + [JsonPropertyName("apply-to")] + public required PolicyTarget ApplyTo { get; set; } + + [JsonPropertyName("definition")] + public required PolicyDefinition Definition { get; set; } + + [JsonPropertyName("priority")] + public int Priority { get; set; } + + [JsonExtensionData] + public IDictionary ExtraProperties { get; } = new Dictionary(); +} + diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyDefinition.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyDefinition.cs new file mode 100644 index 000000000..dc8fd797f --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyDefinition.cs @@ -0,0 +1,19 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; + +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; + +class PolicyDefinition +{ + [JsonPropertyName("delivery-limit")] + [JsonConverter(typeof(DeliveryLimitConverter))] + public int DeliveryLimit { get; set; } + + [JsonExtensionData] + public IDictionary ExtraProperties { get; } = new Dictionary(); +} + diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyTarget.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyTarget.cs new file mode 100644 index 000000000..cd3952f50 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyTarget.cs @@ -0,0 +1,11 @@ +namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; + +enum PolicyTarget +{ + All, + Queues, + ClassicQueues, + QuorumQueues, + Streams, + Exchanges, +} diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs index c6d509a26..af1b6b25a 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs @@ -16,6 +16,15 @@ class Queue [JsonConverter(typeof(DeliveryLimitConverter))] public int DeliveryLimit { get; set; } + [JsonPropertyName("effective_policy_definition")] + public required PolicyDefinition EffectivePolicyDefinition { get; set; } + + [JsonPropertyName("policy")] + public string? AppliedPolicyName { get; set; } + + [JsonPropertyName("operator_policy")] + public string? AppliedOperatorPolicyName { get; set; } + [JsonExtensionData] public IDictionary ExtraProperties { get; } = new Dictionary(); } From 7f4fa07bfb6133156352f9e12447be3c5cf5506c Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Fri, 1 Nov 2024 17:57:09 +1100 Subject: [PATCH 04/46] Added Management Client method for getting Management API overview --- .../Converters/VersionConverter.cs | 24 ++++++++++++ .../ManagementApi/HttpStatusCodeExtensions.cs | 10 +++++ .../ManagementApi/IManagementApi.cs | 4 +- .../ManagementApi/ManagementClient.cs | 33 +++++++++++++++-- .../ManagementApi/Models/Overview.cs | 37 +++++++++++++++++++ .../ManagementApi/Models/PolicyDefinition.cs | 2 +- .../ManagementApi/Response.cs | 12 ++++++ .../Receiving/MessagePump.cs | 29 ++++++++++++--- 8 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/VersionConverter.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/HttpStatusCodeExtensions.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Overview.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Response.cs diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/VersionConverter.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/VersionConverter.cs new file mode 100644 index 000000000..4332a6718 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/VersionConverter.cs @@ -0,0 +1,24 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +class VersionConverter : JsonConverter +{ + public override Version Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString() ?? throw new JsonException("Missing version value"); + + if (Version.TryParse(value, out var version)) + { + return version; + } + + throw new JsonException($"Invalid version value {value}"); + } + + public override void Write(Utf8JsonWriter writer, Version version, JsonSerializerOptions options) => writer.WriteStringValue(version.ToString()); +} diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/HttpStatusCodeExtensions.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/HttpStatusCodeExtensions.cs new file mode 100644 index 000000000..3d8d4c983 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/HttpStatusCodeExtensions.cs @@ -0,0 +1,10 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi; + +using System.Net; + +static class HttpStatusCodeExtensions +{ + public static bool IsSuccessStatusCode(this HttpStatusCode statusCode) => (int)statusCode is >= 200 and <= 299; +} diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs index 483544fa7..9a9b1daeb 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs @@ -8,5 +8,7 @@ namespace NServiceBus.Transport.RabbitMQ.ManagementApi; interface IManagementApi { - Task GetQueue(string queueName, CancellationToken cancellationToken = default); + Task> GetQueue(string queueName, CancellationToken cancellationToken = default); + + Task> GetOverview(CancellationToken cancellationToken = default); } diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs index 687f18330..b49d6907c 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs @@ -35,14 +35,39 @@ public ManagementClient(ConnectionConfiguration connectionConfiguration) Convert.ToBase64String(Encoding.ASCII.GetBytes($"{connectionConfiguration.UserName}:{connectionConfiguration.Password}"))); } - public async Task GetQueue(string queueName, CancellationToken cancellationToken = default) + public async Task> GetQueue(string queueName, CancellationToken cancellationToken = default) { + Queue? value = null; + var escapedQueueName = Uri.EscapeDataString(queueName); var response = await httpClient.GetAsync($"api/queues/{escapedVirtualHost}/{escapedQueueName}", cancellationToken) .ConfigureAwait(false); - return await response.EnsureSuccessStatusCode() - .Content.ReadFromJsonAsync(cancellationToken) - .ConfigureAwait(false); + if (response.IsSuccessStatusCode) + { + value = await response.Content.ReadFromJsonAsync(cancellationToken).ConfigureAwait(false); + } + + return new Response( + response.StatusCode, + response.ReasonPhrase ?? string.Empty, + value); + } + + public async Task> GetOverview(CancellationToken cancellationToken = default) + { + Overview? value = null; + + var response = await httpClient.GetAsync($"api/overview", cancellationToken).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + value = await response.Content.ReadFromJsonAsync(cancellationToken).ConfigureAwait(false); + } + + return new Response( + response.StatusCode, + response.ReasonPhrase ?? string.Empty, + value); } } diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Overview.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Overview.cs new file mode 100644 index 000000000..4f6853f26 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Overview.cs @@ -0,0 +1,37 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; + +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; + +class Overview +{ + [JsonPropertyName("product_name")] + public required string ProductName { get; set; } + + [JsonConverter(typeof(VersionConverter))] + [JsonPropertyName("management_version")] + public required Version ManagementVersion { get; set; } + + [JsonConverter(typeof(VersionConverter))] + [JsonPropertyName("product_version")] + public required Version ProductVersion { get; set; } + + [JsonConverter(typeof(VersionConverter))] + [JsonPropertyName("rabbitmq_version")] + public required Version RabbitMqVersion { get; set; } + + [JsonPropertyName("cluster_name")] + public required string ClusterName { get; set; } + + [JsonPropertyName("node")] + public required string Node { get; set; } + + [JsonExtensionData] + public IDictionary ExtraProperties { get; } = new Dictionary(); +} + diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyDefinition.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyDefinition.cs index dc8fd797f..37807b151 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyDefinition.cs +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyDefinition.cs @@ -11,7 +11,7 @@ class PolicyDefinition { [JsonPropertyName("delivery-limit")] [JsonConverter(typeof(DeliveryLimitConverter))] - public int DeliveryLimit { get; set; } + public int? DeliveryLimit { get; set; } [JsonExtensionData] public IDictionary ExtraProperties { get; } = new Dictionary(); diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Response.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Response.cs new file mode 100644 index 000000000..60710adde --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Response.cs @@ -0,0 +1,12 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi; + +using System.Diagnostics.CodeAnalysis; +using System.Net; + +record Response(HttpStatusCode StatusCode, string Reason, T Value) +{ + [MemberNotNullWhen(true, nameof(Value))] + public bool HasValue => StatusCode.IsSuccessStatusCode(); +} diff --git a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs index 282be7678..853faa6ea 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs @@ -55,8 +55,7 @@ public MessagePump( IManagementApi managementApi, TimeSpan timeToWaitBeforeTriggeringCircuitBreaker, PrefetchCountCalculation prefetchCountCalculation, - Action criticalErrorAction, + Action criticalErrorAction, TimeSpan retryDelay) { this.settings = settings; @@ -98,11 +97,31 @@ public async Task Initialize(PushRuntimeSettings limitations, OnMessage onMessag await queuePurger.Purge(ReceiveAddress, cancellationToken).ConfigureAwait(false); } - var queue = await managementApi.GetQueue(ReceiveAddress, cancellationToken).ConfigureAwait(false); - if (queue.DeliveryLimit != -1) + await ValidateDeliveryLimit(cancellationToken).ConfigureAwait(false); + } + + async Task ValidateDeliveryLimit(CancellationToken cancellationToken) + { + var response = await managementApi.GetQueue(ReceiveAddress, cancellationToken).ConfigureAwait(false); + + if (!response.HasValue) + { + // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning + Logger.WarnFormat("Could not determine delivery limit for {0}. ({1}: {2})", ReceiveAddress, response.StatusCode, response.Reason); + return; + } + + var queue = response.Value; + if (queue.DeliveryLimit == -1) + { + return; + } + + if (queue.EffectivePolicyDefinition.DeliveryLimit.HasValue && + queue.EffectivePolicyDefinition.DeliveryLimit != -1) { - Logger.WarnFormat("Delivery limit set to {0}.", queue.DeliveryLimit); } + Logger.WarnFormat("Delivery limit set to {0}.", queue.DeliveryLimit); } public async Task StartReceive(CancellationToken cancellationToken = default) From 5f5deb2bb1ab8f74756a42a638b07fd662ae8740 Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Thu, 31 Oct 2024 19:00:14 -0700 Subject: [PATCH 05/46] Update exception messages changes from RabbitMQ 4.0 --- .../When_classic_endpoint_uses_quorum_error_queue.cs | 2 +- .../QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs | 2 +- .../When_quorum_endpoint_uses_classic_error_queue.cs | 2 +- .../QuorumQueues/When_quorum_endpoint_uses_classic_queue.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_error_queue.cs b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_error_queue.cs index a9570ea94..4e9d9b551 100644 --- a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_error_queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_error_queue.cs @@ -24,7 +24,7 @@ public async Task Should_fail_to_start() .Run()); Assert.That(exception.Message, Does.Contain("PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'rabbitmq.transport.tests.quorum-error'")); - Assert.That(exception.Message, Does.Contain("received none but current is the value 'quorum'")); + Assert.That(exception.Message, Does.Contain("received 'classic' but current is 'quorum'")); } class ClassicQueueEndpoint : EndpointConfigurationBuilder diff --git a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs index 73750a13f..14a8f4653 100644 --- a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs @@ -25,7 +25,7 @@ public async Task Should_fail_to_start() .Run()); Assert.That(exception.Message, Does.Contain("PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'ClassicEndpointUsesQuorumQueue.ClassicQueueEndpoint'")); - Assert.That(exception.Message, Does.Contain("received none but current is the value 'quorum'")); + Assert.That(exception.Message, Does.Contain("received 'classic' but current is 'quorum'")); } class ClassicQueueEndpoint : EndpointConfigurationBuilder diff --git a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_error_queue.cs b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_error_queue.cs index 7909eb905..7167c648b 100644 --- a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_error_queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_error_queue.cs @@ -24,7 +24,7 @@ public async Task Should_fail_to_start() .Run()); Assert.That(exception.Message, Does.Contain("PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'rabbitmq.transport.tests.classic-error'")); - Assert.That(exception.Message, Does.Contain("received the value 'quorum' of type 'longstr' but current is none'")); + Assert.That(exception.Message, Does.Contain("received 'quorum' but current is 'classic'")); } class QuorumQueueEndpoint : EndpointConfigurationBuilder diff --git a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_queue.cs b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_queue.cs index c5a8aaa65..2bec0bef5 100644 --- a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_queue.cs @@ -24,7 +24,7 @@ public async Task Should_fail_to_start() .Run()); Assert.That(exception.Message, Does.Contain("PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'QuorumEndpointUsesClassicQueue.QuorumQueueEndpoint'")); - Assert.That(exception.Message, Does.Contain("received the value 'quorum' of type 'longstr' but current is none")); + Assert.That(exception.Message, Does.Contain("received 'quorum' but current is 'classic'")); } class QuorumQueueEndpoint : EndpointConfigurationBuilder From 90a394f0c5ce4b2d90af8175c871ca160df53a54 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Sat, 2 Nov 2024 01:01:05 +1100 Subject: [PATCH 06/46] Determine whether access to management API is available Determine whether policy override of delivery limit is supported by the broker version --- .../Connection/ConnectionExtensions.cs | 18 +++++++-- .../Receiving/MessagePump.cs | 39 ++++++++++++++++++- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Connection/ConnectionExtensions.cs b/src/NServiceBus.Transport.RabbitMQ/Connection/ConnectionExtensions.cs index 44f39792f..7843f3522 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Connection/ConnectionExtensions.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Connection/ConnectionExtensions.cs @@ -11,9 +11,8 @@ namespace NServiceBus.Transport.RabbitMQ static class ConnectionExtensions { - public static async Task VerifyBrokerRequirements(this IConnection connection, CancellationToken cancellationToken = default) + public static Version GetBrokerVersion(this IConnection connection) { - var minimumBrokerVersion = Version.Parse("3.10.0"); var versionValue = connection.ServerProperties?["version"]; var versionBytes = Array.Empty(); @@ -24,7 +23,20 @@ public static async Task VerifyBrokerRequirements(this IConnection connection, C var brokerVersionString = Encoding.UTF8.GetString(versionBytes); - if (Version.TryParse(brokerVersionString, out var brokerVersion) && brokerVersion < minimumBrokerVersion) + if (!Version.TryParse(brokerVersionString, out var brokerVersion)) + { + throw new FormatException($"Could not parse broker version: {brokerVersion}"); + } + + return brokerVersion; + } + + public static async Task VerifyBrokerRequirements(this IConnection connection, CancellationToken cancellationToken = default) + { + var minimumBrokerVersion = Version.Parse("3.10.0"); + + var brokerVersion = connection.GetBrokerVersion(); + if (brokerVersion < minimumBrokerVersion) { throw new Exception($"An unsupported broker version was detected: {brokerVersion}. The broker must be at least version {minimumBrokerVersion}."); } diff --git a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs index 853faa6ea..57762757c 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs @@ -12,6 +12,7 @@ using global::RabbitMQ.Client.Exceptions; using Logging; using NServiceBus.Transport.RabbitMQ.ManagementApi; + using NServiceBus.Transport.RabbitMQ.ManagementApi.Models; sealed partial class MessagePump : IMessageReceiver { @@ -41,6 +42,8 @@ sealed partial class MessagePump : IMessageReceiver CancellationTokenSource messageProcessingCancellationTokenSource; MessagePumpConnectionFailedCircuitBreaker circuitBreaker; IConnection connection; + Overview overview; + Version brokerVersion; // Stop TaskCompletionSource connectionShutdownCompleted; @@ -92,6 +95,8 @@ public async Task Initialize(PushRuntimeSettings limitations, OnMessage onMessag this.onError = onError; maxConcurrency = limitations.MaxConcurrency; + await CheckConnectivity(cancellationToken).ConfigureAwait(false); + if (settings.PurgeOnStartup) { await queuePurger.Purge(ReceiveAddress, cancellationToken).ConfigureAwait(false); @@ -100,10 +105,34 @@ public async Task Initialize(PushRuntimeSettings limitations, OnMessage onMessag await ValidateDeliveryLimit(cancellationToken).ConfigureAwait(false); } + internal async Task CheckConnectivity(CancellationToken cancellationToken = default) + { + var response = await managementApi.GetOverview(cancellationToken).ConfigureAwait(false); + if (response.HasValue) + { + overview = response.Value; + brokerVersion = overview.RabbitMqVersion; + } + else + { + // TODO: Need logic/config settings for determining which action to take if management API unavailable, e.g. should we throw an exception to refuse to start, or just log a warning + Logger.WarnFormat("Could not access RabbitMQ Management API. ({0}: {1})", response.StatusCode, response.Reason); + + using var connection = await connectionFactory.CreateAdministrationConnection(cancellationToken).ConfigureAwait(false); + brokerVersion = connection.GetBrokerVersion(); + } + } + + bool HasManagementApiAccess => overview != null; + async Task ValidateDeliveryLimit(CancellationToken cancellationToken) { - var response = await managementApi.GetQueue(ReceiveAddress, cancellationToken).ConfigureAwait(false); + if (!HasManagementApiAccess) + { + return; + } + var response = await managementApi.GetQueue(ReceiveAddress, cancellationToken).ConfigureAwait(false); if (!response.HasValue) { // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning @@ -120,8 +149,14 @@ async Task ValidateDeliveryLimit(CancellationToken cancellationToken) if (queue.EffectivePolicyDefinition.DeliveryLimit.HasValue && queue.EffectivePolicyDefinition.DeliveryLimit != -1) { + Logger.WarnFormat("The RabbitMQ policy {0} is setting delivery limit to {1} for {2}.", + queue.AppliedPolicyName, queue.EffectivePolicyDefinition.DeliveryLimit, ReceiveAddress); + } + + if (string.IsNullOrEmpty(queue.AppliedPolicyName) && brokerVersion.Major >= 4) + { + // Create policy to set default deliver limit to -1 } - Logger.WarnFormat("Delivery limit set to {0}.", queue.DeliveryLimit); } public async Task StartReceive(CancellationToken cancellationToken = default) From 6164a9bb605e13f2db00dad2c949125c2e912c0f Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Wed, 6 Nov 2024 14:00:35 +1100 Subject: [PATCH 07/46] Add ability to create policies and query queue arguments through RabbitMQ management client --- .../Converters/QueueTypeConverter.cs | 35 +++++++++++++++ .../ManagementApi/IManagementApi.cs | 2 + .../ManagementApi/ManagementClient.cs | 11 +++++ .../ManagementApi/Models/Policy.cs | 5 +-- .../ManagementApi/Models/Queue.cs | 3 ++ .../ManagementApi/Models/QueueArguments.cs | 24 +++++++++++ .../ManagementApi/Models/QueueType.cs | 15 +++++++ .../Receiving/MessagePump.cs | 43 +++++++++++++++++-- 8 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/QueueTypeConverter.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueArguments.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueType.cs diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/QueueTypeConverter.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/QueueTypeConverter.cs new file mode 100644 index 000000000..a10db3705 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/QueueTypeConverter.cs @@ -0,0 +1,35 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using NServiceBus.Transport.RabbitMQ.ManagementApi.Models; + +class QueueTypeConverter : JsonConverter +{ + public override QueueType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return value switch + { + "quorum" => QueueType.Quorum, + "classic" => QueueType.Classic, + "stream" => QueueType.Stream, + _ => throw new JsonException($"Unknown QueueType: {value}") + }; + } + + public override void Write(Utf8JsonWriter writer, QueueType queueType, JsonSerializerOptions options) + { + var value = queueType switch + { + QueueType.Quorum => "quorum", + QueueType.Classic => "classic", + QueueType.Stream => "stream", + _ => throw new ArgumentOutOfRangeException(nameof(queueType), $"QueueType value out of range: {queueType}") + }; + writer.WriteStringValue(value); + } +} diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs index 9a9b1daeb..f0a2f30bf 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs @@ -11,4 +11,6 @@ interface IManagementApi Task> GetQueue(string queueName, CancellationToken cancellationToken = default); Task> GetOverview(CancellationToken cancellationToken = default); + + Task CreatePolicy(Policy policy, CancellationToken cancellationToken = default); } diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs index b49d6907c..a5bdd2c1b 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs @@ -70,4 +70,15 @@ public ManagementClient(ConnectionConfiguration connectionConfiguration) response.ReasonPhrase ?? string.Empty, value); } + + public async Task CreatePolicy(Policy policy, CancellationToken cancellationToken = default) + { + policy.VirtualHost = virtualHost; + + var escapedPolicyName = Uri.EscapeDataString(policy.Name); + var response = await httpClient.PutAsJsonAsync($"api/policies/{escapedPolicyName}", policy, cancellationToken) + .ConfigureAwait(false); + + response.EnsureSuccessStatusCode(); + } } diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Policy.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Policy.cs index cfee275c3..15d107c9c 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Policy.cs +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Policy.cs @@ -5,19 +5,18 @@ namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using System.Text.RegularExpressions; using NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; class Policy { [JsonPropertyName("vhost")] - public required string VirtualHost { get; set; } + public string VirtualHost { get; set; } = "/"; [JsonPropertyName("name")] public required string Name { get; set; } [JsonPropertyName("pattern")] - public required Regex Pattern { get; set; } + public required string Pattern { get; set; } [JsonConverter(typeof(PolicyTargetConverter))] [JsonPropertyName("apply-to")] diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs index af1b6b25a..176c123ca 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs @@ -12,6 +12,9 @@ class Queue [JsonPropertyName("name")] public required string Name { get; set; } + [JsonPropertyName("arguments")] + public required QueueArguments Arguments { get; set; } + [JsonPropertyName("delivery_limit")] [JsonConverter(typeof(DeliveryLimitConverter))] public int DeliveryLimit { get; set; } diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueArguments.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueArguments.cs new file mode 100644 index 000000000..809a37888 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueArguments.cs @@ -0,0 +1,24 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; + +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; + +class QueueArguments +{ + [JsonRequired] + [JsonPropertyName("x-queue-type")] + [JsonConverter(typeof(QueueTypeConverter))] + public QueueType QueueType { get; set; } + + [JsonPropertyName("x-delivery-limit")] + [JsonConverter(typeof(DeliveryLimitConverter))] + public int? DeliveryLimit { get; set; } + + [JsonExtensionData] + public IDictionary ExtraProperties { get; } = new Dictionary(); +} + diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueType.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueType.cs new file mode 100644 index 000000000..473ecb85f --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueType.cs @@ -0,0 +1,15 @@ +namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; + +/// +/// The types of queues supported by RabbitMQ +/// +/// +/// Note that this is different to which lists the types of queues supported by the NServiceBus transport +/// and doesn't include the Stream value +/// +enum QueueType +{ + Classic, + Quorum, + Stream +} diff --git a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs index 57762757c..fb2a6b056 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs @@ -146,17 +146,52 @@ async Task ValidateDeliveryLimit(CancellationToken cancellationToken) return; } + if (queue.Arguments.DeliveryLimit.HasValue && + queue.Arguments.DeliveryLimit != -1) + { + // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning + Logger.WarnFormat("The delivery limit for {0} is set to {1} by a queue argument. This can interfere with the transport's retry implementation", + queue.Name, queue.Arguments.DeliveryLimit); + return; + } + if (queue.EffectivePolicyDefinition.DeliveryLimit.HasValue && queue.EffectivePolicyDefinition.DeliveryLimit != -1) { - Logger.WarnFormat("The RabbitMQ policy {0} is setting delivery limit to {1} for {2}.", - queue.AppliedPolicyName, queue.EffectivePolicyDefinition.DeliveryLimit, ReceiveAddress); + // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning + Logger.WarnFormat("The RabbitMQ policy {2} is setting delivery limit to {1} for {0}.", + queue.Name, queue.EffectivePolicyDefinition.DeliveryLimit, queue.AppliedPolicyName); + return; } - if (string.IsNullOrEmpty(queue.AppliedPolicyName) && brokerVersion.Major >= 4) + await SetDeliveryLimitViaPolicy(queue, cancellationToken).ConfigureAwait(false); + } + + async Task SetDeliveryLimitViaPolicy(Queue queue, CancellationToken cancellationToken) + { + if (!string.IsNullOrEmpty(queue.AppliedPolicyName)) { - // Create policy to set default deliver limit to -1 + // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning + Logger.WarnFormat("The {0} queue already has an associated policy.", queue.Name, queue.AppliedPolicyName); + return; } + + if (brokerVersion.Major < 4) + { + // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning + Logger.WarnFormat("Cannot override delivery limit on the {0} queue by policy in RabbitMQ versions prior to 4.", queue.Name); + return; + } + + var policy = new Policy + { + Name = $"nsb.{queue.Name}.delivery-limit", + ApplyTo = PolicyTarget.QuorumQueues, + Definition = new PolicyDefinition { DeliveryLimit = -1 }, + Pattern = queue.Name, + Priority = 100 + }; + await managementApi.CreatePolicy(policy, cancellationToken).ConfigureAwait(false); } public async Task StartReceive(CancellationToken cancellationToken = default) From fffd39c160d217a27270cb17922820f56a9c8017 Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Sat, 2 Nov 2024 17:35:13 -0700 Subject: [PATCH 08/46] Rename and move management client to Administration folder --- .../Converters/DeliveryLimitConverter.cs | 2 +- .../Converters/PolicyTargetConverter.cs | 4 +-- .../Converters/VersionConverter.cs | 2 +- .../HttpStatusCodeExtensions.cs | 2 +- .../ManagementClient}/IManagementApi.cs | 4 +-- .../ManagementClient}/ManagementClient.cs | 4 +-- .../ManagementClient}/Models/Overview.cs | 4 +-- .../ManagementClient}/Models/Policy.cs | 4 +-- .../Models/PolicyDefinition.cs | 4 +-- .../ManagementClient}/Models/PolicyTarget.cs | 2 +- .../ManagementClient}/Models/Queue.cs | 4 +-- .../ManagementClient}/Response.cs | 2 +- .../Converters/QueueTypeConverter.cs | 35 ------------------- .../ManagementApi/Models/QueueArguments.cs | 24 ------------- .../ManagementApi/Models/QueueType.cs | 15 -------- .../RabbitMQTransport.cs | 2 +- .../RabbitMQTransportInfrastructure.cs | 2 +- .../Receiving/MessagePump.cs | 4 +-- 18 files changed, 23 insertions(+), 97 deletions(-) rename src/NServiceBus.Transport.RabbitMQ/{ManagementApi => Administration/ManagementClient}/Converters/DeliveryLimitConverter.cs (92%) rename src/NServiceBus.Transport.RabbitMQ/{ManagementApi => Administration/ManagementClient}/Converters/PolicyTargetConverter.cs (89%) rename src/NServiceBus.Transport.RabbitMQ/{ManagementApi => Administration/ManagementClient}/Converters/VersionConverter.cs (89%) rename src/NServiceBus.Transport.RabbitMQ/{ManagementApi => Administration/ManagementClient}/HttpStatusCodeExtensions.cs (72%) rename src/NServiceBus.Transport.RabbitMQ/{ManagementApi => Administration/ManagementClient}/IManagementApi.cs (71%) rename src/NServiceBus.Transport.RabbitMQ/{ManagementApi => Administration/ManagementClient}/ManagementClient.cs (94%) rename src/NServiceBus.Transport.RabbitMQ/{ManagementApi => Administration/ManagementClient}/Models/Overview.cs (86%) rename src/NServiceBus.Transport.RabbitMQ/{ManagementApi => Administration/ManagementClient}/Models/Policy.cs (83%) rename src/NServiceBus.Transport.RabbitMQ/{ManagementApi => Administration/ManagementClient}/Models/PolicyDefinition.cs (72%) rename src/NServiceBus.Transport.RabbitMQ/{ManagementApi => Administration/ManagementClient}/Models/PolicyTarget.cs (56%) rename src/NServiceBus.Transport.RabbitMQ/{ManagementApi => Administration/ManagementClient}/Models/Queue.cs (84%) rename src/NServiceBus.Transport.RabbitMQ/{ManagementApi => Administration/ManagementClient}/Response.cs (77%) delete mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/QueueTypeConverter.cs delete mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueArguments.cs delete mode 100644 src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueType.cs diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/DeliveryLimitConverter.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/DeliveryLimitConverter.cs similarity index 92% rename from src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/DeliveryLimitConverter.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/DeliveryLimitConverter.cs index cf37b5c64..37eeb55c0 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/DeliveryLimitConverter.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/DeliveryLimitConverter.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; using System; using System.Text.Json; diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/PolicyTargetConverter.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/PolicyTargetConverter.cs similarity index 89% rename from src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/PolicyTargetConverter.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/PolicyTargetConverter.cs index 6d1e893ae..eedc5d547 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/PolicyTargetConverter.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/PolicyTargetConverter.cs @@ -1,11 +1,11 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; using System; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.ManagementApi.Models; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; class PolicyTargetConverter : JsonConverter { diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/VersionConverter.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/VersionConverter.cs similarity index 89% rename from src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/VersionConverter.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/VersionConverter.cs index 4332a6718..7cbc9e45f 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/VersionConverter.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/VersionConverter.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; using System; using System.Text.Json; diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/HttpStatusCodeExtensions.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/HttpStatusCodeExtensions.cs similarity index 72% rename from src/NServiceBus.Transport.RabbitMQ/ManagementApi/HttpStatusCodeExtensions.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/HttpStatusCodeExtensions.cs index 3d8d4c983..716e0df57 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/HttpStatusCodeExtensions.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/HttpStatusCodeExtensions.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.ManagementApi; +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using System.Net; diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementApi.cs similarity index 71% rename from src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementApi.cs index f0a2f30bf..95033a5b5 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/IManagementApi.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementApi.cs @@ -1,10 +1,10 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.ManagementApi; +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using System.Threading; using System.Threading.Tasks; -using NServiceBus.Transport.RabbitMQ.ManagementApi.Models; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; interface IManagementApi { diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs similarity index 94% rename from src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs index a5bdd2c1b..11d775532 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.ManagementApi; +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using System; using System.Net.Http; @@ -9,7 +9,7 @@ namespace NServiceBus.Transport.RabbitMQ.ManagementApi; using System.Text; using System.Threading; using System.Threading.Tasks; -using NServiceBus.Transport.RabbitMQ.ManagementApi.Models; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; class ManagementClient : IManagementApi { diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Overview.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs similarity index 86% rename from src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Overview.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs index 4f6853f26..982eb0996 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Overview.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs @@ -1,12 +1,12 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; class Overview { diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Policy.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs similarity index 83% rename from src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Policy.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs index 15d107c9c..cc262d17c 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Policy.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs @@ -1,11 +1,11 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; class Policy { diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyDefinition.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyDefinition.cs similarity index 72% rename from src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyDefinition.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyDefinition.cs index 37807b151..5e601b96d 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyDefinition.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyDefinition.cs @@ -1,11 +1,11 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; class PolicyDefinition { diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyTarget.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyTarget.cs similarity index 56% rename from src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyTarget.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyTarget.cs index cd3952f50..76ec0eb21 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/PolicyTarget.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyTarget.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; enum PolicyTarget { diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs similarity index 84% rename from src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs index 176c123ca..c39b504dc 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/Queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs @@ -1,11 +1,11 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; class Queue { diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Response.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Response.cs similarity index 77% rename from src/NServiceBus.Transport.RabbitMQ/ManagementApi/Response.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Response.cs index 60710adde..f3fcb024b 100644 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Response.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Response.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.ManagementApi; +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using System.Diagnostics.CodeAnalysis; using System.Net; diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/QueueTypeConverter.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/QueueTypeConverter.cs deleted file mode 100644 index a10db3705..000000000 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Converters/QueueTypeConverter.cs +++ /dev/null @@ -1,35 +0,0 @@ -#nullable enable - -namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.ManagementApi.Models; - -class QueueTypeConverter : JsonConverter -{ - public override QueueType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var value = reader.GetString(); - return value switch - { - "quorum" => QueueType.Quorum, - "classic" => QueueType.Classic, - "stream" => QueueType.Stream, - _ => throw new JsonException($"Unknown QueueType: {value}") - }; - } - - public override void Write(Utf8JsonWriter writer, QueueType queueType, JsonSerializerOptions options) - { - var value = queueType switch - { - QueueType.Quorum => "quorum", - QueueType.Classic => "classic", - QueueType.Stream => "stream", - _ => throw new ArgumentOutOfRangeException(nameof(queueType), $"QueueType value out of range: {queueType}") - }; - writer.WriteStringValue(value); - } -} diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueArguments.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueArguments.cs deleted file mode 100644 index 809a37888..000000000 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueArguments.cs +++ /dev/null @@ -1,24 +0,0 @@ -#nullable enable - -namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; - -using System.Collections.Generic; -using System.Text.Json; -using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.ManagementApi.Converters; - -class QueueArguments -{ - [JsonRequired] - [JsonPropertyName("x-queue-type")] - [JsonConverter(typeof(QueueTypeConverter))] - public QueueType QueueType { get; set; } - - [JsonPropertyName("x-delivery-limit")] - [JsonConverter(typeof(DeliveryLimitConverter))] - public int? DeliveryLimit { get; set; } - - [JsonExtensionData] - public IDictionary ExtraProperties { get; } = new Dictionary(); -} - diff --git a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueType.cs b/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueType.cs deleted file mode 100644 index 473ecb85f..000000000 --- a/src/NServiceBus.Transport.RabbitMQ/ManagementApi/Models/QueueType.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace NServiceBus.Transport.RabbitMQ.ManagementApi.Models; - -/// -/// The types of queues supported by RabbitMQ -/// -/// -/// Note that this is different to which lists the types of queues supported by the NServiceBus transport -/// and doesn't include the Stream value -/// -enum QueueType -{ - Classic, - Quorum, - Stream -} diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index 33090cca7..00fcb1d41 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -5,7 +5,7 @@ using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; - using NServiceBus.Transport.RabbitMQ.ManagementApi; + using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using RabbitMQ.Client; using RabbitMQ.Client.Events; using Transport; diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs index 34c702d73..02e2d3773 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs @@ -5,7 +5,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; - using NServiceBus.Transport.RabbitMQ.ManagementApi; + using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using global::RabbitMQ.Client; sealed class RabbitMQTransportInfrastructure : TransportInfrastructure diff --git a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs index fb2a6b056..8bc42983f 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs @@ -11,8 +11,8 @@ using global::RabbitMQ.Client.Events; using global::RabbitMQ.Client.Exceptions; using Logging; - using NServiceBus.Transport.RabbitMQ.ManagementApi; - using NServiceBus.Transport.RabbitMQ.ManagementApi.Models; + using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; + using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; sealed partial class MessagePump : IMessageReceiver { From ebe44d24a7baeee8a9eb374592a50bd8d827789c Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Sat, 2 Nov 2024 17:36:03 -0700 Subject: [PATCH 09/46] Add test for getting queue information from management client --- ...nnecting_to_the_rabbitmq_management_api.cs | 90 +++++++++++++++++++ .../RabbitMQTransport.cs | 2 +- 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs new file mode 100644 index 000000000..379b589a0 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs @@ -0,0 +1,90 @@ +namespace NServiceBus.Transport.RabbitMQ.Tests.Connection.ManagementConnection +{ + using System; + using System.Collections.Generic; + using System.Net; + using System.Security.Cryptography.X509Certificates; + using System.Threading.Tasks; + using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; + using NUnit.Framework; + using System.Threading; + + [TestFixture] + class When_connecting_to_the_rabbitmq_management_api : RabbitMQTransport + { + ConnectionConfiguration connectionConfiguration = ConnectionConfiguration.Create("host=localhost"); + protected IList AdditionalReceiverQueues = []; + protected QueueType queueType = QueueType.Quorum; + protected string ReceiverQueue => GetTestQueueName("ManagementAPITestQueue"); + protected string ErrorQueue => GetTestQueueName("error"); + protected string GetTestQueueName(string queueName) => $"{queueName}-{queueType}"; + readonly List<(string hostName, int port, bool useTls)> additionalClusterNodes = []; + internal new IRoutingTopology RoutingTopology; + internal RabbitMQTransport transport; + + [SetUp] + public async Task SetUp() + { + var connectionString = Environment.GetEnvironmentVariable("RabbitMQTransport_ConnectionString") ?? "host=localhost"; + + var useTls = connectionString.StartsWith("https", StringComparison.InvariantCultureIgnoreCase) || connectionString.StartsWith("amqps", StringComparison.InvariantCultureIgnoreCase); + + connectionConfiguration = ConnectionConfiguration.Create(connectionString); + RoutingTopology = NServiceBus.RoutingTopology.Conventional(queueType).Create(); + + transport = new RabbitMQTransport(NServiceBus.RoutingTopology.Conventional(queueType), connectionString); + + _ = await Initialize(new HostSettings(ReceiverQueue, ReceiverQueue, new StartupDiagnosticEntries(), (_, _, _) => { }, true), + [new ReceiveSettings(ReceiverQueue, new QueueAddress(ReceiverQueue), true, true, ErrorQueue)], [.. AdditionalReceiverQueues, ErrorQueue]); + } + + public override async Task Initialize(HostSettings hostSettings, ReceiveSettings[] receivers, string[] sendingAddresses, CancellationToken cancellationToken = default) + { + transport.ValidateAndApplyLegacyConfiguration(); + + X509Certificate2Collection certCollection = null; + + if (ClientCertificate != null) + { + certCollection = new X509Certificate2Collection(ClientCertificate); + } + + var connectionFactory = new ConnectionFactory(hostSettings.Name, connectionConfiguration, certCollection, !ValidateRemoteCertificate, + UseExternalAuthMechanism, HeartbeatInterval, NetworkRecoveryInterval, additionalClusterNodes); + + //var managementClient = new ManagementClient(ConnectionConfiguration); + + var channelProvider = new ChannelProvider(connectionFactory, NetworkRecoveryInterval, RoutingTopology); + await channelProvider.CreateConnection(cancellationToken).ConfigureAwait(false); + + var converter = new MessageConverter(MessageIdStrategy); + + var infra = new RabbitMQTransportInfrastructure(hostSettings, receivers, connectionFactory, + RoutingTopology, channelProvider, converter, null, TimeToWaitBeforeTriggeringCircuitBreaker, + PrefetchCountCalculation, NetworkRecoveryInterval, SupportsDelayedDelivery); + + if (hostSettings.SetupInfrastructure) + { + await infra.SetupInfrastructure(sendingAddresses, cancellationToken).ConfigureAwait(false); + } + + return infra; + } + + [Test] + public async Task GetQueue_Should_Return_Queue_When_Exists() + { + var client = new ManagementClient(connectionConfiguration); + + var response = await client.GetQueue(ReceiverQueue); + + // Assert + Assert.Multiple(() => + { + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + Assert.That(response.Value, Is.Not.Null); + Assert.That(response.Value?.Name, Is.EqualTo(ReceiverQueue)); + }); + } + } +} diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index 00fcb1d41..1a144ead4 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -260,7 +260,7 @@ internal RabbitMQTransport() : base(TransportTransactionMode.ReceiveOnly, true, legacyMode = true; } - void ValidateAndApplyLegacyConfiguration() + internal void ValidateAndApplyLegacyConfiguration() { if (!legacyMode) { From 94955b2c193e819a050f2a47742f8ab5abfec205 Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Sat, 2 Nov 2024 18:47:11 -0700 Subject: [PATCH 10/46] Trying to remove the Fody errors --- .../ManagementClient/Models/Overview.cs | 18 ++++++++++++------ .../ManagementClient/Models/Policy.cs | 11 ++++++++--- .../ManagementClient/Models/Queue.cs | 8 ++++++-- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs index 982eb0996..20bea983d 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs @@ -10,26 +10,32 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; class Overview { + [JsonRequired] [JsonPropertyName("product_name")] - public required string ProductName { get; set; } + public string? ProductName { get; set; } + [JsonRequired] [JsonConverter(typeof(VersionConverter))] [JsonPropertyName("management_version")] - public required Version ManagementVersion { get; set; } + public Version? ManagementVersion { get; set; } + [JsonRequired] [JsonConverter(typeof(VersionConverter))] [JsonPropertyName("product_version")] - public required Version ProductVersion { get; set; } + public Version? ProductVersion { get; set; } + [JsonRequired] [JsonConverter(typeof(VersionConverter))] [JsonPropertyName("rabbitmq_version")] - public required Version RabbitMqVersion { get; set; } + public Version? RabbitMqVersion { get; set; } + [JsonRequired] [JsonPropertyName("cluster_name")] - public required string ClusterName { get; set; } + public string? ClusterName { get; set; } + [JsonRequired] [JsonPropertyName("node")] - public required string Node { get; set; } + public string? Node { get; set; } [JsonExtensionData] public IDictionary ExtraProperties { get; } = new Dictionary(); diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs index cc262d17c..8ee3a3de0 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs @@ -9,21 +9,26 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; class Policy { + [JsonRequired] [JsonPropertyName("vhost")] public string VirtualHost { get; set; } = "/"; + [JsonRequired] [JsonPropertyName("name")] - public required string Name { get; set; } + public string? Name { get; set; } + [JsonRequired] [JsonPropertyName("pattern")] public required string Pattern { get; set; } + [JsonRequired] [JsonConverter(typeof(PolicyTargetConverter))] [JsonPropertyName("apply-to")] - public required PolicyTarget ApplyTo { get; set; } + public PolicyTarget? ApplyTo { get; set; } + [JsonRequired] [JsonPropertyName("definition")] - public required PolicyDefinition Definition { get; set; } + public PolicyDefinition? Definition { get; set; } [JsonPropertyName("priority")] public int Priority { get; set; } diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs index c39b504dc..0fee27a00 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs @@ -9,8 +9,9 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; class Queue { + [JsonRequired] [JsonPropertyName("name")] - public required string Name { get; set; } + public string? Name { get; set; } [JsonPropertyName("arguments")] public required QueueArguments Arguments { get; set; } @@ -19,12 +20,15 @@ class Queue [JsonConverter(typeof(DeliveryLimitConverter))] public int DeliveryLimit { get; set; } + [JsonRequired] [JsonPropertyName("effective_policy_definition")] - public required PolicyDefinition EffectivePolicyDefinition { get; set; } + public PolicyDefinition? EffectivePolicyDefinition { get; set; } + [JsonRequired] [JsonPropertyName("policy")] public string? AppliedPolicyName { get; set; } + [JsonRequired] [JsonPropertyName("operator_policy")] public string? AppliedOperatorPolicyName { get; set; } From d60388d6df3fbba4e285aec896f0ca2e2ffe505a Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Sat, 2 Nov 2024 19:21:59 -0700 Subject: [PATCH 11/46] Remove required fields on the queue model --- .../Administration/ManagementClient/Models/Queue.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs index 0fee27a00..e7e586d30 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs @@ -20,15 +20,12 @@ class Queue [JsonConverter(typeof(DeliveryLimitConverter))] public int DeliveryLimit { get; set; } - [JsonRequired] [JsonPropertyName("effective_policy_definition")] public PolicyDefinition? EffectivePolicyDefinition { get; set; } - [JsonRequired] [JsonPropertyName("policy")] public string? AppliedPolicyName { get; set; } - [JsonRequired] [JsonPropertyName("operator_policy")] public string? AppliedOperatorPolicyName { get; set; } From 20fa5123d5727428f1dfb67d93cc507b61c8d413 Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Mon, 4 Nov 2024 17:51:00 -0800 Subject: [PATCH 12/46] Simplify management API test for creating quorum queue --- ...nnecting_to_the_rabbitmq_management_api.cs | 67 +++++-------------- 1 file changed, 18 insertions(+), 49 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs index 379b589a0..df8e1e5e7 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs @@ -1,74 +1,43 @@ -namespace NServiceBus.Transport.RabbitMQ.Tests.Connection.ManagementConnection +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.Tests.Connection.ManagementConnection { using System; using System.Collections.Generic; using System.Net; - using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using NUnit.Framework; - using System.Threading; + using NUnit.Framework.Internal; + using global::RabbitMQ.Client; + using global::RabbitMQ.Client.Events; + using ConnectionFactory = ConnectionFactory; + [TestFixture] class When_connecting_to_the_rabbitmq_management_api : RabbitMQTransport { ConnectionConfiguration connectionConfiguration = ConnectionConfiguration.Create("host=localhost"); - protected IList AdditionalReceiverQueues = []; protected QueueType queueType = QueueType.Quorum; protected string ReceiverQueue => GetTestQueueName("ManagementAPITestQueue"); - protected string ErrorQueue => GetTestQueueName("error"); protected string GetTestQueueName(string queueName) => $"{queueName}-{queueType}"; - readonly List<(string hostName, int port, bool useTls)> additionalClusterNodes = []; - internal new IRoutingTopology RoutingTopology; - internal RabbitMQTransport transport; [SetUp] public async Task SetUp() { - var connectionString = Environment.GetEnvironmentVariable("RabbitMQTransport_ConnectionString") ?? "host=localhost"; - - var useTls = connectionString.StartsWith("https", StringComparison.InvariantCultureIgnoreCase) || connectionString.StartsWith("amqps", StringComparison.InvariantCultureIgnoreCase); - - connectionConfiguration = ConnectionConfiguration.Create(connectionString); - RoutingTopology = NServiceBus.RoutingTopology.Conventional(queueType).Create(); - - transport = new RabbitMQTransport(NServiceBus.RoutingTopology.Conventional(queueType), connectionString); - - _ = await Initialize(new HostSettings(ReceiverQueue, ReceiverQueue, new StartupDiagnosticEntries(), (_, _, _) => { }, true), - [new ReceiveSettings(ReceiverQueue, new QueueAddress(ReceiverQueue), true, true, ErrorQueue)], [.. AdditionalReceiverQueues, ErrorQueue]); - } - - public override async Task Initialize(HostSettings hostSettings, ReceiveSettings[] receivers, string[] sendingAddresses, CancellationToken cancellationToken = default) - { - transport.ValidateAndApplyLegacyConfiguration(); + var connectionFactory = new ConnectionFactory(ReceiverQueue, connectionConfiguration, null, !ValidateRemoteCertificate, UseExternalAuthMechanism, HeartbeatInterval, NetworkRecoveryInterval, []); + IConnection connection = await connectionFactory.CreateConnection(ReceiverQueue).ConfigureAwait(false); + var createChannelOptions = new CreateChannelOptions(publisherConfirmationsEnabled: false, publisherConfirmationTrackingEnabled: false); + var channel = await connection.CreateChannelAsync(createChannelOptions).ConfigureAwait(false); + var arguments = new Dictionary { { "x-queue-type", "quorum" } }; - X509Certificate2Collection certCollection = null; + _ = await channel.QueueDeclareAsync(queue: ReceiverQueue, durable: true, exclusive: false, autoDelete: false, arguments: arguments).ConfigureAwait(false); - if (ClientCertificate != null) - { - certCollection = new X509Certificate2Collection(ClientCertificate); - } - - var connectionFactory = new ConnectionFactory(hostSettings.Name, connectionConfiguration, certCollection, !ValidateRemoteCertificate, - UseExternalAuthMechanism, HeartbeatInterval, NetworkRecoveryInterval, additionalClusterNodes); - - //var managementClient = new ManagementClient(ConnectionConfiguration); - - var channelProvider = new ChannelProvider(connectionFactory, NetworkRecoveryInterval, RoutingTopology); - await channelProvider.CreateConnection(cancellationToken).ConfigureAwait(false); - - var converter = new MessageConverter(MessageIdStrategy); - - var infra = new RabbitMQTransportInfrastructure(hostSettings, receivers, connectionFactory, - RoutingTopology, channelProvider, converter, null, TimeToWaitBeforeTriggeringCircuitBreaker, - PrefetchCountCalculation, NetworkRecoveryInterval, SupportsDelayedDelivery); - - if (hostSettings.SetupInfrastructure) - { - await infra.SetupInfrastructure(sendingAddresses, cancellationToken).ConfigureAwait(false); - } + var consumer = new AsyncEventingBasicConsumer(channel); + consumer.ReceivedAsync += async (o, a) => await Task.Yield(); - return infra; + var consumerTag = $"localhost - {ReceiverQueue}"; + _ = await channel.BasicConsumeAsync(ReceiverQueue, true, consumerTag, consumer).ConfigureAwait(false); } [Test] From 815108cf57dc707a6a81f2803f0f5e3ec2792c4a Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Tue, 5 Nov 2024 10:12:33 -0800 Subject: [PATCH 13/46] Add another management API test --- ...nnecting_to_the_rabbitmq_management_api.cs | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs index df8e1e5e7..b910998e5 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs @@ -41,13 +41,12 @@ public async Task SetUp() } [Test] - public async Task GetQueue_Should_Return_Queue_When_Exists() + public async Task GetQueue_Should_Return_Queue_Information_When_Exists() { var client = new ManagementClient(connectionConfiguration); var response = await client.GetQueue(ReceiverQueue); - // Assert Assert.Multiple(() => { Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); @@ -55,5 +54,23 @@ public async Task GetQueue_Should_Return_Queue_When_Exists() Assert.That(response.Value?.Name, Is.EqualTo(ReceiverQueue)); }); } + + [Test] + public async Task GetOverview_Should_Return_Broker_Information_When_Exists() + { + var client = new ManagementClient(connectionConfiguration); + + var response = await client.GetOverview(); + + Assert.Multiple(() => + { + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + Assert.That(response.Value, Is.Not.Null); + Assert.That(response.Value?.ProductName, Is.EqualTo("RabbitMQ")); + Assert.That(response.Value?.ManagementVersion, Is.GreaterThanOrEqualTo(Version.Parse("4.0.0"))); + Assert.That(response.Value?.ProductVersion, Is.GreaterThanOrEqualTo(Version.Parse("4.0.0"))); + Assert.That(response.Value?.RabbitMqVersion, Is.GreaterThanOrEqualTo(Version.Parse("4.0.0"))); + }); + } } } From 28957a5a3e889865e0dbb727b28476bbd970e643 Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Tue, 5 Nov 2024 15:18:40 -0800 Subject: [PATCH 14/46] Suppress Fody obsolete usage error --- .../ManagementClient/Models/Overview.cs | 25 ++++++++++--------- .../ManagementClient/Models/Policy.cs | 18 +++++++------ .../ManagementClient/Models/Queue.cs | 15 +++++++---- 3 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs index 20bea983d..3c7bcd6ac 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs @@ -10,32 +10,33 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; class Overview { - [JsonRequired] + // This is because Fody is throwing an error when using the 'required' keyword that ctor has an obsoleteAttribute. + // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation + [DoNotWarnAboutObsoleteUsage] + public Overview() + { + } + [JsonPropertyName("product_name")] - public string? ProductName { get; set; } + public required string ProductName { get; set; } - [JsonRequired] [JsonConverter(typeof(VersionConverter))] [JsonPropertyName("management_version")] - public Version? ManagementVersion { get; set; } + public required Version ManagementVersion { get; set; } - [JsonRequired] [JsonConverter(typeof(VersionConverter))] [JsonPropertyName("product_version")] - public Version? ProductVersion { get; set; } + public required Version ProductVersion { get; set; } - [JsonRequired] [JsonConverter(typeof(VersionConverter))] [JsonPropertyName("rabbitmq_version")] - public Version? RabbitMqVersion { get; set; } + public required Version RabbitMqVersion { get; set; } - [JsonRequired] [JsonPropertyName("cluster_name")] - public string? ClusterName { get; set; } + public required string ClusterName { get; set; } - [JsonRequired] [JsonPropertyName("node")] - public string? Node { get; set; } + public required string Node { get; set; } [JsonExtensionData] public IDictionary ExtraProperties { get; } = new Dictionary(); diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs index 8ee3a3de0..3d2d0bc00 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs @@ -9,26 +9,28 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; class Policy { - [JsonRequired] + // This is because Fody is throwing an error when using the 'required' keyword that ctor has an obsoleteAttribute. + // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation + [DoNotWarnAboutObsoleteUsage] + public Policy() + { + } + [JsonPropertyName("vhost")] public string VirtualHost { get; set; } = "/"; - [JsonRequired] [JsonPropertyName("name")] - public string? Name { get; set; } + public required string Name { get; set; } - [JsonRequired] [JsonPropertyName("pattern")] public required string Pattern { get; set; } - [JsonRequired] [JsonConverter(typeof(PolicyTargetConverter))] [JsonPropertyName("apply-to")] - public PolicyTarget? ApplyTo { get; set; } + public required PolicyTarget ApplyTo { get; set; } - [JsonRequired] [JsonPropertyName("definition")] - public PolicyDefinition? Definition { get; set; } + public required PolicyDefinition Definition { get; set; } [JsonPropertyName("priority")] public int Priority { get; set; } diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs index e7e586d30..1dfe9f444 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs @@ -9,9 +9,15 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; class Queue { - [JsonRequired] + // This is because Fody is throwing an error when using the 'required' keyword that ctor has an obsoleteAttribute. + // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation + [DoNotWarnAboutObsoleteUsage] + public Queue() + { + } + [JsonPropertyName("name")] - public string? Name { get; set; } + public required string Name { get; set; } [JsonPropertyName("arguments")] public required QueueArguments Arguments { get; set; } @@ -21,7 +27,7 @@ class Queue public int DeliveryLimit { get; set; } [JsonPropertyName("effective_policy_definition")] - public PolicyDefinition? EffectivePolicyDefinition { get; set; } + public required PolicyDefinition EffectivePolicyDefinition { get; set; } [JsonPropertyName("policy")] public string? AppliedPolicyName { get; set; } @@ -31,5 +37,4 @@ class Queue [JsonExtensionData] public IDictionary ExtraProperties { get; } = new Dictionary(); -} - +} \ No newline at end of file From 34bff940dde054e20ed67e2f8a7d745ff0a05fe5 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Wed, 6 Nov 2024 14:23:32 +1100 Subject: [PATCH 15/46] Move QueueArguments to the correct namespace --- .../Converters/QueueTypeConverter.cs | 35 +++++++++++++++++++ .../ManagementClient/Models/QueueArguments.cs | 24 +++++++++++++ .../ManagementClient/Models/QueueType.cs | 15 ++++++++ 3 files changed, 74 insertions(+) create mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/QueueTypeConverter.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueArguments.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueType.cs diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/QueueTypeConverter.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/QueueTypeConverter.cs new file mode 100644 index 000000000..d956d95cf --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/QueueTypeConverter.cs @@ -0,0 +1,35 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; + +class QueueTypeConverter : JsonConverter +{ + public override QueueType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var value = reader.GetString(); + return value switch + { + "quorum" => QueueType.Quorum, + "classic" => QueueType.Classic, + "stream" => QueueType.Stream, + _ => throw new JsonException($"Unknown QueueType: {value}") + }; + } + + public override void Write(Utf8JsonWriter writer, QueueType queueType, JsonSerializerOptions options) + { + var value = queueType switch + { + QueueType.Quorum => "quorum", + QueueType.Classic => "classic", + QueueType.Stream => "stream", + _ => throw new ArgumentOutOfRangeException(nameof(queueType), $"QueueType value out of range: {queueType}") + }; + writer.WriteStringValue(value); + } +} diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueArguments.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueArguments.cs new file mode 100644 index 000000000..dad5e6dcf --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueArguments.cs @@ -0,0 +1,24 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; + +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; + +class QueueArguments +{ + [JsonRequired] + [JsonPropertyName("x-queue-type")] + [JsonConverter(typeof(QueueTypeConverter))] + public QueueType QueueType { get; set; } + + [JsonPropertyName("x-delivery-limit")] + [JsonConverter(typeof(DeliveryLimitConverter))] + public int? DeliveryLimit { get; set; } + + [JsonExtensionData] + public IDictionary ExtraProperties { get; } = new Dictionary(); +} + diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueType.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueType.cs new file mode 100644 index 000000000..c9820d8a1 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueType.cs @@ -0,0 +1,15 @@ +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; + +/// +/// The types of queues supported by RabbitMQ +/// +/// +/// Note that this is different to which lists the types of queues supported by the NServiceBus transport +/// and doesn't include the Stream value +/// +enum QueueType +{ + Classic, + Quorum, + Stream +} From 27fbf2981da7386363dc9fb7a2f638b69d66eab3 Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Tue, 5 Nov 2024 23:39:37 -0800 Subject: [PATCH 16/46] Add the escaped virtual host to the PUT method --- .../Administration/ManagementClient/ManagementClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs index 11d775532..b9bebe156 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs @@ -76,7 +76,7 @@ public async Task CreatePolicy(Policy policy, CancellationToken cancellationToke policy.VirtualHost = virtualHost; var escapedPolicyName = Uri.EscapeDataString(policy.Name); - var response = await httpClient.PutAsJsonAsync($"api/policies/{escapedPolicyName}", policy, cancellationToken) + var response = await httpClient.PutAsJsonAsync($"api/policies/{escapedVirtualHost}/{escapedPolicyName}", policy, cancellationToken) .ConfigureAwait(false); response.EnsureSuccessStatusCode(); From ba7d2490e4f860701764ce4dae27e6e1eb40d6b1 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Thu, 7 Nov 2024 01:00:53 +1100 Subject: [PATCH 17/46] Wider allowed version range in Get Management Overview test --- .../When_connecting_to_the_rabbitmq_management_api.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs index b910998e5..66effe5d4 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs @@ -2,15 +2,14 @@ namespace NServiceBus.Transport.RabbitMQ.Tests.Connection.ManagementConnection { - using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; + using global::RabbitMQ.Client; + using global::RabbitMQ.Client.Events; using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using NUnit.Framework; using NUnit.Framework.Internal; - using global::RabbitMQ.Client; - using global::RabbitMQ.Client.Events; using ConnectionFactory = ConnectionFactory; @@ -67,9 +66,9 @@ public async Task GetOverview_Should_Return_Broker_Information_When_Exists() Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); Assert.That(response.Value, Is.Not.Null); Assert.That(response.Value?.ProductName, Is.EqualTo("RabbitMQ")); - Assert.That(response.Value?.ManagementVersion, Is.GreaterThanOrEqualTo(Version.Parse("4.0.0"))); - Assert.That(response.Value?.ProductVersion, Is.GreaterThanOrEqualTo(Version.Parse("4.0.0"))); - Assert.That(response.Value?.RabbitMqVersion, Is.GreaterThanOrEqualTo(Version.Parse("4.0.0"))); + Assert.That(response.Value?.ManagementVersion.Major, Is.InRange(3, 4)); + Assert.That(response.Value?.ProductVersion.Major, Is.InRange(3, 4)); + Assert.That(response.Value?.RabbitMqVersion.Major, Is.InRange(3, 4)); }); } } From 90ba42a9c342c1662309b3f979c2f230003238c3 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Wed, 6 Nov 2024 14:25:31 +1100 Subject: [PATCH 18/46] Rename IManagementApi --- .../{IManagementApi.cs => IManagementClient.cs} | 2 +- .../Administration/ManagementClient/ManagementClient.cs | 2 +- .../RabbitMQTransportInfrastructure.cs | 4 ++-- src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) rename src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/{IManagementApi.cs => IManagementClient.cs} (94%) diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementApi.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs similarity index 94% rename from src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementApi.cs rename to src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs index 95033a5b5..ebe53d023 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementApi.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs @@ -6,7 +6,7 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using System.Threading.Tasks; using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; -interface IManagementApi +interface IManagementClient { Task> GetQueue(string queueName, CancellationToken cancellationToken = default); diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs index b9bebe156..27ec23eaf 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs @@ -11,7 +11,7 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using System.Threading.Tasks; using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; -class ManagementClient : IManagementApi +class ManagementClient : IManagementClient { readonly HttpClient httpClient; readonly string virtualHost; diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs index 02e2d3773..5ed521ee6 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs @@ -15,12 +15,12 @@ sealed class RabbitMQTransportInfrastructure : TransportInfrastructure readonly IRoutingTopology routingTopology; readonly TimeSpan networkRecoveryInterval; readonly bool supportsDelayedDelivery; - readonly IManagementApi managementApi; + readonly IManagementClient managementApi; public RabbitMQTransportInfrastructure(HostSettings hostSettings, ReceiveSettings[] receiverSettings, ConnectionFactory connectionFactory, IRoutingTopology routingTopology, ChannelProvider channelProvider, MessageConverter messageConverter, - IManagementApi managementApi, + IManagementClient managementApi, Action messageCustomization, TimeSpan timeToWaitBeforeTriggeringCircuitBreaker, PrefetchCountCalculation prefetchCountCalculation, TimeSpan networkRecoveryInterval, bool supportsDelayedDelivery) diff --git a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs index 8bc42983f..d5f445106 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs @@ -24,7 +24,7 @@ sealed partial class MessagePump : IMessageReceiver readonly MessageConverter messageConverter; readonly string consumerTag; readonly ChannelProvider channelProvider; - readonly IManagementApi managementApi; + readonly IManagementClient managementApi; readonly TimeSpan timeToWaitBeforeTriggeringCircuitBreaker; readonly QueuePurger queuePurger; readonly PrefetchCountCalculation prefetchCountCalculation; @@ -55,7 +55,7 @@ public MessagePump( MessageConverter messageConverter, string consumerTag, ChannelProvider channelProvider, - IManagementApi managementApi, + IManagementClient managementApi, TimeSpan timeToWaitBeforeTriggeringCircuitBreaker, PrefetchCountCalculation prefetchCountCalculation, Action criticalErrorAction, From e1bff5f5a1a12f12ae54d57a0c876b9f2ba2198e Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Wed, 6 Nov 2024 23:02:56 +1100 Subject: [PATCH 19/46] Extract broker verification into its own class --- .../Administration/BrokerVerifier.cs | 149 ++++++++++++++++++ .../Administration/IBrokerVerifier.cs | 13 ++ .../Converters/FeatureFlagStateConverter.cs | 16 ++ .../ManagementClient/IManagementClient.cs | 2 + .../IManagementClientFactory.cs | 8 + .../ManagementClient/ManagementClient.cs | 17 ++ .../ManagementClientFactory.cs | 8 + .../ManagementClient/Models/FeatureFlag.cs | 26 +++ .../Models/FeatureFlagList.cs | 14 ++ .../ManagementClient/Models/FeatureFlags.cs | 32 ++++ .../ManagementClient/Models/Overview.cs | 12 +- .../ManagementClient/Models/Policy.cs | 12 +- .../ManagementClient/Models/Queue.cs | 12 +- .../Connection/ConnectionExtensions.cs | 12 +- .../RabbitMQTransport.cs | 7 +- .../RabbitMQTransportInfrastructure.cs | 10 +- .../Receiving/MessagePump.cs | 104 +----------- 17 files changed, 323 insertions(+), 131 deletions(-) create mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/IBrokerVerifier.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/FeatureFlagStateConverter.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlag.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlagList.cs create mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlags.cs diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs new file mode 100644 index 000000000..d2eb37c80 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs @@ -0,0 +1,149 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.Administration; + +using System; +using System.Threading; +using System.Threading.Tasks; +using NServiceBus.Logging; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; + +class BrokerVerifier(ConnectionFactory connectionFactory, IManagementClientFactory managementClientFactory) : IBrokerVerifier +{ + static readonly ILog Logger = LogManager.GetLogger(typeof(BrokerVerifier)); + static readonly Version MinimumSupportedRabbitMqVersion = Version.Parse("3.10.0"); + + readonly IManagementClient managementClient = managementClientFactory.CreateManagementClient(); + + Overview? overview; + Version? brokerVersion; + + public async Task Initialize(CancellationToken cancellationToken = default) + { + var response = await managementClient.GetOverview(cancellationToken).ConfigureAwait(false); + if (response.HasValue) + { + overview = response.Value; + brokerVersion = overview.RabbitMqVersion; + } + else + { + // TODO: Need logic/config settings for determining which action to take if management API unavailable, e.g. should we throw an exception to refuse to start, or just log a warning + Logger.WarnFormat("Could not access RabbitMQ Management API. ({0}: {1})", response.StatusCode, response.Reason); + + using var connection = await connectionFactory.CreateAdministrationConnection(cancellationToken).ConfigureAwait(false); + brokerVersion = connection.GetBrokerVersion(); + } + } + + bool HasManagementClientAccess => overview != null; + + Version BrokerVersion + { + get + { + if (brokerVersion == null) + { + throw new InvalidOperationException($"Need to call Initialize before accessing {nameof(BrokerVersion)} property"); + } + + return brokerVersion; + } + } + + public async Task VerifyRequirements(CancellationToken cancellationToken = default) + { + if (BrokerVersion < MinimumSupportedRabbitMqVersion) + { + throw new Exception($"An unsupported broker version was detected: {BrokerVersion}. The broker must be at least version {MinimumSupportedRabbitMqVersion}."); + } + + bool streamsEnabled; + if (HasManagementClientAccess) + { + var response = await managementClient.GetFeatureFlags(cancellationToken).ConfigureAwait(false); + streamsEnabled = response.HasValue && response.Value.HasEnabledFeature(FeatureFlags.StreamQueue); + } + else + { + var connection = await connectionFactory.CreateAdministrationConnection(cancellationToken).ConfigureAwait(false); + streamsEnabled = await connection.TryCreateStream(cancellationToken).ConfigureAwait(false); + } + + if (!streamsEnabled) + { + throw new Exception("An unsupported broker configuration was detected. The 'stream_queue' feature flag needs to be enabled."); + } + } + + public async Task ValidateDeliveryLimit(string queueName, CancellationToken cancellationToken = default) + { + if (!HasManagementClientAccess) + { + return; + } + + var response = await managementClient.GetQueue(queueName, cancellationToken).ConfigureAwait(false); + if (!response.HasValue) + { + // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning + Logger.WarnFormat("Could not determine delivery limit for {0}. ({1}: {2})", queueName, response.StatusCode, response.Reason); + return; + } + + var queue = response.Value; + if (queue.DeliveryLimit == -1) + { + return; + } + + if (queue.Arguments.DeliveryLimit.HasValue && + queue.Arguments.DeliveryLimit != -1) + { + // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning + Logger.WarnFormat("The delivery limit for {0} is set to {1} by a queue argument. This can interfere with the transport's retry implementation", + queue.Name, queue.Arguments.DeliveryLimit); + return; + } + + if (queue.EffectivePolicyDefinition.DeliveryLimit.HasValue && + queue.EffectivePolicyDefinition.DeliveryLimit != -1) + { + // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning + Logger.WarnFormat("The RabbitMQ policy {2} is setting delivery limit to {1} for {0}.", + queue.Name, queue.EffectivePolicyDefinition.DeliveryLimit, queue.AppliedPolicyName); + return; + } + + await SetDeliveryLimitViaPolicy(queue, cancellationToken).ConfigureAwait(false); + } + + async Task SetDeliveryLimitViaPolicy(Queue queue, CancellationToken cancellationToken) + { + if (!string.IsNullOrEmpty(queue.AppliedPolicyName)) + { + // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning + Logger.WarnFormat("The {0} queue already has an associated policy.", queue.Name, queue.AppliedPolicyName); + return; + } + + if (BrokerVersion.Major < 4) + { + // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning + Logger.WarnFormat("Cannot override delivery limit on the {0} queue by policy in RabbitMQ versions prior to 4.", queue.Name); + return; + } + + var policy = new Policy + { + Name = $"nsb.{queue.Name}.delivery-limit", + ApplyTo = PolicyTarget.QuorumQueues, + Definition = new PolicyDefinition { DeliveryLimit = -1 }, + Pattern = queue.Name, + Priority = 100 + }; + + await managementClient.CreatePolicy(policy, cancellationToken).ConfigureAwait(false); + } +} diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/IBrokerVerifier.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/IBrokerVerifier.cs new file mode 100644 index 000000000..a10dd3faf --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/IBrokerVerifier.cs @@ -0,0 +1,13 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.Administration; + +using System.Threading.Tasks; +using System.Threading; + +interface IBrokerVerifier +{ + Task Initialize(CancellationToken cancellationToken = default); + + Task ValidateDeliveryLimit(string queueName, CancellationToken cancellationToken = default); +} diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/FeatureFlagStateConverter.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/FeatureFlagStateConverter.cs new file mode 100644 index 000000000..66da757d2 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/FeatureFlagStateConverter.cs @@ -0,0 +1,16 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; + +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +class FeatureFlagEnabledConverter : JsonConverter +{ + public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + reader.GetString()?.Equals("enabled", StringComparison.OrdinalIgnoreCase) ?? false; + + public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) => + writer.WriteStringValue(value ? "enabled" : "disabled"); +} diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs index ebe53d023..f282b3a3a 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs @@ -12,5 +12,7 @@ interface IManagementClient Task> GetOverview(CancellationToken cancellationToken = default); + Task> GetFeatureFlags(CancellationToken cancellationToken = default); + Task CreatePolicy(Policy policy, CancellationToken cancellationToken = default); } diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs new file mode 100644 index 000000000..26047345e --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs @@ -0,0 +1,8 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; + +interface IManagementClientFactory +{ + IManagementClient CreateManagementClient(); +} diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs index 27ec23eaf..07308c96d 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs @@ -71,6 +71,23 @@ public ManagementClient(ConnectionConfiguration connectionConfiguration) value); } + public async Task> GetFeatureFlags(CancellationToken cancellationToken = default) + { + FeatureFlagList? value = null; + + var response = await httpClient.GetAsync($"api/feature-flags", cancellationToken).ConfigureAwait(false); + + if (response.IsSuccessStatusCode) + { + value = await response.Content.ReadFromJsonAsync(cancellationToken).ConfigureAwait(false); + } + + return new Response( + response.StatusCode, + response.ReasonPhrase ?? string.Empty, + value); + } + public async Task CreatePolicy(Policy policy, CancellationToken cancellationToken = default) { policy.VirtualHost = virtualHost; diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs new file mode 100644 index 000000000..1bab8d7fd --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs @@ -0,0 +1,8 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; + +class ManagementClientFactory(ConnectionConfiguration connectionConfiguration) : IManagementClientFactory +{ + public IManagementClient CreateManagementClient() => new ManagementClient(connectionConfiguration); +} diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlag.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlag.cs new file mode 100644 index 000000000..80579f477 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlag.cs @@ -0,0 +1,26 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; + +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; + +// This is to prevent Fody throwing an error on classes with `required` properties (since the compiler marks the default constructor with an `[Obsolete]` attribute) +// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation +[method: DoNotWarnAboutObsoleteUsage] +class FeatureFlag() +{ + [JsonRequired] + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonConverter(typeof(FeatureFlagEnabledConverter))] + [JsonPropertyName("state")] + public bool IsEnabled { get; set; } + + [JsonExtensionData] + public IDictionary ExtraProperties { get; } = new Dictionary(); +} + diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlagList.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlagList.cs new file mode 100644 index 000000000..6bcb92ac7 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlagList.cs @@ -0,0 +1,14 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; + +using System; +using System.Collections.Generic; +using System.Linq; + +class FeatureFlagList : List +{ + public bool HasEnabledFeature(string featureName) => + this.Any(feature => feature.Name.Equals(featureName, StringComparison.OrdinalIgnoreCase) && feature.IsEnabled); +} + diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlags.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlags.cs new file mode 100644 index 000000000..94bd797ce --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlags.cs @@ -0,0 +1,32 @@ +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; + +static class FeatureFlags +{ + /// + /// Add a detailed queues HTTP API endpoint. Reduce number of metrics in the default endpoint. + /// + public const string DetailedQueuesEndpoints = "detailed_queues_endpoint"; + + /// + /// New Raft-based metadata store. Fully supported as of RabbitMQ 4.0 + /// + public const string KhepriDatabase = "khepri_db"; + + /// + /// Support queues of type `quorum` + /// + public const string QuorumQueue = "quorum_queue"; + + /// + /// Support for stream filtering. + /// + public const string StreamFiltering = "stream_filtering"; + + /// + /// Support queues of type `stream`. + /// + public const string StreamQueue = "stream_queue"; +} + diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs index 3c7bcd6ac..9c5ab216d 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs @@ -8,15 +8,11 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; using System.Text.Json.Serialization; using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; -class Overview +// This is to prevent Fody throwing an error on classes with `required` properties (since the compiler marks the default constructor with an `[Obsolete]` attribute) +// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation +[method: DoNotWarnAboutObsoleteUsage] +class Overview() { - // This is because Fody is throwing an error when using the 'required' keyword that ctor has an obsoleteAttribute. - // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation - [DoNotWarnAboutObsoleteUsage] - public Overview() - { - } - [JsonPropertyName("product_name")] public required string ProductName { get; set; } diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs index 3d2d0bc00..714b2e2f1 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs @@ -7,15 +7,11 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; using System.Text.Json.Serialization; using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; -class Policy +// This is to prevent Fody throwing an error on classes with `required` properties (since the compiler marks the default constructor with an `[Obsolete]` attribute) +// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation +[method: DoNotWarnAboutObsoleteUsage] +class Policy() { - // This is because Fody is throwing an error when using the 'required' keyword that ctor has an obsoleteAttribute. - // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation - [DoNotWarnAboutObsoleteUsage] - public Policy() - { - } - [JsonPropertyName("vhost")] public string VirtualHost { get; set; } = "/"; diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs index 1dfe9f444..c5dafc9e7 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs @@ -7,15 +7,11 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; using System.Text.Json.Serialization; using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; -class Queue +// This is to prevent Fody throwing an error on classes with `required` properties (since the compiler marks the default constructor with an `[Obsolete]` attribute) +// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation +[method: DoNotWarnAboutObsoleteUsage] +class Queue() { - // This is because Fody is throwing an error when using the 'required' keyword that ctor has an obsoleteAttribute. - // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation - [DoNotWarnAboutObsoleteUsage] - public Queue() - { - } - [JsonPropertyName("name")] public required string Name { get; set; } diff --git a/src/NServiceBus.Transport.RabbitMQ/Connection/ConnectionExtensions.cs b/src/NServiceBus.Transport.RabbitMQ/Connection/ConnectionExtensions.cs index 7843f3522..80f00630d 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Connection/ConnectionExtensions.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Connection/ConnectionExtensions.cs @@ -41,6 +41,15 @@ public static async Task VerifyBrokerRequirements(this IConnection connection, C throw new Exception($"An unsupported broker version was detected: {brokerVersion}. The broker must be at least version {minimumBrokerVersion}."); } + var streamsEnabled = await connection.TryCreateStream(cancellationToken).ConfigureAwait(false); + if (!streamsEnabled) + { + throw new Exception("An unsupported broker configuration was detected. The 'stream_queue' feature flag needs to be enabled."); + } + } + + public static async Task TryCreateStream(this IConnection connection, CancellationToken cancellationToken = default) + { using var channel = await connection.CreateChannelAsync(cancellationToken: cancellationToken).ConfigureAwait(false); var arguments = new Dictionary { { "x-queue-type", "stream" } }; @@ -48,10 +57,11 @@ public static async Task VerifyBrokerRequirements(this IConnection connection, C try { await channel.QueueDeclareAsync("nsb.v2.verify-stream-flag-enabled", true, false, false, arguments, cancellationToken: cancellationToken).ConfigureAwait(false); + return true; } catch (Exception ex) when (!ex.IsCausedBy(cancellationToken) && ex.Message.Contains("the corresponding feature flag is disabled")) { - throw new Exception("An unsupported broker configuration was detected. The 'stream_queue' feature flag needs to be enabled."); + return false; } } } diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index 1a144ead4..2ab0a8f0e 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -5,6 +5,7 @@ using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; + using NServiceBus.Transport.RabbitMQ.Administration; using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using RabbitMQ.Client; using RabbitMQ.Client.Events; @@ -212,7 +213,9 @@ public override async Task Initialize(HostSettings host additionalClusterNodes ); - var managementClient = new ManagementClient(ConnectionConfiguration); + var managementClientFactory = new ManagementClientFactory(ConnectionConfiguration); + var brokerVerifier = new BrokerVerifier(connectionFactory, managementClientFactory); + await brokerVerifier.Initialize(cancellationToken).ConfigureAwait(false); var channelProvider = new ChannelProvider(connectionFactory, NetworkRecoveryInterval, RoutingTopology); await channelProvider.CreateConnection(cancellationToken).ConfigureAwait(false); @@ -226,7 +229,7 @@ public override async Task Initialize(HostSettings host RoutingTopology, channelProvider, converter, - managementClient, + brokerVerifier, OutgoingNativeMessageCustomization, TimeToWaitBeforeTriggeringCircuitBreaker, PrefetchCountCalculation, diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs index 5ed521ee6..dfc4bb8c0 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs @@ -5,22 +5,22 @@ using System.Text; using System.Threading; using System.Threading.Tasks; - using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; + using NServiceBus.Transport.RabbitMQ.Administration; using global::RabbitMQ.Client; sealed class RabbitMQTransportInfrastructure : TransportInfrastructure { readonly ConnectionFactory connectionFactory; readonly ChannelProvider channelProvider; + readonly IBrokerVerifier brokerVerifier; readonly IRoutingTopology routingTopology; readonly TimeSpan networkRecoveryInterval; readonly bool supportsDelayedDelivery; - readonly IManagementClient managementApi; public RabbitMQTransportInfrastructure(HostSettings hostSettings, ReceiveSettings[] receiverSettings, ConnectionFactory connectionFactory, IRoutingTopology routingTopology, ChannelProvider channelProvider, MessageConverter messageConverter, - IManagementClient managementApi, + IBrokerVerifier brokerVerifier, Action messageCustomization, TimeSpan timeToWaitBeforeTriggeringCircuitBreaker, PrefetchCountCalculation prefetchCountCalculation, TimeSpan networkRecoveryInterval, bool supportsDelayedDelivery) @@ -28,7 +28,7 @@ public RabbitMQTransportInfrastructure(HostSettings hostSettings, ReceiveSetting this.connectionFactory = connectionFactory; this.routingTopology = routingTopology; this.channelProvider = channelProvider; - this.managementApi = managementApi; + this.brokerVerifier = brokerVerifier; this.networkRecoveryInterval = networkRecoveryInterval; this.supportsDelayedDelivery = supportsDelayedDelivery; @@ -43,7 +43,7 @@ IMessageReceiver CreateMessagePump(HostSettings hostSettings, ReceiveSettings se return new MessagePump( settings, connectionFactory, routingTopology, messageConverter, - consumerTag, channelProvider, managementApi, + consumerTag, channelProvider, brokerVerifier, timeToWaitBeforeTriggeringCircuitBreaker, prefetchCountCalculation, hostSettings.CriticalErrorAction, networkRecoveryInterval); } diff --git a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs index d5f445106..754091640 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs @@ -11,8 +11,7 @@ using global::RabbitMQ.Client.Events; using global::RabbitMQ.Client.Exceptions; using Logging; - using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; - using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; + using NServiceBus.Transport.RabbitMQ.Administration; sealed partial class MessagePump : IMessageReceiver { @@ -24,7 +23,7 @@ sealed partial class MessagePump : IMessageReceiver readonly MessageConverter messageConverter; readonly string consumerTag; readonly ChannelProvider channelProvider; - readonly IManagementClient managementApi; + readonly IBrokerVerifier brokerVerifier; readonly TimeSpan timeToWaitBeforeTriggeringCircuitBreaker; readonly QueuePurger queuePurger; readonly PrefetchCountCalculation prefetchCountCalculation; @@ -42,8 +41,6 @@ sealed partial class MessagePump : IMessageReceiver CancellationTokenSource messageProcessingCancellationTokenSource; MessagePumpConnectionFailedCircuitBreaker circuitBreaker; IConnection connection; - Overview overview; - Version brokerVersion; // Stop TaskCompletionSource connectionShutdownCompleted; @@ -55,7 +52,7 @@ public MessagePump( MessageConverter messageConverter, string consumerTag, ChannelProvider channelProvider, - IManagementClient managementApi, + IBrokerVerifier brokerVerifier, TimeSpan timeToWaitBeforeTriggeringCircuitBreaker, PrefetchCountCalculation prefetchCountCalculation, Action criticalErrorAction, @@ -66,7 +63,7 @@ public MessagePump( this.messageConverter = messageConverter; this.consumerTag = consumerTag; this.channelProvider = channelProvider; - this.managementApi = managementApi; + this.brokerVerifier = brokerVerifier; this.timeToWaitBeforeTriggeringCircuitBreaker = timeToWaitBeforeTriggeringCircuitBreaker; this.prefetchCountCalculation = prefetchCountCalculation; this.criticalErrorAction = criticalErrorAction; @@ -95,103 +92,12 @@ public async Task Initialize(PushRuntimeSettings limitations, OnMessage onMessag this.onError = onError; maxConcurrency = limitations.MaxConcurrency; - await CheckConnectivity(cancellationToken).ConfigureAwait(false); - if (settings.PurgeOnStartup) { await queuePurger.Purge(ReceiveAddress, cancellationToken).ConfigureAwait(false); } - await ValidateDeliveryLimit(cancellationToken).ConfigureAwait(false); - } - - internal async Task CheckConnectivity(CancellationToken cancellationToken = default) - { - var response = await managementApi.GetOverview(cancellationToken).ConfigureAwait(false); - if (response.HasValue) - { - overview = response.Value; - brokerVersion = overview.RabbitMqVersion; - } - else - { - // TODO: Need logic/config settings for determining which action to take if management API unavailable, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("Could not access RabbitMQ Management API. ({0}: {1})", response.StatusCode, response.Reason); - - using var connection = await connectionFactory.CreateAdministrationConnection(cancellationToken).ConfigureAwait(false); - brokerVersion = connection.GetBrokerVersion(); - } - } - - bool HasManagementApiAccess => overview != null; - - async Task ValidateDeliveryLimit(CancellationToken cancellationToken) - { - if (!HasManagementApiAccess) - { - return; - } - - var response = await managementApi.GetQueue(ReceiveAddress, cancellationToken).ConfigureAwait(false); - if (!response.HasValue) - { - // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("Could not determine delivery limit for {0}. ({1}: {2})", ReceiveAddress, response.StatusCode, response.Reason); - return; - } - - var queue = response.Value; - if (queue.DeliveryLimit == -1) - { - return; - } - - if (queue.Arguments.DeliveryLimit.HasValue && - queue.Arguments.DeliveryLimit != -1) - { - // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("The delivery limit for {0} is set to {1} by a queue argument. This can interfere with the transport's retry implementation", - queue.Name, queue.Arguments.DeliveryLimit); - return; - } - - if (queue.EffectivePolicyDefinition.DeliveryLimit.HasValue && - queue.EffectivePolicyDefinition.DeliveryLimit != -1) - { - // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("The RabbitMQ policy {2} is setting delivery limit to {1} for {0}.", - queue.Name, queue.EffectivePolicyDefinition.DeliveryLimit, queue.AppliedPolicyName); - return; - } - - await SetDeliveryLimitViaPolicy(queue, cancellationToken).ConfigureAwait(false); - } - - async Task SetDeliveryLimitViaPolicy(Queue queue, CancellationToken cancellationToken) - { - if (!string.IsNullOrEmpty(queue.AppliedPolicyName)) - { - // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("The {0} queue already has an associated policy.", queue.Name, queue.AppliedPolicyName); - return; - } - - if (brokerVersion.Major < 4) - { - // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("Cannot override delivery limit on the {0} queue by policy in RabbitMQ versions prior to 4.", queue.Name); - return; - } - - var policy = new Policy - { - Name = $"nsb.{queue.Name}.delivery-limit", - ApplyTo = PolicyTarget.QuorumQueues, - Definition = new PolicyDefinition { DeliveryLimit = -1 }, - Pattern = queue.Name, - Priority = 100 - }; - await managementApi.CreatePolicy(policy, cancellationToken).ConfigureAwait(false); + await brokerVerifier.ValidateDeliveryLimit(ReceiveAddress, cancellationToken).ConfigureAwait(false); } public async Task StartReceive(CancellationToken cancellationToken = default) From 99b68554203dcb3e7ea8c37f4a372569ad7db970 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Thu, 7 Nov 2024 00:44:55 +1100 Subject: [PATCH 20/46] Added retry policy when getting queue details from Management API to ensure we get complete queue object --- .../Administration/BrokerVerifier.cs | 46 +++++++++++++++---- .../ManagementClient/Models/Queue.cs | 4 +- .../NServiceBus.Transport.RabbitMQ.csproj | 1 + 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs index d2eb37c80..b8a1cc71a 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs @@ -5,9 +5,10 @@ namespace NServiceBus.Transport.RabbitMQ.Administration; using System; using System.Threading; using System.Threading.Tasks; +using ManagementClient; +using ManagementClient.Models; using NServiceBus.Logging; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; +using Polly; class BrokerVerifier(ConnectionFactory connectionFactory, IManagementClientFactory managementClientFactory) : IBrokerVerifier { @@ -84,15 +85,14 @@ public async Task ValidateDeliveryLimit(string queueName, CancellationToken canc return; } - var response = await managementClient.GetQueue(queueName, cancellationToken).ConfigureAwait(false); - if (!response.HasValue) + var queue = await GetFullQueueDetails(queueName, cancellationToken).ConfigureAwait(false); + if (queue is null) { // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("Could not determine delivery limit for {0}. ({1}: {2})", queueName, response.StatusCode, response.Reason); + Logger.WarnFormat("Could not retrieve full queue details for {0}.", queueName); return; } - var queue = response.Value; if (queue.DeliveryLimit == -1) { return; @@ -107,7 +107,7 @@ public async Task ValidateDeliveryLimit(string queueName, CancellationToken canc return; } - if (queue.EffectivePolicyDefinition.DeliveryLimit.HasValue && + if (queue.EffectivePolicyDefinition!.DeliveryLimit.HasValue && queue.EffectivePolicyDefinition.DeliveryLimit != -1) { // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning @@ -119,6 +119,36 @@ public async Task ValidateDeliveryLimit(string queueName, CancellationToken canc await SetDeliveryLimitViaPolicy(queue, cancellationToken).ConfigureAwait(false); } + async Task GetFullQueueDetails(string queueName, CancellationToken cancellationToken) + { + var retryPolicy = Polly.Policy + .HandleResult>(response => response.Value?.EffectivePolicyDefinition is null) + .WaitAndRetryAsync( + 5, + attempt => TimeSpan.FromMilliseconds(100 * Math.Pow(2, attempt - 1)), + onRetry: (outcome, timespan, retryCount, context) => + { + if (outcome.Exception is not null) + { + Logger.Error($"Failed to get {queueName} queue", outcome.Exception); + } + else if (!outcome.Result.HasValue) + { + var response = outcome.Result; + Logger.WarnFormat("Could not get queue details for {0} - Attempt #{1}. ({2}: {3})", queueName, retryCount, response.StatusCode, response.Reason); + } + else + { + var response = outcome.Result; + Logger.WarnFormat("Did not receive full queue details for {0} - Attempt #{1})", queueName, retryCount); + } + }); + + var response = await retryPolicy.ExecuteAsync(() => managementClient.GetQueue(queueName, cancellationToken)).ConfigureAwait(false); + + return response?.Value?.EffectivePolicyDefinition is not null ? response.Value : null; + } + async Task SetDeliveryLimitViaPolicy(Queue queue, CancellationToken cancellationToken) { if (!string.IsNullOrEmpty(queue.AppliedPolicyName)) @@ -135,7 +165,7 @@ async Task SetDeliveryLimitViaPolicy(Queue queue, CancellationToken cancellation return; } - var policy = new Policy + var policy = new ManagementClient.Models.Policy { Name = $"nsb.{queue.Name}.delivery-limit", ApplyTo = PolicyTarget.QuorumQueues, diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs index c5dafc9e7..a41a60f28 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs @@ -12,9 +12,11 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; [method: DoNotWarnAboutObsoleteUsage] class Queue() { + [JsonRequired] [JsonPropertyName("name")] public required string Name { get; set; } + [JsonRequired] [JsonPropertyName("arguments")] public required QueueArguments Arguments { get; set; } @@ -23,7 +25,7 @@ class Queue() public int DeliveryLimit { get; set; } [JsonPropertyName("effective_policy_definition")] - public required PolicyDefinition EffectivePolicyDefinition { get; set; } + public PolicyDefinition? EffectivePolicyDefinition { get; set; } [JsonPropertyName("policy")] public string? AppliedPolicyName { get; set; } diff --git a/src/NServiceBus.Transport.RabbitMQ/NServiceBus.Transport.RabbitMQ.csproj b/src/NServiceBus.Transport.RabbitMQ/NServiceBus.Transport.RabbitMQ.csproj index 776b7b27c..1fb6dde6e 100644 --- a/src/NServiceBus.Transport.RabbitMQ/NServiceBus.Transport.RabbitMQ.csproj +++ b/src/NServiceBus.Transport.RabbitMQ/NServiceBus.Transport.RabbitMQ.csproj @@ -10,6 +10,7 @@ + From 18804a9892d2fb9b6380f869f915902ef61e2f50 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Thu, 7 Nov 2024 01:05:28 +1100 Subject: [PATCH 21/46] Make QueueArguments.QueueType optional/nullable --- .../Administration/ManagementClient/Models/QueueArguments.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueArguments.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueArguments.cs index dad5e6dcf..5db1f608f 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueArguments.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueArguments.cs @@ -9,10 +9,9 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; class QueueArguments { - [JsonRequired] [JsonPropertyName("x-queue-type")] [JsonConverter(typeof(QueueTypeConverter))] - public QueueType QueueType { get; set; } + public QueueType? QueueType { get; set; } [JsonPropertyName("x-delivery-limit")] [JsonConverter(typeof(DeliveryLimitConverter))] From 3772929402f291d40a371ca5f8fbafdbb011d52b Mon Sep 17 00:00:00 2001 From: Andreas Bednarz <110360248+abparticular@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:14:47 +1100 Subject: [PATCH 22/46] Add configuration option to prevent the transport using the RabbitMQ management API (#1498) --- ...lassic_endpoint_uses_quorum_error_queue.cs | 3 +- ...When_classic_endpoint_uses_quorum_queue.cs | 3 +- ...uorum_endpoint_uses_classic_error_queue.cs | 3 +- ...When_quorum_endpoint_uses_classic_queue.cs | 3 +- .../BrokerConnectionBinder.cs | 2 +- .../APIApprovals.Approve.approved.txt | 1 + ...nnecting_to_the_rabbitmq_management_api.cs | 8 +- ...ServiceBus.Transport.RabbitMQ.Tests.csproj | 2 +- .../Administration/BrokerVerifier.cs | 97 ++++++++++--------- .../NServiceBus.Transport.RabbitMQ.csproj | 2 +- .../RabbitMQTransport.cs | 8 +- 11 files changed, 74 insertions(+), 58 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_error_queue.cs b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_error_queue.cs index 4e9d9b551..8174bc403 100644 --- a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_error_queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_error_queue.cs @@ -24,7 +24,8 @@ public async Task Should_fail_to_start() .Run()); Assert.That(exception.Message, Does.Contain("PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'rabbitmq.transport.tests.quorum-error'")); - Assert.That(exception.Message, Does.Contain("received 'classic' but current is 'quorum'")); + Assert.That(exception.Message, Does.Contain("received none but current is the value 'quorum'") + .Or.Contain("received 'classic' but current is 'quorum'")); } class ClassicQueueEndpoint : EndpointConfigurationBuilder diff --git a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs index 14a8f4653..655a6321b 100644 --- a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs @@ -25,7 +25,8 @@ public async Task Should_fail_to_start() .Run()); Assert.That(exception.Message, Does.Contain("PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'ClassicEndpointUsesQuorumQueue.ClassicQueueEndpoint'")); - Assert.That(exception.Message, Does.Contain("received 'classic' but current is 'quorum'")); + Assert.That(exception.Message, Does.Contain("received none but current is the value 'quorum'") + .Or.Contain("received 'classic' but current is 'quorum'")); } class ClassicQueueEndpoint : EndpointConfigurationBuilder diff --git a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_error_queue.cs b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_error_queue.cs index 7167c648b..1fd9da994 100644 --- a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_error_queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_error_queue.cs @@ -24,7 +24,8 @@ public async Task Should_fail_to_start() .Run()); Assert.That(exception.Message, Does.Contain("PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'rabbitmq.transport.tests.classic-error'")); - Assert.That(exception.Message, Does.Contain("received 'quorum' but current is 'classic'")); + Assert.That(exception.Message, Does.Contain("received the value 'quorum' of type 'longstr' but current is none'") // RabbitMQ v3.x + .Or.Contain("received 'quorum' but current is 'classic'")); // RabbitMQ v4.x } class QuorumQueueEndpoint : EndpointConfigurationBuilder diff --git a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_queue.cs b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_queue.cs index 2bec0bef5..4b8733263 100644 --- a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_quorum_endpoint_uses_classic_queue.cs @@ -24,7 +24,8 @@ public async Task Should_fail_to_start() .Run()); Assert.That(exception.Message, Does.Contain("PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'QuorumEndpointUsesClassicQueue.QuorumQueueEndpoint'")); - Assert.That(exception.Message, Does.Contain("received 'quorum' but current is 'classic'")); + Assert.That(exception.Message, Does.Contain("received the value 'quorum' of type 'longstr' but current is none'") // RabbitMQ v3.x + .Or.Contain("received 'quorum' but current is 'classic'")); // RabbitMQ v4.x } class QuorumQueueEndpoint : EndpointConfigurationBuilder diff --git a/src/NServiceBus.Transport.RabbitMQ.CommandLine/BrokerConnectionBinder.cs b/src/NServiceBus.Transport.RabbitMQ.CommandLine/BrokerConnectionBinder.cs index 581df4950..4b98b4c82 100644 --- a/src/NServiceBus.Transport.RabbitMQ.CommandLine/BrokerConnectionBinder.cs +++ b/src/NServiceBus.Transport.RabbitMQ.CommandLine/BrokerConnectionBinder.cs @@ -42,7 +42,7 @@ protected override BrokerConnection GetBoundValue(BindingContext bindingContext) return brokerConnection; } - string GetConnectionString(string? connectionString, string? connectionStringEnv) + static string GetConnectionString(string? connectionString, string? connectionStringEnv) { if (string.IsNullOrWhiteSpace(connectionString)) { diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.Transport.RabbitMQ.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt index 01faebeab..272da9d1d 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -19,6 +19,7 @@ namespace NServiceBus public RabbitMQTransport(NServiceBus.RoutingTopology routingTopology, string connectionString) { } public RabbitMQTransport(NServiceBus.RoutingTopology routingTopology, string connectionString, bool enableDelayedDelivery) { } public System.Security.Cryptography.X509Certificates.X509Certificate2 ClientCertificate { get; set; } + public bool DoNotUseManagementClient { get; set; } public System.TimeSpan HeartbeatInterval { get; set; } public System.Func MessageIdStrategy { get; set; } public System.TimeSpan NetworkRecoveryInterval { get; set; } diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs index 66effe5d4..7436e8a21 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs @@ -2,6 +2,7 @@ namespace NServiceBus.Transport.RabbitMQ.Tests.Connection.ManagementConnection { + using System; using System.Collections.Generic; using System.Net; using System.Threading.Tasks; @@ -14,9 +15,10 @@ namespace NServiceBus.Transport.RabbitMQ.Tests.Connection.ManagementConnection [TestFixture] - class When_connecting_to_the_rabbitmq_management_api : RabbitMQTransport + class When_connecting_to_the_rabbitmq_management_api { - ConnectionConfiguration connectionConfiguration = ConnectionConfiguration.Create("host=localhost"); + static readonly string connectionString = Environment.GetEnvironmentVariable("RabbitMQTransport_ConnectionString") ?? "host=localhost"; + readonly ConnectionConfiguration connectionConfiguration = ConnectionConfiguration.Create(connectionString); protected QueueType queueType = QueueType.Quorum; protected string ReceiverQueue => GetTestQueueName("ManagementAPITestQueue"); protected string GetTestQueueName(string queueName) => $"{queueName}-{queueType}"; @@ -24,7 +26,7 @@ class When_connecting_to_the_rabbitmq_management_api : RabbitMQTransport [SetUp] public async Task SetUp() { - var connectionFactory = new ConnectionFactory(ReceiverQueue, connectionConfiguration, null, !ValidateRemoteCertificate, UseExternalAuthMechanism, HeartbeatInterval, NetworkRecoveryInterval, []); + var connectionFactory = new ConnectionFactory(ReceiverQueue, connectionConfiguration, null, false, false, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(10), []); IConnection connection = await connectionFactory.CreateConnection(ReceiverQueue).ConfigureAwait(false); var createChannelOptions = new CreateChannelOptions(publisherConfirmationsEnabled: false, publisherConfirmationTrackingEnabled: false); var channel = await connection.CreateChannelAsync(createChannelOptions).ConfigureAwait(false); diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/NServiceBus.Transport.RabbitMQ.Tests.csproj b/src/NServiceBus.Transport.RabbitMQ.Tests/NServiceBus.Transport.RabbitMQ.Tests.csproj index bbae26ae5..8b22a02c3 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/NServiceBus.Transport.RabbitMQ.Tests.csproj +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/NServiceBus.Transport.RabbitMQ.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0 diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs index b8a1cc71a..01d0cd3c4 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs @@ -10,36 +10,41 @@ namespace NServiceBus.Transport.RabbitMQ.Administration; using NServiceBus.Logging; using Polly; -class BrokerVerifier(ConnectionFactory connectionFactory, IManagementClientFactory managementClientFactory) : IBrokerVerifier +class BrokerVerifier(ConnectionFactory connectionFactory, IManagementClientFactory? managementClientFactory) : IBrokerVerifier { static readonly ILog Logger = LogManager.GetLogger(typeof(BrokerVerifier)); static readonly Version MinimumSupportedRabbitMqVersion = Version.Parse("3.10.0"); + static readonly Version RabbitMqVersion4 = Version.Parse("4.0.0"); - readonly IManagementClient managementClient = managementClientFactory.CreateManagementClient(); + readonly IManagementClient? managementClient = managementClientFactory?.CreateManagementClient(); - Overview? overview; Version? brokerVersion; public async Task Initialize(CancellationToken cancellationToken = default) { - var response = await managementClient.GetOverview(cancellationToken).ConfigureAwait(false); - if (response.HasValue) + if (managementClient != null) { - overview = response.Value; - brokerVersion = overview.RabbitMqVersion; + var response = await managementClient.GetOverview(cancellationToken).ConfigureAwait(false); + if (response.HasValue) + { + brokerVersion = response.Value.RabbitMqVersion; + return; + } + + throw new InvalidOperationException($"Could not access RabbitMQ Management API. ({response.StatusCode}: {response.Reason})"); } - else - { - // TODO: Need logic/config settings for determining which action to take if management API unavailable, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("Could not access RabbitMQ Management API. ({0}: {1})", response.StatusCode, response.Reason); - using var connection = await connectionFactory.CreateAdministrationConnection(cancellationToken).ConfigureAwait(false); - brokerVersion = connection.GetBrokerVersion(); + using var connection = await connectionFactory.CreateAdministrationConnection(cancellationToken).ConfigureAwait(false); + brokerVersion = connection.GetBrokerVersion(); + + if (brokerVersion >= RabbitMqVersion4) + { + Logger.Warn("Use of RabbitMQ Management API has been disabled." + + "The transport will not be able to override the default delivery limit on each queue " + + "which is necessary in order to guarantee that messages are not lost after repeated retries."); } } - bool HasManagementClientAccess => overview != null; - Version BrokerVersion { get @@ -61,7 +66,7 @@ public async Task VerifyRequirements(CancellationToken cancellationToken = defau } bool streamsEnabled; - if (HasManagementClientAccess) + if (managementClient != null) { var response = await managementClient.GetFeatureFlags(cancellationToken).ConfigureAwait(false); streamsEnabled = response.HasValue && response.Value.HasEnabledFeature(FeatureFlags.StreamQueue); @@ -80,57 +85,59 @@ public async Task VerifyRequirements(CancellationToken cancellationToken = defau public async Task ValidateDeliveryLimit(string queueName, CancellationToken cancellationToken = default) { - if (!HasManagementClientAccess) + if (managementClient == null) { return; } - var queue = await GetFullQueueDetails(queueName, cancellationToken).ConfigureAwait(false); - if (queue is null) + var queue = await GetFullQueueDetails(managementClient, queueName, cancellationToken).ConfigureAwait(false) + ?? throw new InvalidOperationException($"Could not retrieve full queue details for {queueName}."); + + if (ShouldOverrideDeliveryLimit(queue)) { - // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("Could not retrieve full queue details for {0}.", queueName); - return; + await SetDeliveryLimitViaPolicy(managementClient, queue, BrokerVersion, cancellationToken).ConfigureAwait(false); + } + } + + bool ShouldOverrideDeliveryLimit(Queue queue) + { + if (BrokerVersion < RabbitMqVersion4) + { + return false; } if (queue.DeliveryLimit == -1) { - return; + return false; } - if (queue.Arguments.DeliveryLimit.HasValue && - queue.Arguments.DeliveryLimit != -1) + if (queue.Arguments.DeliveryLimit.HasValue && queue.Arguments.DeliveryLimit != -1) { - // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("The delivery limit for {0} is set to {1} by a queue argument. This can interfere with the transport's retry implementation", - queue.Name, queue.Arguments.DeliveryLimit); - return; + throw new InvalidOperationException($"The delivery limit for {queue.Name} is set to {queue.Arguments.DeliveryLimit} by a queue argument. " + + "This can interfere with the transport's retry implementation"); } - if (queue.EffectivePolicyDefinition!.DeliveryLimit.HasValue && - queue.EffectivePolicyDefinition.DeliveryLimit != -1) + if (queue.EffectivePolicyDefinition!.DeliveryLimit.HasValue && queue.EffectivePolicyDefinition.DeliveryLimit != -1) { - // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("The RabbitMQ policy {2} is setting delivery limit to {1} for {0}.", - queue.Name, queue.EffectivePolicyDefinition.DeliveryLimit, queue.AppliedPolicyName); - return; + throw new InvalidOperationException($"The RabbitMQ policy {queue.AppliedPolicyName} " + + $"is setting delivery limit to {queue.EffectivePolicyDefinition.DeliveryLimit} for {queue.Name}."); } - await SetDeliveryLimitViaPolicy(queue, cancellationToken).ConfigureAwait(false); + return true; } - async Task GetFullQueueDetails(string queueName, CancellationToken cancellationToken) + static async Task GetFullQueueDetails(IManagementClient managementClient, string queueName, CancellationToken cancellationToken) { var retryPolicy = Polly.Policy .HandleResult>(response => response.Value?.EffectivePolicyDefinition is null) .WaitAndRetryAsync( 5, - attempt => TimeSpan.FromMilliseconds(100 * Math.Pow(2, attempt - 1)), + attempt => TimeSpan.FromMilliseconds(3000 * Math.Pow(2, attempt - 1)), onRetry: (outcome, timespan, retryCount, context) => { if (outcome.Exception is not null) { - Logger.Error($"Failed to get {queueName} queue", outcome.Exception); + Logger.Error($"Failed to get {queueName} queue - Attempt #{retryCount}.", outcome.Exception); } else if (!outcome.Result.HasValue) { @@ -149,20 +156,16 @@ public async Task ValidateDeliveryLimit(string queueName, CancellationToken canc return response?.Value?.EffectivePolicyDefinition is not null ? response.Value : null; } - async Task SetDeliveryLimitViaPolicy(Queue queue, CancellationToken cancellationToken) + static async Task SetDeliveryLimitViaPolicy(IManagementClient managementClient, Queue queue, Version brokerVersion, CancellationToken cancellationToken) { if (!string.IsNullOrEmpty(queue.AppliedPolicyName)) { - // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("The {0} queue already has an associated policy.", queue.Name, queue.AppliedPolicyName); - return; + throw new InvalidOperationException($"The {queue.Name} queue already has the '{queue.AppliedPolicyName}' policy applied."); } - if (BrokerVersion.Major < 4) + if (brokerVersion < RabbitMqVersion4) { - // TODO: Need logic/config settings for determining which action to take, e.g. should we throw an exception to refuse to start, or just log a warning - Logger.WarnFormat("Cannot override delivery limit on the {0} queue by policy in RabbitMQ versions prior to 4.", queue.Name); - return; + throw new InvalidOperationException($"Cannot override delivery limit on the {queue.Name} queue by policy in RabbitMQ versions prior to 4. Version is {brokerVersion}."); } var policy = new ManagementClient.Models.Policy diff --git a/src/NServiceBus.Transport.RabbitMQ/NServiceBus.Transport.RabbitMQ.csproj b/src/NServiceBus.Transport.RabbitMQ/NServiceBus.Transport.RabbitMQ.csproj index 1fb6dde6e..dc6e9a2f6 100644 --- a/src/NServiceBus.Transport.RabbitMQ/NServiceBus.Transport.RabbitMQ.csproj +++ b/src/NServiceBus.Transport.RabbitMQ/NServiceBus.Transport.RabbitMQ.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index 2ab0a8f0e..6cf64f7a1 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -138,6 +138,12 @@ public PrefetchCountCalculation PrefetchCountCalculation /// public bool UseExternalAuthMechanism { get; set; } = false; + /// + /// Set this to prevent the transport from using the RabbitMQ Management API. + /// This is not recommended as it can prevent the transport from setting appropriate delivery limits for retry functionality. + /// + public bool DoNotUseManagementClient { get; set; } = false; + /// /// The interval for heartbeats between the endpoint and the broker. /// @@ -213,7 +219,7 @@ public override async Task Initialize(HostSettings host additionalClusterNodes ); - var managementClientFactory = new ManagementClientFactory(ConnectionConfiguration); + var managementClientFactory = DoNotUseManagementClient ? null : new ManagementClientFactory(ConnectionConfiguration); var brokerVerifier = new BrokerVerifier(connectionFactory, managementClientFactory); await brokerVerifier.Initialize(cancellationToken).ConfigureAwait(false); From 3b0085f146f7e3d4e72bb12180eb8a736a3a788d Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Fri, 8 Nov 2024 19:04:07 -0800 Subject: [PATCH 23/46] Add functionality to set management client setting --- .../ManagementClient/ManagementClient.cs | 32 ++++++++++-- .../RabbitMQTransportSettingsExtensions.cs | 32 ++++++++++++ .../RabbitMQTransport.cs | 51 +++++++++++++++---- 3 files changed, 101 insertions(+), 14 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs index 07308c96d..9d7174d65 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs @@ -6,6 +6,7 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -17,11 +18,22 @@ class ManagementClient : IManagementClient readonly string virtualHost; readonly string escapedVirtualHost; - public ManagementClient(ConnectionConfiguration connectionConfiguration) + public ManagementClient(ConnectionConfiguration connectionConfiguration, X509Certificate2Collection? managementCertCollection = null) { + if (connectionConfiguration == null) + { + throw new ArgumentNullException(nameof(connectionConfiguration)); + } + virtualHost = connectionConfiguration.VirtualHost; escapedVirtualHost = Uri.EscapeDataString(virtualHost); + var handler = new HttpClientHandler(); + if (connectionConfiguration.UseTls) + { + ConfigureSsl(handler, managementCertCollection); + } + var uriBuilder = new UriBuilder { Scheme = connectionConfiguration.UseTls ? "https" : "http", @@ -29,12 +41,21 @@ public ManagementClient(ConnectionConfiguration connectionConfiguration) Port = 15672 // TODO: fallback to default only if specific details aren't given in config }; - httpClient = new HttpClient { BaseAddress = uriBuilder.Uri }; + httpClient = new HttpClient(handler) { BaseAddress = uriBuilder.Uri }; httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{connectionConfiguration.UserName}:{connectionConfiguration.Password}"))); } + void ConfigureSsl(HttpClientHandler handler, X509Certificate2Collection? managementCertCollection) + { + if (managementCertCollection != null) + { + handler.ClientCertificates.AddRange(managementCertCollection); + } + handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls13; + } + public async Task> GetQueue(string queueName, CancellationToken cancellationToken = default) { Queue? value = null; @@ -90,7 +111,12 @@ public ManagementClient(ConnectionConfiguration connectionConfiguration) public async Task CreatePolicy(Policy policy, CancellationToken cancellationToken = default) { - policy.VirtualHost = virtualHost; + if (policy.Name == null) + { + throw new ArgumentNullException(nameof(policy.Name)); + } + + policy.VirtualHost = Uri.EscapeDataString(virtualHost); var escapedPolicyName = Uri.EscapeDataString(policy.Name); var response = await httpClient.PutAsJsonAsync($"api/policies/{escapedVirtualHost}/{escapedPolicyName}", policy, cancellationToken) diff --git a/src/NServiceBus.Transport.RabbitMQ/Configuration/RabbitMQTransportSettingsExtensions.cs b/src/NServiceBus.Transport.RabbitMQ/Configuration/RabbitMQTransportSettingsExtensions.cs index 4f1dc4341..a83e5e0d3 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Configuration/RabbitMQTransportSettingsExtensions.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Configuration/RabbitMQTransportSettingsExtensions.cs @@ -96,6 +96,36 @@ public static TransportExtensions ConnectionString(this Trans return transportExtensions; } + /// + /// The connection string to use when connecting to the broker management API. + /// + [PreObsolete("https://github.com/Particular/NServiceBus/issues/6811", + Message = "The configuration has been moved to RabbitMQTransport class.", + Note = "Should not be converted to an ObsoleteEx until API mismatch described in issue is resolved.")] + public static TransportExtensions ManagementConnectionString(this TransportExtensions transportExtensions, string connectionString) + { + ArgumentNullException.ThrowIfNull(transportExtensions); + ArgumentException.ThrowIfNullOrWhiteSpace(connectionString); + + transportExtensions.Transport.LegacyManagementApiConnectionString = connectionString; + return transportExtensions; + } + + /// + /// The connection string to use when connecting to the broker management API. + /// + [PreObsolete("https://github.com/Particular/NServiceBus/issues/6811", + Message = "The configuration has been moved to RabbitMQTransport class.", + Note = "Should not be converted to an ObsoleteEx until API mismatch described in issue is resolved.")] + public static TransportExtensions ManagementConnectionString(this TransportExtensions transportExtensions, Func getConnectionString) + { + ArgumentNullException.ThrowIfNull(transportExtensions); + ArgumentNullException.ThrowIfNull(getConnectionString); + + transportExtensions.Transport.LegacyManagementApiConnectionString = getConnectionString(); + return transportExtensions; + } + /// /// Allows the user to control how the message ID is determined. Mostly useful when consuming native messages from non-NServiceBus endpoints. /// @@ -217,6 +247,8 @@ public static TransportExtensions SetClientCertificate(this T return transportExtensions; } + //public static TransportExtensions SetManagementClient(this TransportExtensions) + /// /// Sets the interval for heartbeats between the endpoint and the broker. /// diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index 6cf64f7a1..15caa264a 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -23,6 +23,8 @@ public class RabbitMQTransport : TransportDefinition Func messageIdStrategy = MessageConverter.DefaultMessageIdStrategy; PrefetchCountCalculation prefetchCountCalculation = maxConcurrency => 3 * maxConcurrency; TimeSpan timeToWaitBeforeTriggeringCircuitBreaker = TimeSpan.FromMinutes(2); + X509Certificate2Collection certCollection = null; + X509Certificate2Collection ManagementCertCollection = null; readonly List<(string hostName, int port, bool useTls)> additionalClusterNodes = []; @@ -42,6 +44,7 @@ public RabbitMQTransport(RoutingTopology routingTopology, string connectionStrin RoutingTopology = routingTopology.Create(); ConnectionConfiguration = ConnectionConfiguration.Create(connectionString); + ManagementConnectionConfiguration = ConnectionConfiguration; } /// @@ -61,10 +64,13 @@ public RabbitMQTransport(RoutingTopology routingTopology, string connectionStrin RoutingTopology = routingTopology.Create(); ConnectionConfiguration = ConnectionConfiguration.Create(connectionString); + ManagementConnectionConfiguration = ConnectionConfiguration; } internal ConnectionConfiguration ConnectionConfiguration { get; set; } + internal ConnectionConfiguration ManagementConnectionConfiguration { get; set; } + internal IRoutingTopology RoutingTopology { get; set; } /// @@ -128,6 +134,11 @@ public PrefetchCountCalculation PrefetchCountCalculation /// public X509Certificate2 ClientCertificate { get; set; } + /// + /// The certificate to use for client authentication when connecting to the broker management API via TLS. + /// + public X509Certificate2 ManagementClientCertificate { get; set; } + /// /// Should the client validate the broker certificate when connecting via TLS. /// @@ -200,13 +211,7 @@ public void AddClusterNode(string hostName, int port, bool useTls) public override async Task Initialize(HostSettings hostSettings, ReceiveSettings[] receivers, string[] sendingAddresses, CancellationToken cancellationToken = default) { ValidateAndApplyLegacyConfiguration(); - - X509Certificate2Collection certCollection = null; - - if (ClientCertificate != null) - { - certCollection = new X509Certificate2Collection(ClientCertificate); - } + ValidateAndApplyCertCollections(); var connectionFactory = new ConnectionFactory( hostSettings.Name, @@ -219,6 +224,7 @@ public override async Task Initialize(HostSettings host additionalClusterNodes ); + // var managementClient = new ManagementClient(ManagementConnectionConfiguration, ManagementCertCollection); var managementClientFactory = DoNotUseManagementClient ? null : new ManagementClientFactory(ConnectionConfiguration); var brokerVerifier = new BrokerVerifier(connectionFactory, managementClientFactory); await brokerVerifier.Initialize(cancellationToken).ConfigureAwait(false); @@ -251,6 +257,15 @@ public override async Task Initialize(HostSettings host return infra; } + void ValidateAndApplyCertCollections() + { + certCollection ??= ClientCertificate != null + ? new X509Certificate2Collection(ClientCertificate) : null; + + ManagementCertCollection = ManagementClientCertificate != null + ? new X509Certificate2Collection(ManagementClientCertificate) : certCollection; + } + /// public override IReadOnlyCollection GetSupportedTransactionModes() => new[] { TransportTransactionMode.ReceiveOnly }; @@ -258,6 +273,8 @@ public override async Task Initialize(HostSettings host internal string LegacyApiConnectionString { get; set; } + internal string LegacyManagementApiConnectionString { get; set; } + internal Func TopologyFactory { get; set; } internal bool UseDurableExchangesAndQueues { get; set; } = true; @@ -276,19 +293,31 @@ internal void ValidateAndApplyLegacyConfiguration() return; } + VaildateTopologyFactory(); + ValidateConnectionString(); + + RoutingTopology = TopologyFactory(UseDurableExchangesAndQueues); + ConnectionConfiguration = ConnectionConfiguration.Create(LegacyApiConnectionString); + + ManagementConnectionConfiguration = !string.IsNullOrEmpty(LegacyManagementApiConnectionString) + ? ConnectionConfiguration.Create(LegacyManagementApiConnectionString) + : ConnectionConfiguration; + } + + void VaildateTopologyFactory() + { if (TopologyFactory == null) { throw new Exception("A routing topology must be configured with one of the 'EndpointConfiguration.UseTransport().UseXXXXRoutingTopology()` methods. Most new projects should use the Conventional routing topology."); } + } - RoutingTopology = TopologyFactory(UseDurableExchangesAndQueues); - + void ValidateConnectionString() + { if (string.IsNullOrEmpty(LegacyApiConnectionString)) { throw new Exception("A connection string must be configured with 'EndpointConfiguration.UseTransport().ConnectionString()` method."); } - - ConnectionConfiguration = ConnectionConfiguration.Create(LegacyApiConnectionString); } } } \ No newline at end of file From c329db9c72ac107e45780e28191a332cdc5ab27c Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Wed, 4 Dec 2024 19:12:46 -0800 Subject: [PATCH 24/46] Add management connection string API --- .../APIApprovals.Approve.approved.txt | 6 ++- .../ManagementClient/ManagementClient.cs | 2 +- .../Configuration/ConnectionConfiguration.cs | 13 +++-- .../RabbitMQTransport.cs | 48 +++++++++++++++---- 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.Transport.RabbitMQ.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt index 272da9d1d..53d8d93bc 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -16,8 +16,8 @@ namespace NServiceBus } public class RabbitMQTransport : NServiceBus.Transport.TransportDefinition { - public RabbitMQTransport(NServiceBus.RoutingTopology routingTopology, string connectionString) { } - public RabbitMQTransport(NServiceBus.RoutingTopology routingTopology, string connectionString, bool enableDelayedDelivery) { } + public RabbitMQTransport(NServiceBus.RoutingTopology routingTopology, string connectionString, string managementConnectionString = null) { } + public RabbitMQTransport(NServiceBus.RoutingTopology routingTopology, string connectionString, bool enableDelayedDelivery, string managementConnectionString = null) { } public System.Security.Cryptography.X509Certificates.X509Certificate2 ClientCertificate { get; set; } public bool DoNotUseManagementClient { get; set; } public System.TimeSpan HeartbeatInterval { get; set; } @@ -42,6 +42,8 @@ namespace NServiceBus public static NServiceBus.TransportExtensions CustomMessageIdStrategy(this NServiceBus.TransportExtensions transportExtensions, System.Func customIdStrategy) { } public static NServiceBus.TransportExtensions DisableDurableExchangesAndQueues(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.TransportExtensions DisableRemoteCertificateValidation(this NServiceBus.TransportExtensions transportExtensions) { } + public static NServiceBus.TransportExtensions ManagementConnectionString(this NServiceBus.TransportExtensions transportExtensions, System.Func getConnectionString) { } + public static NServiceBus.TransportExtensions ManagementConnectionString(this NServiceBus.TransportExtensions transportExtensions, string connectionString) { } public static NServiceBus.TransportExtensions PrefetchCount(this NServiceBus.TransportExtensions transportExtensions, ushort prefetchCount) { } public static NServiceBus.TransportExtensions PrefetchMultiplier(this NServiceBus.TransportExtensions transportExtensions, int prefetchMultiplier) { } public static NServiceBus.TransportExtensions SetClientCertificate(this NServiceBus.TransportExtensions transportExtensions, System.Security.Cryptography.X509Certificates.X509Certificate2 clientCertificate) { } diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs index 9d7174d65..22ad92cb6 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs @@ -38,7 +38,7 @@ public ManagementClient(ConnectionConfiguration connectionConfiguration, X509Cer { Scheme = connectionConfiguration.UseTls ? "https" : "http", Host = connectionConfiguration.Host, - Port = 15672 // TODO: fallback to default only if specific details aren't given in config + Port = connectionConfiguration.Port, }; httpClient = new HttpClient(handler) { BaseAddress = uriBuilder.Uri }; diff --git a/src/NServiceBus.Transport.RabbitMQ/Configuration/ConnectionConfiguration.cs b/src/NServiceBus.Transport.RabbitMQ/Configuration/ConnectionConfiguration.cs index 3eca08219..2e4c3a8fe 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Configuration/ConnectionConfiguration.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Configuration/ConnectionConfiguration.cs @@ -9,8 +9,10 @@ class ConnectionConfiguration { const bool defaultUseTls = false; - const int defaultPort = 5672; - const int defaultTlsPort = 5671; + const int defaultBrokerPort = 5672; + const int defaultBrokerTlsPort = 5671; + const int defaultManagementPort = 15672; + const int defaultManagementTlsPort = 15671; const string defaultVirtualHost = "/"; const string defaultUserName = "guest"; const string defaultPassword = "guest"; @@ -43,7 +45,7 @@ class ConnectionConfiguration UseTls = useTls; } - public static ConnectionConfiguration Create(string connectionString) + public static ConnectionConfiguration Create(string connectionString, bool isManagementConnection = false) { Dictionary dictionary; var invalidOptionsMessage = new StringBuilder(); @@ -59,7 +61,10 @@ public static ConnectionConfiguration Create(string connectionString) var host = GetValue(dictionary, "host", string.Empty); var useTls = GetValue(dictionary, "useTls", bool.TryParse, defaultUseTls, invalidOptionsMessage); - var port = GetValue(dictionary, "port", int.TryParse, useTls ? defaultTlsPort : defaultPort, invalidOptionsMessage); + var port = GetValue(dictionary, "port", int.TryParse, useTls ? + (isManagementConnection ? defaultManagementTlsPort : defaultBrokerTlsPort) : + (isManagementConnection ? defaultManagementPort : defaultBrokerPort), + invalidOptionsMessage); var virtualHost = GetValue(dictionary, "virtualHost", defaultVirtualHost); var userName = GetValue(dictionary, "userName", defaultUserName); var password = GetValue(dictionary, "password", defaultPassword); diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index 15caa264a..8a0d9f322 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; + using System.Text; using System.Threading; using System.Threading.Tasks; using NServiceBus.Transport.RabbitMQ.Administration; @@ -33,7 +34,8 @@ public class RabbitMQTransport : TransportDefinition /// /// The routing topology to use. /// The connection string to use when connecting to the broker. - public RabbitMQTransport(RoutingTopology routingTopology, string connectionString) + /// The connection string to use when connecting to the management API + public RabbitMQTransport(RoutingTopology routingTopology, string connectionString, string managementConnectionString = null) : base(TransportTransactionMode.ReceiveOnly, supportsDelayedDelivery: true, supportsPublishSubscribe: true, @@ -44,7 +46,10 @@ public RabbitMQTransport(RoutingTopology routingTopology, string connectionStrin RoutingTopology = routingTopology.Create(); ConnectionConfiguration = ConnectionConfiguration.Create(connectionString); - ManagementConnectionConfiguration = ConnectionConfiguration; + + ManagementConnectionConfiguration = string.IsNullOrEmpty(managementConnectionString) ? + ConnectionConfiguration.Create(BuildManagementConnectionString(ConnectionConfiguration), isManagementConnection: true) : + ConnectionConfiguration.Create(managementConnectionString, isManagementConnection: true); } /// @@ -52,8 +57,9 @@ public RabbitMQTransport(RoutingTopology routingTopology, string connectionStrin /// /// The routing topology to use. /// The connection string to use when connecting to the broker. + /// The connection string to use when connecting to the management API /// Should the delayed delivery infrastructure be created by the endpoint - public RabbitMQTransport(RoutingTopology routingTopology, string connectionString, bool enableDelayedDelivery) + public RabbitMQTransport(RoutingTopology routingTopology, string connectionString, bool enableDelayedDelivery, string managementConnectionString = null) : base(TransportTransactionMode.ReceiveOnly, supportsDelayedDelivery: enableDelayedDelivery, supportsPublishSubscribe: true, @@ -64,7 +70,9 @@ public RabbitMQTransport(RoutingTopology routingTopology, string connectionStrin RoutingTopology = routingTopology.Create(); ConnectionConfiguration = ConnectionConfiguration.Create(connectionString); - ManagementConnectionConfiguration = ConnectionConfiguration; + ManagementConnectionConfiguration = string.IsNullOrEmpty(managementConnectionString) ? + ConnectionConfiguration.Create(BuildManagementConnectionString(ConnectionConfiguration), isManagementConnection: true) : + ConnectionConfiguration.Create(managementConnectionString, isManagementConnection: true); } internal ConnectionConfiguration ConnectionConfiguration { get; set; } @@ -224,8 +232,13 @@ public override async Task Initialize(HostSettings host additionalClusterNodes ); - // var managementClient = new ManagementClient(ManagementConnectionConfiguration, ManagementCertCollection); - var managementClientFactory = DoNotUseManagementClient ? null : new ManagementClientFactory(ConnectionConfiguration); + // Uses the legacy Management API connection string or default to the RabbitMQ broker connection credentials + if (!string.IsNullOrEmpty(LegacyManagementApiConnectionString)) + { + ManagementConnectionConfiguration = ConnectionConfiguration.Create(LegacyManagementApiConnectionString, isManagementConnection: true); + } + + var managementClientFactory = DoNotUseManagementClient ? null : new ManagementClientFactory(ManagementConnectionConfiguration); var brokerVerifier = new BrokerVerifier(connectionFactory, managementClientFactory); await brokerVerifier.Initialize(cancellationToken).ConfigureAwait(false); @@ -299,9 +312,10 @@ internal void ValidateAndApplyLegacyConfiguration() RoutingTopology = TopologyFactory(UseDurableExchangesAndQueues); ConnectionConfiguration = ConnectionConfiguration.Create(LegacyApiConnectionString); + // Uses the legacy management API connection string or build the string from the legacy broker connection configuration ManagementConnectionConfiguration = !string.IsNullOrEmpty(LegacyManagementApiConnectionString) - ? ConnectionConfiguration.Create(LegacyManagementApiConnectionString) - : ConnectionConfiguration; + ? ConnectionConfiguration.Create(LegacyManagementApiConnectionString, isManagementConnection: true) + : ConnectionConfiguration.Create(BuildManagementConnectionString(ConnectionConfiguration), isManagementConnection: true); } void VaildateTopologyFactory() @@ -318,6 +332,24 @@ void ValidateConnectionString() { throw new Exception("A connection string must be configured with 'EndpointConfiguration.UseTransport().ConnectionString()` method."); } + + if (string.IsNullOrEmpty(LegacyManagementApiConnectionString)) + { + throw new Exception("A management API connection string must be configured with 'EndpointConfiguration.UseTransport().ManagementConnectionString()` method."); + } + } + + string BuildManagementConnectionString(ConnectionConfiguration connectionConfiguration) + { + var managementConnectionString = new StringBuilder(); + + _ = managementConnectionString.Append($"{nameof(connectionConfiguration.VirtualHost)}={connectionConfiguration.VirtualHost};"); + _ = managementConnectionString.Append($"{nameof(connectionConfiguration.Host)}={connectionConfiguration.Host};"); + _ = managementConnectionString.Append($"{nameof(connectionConfiguration.UserName)}={connectionConfiguration.UserName};"); + _ = managementConnectionString.Append($"{nameof(connectionConfiguration.Password)}={connectionConfiguration.Password};"); + _ = managementConnectionString.Append($"{nameof(connectionConfiguration.UseTls)}={connectionConfiguration.UseTls}"); + + return managementConnectionString.ToString(); } } } \ No newline at end of file From 43e6c23ae5826990a93502155848d262c0c671aa Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Wed, 4 Dec 2024 19:14:05 -0800 Subject: [PATCH 25/46] Remove X509 management certificate --- .../RabbitMQTransportSettingsExtensions.cs | 2 -- .../RabbitMQTransport.cs | 18 +++--------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Configuration/RabbitMQTransportSettingsExtensions.cs b/src/NServiceBus.Transport.RabbitMQ/Configuration/RabbitMQTransportSettingsExtensions.cs index a83e5e0d3..6227c20f9 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Configuration/RabbitMQTransportSettingsExtensions.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Configuration/RabbitMQTransportSettingsExtensions.cs @@ -247,8 +247,6 @@ public static TransportExtensions SetClientCertificate(this T return transportExtensions; } - //public static TransportExtensions SetManagementClient(this TransportExtensions) - /// /// Sets the interval for heartbeats between the endpoint and the broker. /// diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index 8a0d9f322..b6c66d7e7 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -25,7 +25,6 @@ public class RabbitMQTransport : TransportDefinition PrefetchCountCalculation prefetchCountCalculation = maxConcurrency => 3 * maxConcurrency; TimeSpan timeToWaitBeforeTriggeringCircuitBreaker = TimeSpan.FromMinutes(2); X509Certificate2Collection certCollection = null; - X509Certificate2Collection ManagementCertCollection = null; readonly List<(string hostName, int port, bool useTls)> additionalClusterNodes = []; @@ -109,8 +108,8 @@ public TimeSpan TimeToWaitBeforeTriggeringCircuitBreaker } /// - /// Gets or sets the action that allows customization of the native - /// just before it is dispatched to the rabbitmq client. + /// Gets or sets the action that allows customization of the native + /// just before it is dispatched to the rabbitmq client. /// /// /// @@ -142,11 +141,6 @@ public PrefetchCountCalculation PrefetchCountCalculation /// public X509Certificate2 ClientCertificate { get; set; } - /// - /// The certificate to use for client authentication when connecting to the broker management API via TLS. - /// - public X509Certificate2 ManagementClientCertificate { get; set; } - /// /// Should the client validate the broker certificate when connecting via TLS. /// @@ -270,15 +264,9 @@ public override async Task Initialize(HostSettings host return infra; } - void ValidateAndApplyCertCollections() - { - certCollection ??= ClientCertificate != null + void ValidateAndApplyCertCollections() => certCollection ??= ClientCertificate != null ? new X509Certificate2Collection(ClientCertificate) : null; - ManagementCertCollection = ManagementClientCertificate != null - ? new X509Certificate2Collection(ManagementClientCertificate) : certCollection; - } - /// public override IReadOnlyCollection GetSupportedTransactionModes() => new[] { TransportTransactionMode.ReceiveOnly }; From 65df78720fb5188aabd29bc58c29b2781608d3c3 Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Wed, 4 Dec 2024 19:15:13 -0800 Subject: [PATCH 26/46] Add and update ConnectionConfiguration tests --- .../ConnectionConfigurationTests.cs | 198 +++++++++++++++++- 1 file changed, 191 insertions(+), 7 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs index 3e8cf336e..ca975afb8 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs @@ -1,15 +1,32 @@ namespace NServiceBus.Transport.RabbitMQ.Tests.ConnectionString { using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using global::RabbitMQ.Client.Exceptions; using NUnit.Framework; using RabbitMQ; [TestFixture] - public class ConnectionConfigurationTests + class ConnectionConfigurationTests { const string connectionString = "virtualHost=Copa;username=Copa;host=192.168.1.1:1234;password=abc_xyz;port=12345;useTls=true"; + protected string ReceiverQueue => GetTestQueueName("testreceiver"); + protected string ErrorQueue => GetTestQueueName("error"); + protected string GetTestQueueName(string queueName) => $"{queueName}-{queueType}"; + protected IList AdditionalReceiverQueues = []; + protected string[] SendingAddresses => [.. AdditionalReceiverQueues, ErrorQueue]; + protected QueueType queueType = QueueType.Quorum; + + protected HostSettings HostSettings => new(ReceiverQueue, ReceiverQueue, new StartupDiagnosticEntries(), (_, _, _) => { }, true); + protected ReceiveSettings[] ReceiveSettings => + [ + new ReceiveSettings( ReceiverQueue, new QueueAddress(ReceiverQueue), true, true, ErrorQueue) + ]; + + readonly ConnectionConfiguration brokerDefaults = ConnectionConfiguration.Create("host=localhost"); + readonly ConnectionConfiguration managementDefaults = ConnectionConfiguration.Create("host=localhost", isManagementConnection: true); - ConnectionConfiguration defaults = ConnectionConfiguration.Create("host=localhost"); [Test] public void Should_correctly_parse_full_connection_string() @@ -164,31 +181,198 @@ public void Should_list_all_invalid_options() [Test] public void Should_set_default_port() { - Assert.That(defaults.Port, Is.EqualTo(5672)); + Assert.Multiple(() => + { + Assert.That(brokerDefaults.Port, Is.EqualTo(5672)); + Assert.That(managementDefaults.Port, Is.EqualTo(15672)); + }); } [Test] public void Should_set_default_virtual_host() { - Assert.That(defaults.VirtualHost, Is.EqualTo("/")); + Assert.Multiple(() => + { + Assert.That(brokerDefaults.VirtualHost, Is.EqualTo("/")); + Assert.That(managementDefaults.VirtualHost, Is.EqualTo("/")); + }); } [Test] public void Should_set_default_username() { - Assert.That(defaults.UserName, Is.EqualTo("guest")); + Assert.Multiple(() => + { + Assert.That(brokerDefaults.UserName, Is.EqualTo("guest")); + Assert.That(managementDefaults.UserName, Is.EqualTo("guest")); + }); } [Test] public void Should_set_default_password() { - Assert.That(defaults.Password, Is.EqualTo("guest")); + Assert.Multiple(() => + { + Assert.That(brokerDefaults.Password, Is.EqualTo("guest")); + Assert.That(managementDefaults.Password, Is.EqualTo("guest")); + }); } [Test] public void Should_set_default_use_tls() { - Assert.That(defaults.UseTls, Is.EqualTo(false)); + Assert.Multiple(() => + { + Assert.That(brokerDefaults.UseTls, Is.EqualTo(false)); + Assert.That(managementDefaults.UseTls, Is.EqualTo(false)); + }); + } + + [Test] + public void Should_configure_broker_and_management_connection_configurations_with_single_connection_string() + { + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "virtualHost=/;host=localhost;username=guest;password=guest;port=5672;useTls=false"); + + Assert.Multiple(() => + { + Assert.That(transport.ConnectionConfiguration.VirtualHost, Is.EqualTo("/")); + Assert.That(transport.ConnectionConfiguration.Host, Is.EqualTo("localhost")); + Assert.That(transport.ConnectionConfiguration.UserName, Is.EqualTo("guest")); + Assert.That(transport.ConnectionConfiguration.Password, Is.EqualTo("guest")); + Assert.That(transport.ConnectionConfiguration.Port, Is.EqualTo(5672)); + Assert.That(transport.ConnectionConfiguration.UseTls, Is.EqualTo(false)); + + Assert.That(transport.ManagementConnectionConfiguration.VirtualHost, Is.EqualTo("/")); + Assert.That(transport.ManagementConnectionConfiguration.Host, Is.EqualTo("localhost")); + Assert.That(transport.ManagementConnectionConfiguration.UserName, Is.EqualTo("guest")); + Assert.That(transport.ManagementConnectionConfiguration.Password, Is.EqualTo("guest")); + Assert.That(transport.ManagementConnectionConfiguration.Port, Is.EqualTo(15672)); // This should be set to the default management port + Assert.That(transport.ManagementConnectionConfiguration.UseTls, Is.EqualTo(false)); + }); + } + + [Test] + public void Should_configure_broker_and_management_connection_configurations_with_respective_connection_strings() + { + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "virtualHost=/;host=localhost;username=guest;password=guest;port=5672;useTls=false", connectionString); + + Assert.Multiple(() => + { + Assert.That(transport.ConnectionConfiguration.VirtualHost, Is.EqualTo("/")); + Assert.That(transport.ConnectionConfiguration.Host, Is.EqualTo("localhost")); + Assert.That(transport.ConnectionConfiguration.UserName, Is.EqualTo("guest")); + Assert.That(transport.ConnectionConfiguration.Password, Is.EqualTo("guest")); + Assert.That(transport.ConnectionConfiguration.Port, Is.EqualTo(5672)); + Assert.That(transport.ConnectionConfiguration.UseTls, Is.EqualTo(false)); + + Assert.That(transport.ManagementConnectionConfiguration.VirtualHost, Is.EqualTo("Copa")); + Assert.That(transport.ManagementConnectionConfiguration.Host, Is.EqualTo("192.168.1.1")); + Assert.That(transport.ManagementConnectionConfiguration.UserName, Is.EqualTo("Copa")); + Assert.That(transport.ManagementConnectionConfiguration.Password, Is.EqualTo("abc_xyz")); + Assert.That(transport.ManagementConnectionConfiguration.Port, Is.EqualTo(1234)); + Assert.That(transport.ManagementConnectionConfiguration.UseTls, Is.EqualTo(true)); + }); + } + + [Test] + public void Should_throw_on_invalid_management_connection_string() + { + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "virtualHost=/;username=guest;host=localhost;password=guest;port=5672;useTls=false", "host=127.0.0.1;username=Copa"); + + var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + + Assert.That(exception.Message, Does.Contain("Could not access RabbitMQ Management API")); + } + + [Test] + public void Should_throw_on_invalid_broker_connection_string() + { + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "host=127.0.0.1;username=Copa", "virtualHost=/;username=guest;host=localhost;password=guest;port=15672;useTls=false"); + + var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + + Assert.That(exception.Message, Does.Contain("None of the specified endpoints were reachable")); + } + + [Test] + public void Should_throw_on_invalid_legacy_management_connection_string() + { + // Create transport in legacy mode + var transport = new RabbitMQTransport + { + TopologyFactory = durable => new ConventionalRoutingTopology(durable, queueType), + LegacyApiConnectionString = "virtualHost=/;username=guest;host=localhost;password=guest;port=5672;useTls=false", + LegacyManagementApiConnectionString = "host=127.0.0.1;username=Copa" + }; + + var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + + Assert.That(exception.Message, Does.Contain("Could not access RabbitMQ Management API")); + } + + [Test] + public void Should_throw_on_invalid_legacy_broker_connection_string() + { + // Create transport in legacy mode + var transport = new RabbitMQTransport + { + TopologyFactory = durable => new ConventionalRoutingTopology(durable, queueType), + LegacyApiConnectionString = "virtualHost=/;username=Copa;host=localhost;password=guest;port=5672;useTls=false", + LegacyManagementApiConnectionString = "host=127.0.0.1;username=guest" + }; + + var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + + Assert.That(exception.Message, Does.Contain("None of the specified endpoints were reachable")); + + } + + [Test] + public async Task Should_connect_to_management_api_with_broker_credentials() + { + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "virtualHost=/;username=guest;host=localhost;password=guest;port=5672;useTls=false"); + + var infra = await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false); + + Assert.Multiple(() => + { + Assert.That(transport.ConnectionConfiguration.Port, Is.EqualTo(5672)); + Assert.That(transport.ManagementConnectionConfiguration.Port, Is.EqualTo(15672)); + }); + + } + + [Test] + public async Task Should_set_default_port_values_for_broker_and_management_connections() + { + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "host=localhost"); + + _ = await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false); + + Assert.Multiple(() => + { + Assert.That(transport.ConnectionConfiguration.Port, Is.EqualTo(5672)); + Assert.That(transport.ManagementConnectionConfiguration.Port, Is.EqualTo(15672)); + }); + + } + + [Test] + public async Task Should_not_throw_when_DoNotUseManagementClient_is_enabled_and_management_connection_is_invalid() + { + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "host=localhost", "host=Copa") + { + DoNotUseManagementClient = true + }; + + _ = await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false); + + Assert.Multiple(() => + { + Assert.That(transport.ConnectionConfiguration.Port, Is.EqualTo(5672)); + Assert.That(transport.ManagementConnectionConfiguration.Port, Is.EqualTo(15672)); + }); + } } } From f9d7b63b90a9ad3f038181c5913969fbf4f529b0 Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Wed, 4 Dec 2024 19:16:45 -0800 Subject: [PATCH 27/46] Update ManagementConnection tests to use a separate managementConnectionConfiguration --- .../When_connecting_to_the_rabbitmq_management_api.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs index 7436e8a21..027a6745e 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Tests.Connection.ManagementConnection +namespace NServiceBus.Transport.RabbitMQ.Tests.ManagementConnection { using System; using System.Collections.Generic; @@ -19,6 +19,7 @@ class When_connecting_to_the_rabbitmq_management_api { static readonly string connectionString = Environment.GetEnvironmentVariable("RabbitMQTransport_ConnectionString") ?? "host=localhost"; readonly ConnectionConfiguration connectionConfiguration = ConnectionConfiguration.Create(connectionString); + readonly ConnectionConfiguration managementConnectionConfiguration = ConnectionConfiguration.Create(connectionString, isManagementConnection: true); protected QueueType queueType = QueueType.Quorum; protected string ReceiverQueue => GetTestQueueName("ManagementAPITestQueue"); protected string GetTestQueueName(string queueName) => $"{queueName}-{queueType}"; @@ -44,7 +45,7 @@ public async Task SetUp() [Test] public async Task GetQueue_Should_Return_Queue_Information_When_Exists() { - var client = new ManagementClient(connectionConfiguration); + var client = new ManagementClient(managementConnectionConfiguration); var response = await client.GetQueue(ReceiverQueue); @@ -59,7 +60,7 @@ public async Task GetQueue_Should_Return_Queue_Information_When_Exists() [Test] public async Task GetOverview_Should_Return_Broker_Information_When_Exists() { - var client = new ManagementClient(connectionConfiguration); + var client = new ManagementClient(managementConnectionConfiguration); var response = await client.GetOverview(); From 39f88f16c019ff1f1a7392a107e1084d27b72a66 Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Fri, 6 Dec 2024 14:23:12 -0800 Subject: [PATCH 28/46] Refactor null checks --- .../ManagementClient/ManagementClient.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs index 22ad92cb6..64fcda10c 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs @@ -20,10 +20,7 @@ class ManagementClient : IManagementClient public ManagementClient(ConnectionConfiguration connectionConfiguration, X509Certificate2Collection? managementCertCollection = null) { - if (connectionConfiguration == null) - { - throw new ArgumentNullException(nameof(connectionConfiguration)); - } + ArgumentNullException.ThrowIfNull(connectionConfiguration, nameof(connectionConfiguration)); virtualHost = connectionConfiguration.VirtualHost; escapedVirtualHost = Uri.EscapeDataString(virtualHost); @@ -111,10 +108,7 @@ void ConfigureSsl(HttpClientHandler handler, X509Certificate2Collection? managem public async Task CreatePolicy(Policy policy, CancellationToken cancellationToken = default) { - if (policy.Name == null) - { - throw new ArgumentNullException(nameof(policy.Name)); - } + ArgumentNullException.ThrowIfNull(policy, nameof(policy)); policy.VirtualHost = Uri.EscapeDataString(virtualHost); From af8e042c9189577a6cc23c3bae5c4cdc1ae24934 Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Fri, 6 Dec 2024 14:26:55 -0800 Subject: [PATCH 29/46] Check if management client has been disabled --- src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index b6c66d7e7..ea127ab28 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -321,7 +321,7 @@ void ValidateConnectionString() throw new Exception("A connection string must be configured with 'EndpointConfiguration.UseTransport().ConnectionString()` method."); } - if (string.IsNullOrEmpty(LegacyManagementApiConnectionString)) + if (!DoNotUseManagementClient && string.IsNullOrEmpty(LegacyManagementApiConnectionString)) { throw new Exception("A management API connection string must be configured with 'EndpointConfiguration.UseTransport().ManagementConnectionString()` method."); } From 5753e11db9e4edad6564ea7938a10e814a2ec313 Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Fri, 6 Dec 2024 15:59:43 -0800 Subject: [PATCH 30/46] Remove Cert checks --- .../ManagementClient/ManagementClient.cs | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs index 64fcda10c..9e363fff4 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs @@ -6,7 +6,6 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; -using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -18,19 +17,13 @@ class ManagementClient : IManagementClient readonly string virtualHost; readonly string escapedVirtualHost; - public ManagementClient(ConnectionConfiguration connectionConfiguration, X509Certificate2Collection? managementCertCollection = null) + public ManagementClient(ConnectionConfiguration connectionConfiguration) { ArgumentNullException.ThrowIfNull(connectionConfiguration, nameof(connectionConfiguration)); virtualHost = connectionConfiguration.VirtualHost; escapedVirtualHost = Uri.EscapeDataString(virtualHost); - var handler = new HttpClientHandler(); - if (connectionConfiguration.UseTls) - { - ConfigureSsl(handler, managementCertCollection); - } - var uriBuilder = new UriBuilder { Scheme = connectionConfiguration.UseTls ? "https" : "http", @@ -38,21 +31,12 @@ public ManagementClient(ConnectionConfiguration connectionConfiguration, X509Cer Port = connectionConfiguration.Port, }; - httpClient = new HttpClient(handler) { BaseAddress = uriBuilder.Uri }; + httpClient = new HttpClient { BaseAddress = uriBuilder.Uri }; httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{connectionConfiguration.UserName}:{connectionConfiguration.Password}"))); } - void ConfigureSsl(HttpClientHandler handler, X509Certificate2Collection? managementCertCollection) - { - if (managementCertCollection != null) - { - handler.ClientCertificates.AddRange(managementCertCollection); - } - handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls13; - } - public async Task> GetQueue(string queueName, CancellationToken cancellationToken = default) { Queue? value = null; From 68bacf12f27a49c100ed97930f0ad8a50f114dcb Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Fri, 6 Dec 2024 16:00:13 -0800 Subject: [PATCH 31/46] Update rabbitmq setup action --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f0f1138e..6b607d0c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: creds: ${{ secrets.AZURE_ACI_CREDENTIALS }} enable-AzPSSession: true - name: Setup RabbitMQ - uses: Particular/setup-rabbitmq-action@v1.7.0 + uses: Particular/setup-rabbitmq-action@v1.7.1 with: connection-string-name: RabbitMQTransport_ConnectionString tag: RabbitMQTransport From 3251ca461de1c645916b336aa285731809a7f357 Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Fri, 6 Dec 2024 16:00:38 -0800 Subject: [PATCH 32/46] Refactor ConnectionConfiguration --- .../ConnectionConfigurationTests.cs | 30 ++++++++-------- .../RabbitMqContext.cs | 2 +- .../Configuration/ConnectionConfiguration.cs | 12 +++++++ .../RabbitMQTransport.cs | 34 ++++++------------- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs index ca975afb8..2813f8d28 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs @@ -235,12 +235,12 @@ public void Should_configure_broker_and_management_connection_configurations_wit Assert.Multiple(() => { - Assert.That(transport.ConnectionConfiguration.VirtualHost, Is.EqualTo("/")); - Assert.That(transport.ConnectionConfiguration.Host, Is.EqualTo("localhost")); - Assert.That(transport.ConnectionConfiguration.UserName, Is.EqualTo("guest")); - Assert.That(transport.ConnectionConfiguration.Password, Is.EqualTo("guest")); - Assert.That(transport.ConnectionConfiguration.Port, Is.EqualTo(5672)); - Assert.That(transport.ConnectionConfiguration.UseTls, Is.EqualTo(false)); + Assert.That(transport.BrokerConnectionConfiguration.VirtualHost, Is.EqualTo("/")); + Assert.That(transport.BrokerConnectionConfiguration.Host, Is.EqualTo("localhost")); + Assert.That(transport.BrokerConnectionConfiguration.UserName, Is.EqualTo("guest")); + Assert.That(transport.BrokerConnectionConfiguration.Password, Is.EqualTo("guest")); + Assert.That(transport.BrokerConnectionConfiguration.Port, Is.EqualTo(5672)); + Assert.That(transport.BrokerConnectionConfiguration.UseTls, Is.EqualTo(false)); Assert.That(transport.ManagementConnectionConfiguration.VirtualHost, Is.EqualTo("/")); Assert.That(transport.ManagementConnectionConfiguration.Host, Is.EqualTo("localhost")); @@ -258,12 +258,12 @@ public void Should_configure_broker_and_management_connection_configurations_wit Assert.Multiple(() => { - Assert.That(transport.ConnectionConfiguration.VirtualHost, Is.EqualTo("/")); - Assert.That(transport.ConnectionConfiguration.Host, Is.EqualTo("localhost")); - Assert.That(transport.ConnectionConfiguration.UserName, Is.EqualTo("guest")); - Assert.That(transport.ConnectionConfiguration.Password, Is.EqualTo("guest")); - Assert.That(transport.ConnectionConfiguration.Port, Is.EqualTo(5672)); - Assert.That(transport.ConnectionConfiguration.UseTls, Is.EqualTo(false)); + Assert.That(transport.BrokerConnectionConfiguration.VirtualHost, Is.EqualTo("/")); + Assert.That(transport.BrokerConnectionConfiguration.Host, Is.EqualTo("localhost")); + Assert.That(transport.BrokerConnectionConfiguration.UserName, Is.EqualTo("guest")); + Assert.That(transport.BrokerConnectionConfiguration.Password, Is.EqualTo("guest")); + Assert.That(transport.BrokerConnectionConfiguration.Port, Is.EqualTo(5672)); + Assert.That(transport.BrokerConnectionConfiguration.UseTls, Is.EqualTo(false)); Assert.That(transport.ManagementConnectionConfiguration.VirtualHost, Is.EqualTo("Copa")); Assert.That(transport.ManagementConnectionConfiguration.Host, Is.EqualTo("192.168.1.1")); @@ -336,7 +336,7 @@ public async Task Should_connect_to_management_api_with_broker_credentials() Assert.Multiple(() => { - Assert.That(transport.ConnectionConfiguration.Port, Is.EqualTo(5672)); + Assert.That(transport.BrokerConnectionConfiguration.Port, Is.EqualTo(5672)); Assert.That(transport.ManagementConnectionConfiguration.Port, Is.EqualTo(15672)); }); @@ -351,7 +351,7 @@ public async Task Should_set_default_port_values_for_broker_and_management_conne Assert.Multiple(() => { - Assert.That(transport.ConnectionConfiguration.Port, Is.EqualTo(5672)); + Assert.That(transport.BrokerConnectionConfiguration.Port, Is.EqualTo(5672)); Assert.That(transport.ManagementConnectionConfiguration.Port, Is.EqualTo(15672)); }); @@ -369,7 +369,7 @@ public async Task Should_not_throw_when_DoNotUseManagementClient_is_enabled_and_ Assert.Multiple(() => { - Assert.That(transport.ConnectionConfiguration.Port, Is.EqualTo(5672)); + Assert.That(transport.BrokerConnectionConfiguration.Port, Is.EqualTo(5672)); Assert.That(transport.ManagementConnectionConfiguration.Port, Is.EqualTo(15672)); }); diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/RabbitMqContext.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/RabbitMqContext.cs index a2bc8be4c..d2a47527c 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/RabbitMqContext.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/RabbitMqContext.cs @@ -21,7 +21,7 @@ public virtual async Task SetUp() var useTls = connectionString.StartsWith("https", StringComparison.InvariantCultureIgnoreCase) || connectionString.StartsWith("amqps", StringComparison.InvariantCultureIgnoreCase); var transport = new RabbitMQTransport(RoutingTopology.Conventional(queueType), connectionString); - var connectionConfig = transport.ConnectionConfiguration; + var connectionConfig = transport.BrokerConnectionConfiguration; connectionFactory = new ConnectionFactory(ReceiverQueue, connectionConfig, null, true, false, transport.HeartbeatInterval, transport.NetworkRecoveryInterval, null); diff --git a/src/NServiceBus.Transport.RabbitMQ/Configuration/ConnectionConfiguration.cs b/src/NServiceBus.Transport.RabbitMQ/Configuration/ConnectionConfiguration.cs index 2e4c3a8fe..80fe920e8 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Configuration/ConnectionConfiguration.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Configuration/ConnectionConfiguration.cs @@ -77,6 +77,18 @@ public static ConnectionConfiguration Create(string connectionString, bool isMan return new ConnectionConfiguration(host, port, virtualHost, userName, password, useTls); } + public static ConnectionConfiguration ConvertToManagementConnection(ConnectionConfiguration brokerConnectionConfiguration) + { + var virtualHost = brokerConnectionConfiguration.VirtualHost; + var host = brokerConnectionConfiguration.Host; + var port = defaultManagementPort; + var useTls = brokerConnectionConfiguration.UseTls; + var userName = brokerConnectionConfiguration.UserName; + var password = brokerConnectionConfiguration.Password; + + return new ConnectionConfiguration(host, port, virtualHost, userName, password, useTls); + } + static Dictionary ParseAmqpConnectionString(string connectionString, StringBuilder invalidOptionsMessage) { var dictionary = new Dictionary(); diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index ea127ab28..b705def00 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; - using System.Text; using System.Threading; using System.Threading.Tasks; using NServiceBus.Transport.RabbitMQ.Administration; @@ -44,10 +43,10 @@ public RabbitMQTransport(RoutingTopology routingTopology, string connectionStrin ArgumentNullException.ThrowIfNull(connectionString); RoutingTopology = routingTopology.Create(); - ConnectionConfiguration = ConnectionConfiguration.Create(connectionString); + BrokerConnectionConfiguration = ConnectionConfiguration.Create(connectionString); ManagementConnectionConfiguration = string.IsNullOrEmpty(managementConnectionString) ? - ConnectionConfiguration.Create(BuildManagementConnectionString(ConnectionConfiguration), isManagementConnection: true) : + ConnectionConfiguration.ConvertToManagementConnection(BrokerConnectionConfiguration) : ConnectionConfiguration.Create(managementConnectionString, isManagementConnection: true); } @@ -68,13 +67,13 @@ public RabbitMQTransport(RoutingTopology routingTopology, string connectionStrin ArgumentNullException.ThrowIfNull(connectionString); RoutingTopology = routingTopology.Create(); - ConnectionConfiguration = ConnectionConfiguration.Create(connectionString); + BrokerConnectionConfiguration = ConnectionConfiguration.Create(connectionString); ManagementConnectionConfiguration = string.IsNullOrEmpty(managementConnectionString) ? - ConnectionConfiguration.Create(BuildManagementConnectionString(ConnectionConfiguration), isManagementConnection: true) : + ConnectionConfiguration.ConvertToManagementConnection(BrokerConnectionConfiguration) : ConnectionConfiguration.Create(managementConnectionString, isManagementConnection: true); } - internal ConnectionConfiguration ConnectionConfiguration { get; set; } + internal ConnectionConfiguration BrokerConnectionConfiguration { get; set; } internal ConnectionConfiguration ManagementConnectionConfiguration { get; set; } @@ -217,7 +216,7 @@ public override async Task Initialize(HostSettings host var connectionFactory = new ConnectionFactory( hostSettings.Name, - ConnectionConfiguration, + BrokerConnectionConfiguration, certCollection, !ValidateRemoteCertificate, UseExternalAuthMechanism, @@ -298,12 +297,12 @@ internal void ValidateAndApplyLegacyConfiguration() ValidateConnectionString(); RoutingTopology = TopologyFactory(UseDurableExchangesAndQueues); - ConnectionConfiguration = ConnectionConfiguration.Create(LegacyApiConnectionString); + BrokerConnectionConfiguration = ConnectionConfiguration.Create(LegacyApiConnectionString); // Uses the legacy management API connection string or build the string from the legacy broker connection configuration - ManagementConnectionConfiguration = !string.IsNullOrEmpty(LegacyManagementApiConnectionString) - ? ConnectionConfiguration.Create(LegacyManagementApiConnectionString, isManagementConnection: true) - : ConnectionConfiguration.Create(BuildManagementConnectionString(ConnectionConfiguration), isManagementConnection: true); + ManagementConnectionConfiguration = !string.IsNullOrEmpty(LegacyManagementApiConnectionString) ? + ConnectionConfiguration.Create(LegacyManagementApiConnectionString, isManagementConnection: true) : + ConnectionConfiguration.ConvertToManagementConnection(BrokerConnectionConfiguration); } void VaildateTopologyFactory() @@ -326,18 +325,5 @@ void ValidateConnectionString() throw new Exception("A management API connection string must be configured with 'EndpointConfiguration.UseTransport().ManagementConnectionString()` method."); } } - - string BuildManagementConnectionString(ConnectionConfiguration connectionConfiguration) - { - var managementConnectionString = new StringBuilder(); - - _ = managementConnectionString.Append($"{nameof(connectionConfiguration.VirtualHost)}={connectionConfiguration.VirtualHost};"); - _ = managementConnectionString.Append($"{nameof(connectionConfiguration.Host)}={connectionConfiguration.Host};"); - _ = managementConnectionString.Append($"{nameof(connectionConfiguration.UserName)}={connectionConfiguration.UserName};"); - _ = managementConnectionString.Append($"{nameof(connectionConfiguration.Password)}={connectionConfiguration.Password};"); - _ = managementConnectionString.Append($"{nameof(connectionConfiguration.UseTls)}={connectionConfiguration.UseTls}"); - - return managementConnectionString.ToString(); - } } } \ No newline at end of file From 549d3957c9bfce47a2c27c1ba7a08916b186afff Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Fri, 6 Dec 2024 20:39:12 -0800 Subject: [PATCH 33/46] Add a fake connection configuration for tests --- .../ConnectionConfigurationTests.cs | 150 ++++++++++++++---- 1 file changed, 117 insertions(+), 33 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs index 2813f8d28..4b61c2eb6 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs @@ -1,7 +1,11 @@ -namespace NServiceBus.Transport.RabbitMQ.Tests.ConnectionString +#nullable enable + +namespace NServiceBus.Transport.RabbitMQ.Tests.ConnectionString { using System; using System.Collections.Generic; + using System.Linq; + using System.Text; using System.Threading.Tasks; using global::RabbitMQ.Client.Exceptions; using NUnit.Framework; @@ -10,7 +14,9 @@ [TestFixture] class ConnectionConfigurationTests { - const string connectionString = "virtualHost=Copa;username=Copa;host=192.168.1.1:1234;password=abc_xyz;port=12345;useTls=true"; + const string FakeConnectionString = "virtualHost=Copa;username=Copa;host=192.168.1.1:1234;password=abc_xyz;port=12345;useTls=true"; + static string BrokerConnectionString = Environment.GetEnvironmentVariable("RabbitMQTransport_ConnectionString") ?? "host=localhost"; + static string ManagementConnectionString => CreateManagementConnectionString(BrokerConnectionString); protected string ReceiverQueue => GetTestQueueName("testreceiver"); protected string ErrorQueue => GetTestQueueName("error"); protected string GetTestQueueName(string queueName) => $"{queueName}-{queueType}"; @@ -19,19 +25,22 @@ class ConnectionConfigurationTests protected QueueType queueType = QueueType.Quorum; protected HostSettings HostSettings => new(ReceiverQueue, ReceiverQueue, new StartupDiagnosticEntries(), (_, _, _) => { }, true); - protected ReceiveSettings[] ReceiveSettings => - [ - new ReceiveSettings( ReceiverQueue, new QueueAddress(ReceiverQueue), true, true, ErrorQueue) - ]; + protected ReceiveSettings[] ReceiveSettings => [new ReceiveSettings(ReceiverQueue, new QueueAddress(ReceiverQueue), true, true, ErrorQueue)]; readonly ConnectionConfiguration brokerDefaults = ConnectionConfiguration.Create("host=localhost"); readonly ConnectionConfiguration managementDefaults = ConnectionConfiguration.Create("host=localhost", isManagementConnection: true); + static string CreateManagementConnectionString(string connectionString) + { + var parameters = connectionString.Split(';').Select(param => param.Split('=')).ToDictionary(parts => parts[0], parts => parts[1]); + parameters["port"] = "15672"; + return string.Join(";", parameters.Select(kv => $"{kv.Key}={kv.Value}")); + } [Test] public void Should_correctly_parse_full_connection_string() { - var connectionConfiguration = ConnectionConfiguration.Create(connectionString); + var connectionConfiguration = ConnectionConfiguration.Create(FakeConnectionString); Assert.Multiple(() => { @@ -254,7 +263,7 @@ public void Should_configure_broker_and_management_connection_configurations_wit [Test] public void Should_configure_broker_and_management_connection_configurations_with_respective_connection_strings() { - var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "virtualHost=/;host=localhost;username=guest;password=guest;port=5672;useTls=false", connectionString); + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "virtualHost=/;host=localhost;username=guest;password=guest;port=5672;useTls=false", FakeConnectionString); Assert.Multiple(() => { @@ -277,7 +286,9 @@ public void Should_configure_broker_and_management_connection_configurations_wit [Test] public void Should_throw_on_invalid_management_connection_string() { - var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "virtualHost=/;username=guest;host=localhost;password=guest;port=5672;useTls=false", "host=127.0.0.1;username=Copa"); + var invalidManagementConnection = new FakeConnectionConfiguration(host: "localhost", userName: "Copa"); + + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), BrokerConnectionString, invalidManagementConnection.ToConnectionString()); var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); @@ -287,7 +298,9 @@ public void Should_throw_on_invalid_management_connection_string() [Test] public void Should_throw_on_invalid_broker_connection_string() { - var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "host=127.0.0.1;username=Copa", "virtualHost=/;username=guest;host=localhost;password=guest;port=15672;useTls=false"); + var invalidBrokerConnection = new FakeConnectionConfiguration(host: "127.0.0.1", userName: "Copa"); + + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), invalidBrokerConnection.ToConnectionString(), ManagementConnectionString); var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); @@ -297,12 +310,14 @@ public void Should_throw_on_invalid_broker_connection_string() [Test] public void Should_throw_on_invalid_legacy_management_connection_string() { + var invalidManagementConnection = new FakeConnectionConfiguration(host: "127.0.0.1", userName: "Copa"); + // Create transport in legacy mode var transport = new RabbitMQTransport { TopologyFactory = durable => new ConventionalRoutingTopology(durable, queueType), - LegacyApiConnectionString = "virtualHost=/;username=guest;host=localhost;password=guest;port=5672;useTls=false", - LegacyManagementApiConnectionString = "host=127.0.0.1;username=Copa" + LegacyApiConnectionString = BrokerConnectionString, + LegacyManagementApiConnectionString = invalidManagementConnection.ToConnectionString() }; var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); @@ -313,12 +328,14 @@ public void Should_throw_on_invalid_legacy_management_connection_string() [Test] public void Should_throw_on_invalid_legacy_broker_connection_string() { + var invalidBrokerConnection = new FakeConnectionConfiguration(host: "localhost", port: "5672", virtualHost: "/", userName: "Copa", password: "guest", useTls: "false"); + // Create transport in legacy mode var transport = new RabbitMQTransport { TopologyFactory = durable => new ConventionalRoutingTopology(durable, queueType), - LegacyApiConnectionString = "virtualHost=/;username=Copa;host=localhost;password=guest;port=5672;useTls=false", - LegacyManagementApiConnectionString = "host=127.0.0.1;username=guest" + LegacyApiConnectionString = invalidBrokerConnection.ToConnectionString(), + LegacyManagementApiConnectionString = ManagementConnectionString }; var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); @@ -328,24 +345,22 @@ public void Should_throw_on_invalid_legacy_broker_connection_string() } [Test] - public async Task Should_connect_to_management_api_with_broker_credentials() + public void Should_connect_to_management_api_with_broker_credentials() { - var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "virtualHost=/;username=guest;host=localhost;password=guest;port=5672;useTls=false"); - - var infra = await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false); - - Assert.Multiple(() => - { - Assert.That(transport.BrokerConnectionConfiguration.Port, Is.EqualTo(5672)); - Assert.That(transport.ManagementConnectionConfiguration.Port, Is.EqualTo(15672)); - }); + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), BrokerConnectionString); + Assert.DoesNotThrowAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); } [Test] public async Task Should_set_default_port_values_for_broker_and_management_connections() { - var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "host=localhost"); + var validConnectionWithoutPort = new FakeConnectionConfiguration(BrokerConnectionString) + { + Port = null + }; + + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), validConnectionWithoutPort.ToConnectionString()); _ = await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false); @@ -358,21 +373,90 @@ public async Task Should_set_default_port_values_for_broker_and_management_conne } [Test] - public async Task Should_not_throw_when_DoNotUseManagementClient_is_enabled_and_management_connection_is_invalid() + public void Should_not_throw_when_DoNotUseManagementClient_is_enabled_and_management_connection_is_invalid() { - var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), "host=localhost", "host=Copa") + var invalidManagementConnection = new FakeConnectionConfiguration(host: "Copa"); + + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), BrokerConnectionString, invalidManagementConnection.ToConnectionString()) { DoNotUseManagementClient = true }; - _ = await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false); + Assert.DoesNotThrowAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + } + } - Assert.Multiple(() => - { - Assert.That(transport.BrokerConnectionConfiguration.Port, Is.EqualTo(5672)); - Assert.That(transport.ManagementConnectionConfiguration.Port, Is.EqualTo(15672)); - }); + public class FakeConnectionConfiguration + { + internal string Host { get; set; } + + internal string? Port { get; set; } + + internal string? VirtualHost { get; set; } + + internal string? UserName { get; set; } + internal string? Password { get; set; } + + internal string? UseTls { get; set; } + + internal FakeConnectionConfiguration( + string host, + string? port = null, + string? virtualHost = null, + string? userName = null, + string? password = null, + string? useTls = null) + { + Host = host; + Port = port; + VirtualHost = virtualHost; + UserName = userName; + Password = password; + UseTls = useTls; + } + + internal FakeConnectionConfiguration(string connectionString) + { + var parameters = connectionString.Split(';').Select(param => param.Split('=')).ToDictionary(parts => parts[0].ToLower(), parts => parts[1]); + + Host = parameters["host"]; + Port = GetParameterValue(parameters, "port"); + VirtualHost = GetParameterValue(parameters, "virtualhost"); + UserName = GetParameterValue(parameters, "username"); + Password = GetParameterValue(parameters, "password"); + UseTls = GetParameterValue(parameters, "usetls"); } + + static string? GetParameterValue(Dictionary parameters, string key) => parameters.TryGetValue(key, out var value) ? value : null; + + internal string ToConnectionString() + { + var sb = new StringBuilder(); + _ = sb.Append($"{nameof(Host)}={Host}"); + + if (!string.IsNullOrEmpty(VirtualHost)) + { + _ = sb.Append($";{nameof(VirtualHost)}={VirtualHost}"); + } + if (!string.IsNullOrEmpty(Port)) + { + _ = sb.Append($";{nameof(Port)}={Port}"); + } + if (!string.IsNullOrEmpty(UserName)) + { + _ = sb.Append($";{nameof(UserName)}={UserName}"); + } + if (!string.IsNullOrEmpty(Password)) + { + _ = sb.Append($";{nameof(Password)}={Password}"); + } + if (!string.IsNullOrEmpty(UseTls)) + { + _ = sb.Append($";{nameof(UseTls)}={UseTls}"); + } + return sb.ToString(); + } + } } From e2c3cb2fac8c5f5b131e676f7dd159cb5cc2140d Mon Sep 17 00:00:00 2001 From: Travis Nickels Date: Fri, 6 Dec 2024 21:22:28 -0800 Subject: [PATCH 34/46] Update the exception tests for invalid connection strings --- .../ConnectionConfigurationTests.cs | 47 +++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs index 4b61c2eb6..f77a9532a 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs @@ -5,6 +5,7 @@ namespace NServiceBus.Transport.RabbitMQ.Tests.ConnectionString using System; using System.Collections.Generic; using System.Linq; + using System.Net.Http; using System.Text; using System.Threading.Tasks; using global::RabbitMQ.Client.Exceptions; @@ -284,9 +285,12 @@ public void Should_configure_broker_and_management_connection_configurations_wit } [Test] - public void Should_throw_on_invalid_management_connection_string() + public void Should_throw_on_invalid_management_credentials() { - var invalidManagementConnection = new FakeConnectionConfiguration(host: "localhost", userName: "Copa"); + var invalidManagementConnection = new FakeConnectionConfiguration(ManagementConnectionString) + { + UserName = "Copa" + }; var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), BrokerConnectionString, invalidManagementConnection.ToConnectionString()); @@ -295,6 +299,19 @@ public void Should_throw_on_invalid_management_connection_string() Assert.That(exception.Message, Does.Contain("Could not access RabbitMQ Management API")); } + [Test] + public void Should_throw_on_invalid_management_host() + { + var invalidManagementConnection = new FakeConnectionConfiguration(ManagementConnectionString) + { + Host = "WrongHostName" + }; + + var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), BrokerConnectionString, invalidManagementConnection.ToConnectionString()); + + _ = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + } + [Test] public void Should_throw_on_invalid_broker_connection_string() { @@ -308,9 +325,12 @@ public void Should_throw_on_invalid_broker_connection_string() } [Test] - public void Should_throw_on_invalid_legacy_management_connection_string() + public void Should_throw_on_invalid_legacy_management_credentials() { - var invalidManagementConnection = new FakeConnectionConfiguration(host: "127.0.0.1", userName: "Copa"); + var invalidManagementConnection = new FakeConnectionConfiguration(ManagementConnectionString) + { + UserName = "Copa" + }; // Create transport in legacy mode var transport = new RabbitMQTransport @@ -325,6 +345,25 @@ public void Should_throw_on_invalid_legacy_management_connection_string() Assert.That(exception.Message, Does.Contain("Could not access RabbitMQ Management API")); } + [Test] + public void Should_throw_on_invalid_legacy_management_host() + { + var invalidManagementConnection = new FakeConnectionConfiguration(ManagementConnectionString) + { + Host = "WrongHostName" + }; + + // Create transport in legacy mode + var transport = new RabbitMQTransport + { + TopologyFactory = durable => new ConventionalRoutingTopology(durable, queueType), + LegacyApiConnectionString = BrokerConnectionString, + LegacyManagementApiConnectionString = invalidManagementConnection.ToConnectionString() + }; + + _ = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + } + [Test] public void Should_throw_on_invalid_legacy_broker_connection_string() { From e559fed01531c98d92c1c1d3933e02db0b44b064 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz <110360248+abparticular@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:25:59 +1100 Subject: [PATCH 35/46] Remove unnecessary escaping of a Policy's VirtualHost property --- .../Administration/ManagementClient/ManagementClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs index 9e363fff4..6d332104b 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs @@ -94,7 +94,7 @@ public async Task CreatePolicy(Policy policy, CancellationToken cancellationToke { ArgumentNullException.ThrowIfNull(policy, nameof(policy)); - policy.VirtualHost = Uri.EscapeDataString(virtualHost); + policy.VirtualHost = virtualHost; var escapedPolicyName = Uri.EscapeDataString(policy.Name); var response = await httpClient.PutAsJsonAsync($"api/policies/{escapedVirtualHost}/{escapedPolicyName}", policy, cancellationToken) From b0fb7245dc2c6c976dfcbe5b1b6f82b019b8f17a Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Thu, 5 Dec 2024 12:22:44 +1100 Subject: [PATCH 36/46] Minor changes --- .../When_classic_endpoint_uses_quorum_error_queue.cs | 4 ++-- .../QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs | 4 ++-- src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_error_queue.cs b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_error_queue.cs index 8174bc403..521489736 100644 --- a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_error_queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_error_queue.cs @@ -24,8 +24,8 @@ public async Task Should_fail_to_start() .Run()); Assert.That(exception.Message, Does.Contain("PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'rabbitmq.transport.tests.quorum-error'")); - Assert.That(exception.Message, Does.Contain("received none but current is the value 'quorum'") - .Or.Contain("received 'classic' but current is 'quorum'")); + Assert.That(exception.Message, Does.Contain("received none but current is the value 'quorum'") // RabbitMQ v3.x + .Or.Contain("received 'classic' but current is 'quorum'")); // RabbitMQ v4.x } class ClassicQueueEndpoint : EndpointConfigurationBuilder diff --git a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs index 655a6321b..919c95dc4 100644 --- a/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ.AcceptanceTests/QuorumQueues/When_classic_endpoint_uses_quorum_queue.cs @@ -25,8 +25,8 @@ public async Task Should_fail_to_start() .Run()); Assert.That(exception.Message, Does.Contain("PRECONDITION_FAILED - inequivalent arg 'x-queue-type' for queue 'ClassicEndpointUsesQuorumQueue.ClassicQueueEndpoint'")); - Assert.That(exception.Message, Does.Contain("received none but current is the value 'quorum'") - .Or.Contain("received 'classic' but current is 'quorum'")); + Assert.That(exception.Message, Does.Contain("received none but current is the value 'quorum'") // RabbitMQ v3.x + .Or.Contain("received 'classic' but current is 'quorum'")); // RabbitMQ v4.x } class ClassicQueueEndpoint : EndpointConfigurationBuilder diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index b705def00..97f5fa8ee 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -286,7 +286,7 @@ internal RabbitMQTransport() : base(TransportTransactionMode.ReceiveOnly, true, legacyMode = true; } - internal void ValidateAndApplyLegacyConfiguration() + void ValidateAndApplyLegacyConfiguration() { if (!legacyMode) { From 12f3698480bcd10f634cbff68a6a562f39f209f3 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Thu, 5 Dec 2024 14:09:12 +1100 Subject: [PATCH 37/46] Move ManagementClient tests --- ...he_rabbitmq_management_api.cs => ManagementClientTests.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/NServiceBus.Transport.RabbitMQ.Tests/{Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs => ManagementClientTests.cs} (96%) diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/ManagementClientTests.cs similarity index 96% rename from src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs rename to src/NServiceBus.Transport.RabbitMQ.Tests/ManagementClientTests.cs index 027a6745e..2454b48d3 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ManagementConnection/When_connecting_to_the_rabbitmq_management_api.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/ManagementClientTests.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Tests.ManagementConnection +namespace NServiceBus.Transport.RabbitMQ.Tests { using System; using System.Collections.Generic; @@ -15,7 +15,7 @@ namespace NServiceBus.Transport.RabbitMQ.Tests.ManagementConnection [TestFixture] - class When_connecting_to_the_rabbitmq_management_api + class ManagementClientTests { static readonly string connectionString = Environment.GetEnvironmentVariable("RabbitMQTransport_ConnectionString") ?? "host=localhost"; readonly ConnectionConfiguration connectionConfiguration = ConnectionConfiguration.Create(connectionString); From 5ae951e873f638a0edbd14fbcb770d98dd7ba431 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Fri, 6 Dec 2024 14:22:43 +1100 Subject: [PATCH 38/46] Refactor ManagementClient tests and add tests for getting feature flags and creating a policy --- .../FeatureFlagListExtensions.cs | 11 ++ .../ManagementClientTests.cs | 110 +++++++++++++----- 2 files changed, 90 insertions(+), 31 deletions(-) create mode 100644 src/NServiceBus.Transport.RabbitMQ.Tests/FeatureFlagListExtensions.cs diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/FeatureFlagListExtensions.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/FeatureFlagListExtensions.cs new file mode 100644 index 000000000..fef4475b5 --- /dev/null +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/FeatureFlagListExtensions.cs @@ -0,0 +1,11 @@ +namespace NServiceBus.Transport.RabbitMQ.Tests; + +using System; +using System.Linq; +using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; + +static class FeatureFlagListExtensions +{ + public static bool Contains(this FeatureFlagList featureFlagList, string featureName) => + featureFlagList.Any(featureFlag => featureFlag.Name.Equals(featureName, StringComparison.OrdinalIgnoreCase)); +} diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/ManagementClientTests.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/ManagementClientTests.cs index 2454b48d3..c932b911f 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/ManagementClientTests.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/ManagementClientTests.cs @@ -6,9 +6,8 @@ namespace NServiceBus.Transport.RabbitMQ.Tests using System.Collections.Generic; using System.Net; using System.Threading.Tasks; - using global::RabbitMQ.Client; - using global::RabbitMQ.Client.Events; using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; + using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; using NUnit.Framework; using NUnit.Framework.Internal; using ConnectionFactory = ConnectionFactory; @@ -18,52 +17,37 @@ namespace NServiceBus.Transport.RabbitMQ.Tests class ManagementClientTests { static readonly string connectionString = Environment.GetEnvironmentVariable("RabbitMQTransport_ConnectionString") ?? "host=localhost"; - readonly ConnectionConfiguration connectionConfiguration = ConnectionConfiguration.Create(connectionString); - readonly ConnectionConfiguration managementConnectionConfiguration = ConnectionConfiguration.Create(connectionString, isManagementConnection: true); - protected QueueType queueType = QueueType.Quorum; - protected string ReceiverQueue => GetTestQueueName("ManagementAPITestQueue"); - protected string GetTestQueueName(string queueName) => $"{queueName}-{queueType}"; - - [SetUp] - public async Task SetUp() - { - var connectionFactory = new ConnectionFactory(ReceiverQueue, connectionConfiguration, null, false, false, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(10), []); - IConnection connection = await connectionFactory.CreateConnection(ReceiverQueue).ConfigureAwait(false); - var createChannelOptions = new CreateChannelOptions(publisherConfirmationsEnabled: false, publisherConfirmationTrackingEnabled: false); - var channel = await connection.CreateChannelAsync(createChannelOptions).ConfigureAwait(false); - var arguments = new Dictionary { { "x-queue-type", "quorum" } }; - - _ = await channel.QueueDeclareAsync(queue: ReceiverQueue, durable: true, exclusive: false, autoDelete: false, arguments: arguments).ConfigureAwait(false); - - var consumer = new AsyncEventingBasicConsumer(channel); - consumer.ReceivedAsync += async (o, a) => await Task.Yield(); - - var consumerTag = $"localhost - {ReceiverQueue}"; - _ = await channel.BasicConsumeAsync(ReceiverQueue, true, consumerTag, consumer).ConfigureAwait(false); - } + static readonly ConnectionConfiguration connectionConfiguration = ConnectionConfiguration.Create(connectionString); + static readonly ConnectionConfiguration managementConnectionConfiguration = ConnectionConfiguration.Create(connectionString, isManagementConnection: true); + static readonly ConnectionFactory connectionFactory = new(typeof(ManagementClientTests).FullName, connectionConfiguration, null, false, false, TimeSpan.FromSeconds(60), TimeSpan.FromSeconds(10), []); + static readonly ManagementClient client = new(managementConnectionConfiguration); [Test] public async Task GetQueue_Should_Return_Queue_Information_When_Exists() { - var client = new ManagementClient(managementConnectionConfiguration); + // Arrange + var queueName = nameof(GetQueue_Should_Return_Queue_Information_When_Exists); + await CreateQuorumQueue(queueName).ConfigureAwait(false); - var response = await client.GetQueue(ReceiverQueue); + // Act + var response = await client.GetQueue(queueName); + // Assert Assert.Multiple(() => { Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); Assert.That(response.Value, Is.Not.Null); - Assert.That(response.Value?.Name, Is.EqualTo(ReceiverQueue)); + Assert.That(response.Value?.Name, Is.EqualTo(queueName)); }); } [Test] - public async Task GetOverview_Should_Return_Broker_Information_When_Exists() + public async Task GetOverview_Should_Return_Broker_Information() { - var client = new ManagementClient(managementConnectionConfiguration); - + // Act var response = await client.GetOverview(); + // Assert Assert.Multiple(() => { Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); @@ -74,5 +58,69 @@ public async Task GetOverview_Should_Return_Broker_Information_When_Exists() Assert.That(response.Value?.RabbitMqVersion.Major, Is.InRange(3, 4)); }); } + + [Test] + public async Task GetFeatureFlags_Should_Return_FeatureFlag_Information() + { + // Act + var response = await client.GetFeatureFlags(); + + // Assert + Assert.Multiple(() => + { + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + Assert.That(response.Value, Is.Not.Null); + Assert.That(response.Value, Is.Not.Empty); + Assert.That(response.Value?.Contains(FeatureFlags.QuorumQueue), Is.True); + }); + } + + [Test] + [TestCase(-1)] + [TestCase(200)] + public async Task CreatePolicy_With_DeliveryLimit_Should_Be_Applied_To_Quorum_Queues(int deliveryLimit) + { + // Arrange + var queueName = nameof(CreatePolicy_With_DeliveryLimit_Should_Be_Applied_To_Quorum_Queues); + var policyName = $"{queueName} policy"; + await CreateQuorumQueue(queueName); + + // Act + var policy = new Policy + { + ApplyTo = PolicyTarget.QuorumQueues, + Definition = new PolicyDefinition + { + DeliveryLimit = deliveryLimit + }, + Name = policyName, + Pattern = queueName, + Priority = 100 + }; + await client.CreatePolicy(policy); + + // Assert + + // It can take some time for updated policies to be applied, so we need to wait. + // If this test is randomly failing, consider increasing the delay + await Task.Delay(10000); + var response = await client.GetQueue(queueName); + Assert.Multiple(() => + { + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + Assert.That(response.Value, Is.Not.Null); + Assert.That(response.Value?.AppliedPolicyName, Is.EqualTo(policyName)); + Assert.That(response.Value?.EffectivePolicyDefinition?.DeliveryLimit, Is.EqualTo(deliveryLimit)); + }); + } + + static async Task CreateQuorumQueue(string queueName) + { + using var connection = await connectionFactory.CreateConnection($"{queueName} connection").ConfigureAwait(false); + using var channel = await connection.CreateChannelAsync().ConfigureAwait(false); + var arguments = new Dictionary { { "x-queue-type", "quorum" } }; + + _ = await channel.QueueDeclareAsync(queue: queueName, durable: true, exclusive: false, autoDelete: false, arguments: arguments); + } } } From 7a5e642d3be1dd30c2fb6d40b80d4a9ceb534383 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Fri, 6 Dec 2024 21:28:01 +1100 Subject: [PATCH 39/46] Simplified some connection configuration tests that use the transport --- .../ConnectionConfigurationTests.cs | 169 +++++++++--------- 1 file changed, 81 insertions(+), 88 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs index f77a9532a..c3af26cdd 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/Connection/ConnectionConfigurationTests.cs @@ -18,18 +18,11 @@ class ConnectionConfigurationTests const string FakeConnectionString = "virtualHost=Copa;username=Copa;host=192.168.1.1:1234;password=abc_xyz;port=12345;useTls=true"; static string BrokerConnectionString = Environment.GetEnvironmentVariable("RabbitMQTransport_ConnectionString") ?? "host=localhost"; static string ManagementConnectionString => CreateManagementConnectionString(BrokerConnectionString); - protected string ReceiverQueue => GetTestQueueName("testreceiver"); - protected string ErrorQueue => GetTestQueueName("error"); - protected string GetTestQueueName(string queueName) => $"{queueName}-{queueType}"; - protected IList AdditionalReceiverQueues = []; - protected string[] SendingAddresses => [.. AdditionalReceiverQueues, ErrorQueue]; - protected QueueType queueType = QueueType.Quorum; - protected HostSettings HostSettings => new(ReceiverQueue, ReceiverQueue, new StartupDiagnosticEntries(), (_, _, _) => { }, true); - protected ReceiveSettings[] ReceiveSettings => [new ReceiveSettings(ReceiverQueue, new QueueAddress(ReceiverQueue), true, true, ErrorQueue)]; + static HostSettings HostSettings { get; } = new(nameof(ConnectionConfigurationTests), nameof(ConnectionConfigurationTests), null, null, false); - readonly ConnectionConfiguration brokerDefaults = ConnectionConfiguration.Create("host=localhost"); - readonly ConnectionConfiguration managementDefaults = ConnectionConfiguration.Create("host=localhost", isManagementConnection: true); + static readonly ConnectionConfiguration brokerDefaults = ConnectionConfiguration.Create("host=localhost"); + static readonly ConnectionConfiguration managementDefaults = ConnectionConfiguration.Create("host=localhost", isManagementConnection: true); static string CreateManagementConnectionString(string connectionString) { @@ -170,8 +163,8 @@ public void Should_list_all_invalid_options() "certPath =/path/to/client/keycert.p12;" + "certPassPhrase = abc123;"; - var exception = Assert.Throws(() => - ConnectionConfiguration.Create(connectionString)); + var exception = Assert.Throws(() => ConnectionConfiguration.Create(connectionString)) + ?? throw new ArgumentNullException("exception"); Assert.That(exception.Message, Does.Contain("Multiple hosts are no longer supported")); Assert.That(exception.Message, Does.Contain("Empty host name in 'host' connection string option.")); @@ -294,7 +287,8 @@ public void Should_throw_on_invalid_management_credentials() var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), BrokerConnectionString, invalidManagementConnection.ToConnectionString()); - var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, [], [])) + ?? throw new ArgumentNullException("exception"); Assert.That(exception.Message, Does.Contain("Could not access RabbitMQ Management API")); } @@ -309,7 +303,7 @@ public void Should_throw_on_invalid_management_host() var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), BrokerConnectionString, invalidManagementConnection.ToConnectionString()); - _ = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + _ = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, [], [])); } [Test] @@ -319,7 +313,8 @@ public void Should_throw_on_invalid_broker_connection_string() var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), invalidBrokerConnection.ToConnectionString(), ManagementConnectionString); - var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, [], [])) + ?? throw new ArgumentNullException("exception"); Assert.That(exception.Message, Does.Contain("None of the specified endpoints were reachable")); } @@ -335,14 +330,14 @@ public void Should_throw_on_invalid_legacy_management_credentials() // Create transport in legacy mode var transport = new RabbitMQTransport { - TopologyFactory = durable => new ConventionalRoutingTopology(durable, queueType), + TopologyFactory = durable => new ConventionalRoutingTopology(durable, QueueType.Quorum), LegacyApiConnectionString = BrokerConnectionString, LegacyManagementApiConnectionString = invalidManagementConnection.ToConnectionString() }; - var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, [], [])); - Assert.That(exception.Message, Does.Contain("Could not access RabbitMQ Management API")); + Assert.That(exception!.Message, Does.Contain("Could not access RabbitMQ Management API")); } [Test] @@ -356,12 +351,12 @@ public void Should_throw_on_invalid_legacy_management_host() // Create transport in legacy mode var transport = new RabbitMQTransport { - TopologyFactory = durable => new ConventionalRoutingTopology(durable, queueType), + TopologyFactory = durable => new ConventionalRoutingTopology(durable, QueueType.Quorum), LegacyApiConnectionString = BrokerConnectionString, LegacyManagementApiConnectionString = invalidManagementConnection.ToConnectionString() }; - _ = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + _ = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, [], [])); } [Test] @@ -372,15 +367,15 @@ public void Should_throw_on_invalid_legacy_broker_connection_string() // Create transport in legacy mode var transport = new RabbitMQTransport { - TopologyFactory = durable => new ConventionalRoutingTopology(durable, queueType), + TopologyFactory = durable => new ConventionalRoutingTopology(durable, QueueType.Quorum), LegacyApiConnectionString = invalidBrokerConnection.ToConnectionString(), LegacyManagementApiConnectionString = ManagementConnectionString }; - var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + var exception = Assert.ThrowsAsync(async () => await transport.Initialize(HostSettings, [], [])) + ?? throw new ArgumentNullException("exception"); Assert.That(exception.Message, Does.Contain("None of the specified endpoints were reachable")); - } [Test] @@ -388,7 +383,7 @@ public void Should_connect_to_management_api_with_broker_credentials() { var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), BrokerConnectionString); - Assert.DoesNotThrowAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + Assert.DoesNotThrowAsync(async () => await transport.Initialize(HostSettings, [], [])); } [Test] @@ -401,14 +396,13 @@ public async Task Should_set_default_port_values_for_broker_and_management_conne var transport = new RabbitMQTransport(RoutingTopology.Conventional(QueueType.Quorum), validConnectionWithoutPort.ToConnectionString()); - _ = await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false); + _ = await transport.Initialize(HostSettings, [], []); Assert.Multiple(() => { Assert.That(transport.BrokerConnectionConfiguration.Port, Is.EqualTo(5672)); Assert.That(transport.ManagementConnectionConfiguration.Port, Is.EqualTo(15672)); }); - } [Test] @@ -421,81 +415,80 @@ public void Should_not_throw_when_DoNotUseManagementClient_is_enabled_and_manage DoNotUseManagementClient = true }; - Assert.DoesNotThrowAsync(async () => await transport.Initialize(HostSettings, ReceiveSettings, SendingAddresses).ConfigureAwait(false)); + Assert.DoesNotThrowAsync(async () => await transport.Initialize(HostSettings, [], [])); } - } - - public class FakeConnectionConfiguration - { - internal string Host { get; set; } - internal string? Port { get; set; } - - internal string? VirtualHost { get; set; } + public class FakeConnectionConfiguration + { + internal string Host { get; set; } - internal string? UserName { get; set; } + internal string? Port { get; set; } - internal string? Password { get; set; } + internal string? VirtualHost { get; set; } - internal string? UseTls { get; set; } + internal string? UserName { get; set; } - internal FakeConnectionConfiguration( - string host, - string? port = null, - string? virtualHost = null, - string? userName = null, - string? password = null, - string? useTls = null) - { - Host = host; - Port = port; - VirtualHost = virtualHost; - UserName = userName; - Password = password; - UseTls = useTls; - } + internal string? Password { get; set; } - internal FakeConnectionConfiguration(string connectionString) - { - var parameters = connectionString.Split(';').Select(param => param.Split('=')).ToDictionary(parts => parts[0].ToLower(), parts => parts[1]); - - Host = parameters["host"]; - Port = GetParameterValue(parameters, "port"); - VirtualHost = GetParameterValue(parameters, "virtualhost"); - UserName = GetParameterValue(parameters, "username"); - Password = GetParameterValue(parameters, "password"); - UseTls = GetParameterValue(parameters, "usetls"); - } + internal string? UseTls { get; set; } - static string? GetParameterValue(Dictionary parameters, string key) => parameters.TryGetValue(key, out var value) ? value : null; - - internal string ToConnectionString() - { - var sb = new StringBuilder(); - _ = sb.Append($"{nameof(Host)}={Host}"); - - if (!string.IsNullOrEmpty(VirtualHost)) + internal FakeConnectionConfiguration( + string host, + string? port = null, + string? virtualHost = null, + string? userName = null, + string? password = null, + string? useTls = null) { - _ = sb.Append($";{nameof(VirtualHost)}={VirtualHost}"); + Host = host; + Port = port; + VirtualHost = virtualHost; + UserName = userName; + Password = password; + UseTls = useTls; } - if (!string.IsNullOrEmpty(Port)) - { - _ = sb.Append($";{nameof(Port)}={Port}"); - } - if (!string.IsNullOrEmpty(UserName)) - { - _ = sb.Append($";{nameof(UserName)}={UserName}"); - } - if (!string.IsNullOrEmpty(Password)) + + internal FakeConnectionConfiguration(string connectionString) { - _ = sb.Append($";{nameof(Password)}={Password}"); + var parameters = connectionString.Split(';').Select(param => param.Split('=')).ToDictionary(parts => parts[0].ToLower(), parts => parts[1]); + + Host = parameters["host"]; + Port = GetParameterValue(parameters, "port"); + VirtualHost = GetParameterValue(parameters, "virtualhost"); + UserName = GetParameterValue(parameters, "username"); + Password = GetParameterValue(parameters, "password"); + UseTls = GetParameterValue(parameters, "usetls"); } - if (!string.IsNullOrEmpty(UseTls)) + + static string? GetParameterValue(Dictionary parameters, string key) => parameters.TryGetValue(key, out var value) ? value : null; + + internal string ToConnectionString() { - _ = sb.Append($";{nameof(UseTls)}={UseTls}"); + var sb = new StringBuilder(); + _ = sb.Append($"{nameof(Host)}={Host}"); + + if (!string.IsNullOrEmpty(VirtualHost)) + { + _ = sb.Append($";{nameof(VirtualHost)}={VirtualHost}"); + } + if (!string.IsNullOrEmpty(Port)) + { + _ = sb.Append($";{nameof(Port)}={Port}"); + } + if (!string.IsNullOrEmpty(UserName)) + { + _ = sb.Append($";{nameof(UserName)}={UserName}"); + } + if (!string.IsNullOrEmpty(Password)) + { + _ = sb.Append($";{nameof(Password)}={Password}"); + } + if (!string.IsNullOrEmpty(UseTls)) + { + _ = sb.Append($";{nameof(UseTls)}={UseTls}"); + } + return sb.ToString(); } - return sb.ToString(); } - } -} +} \ No newline at end of file From 032fc1c73fec28f0c4164d3d6589f8c40a598689 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Thu, 12 Dec 2024 06:55:46 +1100 Subject: [PATCH 40/46] Use RabbitMQ v4 for testing --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b607d0c5..c916b7b78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,7 @@ jobs: with: connection-string-name: RabbitMQTransport_ConnectionString tag: RabbitMQTransport + image-tag: 4-management registry-username: ${{ secrets.DOCKERHUB_USERNAME }} registry-password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Run tests From d39c6580f202261dabd6d0990eb487a7e1fcafee Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Thu, 19 Dec 2024 02:58:00 +1100 Subject: [PATCH 41/46] Removed IBrokerVerifier interface Simplified namespaces --- .../FeatureFlagListExtensions.cs | 2 +- .../ManagementClientTests.cs | 3 +-- .../Administration/BrokerVerifier.cs | 7 +++---- .../Administration/IBrokerVerifier.cs | 13 ------------- .../Converters/DeliveryLimitConverter.cs | 2 +- .../Converters/FeatureFlagStateConverter.cs | 2 +- .../Converters/PolicyTargetConverter.cs | 3 +-- .../Converters/QueueTypeConverter.cs | 3 +-- .../ManagementClient/Converters/VersionConverter.cs | 2 +- .../ManagementClient/HttpStatusCodeExtensions.cs | 2 +- .../ManagementClient/IManagementClient.cs | 3 +-- .../ManagementClient/IManagementClientFactory.cs | 2 +- .../ManagementClient/ManagementClient.cs | 3 +-- .../ManagementClient/ManagementClientFactory.cs | 2 +- .../ManagementClient/Models/FeatureFlag.cs | 3 +-- .../ManagementClient/Models/FeatureFlagList.cs | 2 +- .../ManagementClient/Models/FeatureFlags.cs | 2 +- .../ManagementClient/Models/Overview.cs | 3 +-- .../ManagementClient/Models/Policy.cs | 3 +-- .../ManagementClient/Models/PolicyDefinition.cs | 3 +-- .../ManagementClient/Models/PolicyTarget.cs | 2 +- .../Administration/ManagementClient/Models/Queue.cs | 3 +-- .../ManagementClient/Models/QueueArguments.cs | 3 +-- .../ManagementClient/Models/QueueType.cs | 2 +- .../Administration/ManagementClient/Response.cs | 2 +- .../RabbitMQTransport.cs | 3 +-- .../RabbitMQTransportInfrastructure.cs | 5 ++--- .../Receiving/MessagePump.cs | 5 ++--- 28 files changed, 31 insertions(+), 59 deletions(-) delete mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/IBrokerVerifier.cs diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/FeatureFlagListExtensions.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/FeatureFlagListExtensions.cs index fef4475b5..eba5ac045 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/FeatureFlagListExtensions.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/FeatureFlagListExtensions.cs @@ -2,7 +2,7 @@ using System; using System.Linq; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; +using NServiceBus.Transport.RabbitMQ.ManagementClient; static class FeatureFlagListExtensions { diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/ManagementClientTests.cs b/src/NServiceBus.Transport.RabbitMQ.Tests/ManagementClientTests.cs index c932b911f..cede74f2e 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/ManagementClientTests.cs +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/ManagementClientTests.cs @@ -6,8 +6,7 @@ namespace NServiceBus.Transport.RabbitMQ.Tests using System.Collections.Generic; using System.Net; using System.Threading.Tasks; - using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; - using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; + using NServiceBus.Transport.RabbitMQ.ManagementClient; using NUnit.Framework; using NUnit.Framework.Internal; using ConnectionFactory = ConnectionFactory; diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs index 01d0cd3c4..1ad4dd5e2 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs @@ -1,16 +1,15 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration; +namespace NServiceBus.Transport.RabbitMQ; using System; using System.Threading; using System.Threading.Tasks; using ManagementClient; -using ManagementClient.Models; using NServiceBus.Logging; using Polly; -class BrokerVerifier(ConnectionFactory connectionFactory, IManagementClientFactory? managementClientFactory) : IBrokerVerifier +class BrokerVerifier(ConnectionFactory connectionFactory, IManagementClientFactory? managementClientFactory) { static readonly ILog Logger = LogManager.GetLogger(typeof(BrokerVerifier)); static readonly Version MinimumSupportedRabbitMqVersion = Version.Parse("3.10.0"); @@ -168,7 +167,7 @@ static async Task SetDeliveryLimitViaPolicy(IManagementClient managementClient, throw new InvalidOperationException($"Cannot override delivery limit on the {queue.Name} queue by policy in RabbitMQ versions prior to 4. Version is {brokerVersion}."); } - var policy = new ManagementClient.Models.Policy + var policy = new RabbitMQ.ManagementClient.Policy { Name = $"nsb.{queue.Name}.delivery-limit", ApplyTo = PolicyTarget.QuorumQueues, diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/IBrokerVerifier.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/IBrokerVerifier.cs deleted file mode 100644 index a10dd3faf..000000000 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/IBrokerVerifier.cs +++ /dev/null @@ -1,13 +0,0 @@ -#nullable enable - -namespace NServiceBus.Transport.RabbitMQ.Administration; - -using System.Threading.Tasks; -using System.Threading; - -interface IBrokerVerifier -{ - Task Initialize(CancellationToken cancellationToken = default); - - Task ValidateDeliveryLimit(string queueName, CancellationToken cancellationToken = default); -} diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/DeliveryLimitConverter.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/DeliveryLimitConverter.cs index 37eeb55c0..44bd575a0 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/DeliveryLimitConverter.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/DeliveryLimitConverter.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System; using System.Text.Json; diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/FeatureFlagStateConverter.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/FeatureFlagStateConverter.cs index 66da757d2..7e9b346b4 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/FeatureFlagStateConverter.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/FeatureFlagStateConverter.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System; using System.Text.Json; diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/PolicyTargetConverter.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/PolicyTargetConverter.cs index eedc5d547..a339ac39e 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/PolicyTargetConverter.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/PolicyTargetConverter.cs @@ -1,11 +1,10 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; class PolicyTargetConverter : JsonConverter { diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/QueueTypeConverter.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/QueueTypeConverter.cs index d956d95cf..ba0f6842d 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/QueueTypeConverter.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/QueueTypeConverter.cs @@ -1,11 +1,10 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; class QueueTypeConverter : JsonConverter { diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/VersionConverter.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/VersionConverter.cs index 7cbc9e45f..c8b11dca6 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/VersionConverter.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Converters/VersionConverter.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System; using System.Text.Json; diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/HttpStatusCodeExtensions.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/HttpStatusCodeExtensions.cs index 716e0df57..b33313f6d 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/HttpStatusCodeExtensions.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/HttpStatusCodeExtensions.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System.Net; diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs index f282b3a3a..604bc01cf 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs @@ -1,10 +1,9 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System.Threading; using System.Threading.Tasks; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; interface IManagementClient { diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs index 26047345e..57f7803c2 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; interface IManagementClientFactory { diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs index 6d332104b..f7cfe2953 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System; using System.Net.Http; @@ -9,7 +9,6 @@ namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using System.Text; using System.Threading; using System.Threading.Tasks; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; class ManagementClient : IManagementClient { diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs index 1bab8d7fd..440e5a91d 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; class ManagementClientFactory(ConnectionConfiguration connectionConfiguration) : IManagementClientFactory { diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlag.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlag.cs index 80579f477..29cd289e1 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlag.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlag.cs @@ -1,11 +1,10 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; // This is to prevent Fody throwing an error on classes with `required` properties (since the compiler marks the default constructor with an `[Obsolete]` attribute) // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlagList.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlagList.cs index 6bcb92ac7..03d7ba62a 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlagList.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlagList.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System; using System.Collections.Generic; diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlags.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlags.cs index 94bd797ce..7590110c9 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlags.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlags.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; static class FeatureFlags { diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs index 9c5ab216d..8e0fdc425 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Overview.cs @@ -1,12 +1,11 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; // This is to prevent Fody throwing an error on classes with `required` properties (since the compiler marks the default constructor with an `[Obsolete]` attribute) // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs index 714b2e2f1..32da4acaf 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Policy.cs @@ -1,11 +1,10 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; // This is to prevent Fody throwing an error on classes with `required` properties (since the compiler marks the default constructor with an `[Obsolete]` attribute) // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyDefinition.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyDefinition.cs index 5e601b96d..3763f3d5c 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyDefinition.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyDefinition.cs @@ -1,11 +1,10 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; class PolicyDefinition { diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyTarget.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyTarget.cs index 76ec0eb21..8a7e82df0 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyTarget.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/PolicyTarget.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; enum PolicyTarget { diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs index a41a60f28..9fdd72001 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/Queue.cs @@ -1,11 +1,10 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; // This is to prevent Fody throwing an error on classes with `required` properties (since the compiler marks the default constructor with an `[Obsolete]` attribute) // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-11.0/required-members#metadata-representation diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueArguments.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueArguments.cs index 5db1f608f..ecc99d1be 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueArguments.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueArguments.cs @@ -1,11 +1,10 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; -using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Converters; class QueueArguments { diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueType.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueType.cs index c9820d8a1..ead60989a 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueType.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueType.cs @@ -1,4 +1,4 @@ -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient.Models; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; /// /// The types of queues supported by RabbitMQ diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Response.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Response.cs index f3fcb024b..51d0a9c6d 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Response.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Response.cs @@ -1,6 +1,6 @@ #nullable enable -namespace NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; +namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System.Diagnostics.CodeAnalysis; using System.Net; diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index 97f5fa8ee..896551d26 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -5,12 +5,11 @@ using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; - using NServiceBus.Transport.RabbitMQ.Administration; - using NServiceBus.Transport.RabbitMQ.Administration.ManagementClient; using RabbitMQ.Client; using RabbitMQ.Client.Events; using Transport; using Transport.RabbitMQ; + using Transport.RabbitMQ.ManagementClient; using ConnectionFactory = Transport.RabbitMQ.ConnectionFactory; /// diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs index dfc4bb8c0..e3ec93d35 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransportInfrastructure.cs @@ -5,14 +5,13 @@ using System.Text; using System.Threading; using System.Threading.Tasks; - using NServiceBus.Transport.RabbitMQ.Administration; using global::RabbitMQ.Client; sealed class RabbitMQTransportInfrastructure : TransportInfrastructure { readonly ConnectionFactory connectionFactory; readonly ChannelProvider channelProvider; - readonly IBrokerVerifier brokerVerifier; + readonly BrokerVerifier brokerVerifier; readonly IRoutingTopology routingTopology; readonly TimeSpan networkRecoveryInterval; readonly bool supportsDelayedDelivery; @@ -20,7 +19,7 @@ sealed class RabbitMQTransportInfrastructure : TransportInfrastructure public RabbitMQTransportInfrastructure(HostSettings hostSettings, ReceiveSettings[] receiverSettings, ConnectionFactory connectionFactory, IRoutingTopology routingTopology, ChannelProvider channelProvider, MessageConverter messageConverter, - IBrokerVerifier brokerVerifier, + BrokerVerifier brokerVerifier, Action messageCustomization, TimeSpan timeToWaitBeforeTriggeringCircuitBreaker, PrefetchCountCalculation prefetchCountCalculation, TimeSpan networkRecoveryInterval, bool supportsDelayedDelivery) diff --git a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs index 754091640..74c132367 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Receiving/MessagePump.cs @@ -11,7 +11,6 @@ using global::RabbitMQ.Client.Events; using global::RabbitMQ.Client.Exceptions; using Logging; - using NServiceBus.Transport.RabbitMQ.Administration; sealed partial class MessagePump : IMessageReceiver { @@ -23,7 +22,7 @@ sealed partial class MessagePump : IMessageReceiver readonly MessageConverter messageConverter; readonly string consumerTag; readonly ChannelProvider channelProvider; - readonly IBrokerVerifier brokerVerifier; + readonly BrokerVerifier brokerVerifier; readonly TimeSpan timeToWaitBeforeTriggeringCircuitBreaker; readonly QueuePurger queuePurger; readonly PrefetchCountCalculation prefetchCountCalculation; @@ -52,7 +51,7 @@ public MessagePump( MessageConverter messageConverter, string consumerTag, ChannelProvider channelProvider, - IBrokerVerifier brokerVerifier, + BrokerVerifier brokerVerifier, TimeSpan timeToWaitBeforeTriggeringCircuitBreaker, PrefetchCountCalculation prefetchCountCalculation, Action criticalErrorAction, From 9ed7760155375ae9c8dec51a7bda67aee2678ed5 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Thu, 19 Dec 2024 03:01:36 +1100 Subject: [PATCH 42/46] Removed IManagementClient --- .../Administration/BrokerVerifier.cs | 8 ++++---- .../ManagementClient/IManagementClient.cs | 17 ----------------- .../IManagementClientFactory.cs | 2 +- .../ManagementClient/ManagementClient.cs | 2 +- .../ManagementClient/ManagementClientFactory.cs | 2 +- 5 files changed, 7 insertions(+), 24 deletions(-) delete mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs index 1ad4dd5e2..800260978 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs @@ -15,7 +15,7 @@ class BrokerVerifier(ConnectionFactory connectionFactory, IManagementClientFacto static readonly Version MinimumSupportedRabbitMqVersion = Version.Parse("3.10.0"); static readonly Version RabbitMqVersion4 = Version.Parse("4.0.0"); - readonly IManagementClient? managementClient = managementClientFactory?.CreateManagementClient(); + readonly ManagementClient.ManagementClient? managementClient = managementClientFactory?.CreateManagementClient(); Version? brokerVersion; @@ -125,7 +125,7 @@ bool ShouldOverrideDeliveryLimit(Queue queue) return true; } - static async Task GetFullQueueDetails(IManagementClient managementClient, string queueName, CancellationToken cancellationToken) + static async Task GetFullQueueDetails(ManagementClient.ManagementClient managementClient, string queueName, CancellationToken cancellationToken) { var retryPolicy = Polly.Policy .HandleResult>(response => response.Value?.EffectivePolicyDefinition is null) @@ -155,7 +155,7 @@ bool ShouldOverrideDeliveryLimit(Queue queue) return response?.Value?.EffectivePolicyDefinition is not null ? response.Value : null; } - static async Task SetDeliveryLimitViaPolicy(IManagementClient managementClient, Queue queue, Version brokerVersion, CancellationToken cancellationToken) + static async Task SetDeliveryLimitViaPolicy(ManagementClient.ManagementClient managementClient, Queue queue, Version brokerVersion, CancellationToken cancellationToken) { if (!string.IsNullOrEmpty(queue.AppliedPolicyName)) { @@ -167,7 +167,7 @@ static async Task SetDeliveryLimitViaPolicy(IManagementClient managementClient, throw new InvalidOperationException($"Cannot override delivery limit on the {queue.Name} queue by policy in RabbitMQ versions prior to 4. Version is {brokerVersion}."); } - var policy = new RabbitMQ.ManagementClient.Policy + var policy = new ManagementClient.Policy { Name = $"nsb.{queue.Name}.delivery-limit", ApplyTo = PolicyTarget.QuorumQueues, diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs deleted file mode 100644 index 604bc01cf..000000000 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClient.cs +++ /dev/null @@ -1,17 +0,0 @@ -#nullable enable - -namespace NServiceBus.Transport.RabbitMQ.ManagementClient; - -using System.Threading; -using System.Threading.Tasks; - -interface IManagementClient -{ - Task> GetQueue(string queueName, CancellationToken cancellationToken = default); - - Task> GetOverview(CancellationToken cancellationToken = default); - - Task> GetFeatureFlags(CancellationToken cancellationToken = default); - - Task CreatePolicy(Policy policy, CancellationToken cancellationToken = default); -} diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs index 57f7803c2..97e3992c6 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs @@ -4,5 +4,5 @@ namespace NServiceBus.Transport.RabbitMQ.ManagementClient; interface IManagementClientFactory { - IManagementClient CreateManagementClient(); + ManagementClient CreateManagementClient(); } diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs index f7cfe2953..ae05e9684 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClient.cs @@ -10,7 +10,7 @@ namespace NServiceBus.Transport.RabbitMQ.ManagementClient; using System.Threading; using System.Threading.Tasks; -class ManagementClient : IManagementClient +class ManagementClient { readonly HttpClient httpClient; readonly string virtualHost; diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs index 440e5a91d..51cfda9b6 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs @@ -4,5 +4,5 @@ namespace NServiceBus.Transport.RabbitMQ.ManagementClient; class ManagementClientFactory(ConnectionConfiguration connectionConfiguration) : IManagementClientFactory { - public IManagementClient CreateManagementClient() => new ManagementClient(connectionConfiguration); + public ManagementClient CreateManagementClient() => new ManagementClient(connectionConfiguration); } From e679089d3eff34048a3dee1b59fa44fb14c67845 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Thu, 19 Dec 2024 03:08:04 +1100 Subject: [PATCH 43/46] Remove ManagementClientFactory --- .../Administration/BrokerVerifier.cs | 10 +++++----- .../ManagementClient/IManagementClientFactory.cs | 8 -------- .../ManagementClient/ManagementClientFactory.cs | 8 -------- .../RabbitMQTransport.cs | 4 +--- 4 files changed, 6 insertions(+), 24 deletions(-) delete mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs delete mode 100644 src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs index 800260978..99730d95c 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/BrokerVerifier.cs @@ -9,19 +9,19 @@ namespace NServiceBus.Transport.RabbitMQ; using NServiceBus.Logging; using Polly; -class BrokerVerifier(ConnectionFactory connectionFactory, IManagementClientFactory? managementClientFactory) +class BrokerVerifier(ConnectionFactory connectionFactory, bool managementClientAvailable, ConnectionConfiguration connectionConfiguration) { static readonly ILog Logger = LogManager.GetLogger(typeof(BrokerVerifier)); static readonly Version MinimumSupportedRabbitMqVersion = Version.Parse("3.10.0"); static readonly Version RabbitMqVersion4 = Version.Parse("4.0.0"); - readonly ManagementClient.ManagementClient? managementClient = managementClientFactory?.CreateManagementClient(); + readonly ManagementClient.ManagementClient managementClient = new(connectionConfiguration); Version? brokerVersion; public async Task Initialize(CancellationToken cancellationToken = default) { - if (managementClient != null) + if (managementClientAvailable) { var response = await managementClient.GetOverview(cancellationToken).ConfigureAwait(false); if (response.HasValue) @@ -65,7 +65,7 @@ public async Task VerifyRequirements(CancellationToken cancellationToken = defau } bool streamsEnabled; - if (managementClient != null) + if (managementClientAvailable) { var response = await managementClient.GetFeatureFlags(cancellationToken).ConfigureAwait(false); streamsEnabled = response.HasValue && response.Value.HasEnabledFeature(FeatureFlags.StreamQueue); @@ -84,7 +84,7 @@ public async Task VerifyRequirements(CancellationToken cancellationToken = defau public async Task ValidateDeliveryLimit(string queueName, CancellationToken cancellationToken = default) { - if (managementClient == null) + if (!managementClientAvailable) { return; } diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs deleted file mode 100644 index 97e3992c6..000000000 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/IManagementClientFactory.cs +++ /dev/null @@ -1,8 +0,0 @@ -#nullable enable - -namespace NServiceBus.Transport.RabbitMQ.ManagementClient; - -interface IManagementClientFactory -{ - ManagementClient CreateManagementClient(); -} diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs deleted file mode 100644 index 51cfda9b6..000000000 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/ManagementClientFactory.cs +++ /dev/null @@ -1,8 +0,0 @@ -#nullable enable - -namespace NServiceBus.Transport.RabbitMQ.ManagementClient; - -class ManagementClientFactory(ConnectionConfiguration connectionConfiguration) : IManagementClientFactory -{ - public ManagementClient CreateManagementClient() => new ManagementClient(connectionConfiguration); -} diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index 896551d26..4a995a361 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -9,7 +9,6 @@ using RabbitMQ.Client.Events; using Transport; using Transport.RabbitMQ; - using Transport.RabbitMQ.ManagementClient; using ConnectionFactory = Transport.RabbitMQ.ConnectionFactory; /// @@ -230,8 +229,7 @@ public override async Task Initialize(HostSettings host ManagementConnectionConfiguration = ConnectionConfiguration.Create(LegacyManagementApiConnectionString, isManagementConnection: true); } - var managementClientFactory = DoNotUseManagementClient ? null : new ManagementClientFactory(ManagementConnectionConfiguration); - var brokerVerifier = new BrokerVerifier(connectionFactory, managementClientFactory); + var brokerVerifier = new BrokerVerifier(connectionFactory, !DoNotUseManagementClient, ManagementConnectionConfiguration); await brokerVerifier.Initialize(cancellationToken).ConfigureAwait(false); var channelProvider = new ChannelProvider(connectionFactory, NetworkRecoveryInterval, RoutingTopology); From 8e1986a29095e04519ae49b3ce16b1a810761807 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Thu, 19 Dec 2024 03:12:44 +1100 Subject: [PATCH 44/46] Removed XML comments --- .../ManagementClient/Models/FeatureFlags.cs | 15 --------------- .../ManagementClient/Models/QueueType.cs | 9 ++------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlags.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlags.cs index 7590110c9..2699f24f9 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlags.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/FeatureFlags.cs @@ -4,29 +4,14 @@ namespace NServiceBus.Transport.RabbitMQ.ManagementClient; static class FeatureFlags { - /// - /// Add a detailed queues HTTP API endpoint. Reduce number of metrics in the default endpoint. - /// public const string DetailedQueuesEndpoints = "detailed_queues_endpoint"; - /// - /// New Raft-based metadata store. Fully supported as of RabbitMQ 4.0 - /// public const string KhepriDatabase = "khepri_db"; - /// - /// Support queues of type `quorum` - /// public const string QuorumQueue = "quorum_queue"; - /// - /// Support for stream filtering. - /// public const string StreamFiltering = "stream_filtering"; - /// - /// Support queues of type `stream`. - /// public const string StreamQueue = "stream_queue"; } diff --git a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueType.cs b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueType.cs index ead60989a..6959e5eec 100644 --- a/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueType.cs +++ b/src/NServiceBus.Transport.RabbitMQ/Administration/ManagementClient/Models/QueueType.cs @@ -1,12 +1,7 @@ namespace NServiceBus.Transport.RabbitMQ.ManagementClient; -/// -/// The types of queues supported by RabbitMQ -/// -/// -/// Note that this is different to which lists the types of queues supported by the NServiceBus transport -/// and doesn't include the Stream value -/// +// Note that this is different to `NServiceBus.QueueType` which lists the types of queues supported +// by the NServiceBus transport, which doesn't include the `Stream` value enum QueueType { Classic, From 5b4c73d95a2c522f50e01940dc80ef08ff0b1f77 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Thu, 19 Dec 2024 03:18:33 +1100 Subject: [PATCH 45/46] Converted optional parameter in RabbitMQTransport constructor to an overload where parameter is required --- .../RabbitMQTransport.cs | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs index 4a995a361..4c2b1d16e 100644 --- a/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs +++ b/src/NServiceBus.Transport.RabbitMQ/RabbitMQTransport.cs @@ -25,13 +25,23 @@ public class RabbitMQTransport : TransportDefinition readonly List<(string hostName, int port, bool useTls)> additionalClusterNodes = []; + /// + /// Creates a new instance of the RabbitMQ transport. + /// + /// The routing topology to use. + /// The connection string to use when connecting to the broker. + public RabbitMQTransport(RoutingTopology routingTopology, string connectionString) + : this(routingTopology, connectionString, null) + { + } + /// /// Creates a new instance of the RabbitMQ transport. /// /// The routing topology to use. /// The connection string to use when connecting to the broker. /// The connection string to use when connecting to the management API - public RabbitMQTransport(RoutingTopology routingTopology, string connectionString, string managementConnectionString = null) + public RabbitMQTransport(RoutingTopology routingTopology, string connectionString, string managementConnectionString) : base(TransportTransactionMode.ReceiveOnly, supportsDelayedDelivery: true, supportsPublishSubscribe: true, @@ -53,9 +63,20 @@ public RabbitMQTransport(RoutingTopology routingTopology, string connectionStrin /// /// The routing topology to use. /// The connection string to use when connecting to the broker. - /// The connection string to use when connecting to the management API /// Should the delayed delivery infrastructure be created by the endpoint - public RabbitMQTransport(RoutingTopology routingTopology, string connectionString, bool enableDelayedDelivery, string managementConnectionString = null) + public RabbitMQTransport(RoutingTopology routingTopology, string connectionString, bool enableDelayedDelivery) + : this(routingTopology, connectionString, enableDelayedDelivery, null) + { + } + + /// + /// Creates a new instance of the RabbitMQ transport. + /// + /// The routing topology to use. + /// The connection string to use when connecting to the broker. + /// Should the delayed delivery infrastructure be created by the endpoint + /// The connection string to use when connecting to the management API + public RabbitMQTransport(RoutingTopology routingTopology, string connectionString, bool enableDelayedDelivery, string managementConnectionString) : base(TransportTransactionMode.ReceiveOnly, supportsDelayedDelivery: enableDelayedDelivery, supportsPublishSubscribe: true, From d02581d034f1328a1ad5e9aba101ab004c3cafa0 Mon Sep 17 00:00:00 2001 From: Andreas Bednarz Date: Thu, 19 Dec 2024 17:26:53 +1100 Subject: [PATCH 46/46] Fix approval test --- .../ApprovalFiles/APIApprovals.Approve.approved.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/NServiceBus.Transport.RabbitMQ.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.Transport.RabbitMQ.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt index 53d8d93bc..a690d0f59 100644 --- a/src/NServiceBus.Transport.RabbitMQ.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.Transport.RabbitMQ.Tests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -16,8 +16,10 @@ namespace NServiceBus } public class RabbitMQTransport : NServiceBus.Transport.TransportDefinition { - public RabbitMQTransport(NServiceBus.RoutingTopology routingTopology, string connectionString, string managementConnectionString = null) { } - public RabbitMQTransport(NServiceBus.RoutingTopology routingTopology, string connectionString, bool enableDelayedDelivery, string managementConnectionString = null) { } + public RabbitMQTransport(NServiceBus.RoutingTopology routingTopology, string connectionString) { } + public RabbitMQTransport(NServiceBus.RoutingTopology routingTopology, string connectionString, bool enableDelayedDelivery) { } + public RabbitMQTransport(NServiceBus.RoutingTopology routingTopology, string connectionString, string managementConnectionString) { } + public RabbitMQTransport(NServiceBus.RoutingTopology routingTopology, string connectionString, bool enableDelayedDelivery, string managementConnectionString) { } public System.Security.Cryptography.X509Certificates.X509Certificate2 ClientCertificate { get; set; } public bool DoNotUseManagementClient { get; set; } public System.TimeSpan HeartbeatInterval { get; set; }