public async Task Should_handle_from_service_bus_queue() { var fakeHandler = A.Fake <ITestPumpCallback>(); A.CallTo(() => fakeHandler.Handle(A <TransportMessage> .Ignored, A <IEndpoint> .Ignored)).Returns(Task.CompletedTask); var pump = new AzureServiceBusMessagePump(fakeHandler.Handle, IntegrationTestFixture.QueueEndpoint, null, new AzureServiceBusMessagePumpSettings() { MaxConcurrentHandlers = 1, MaxCompletionImmediateRetry = 1 }); await pump.StartAsync(CancellationToken.None); var message = new TestMessage() { Id = Guid.NewGuid(), Description = "Test Handle Queue", MessageDate = DateTime.Now, Name = "Queue", }; var messageBytes = IntegrationTestFixture.Serialize(message); await IntegrationTestFixture.WriteToQueue(IntegrationTestFixture.QueueEndpoint, message.MessageType, messageBytes, null); await Task.Delay(3000); A.CallTo(() => fakeHandler.Handle( A <TransportMessage> .That.Matches(x => x.MessageTypeIdentifier.Equals(message.MessageType) && x.Data.SequenceEqual(messageBytes) ), A <IEndpoint> .That.Matches(x => x.Equals(IntegrationTestFixture.QueueEndpoint)) )).MustHaveHappenedOnceOrMore(); await pump.StopAsync(); }
/// <summary> /// Adds a background job to the <see cref="IServiceCollection"/> to automatically restart a <see cref="AzureServiceBusMessagePump"/> with a specific <paramref name="jobId"/> /// when the Azure Key Vault secret that holds the Azure Service Bus connection string was updated. /// </summary> /// <param name="services">The collection of services to add the job to.</param> /// <param name="jobId">The unique background job ID to identify which message pump to restart.</param> /// <param name="subscriptionNamePrefix">The name of the Azure Service Bus subscription that will be created to receive <see cref="CloudEvent"/>'s.</param> /// <param name="serviceBusTopicConnectionStringSecretKey">The secret key that points to the Azure Service Bus Topic connection string.</param> /// <param name="messagePumpConnectionStringKey"> /// The secret key where the connection string credentials are located for the target message pump that needs to be auto-restarted. /// </param> /// <exception cref="ArgumentNullException"> /// Thrown when the <paramref name="services"/> or the searched for <see cref="AzureServiceBusMessagePump"/> based on the given <paramref name="jobId"/> is <c>null</c>. /// </exception> /// <exception cref="ArgumentException"> /// Thrown when the <paramref name="subscriptionNamePrefix"/> or <paramref name="serviceBusTopicConnectionStringSecretKey"/> is blank. /// </exception> public static IServiceCollection AddAutoRestartServiceBusMessagePumpOnRotatedCredentialsBackgroundJob( this IServiceCollection services, string jobId, string subscriptionNamePrefix, string serviceBusTopicConnectionStringSecretKey, string messagePumpConnectionStringKey) { Guard.NotNull(services, nameof(services), "Requires a collection of services to add the re-authentication background job"); Guard.NotNullOrWhitespace(jobId, nameof(jobId), "Requires a non-blank job ID to identify the Azure Service Bus message pump which needs to restart"); Guard.NotNullOrWhitespace(subscriptionNamePrefix, nameof(subscriptionNamePrefix), "Requires a non-blank subscription name of the Azure Service Bus Topic subscription, to receive Azure Key Vault events"); Guard.NotNullOrWhitespace(serviceBusTopicConnectionStringSecretKey, nameof(serviceBusTopicConnectionStringSecretKey), "Requires a non-blank secret key that points to a Azure Service Bus Topic"); Guard.NotNullOrWhitespace(messagePumpConnectionStringKey, nameof(messagePumpConnectionStringKey), "Requires a non-blank secret key that points to the credentials that holds the connection string of the target message pump"); services.AddCloudEventBackgroundJob(subscriptionNamePrefix, serviceBusTopicConnectionStringSecretKey); services.WithServiceBusMessageHandler <ReAuthenticateOnRotatedCredentialsMessageHandler, CloudEvent>( messageBodyFilter: cloudEvent => cloudEvent?.GetPayload <SecretNewVersionCreated>() != null, implementationFactory: serviceProvider => { AzureServiceBusMessagePump messagePump = serviceProvider.GetServices <IHostedService>() .OfType <AzureServiceBusMessagePump>() .FirstOrDefault(pump => pump.JobId == jobId); if (messagePump is null) { throw new InvalidOperationException( $"Cannot register re-authentication without a '{nameof(AzureServiceBusMessagePump)}' with job id {jobId}"); } var messageHandlerLogger = serviceProvider.GetRequiredService <ILogger <ReAuthenticateOnRotatedCredentialsMessageHandler> >(); return(new ReAuthenticateOnRotatedCredentialsMessageHandler(messagePumpConnectionStringKey, messagePump, messageHandlerLogger)); }); return(services); }
/// <summary> /// Initializes a new instance of the <see cref="ReAuthenticateOnRotatedCredentialsMessageHandler"/> class. /// </summary> /// <param name="targetConnectionStringKey">The secret key where the connection string credentials are located for the target message pump that needs to be auto-restarted.</param> /// <param name="messagePump">The message pump instance to restart when the message handler process an <see cref="SecretNewVersionCreated"/> event.</param> /// <param name="logger">The logger instance to write diagnostic trace messages during the processing of the event.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="messagePump"/> or <paramref name="logger"/> is <c>null</c>.</exception> public ReAuthenticateOnRotatedCredentialsMessageHandler( string targetConnectionStringKey, AzureServiceBusMessagePump messagePump, ILogger <ReAuthenticateOnRotatedCredentialsMessageHandler> logger) { Guard.NotNullOrWhitespace(targetConnectionStringKey, nameof(targetConnectionStringKey), "Requires a non-blank secret key that points to the credentials that holds the connection string of the target message pump"); Guard.NotNull(messagePump, nameof(messagePump), $"Requires an message pump instance to restart when the message handler process an {nameof(SecretNewVersionCreated)} event"); Guard.NotNull(logger, nameof(logger), "Requires an logger instance to write diagnostic trace messages during the processing of the event"); _targetConnectionStringKey = targetConnectionStringKey; _messagePump = messagePump; _logger = logger; }
/// <summary> /// Initializes a new instance of the <see cref="ReAuthenticateOnRotatedCredentialsMessageHandler"/> class. /// </summary> /// <param name="targetConnectionStringKey">The secret key where the connection string credentials are located for the target message pump that needs to be auto-restarted.</param> /// <param name="messagePump">The message pump instance to restart when the message handler process an <see cref="SecretNewVersionCreated"/> event.</param> /// <exception cref="ArgumentNullException">Thrown when the <paramref name="messagePump"/> is <c>null</c>.</exception> public ReAuthenticateOnRotatedCredentialsMessageHandler( string targetConnectionStringKey, AzureServiceBusMessagePump messagePump) : this(targetConnectionStringKey, messagePump, NullLogger <ReAuthenticateOnRotatedCredentialsMessageHandler> .Instance) { }
public async Task Should_defer_and_queue_control_message() { var fakeHandler = A.Fake <ITestPumpCallback>(); A.CallTo(() => fakeHandler.Handle(A <TransportMessage> .Ignored, A <IEndpoint> .Ignored)) .Throws <Exception>().Twice() .Then.Returns(Task.CompletedTask); var deferRecoverabilityProvider = new AzureServiceBusDeferRecoverabilityProvider( new ConstantRetryStrategy(3) ); var wrappedRecoverability = A.Fake <IAzureServiceBusRecoverabilityProvider>(x => x.Wrapping(deferRecoverabilityProvider)); var pump = new AzureServiceBusMessagePump(fakeHandler.Handle, IntegrationTestFixture.QueueEndpoint, null, new AzureServiceBusMessagePumpSettings() { MaxConcurrentHandlers = 1, }, wrappedRecoverability ); await pump.StartAsync(CancellationToken.None); var message = new TestMessage() { Id = Guid.NewGuid(), Description = "Test Handle Deferred Retry", MessageDate = DateTime.Now, Name = "Queue Deferred Retry", }; var messageBytes = IntegrationTestFixture.Serialize(message); await IntegrationTestFixture.WriteToQueue(IntegrationTestFixture.QueueEndpoint, message.MessageType, messageBytes, null); await Task.Delay(11000); ///This doesn't seem to work because the Message may be getting reused so the value changes ////recover should have deferred and scheduled recover control message //A.CallTo(() => wrappedRecoverability.OnPreHandle( // A<RecoverabilityContext>.That.Matches(x => // x.Message.Label == ("ControlMessage.Recover") // ) //)).MustHaveHappenedTwiceExactly(); //handled exception by calling recover A.CallTo(() => wrappedRecoverability.Recover( A <RecoverabilityContext> .That.Matches(x => x.Message.Body.SequenceEqual(messageBytes) && x.Endpoint == IntegrationTestFixture.QueueEndpoint && !x.TempData.ContainsKey("ControlMessage") ) )).MustHaveHappenedOnceExactly(); A.CallTo(() => wrappedRecoverability.Recover( A <RecoverabilityContext> .That.Matches(x => x.Message.Body.SequenceEqual(messageBytes) && x.Endpoint == IntegrationTestFixture.QueueEndpoint && x.TempData.ContainsKey("ControlMessage") ) )).MustHaveHappenedOnceExactly(); // should have been called 2 times with failure then again with success of deferred message A.CallTo(() => fakeHandler.Handle( A <TransportMessage> .That.Matches(x => x.MessageTypeIdentifier.Equals(message.MessageType) && x.Data.SequenceEqual(messageBytes) ), A <IEndpoint> .That.Matches(x => x.Equals(IntegrationTestFixture.QueueEndpoint)) )).MustHaveHappened(3, Times.Exactly); await pump.StopAsync(); }