diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index bb6efbd1e1..9494512cec 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -28,7 +28,7 @@ - + @@ -41,7 +41,7 @@ - + diff --git a/src/ServiceControl.Transports.ASBS.Tests/ServiceControl.Transports.ASBS.Tests.csproj b/src/ServiceControl.Transports.ASBS.Tests/ServiceControl.Transports.ASBS.Tests.csproj index 58b234a7e1..eb5e30a4ce 100644 --- a/src/ServiceControl.Transports.ASBS.Tests/ServiceControl.Transports.ASBS.Tests.csproj +++ b/src/ServiceControl.Transports.ASBS.Tests/ServiceControl.Transports.ASBS.Tests.csproj @@ -5,6 +5,7 @@ + diff --git a/src/ServiceControl.Transports.ASBS/ASBSTransportCustomization.cs b/src/ServiceControl.Transports.ASBS/ASBSTransportCustomization.cs index 7b84f142f5..5a600a385f 100644 --- a/src/ServiceControl.Transports.ASBS/ASBSTransportCustomization.cs +++ b/src/ServiceControl.Transports.ASBS/ASBSTransportCustomization.cs @@ -1,9 +1,12 @@ namespace ServiceControl.Transports.ASBS { using System.Linq; + using System.Text.Json; using BrokerThroughput; + using Configuration; using Microsoft.Extensions.DependencyInjection; using NServiceBus; + using NServiceBus.Transport.AzureServiceBus; public class ASBSTransportCustomization : TransportCustomization { @@ -19,18 +22,27 @@ protected override void CustomizeTransportForMonitoringEndpoint(EndpointConfigur protected override AzureServiceBusTransport CreateTransport(TransportSettings transportSettings, TransportTransactionMode preferredTransactionMode = TransportTransactionMode.ReceiveOnly) { var connectionSettings = ConnectionStringParser.Parse(transportSettings.ConnectionString); - var transport = connectionSettings.AuthenticationMethod.CreateTransportDefinition(connectionSettings); - transport.UseWebSockets = connectionSettings.UseWebSockets; - if (connectionSettings.TopicName != null) + if (!transportSettings.TryGet(out TopicTopology selectedTopology)) { - transport.Topology = TopicTopology.Single(connectionSettings.TopicName); + //Topology is pre-selected and customized only when creating transport for the primary instance + //For all other cases use the connection string to determine which topology to use + if (connectionSettings.TopicName != null) + { +#pragma warning disable CS0618 // Type or member is obsolete + selectedTopology = TopicTopology.MigrateFromNamedSingleTopic(connectionSettings.TopicName); +#pragma warning restore CS0618 // Type or member is obsolete + } + else + { + selectedTopology = TopicTopology.Default; + } } + var transport = connectionSettings.AuthenticationMethod.CreateTransportDefinition(connectionSettings, selectedTopology); + transport.UseWebSockets = connectionSettings.UseWebSockets; transport.EnablePartitioning = connectionSettings.EnablePartitioning; - transport.ConfigureNameShorteners(); - transport.TransportTransactionMode = transport.GetSupportedTransactionModes().Contains(preferredTransactionMode) ? preferredTransactionMode : TransportTransactionMode.ReceiveOnly; return transport; @@ -40,6 +52,46 @@ protected override void AddTransportForPrimaryCore(IServiceCollection services, TransportSettings transportSettings) { services.AddSingleton(); + + var connectionSettings = ConnectionStringParser.Parse(transportSettings.ConnectionString); + TopicTopology selectedTopology; + + var serviceBusRootNamespace = new SettingsRootNamespace("ServiceControl.Transport.ASBS"); + if (connectionSettings.TopicName != null) + { + //Bundle name provided -> use migration topology + //Need to explicitly specific events to be published on the single topic +#pragma warning disable CS0618 // Type or member is obsolete + selectedTopology = TopicTopology.FromOptions(new MigrationTopologyOptions +#pragma warning restore CS0618 // Type or member is obsolete + { + TopicToPublishTo = connectionSettings.TopicName, + TopicToSubscribeOn = connectionSettings.TopicName, + EventsToMigrateMap = [ + "ServiceControl.Contracts.CustomCheckFailed", + "ServiceControl.Contracts.CustomCheckSucceeded", + "ServiceControl.Contracts.HeartbeatRestored", + "ServiceControl.Contracts.HeartbeatStopped", + "ServiceControl.Contracts.FailedMessagesArchived", + "ServiceControl.Contracts.FailedMessagesUnArchived", + "ServiceControl.Contracts.MessageFailed", + "ServiceControl.Contracts.MessageFailureResolvedByRetry", + "ServiceControl.Contracts.MessageFailureResolvedManually" + ] + }); + } + else if (SettingsReader.TryRead(serviceBusRootNamespace, "Topology", out var topologyJson)) + { + //Load topology from json + selectedTopology = TopicTopology.FromOptions(JsonSerializer.Deserialize(topologyJson, TopologyOptionsSerializationContext.Default.TopologyOptions)); + } + else + { + //Default to topic-per-event topology + selectedTopology = TopicTopology.Default; + } + + transportSettings.Set(selectedTopology); } protected override void AddTransportForMonitoringCore(IServiceCollection services, TransportSettings transportSettings) diff --git a/src/ServiceControl.Transports.ASBS/AuthenticationMethod.cs b/src/ServiceControl.Transports.ASBS/AuthenticationMethod.cs index a9f61fc1e3..966b2f3c3d 100644 --- a/src/ServiceControl.Transports.ASBS/AuthenticationMethod.cs +++ b/src/ServiceControl.Transports.ASBS/AuthenticationMethod.cs @@ -6,6 +6,6 @@ public abstract class AuthenticationMethod { public abstract ServiceBusAdministrationClient BuildManagementClient(); - public abstract AzureServiceBusTransport CreateTransportDefinition(ConnectionSettings connectionSettings); + public abstract AzureServiceBusTransport CreateTransportDefinition(ConnectionSettings connectionSettings, TopicTopology topology); } } \ No newline at end of file diff --git a/src/ServiceControl.Transports.ASBS/Helper.cs b/src/ServiceControl.Transports.ASBS/Helper.cs deleted file mode 100644 index 8d73edc2bf..0000000000 --- a/src/ServiceControl.Transports.ASBS/Helper.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace ServiceControl.Transports.ASBS -{ - using System; - using System.Security.Cryptography; - using System.Text; - using NServiceBus; - - public static class Helper - { - public static void ConfigureNameShorteners(this AzureServiceBusTransport transport) - { - transport.SubscriptionNamingConvention = n => n.Length > MaxEntityName ? MD5DeterministicNameBuilder.Build(n) : n; - transport.SubscriptionRuleNamingConvention = n => n.FullName.Length > MaxEntityName ? MD5DeterministicNameBuilder.Build(n.FullName) : n.FullName; - } - - const int MaxEntityName = 50; - - static class MD5DeterministicNameBuilder - { - public static string Build(string input) - { - var inputBytes = Encoding.Default.GetBytes(input); - - // use MD5 hash to get a 16-byte hash of the string - var hashBytes = MD5.HashData(inputBytes); - - // generate a guid from the hash: - return new Guid(hashBytes).ToString(); - } - } - } -} \ No newline at end of file diff --git a/src/ServiceControl.Transports.ASBS/SharedAccessSignatureAuthentication.cs b/src/ServiceControl.Transports.ASBS/SharedAccessSignatureAuthentication.cs index 4539f883d0..5368353371 100644 --- a/src/ServiceControl.Transports.ASBS/SharedAccessSignatureAuthentication.cs +++ b/src/ServiceControl.Transports.ASBS/SharedAccessSignatureAuthentication.cs @@ -12,7 +12,7 @@ public class SharedAccessSignatureAuthentication : AuthenticationMethod public override ServiceBusAdministrationClient BuildManagementClient() => new ServiceBusAdministrationClient(ConnectionString); - public override AzureServiceBusTransport CreateTransportDefinition(ConnectionSettings connectionSettings) - => new AzureServiceBusTransport(ConnectionString); + public override AzureServiceBusTransport CreateTransportDefinition(ConnectionSettings connectionSettings, TopicTopology topology) + => new AzureServiceBusTransport(ConnectionString, topology); } } \ No newline at end of file diff --git a/src/ServiceControl.Transports.ASBS/TokenCredentialAuthentication.cs b/src/ServiceControl.Transports.ASBS/TokenCredentialAuthentication.cs index 4bbf898096..a11f74463f 100644 --- a/src/ServiceControl.Transports.ASBS/TokenCredentialAuthentication.cs +++ b/src/ServiceControl.Transports.ASBS/TokenCredentialAuthentication.cs @@ -4,7 +4,6 @@ using Azure.Identity; using Azure.Messaging.ServiceBus.Administration; using NServiceBus; - using NServiceBus.Transport; public class TokenCredentialAuthentication : AuthenticationMethod { @@ -30,9 +29,9 @@ public TokenCredentialAuthentication(string fullyQualifiedNamespace, string clie public override ServiceBusAdministrationClient BuildManagementClient() => new ServiceBusAdministrationClient(FullyQualifiedNamespace, Credential); - public override AzureServiceBusTransport CreateTransportDefinition(ConnectionSettings connectionSettings) + public override AzureServiceBusTransport CreateTransportDefinition(ConnectionSettings connectionSettings, TopicTopology topology) { - var transport = new AzureServiceBusTransport(FullyQualifiedNamespace, Credential); + var transport = new AzureServiceBusTransport(FullyQualifiedNamespace, Credential, topology); return transport; } } diff --git a/src/ServiceControl.Transports/ServiceControl.Transports.csproj b/src/ServiceControl.Transports/ServiceControl.Transports.csproj index 9d2526b3e9..6bd8eeea61 100644 --- a/src/ServiceControl.Transports/ServiceControl.Transports.csproj +++ b/src/ServiceControl.Transports/ServiceControl.Transports.csproj @@ -4,6 +4,11 @@ net8.0 + + + + +