Skip to content

Commit

Permalink
Merge pull request #182 from Project-MONAI/ac-1619-nds
Browse files Browse the repository at this point in the history
Ac 1619 nds
  • Loading branch information
neildsouth authored Mar 9, 2023
2 parents af25f2d + 41e1f4c commit 42a89ba
Show file tree
Hide file tree
Showing 2 changed files with 249 additions and 39 deletions.
78 changes: 39 additions & 39 deletions src/Plugins/RabbitMQ/Subscriber/RabbitMqMessageSubscriberService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -266,46 +266,7 @@ public void SubscribeAsync(string[] topics, string queue, Func<MessageReceivedEv
{
var deadLetterQueueDeclareResult = _channel.QueueDeclare(queue: deadLetterQueue, durable: true, exclusive: false, autoDelete: false);
BindToRoutingKeys(topics, queueDeclareResult.QueueName, deadLetterQueueDeclareResult.QueueName);

var consumer = new EventingBasicConsumer(_channel);
consumer.Received += async (model, eventArgs) =>
{
using var loggingScope = _logger.BeginScope(new LoggingDataDictionary<string, object>
{
["MessageId"] = eventArgs.BasicProperties.MessageId,
["ApplicationId"] = eventArgs.BasicProperties.AppId,
["CorrelationId"] = eventArgs.BasicProperties.CorrelationId,
["RecievedTime"] = DateTime.UtcNow
});

_logger.MessageReceivedFromQueue(queueDeclareResult.QueueName, eventArgs.RoutingKey);

MessageReceivedEventArgs messageReceivedEventArgs;
try
{
messageReceivedEventArgs = CreateMessage(eventArgs.RoutingKey, eventArgs);
}
catch (Exception ex)
{
_logger.InvalidMessage(queueDeclareResult.QueueName, eventArgs.RoutingKey, eventArgs.BasicProperties.MessageId, ex);

_logger.SendingNAcknowledgement(eventArgs.BasicProperties.MessageId);
_channel.BasicNack(eventArgs.DeliveryTag, multiple: false, requeue: false);
_logger.NAcknowledgementSent(eventArgs.BasicProperties.MessageId, false);
return;
}
try
{
await messageReceivedCallback(messageReceivedEventArgs).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorNotHandledByCallback(queueDeclareResult.QueueName, eventArgs.RoutingKey, eventArgs.BasicProperties.MessageId, ex);
}
};
_channel.BasicQos(0, prefetchCount, false);
_channel.BasicConsume(queueDeclareResult.QueueName, false, consumer);
_logger.SubscribeToRabbitMQQueue(_endpoint, _virtualHost, _exchange, queueDeclareResult.QueueName, string.Join(',', topics));
}
catch (OperationInterruptedException operationInterruptedException)
{
Expand All @@ -320,6 +281,45 @@ public void SubscribeAsync(string[] topics, string queue, Func<MessageReceivedEv
throw;
}
}
var consumer = new EventingBasicConsumer(_channel);
consumer.Received += async (model, eventArgs) =>
{
using var loggingScope = _logger.BeginScope(new LoggingDataDictionary<string, object>
{
["MessageId"] = eventArgs.BasicProperties.MessageId,
["ApplicationId"] = eventArgs.BasicProperties.AppId,
["CorrelationId"] = eventArgs.BasicProperties.CorrelationId,
["RecievedTime"] = DateTime.UtcNow
});

_logger.MessageReceivedFromQueue(queueDeclareResult.QueueName, eventArgs.RoutingKey);

MessageReceivedEventArgs messageReceivedEventArgs;
try
{
messageReceivedEventArgs = CreateMessage(eventArgs.RoutingKey, eventArgs);
}
catch (Exception ex)
{
_logger.InvalidMessage(queueDeclareResult.QueueName, eventArgs.RoutingKey, eventArgs.BasicProperties.MessageId, ex);

_logger.SendingNAcknowledgement(eventArgs.BasicProperties.MessageId);
_channel.BasicNack(eventArgs.DeliveryTag, multiple: false, requeue: false);
_logger.NAcknowledgementSent(eventArgs.BasicProperties.MessageId, false);
return;
}
try
{
await messageReceivedCallback(messageReceivedEventArgs).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorNotHandledByCallback(queueDeclareResult.QueueName, eventArgs.RoutingKey, eventArgs.BasicProperties.MessageId, ex);
}
};

_channel.BasicConsume(queueDeclareResult.QueueName, false, consumer);
_logger.SubscribeToRabbitMQQueue(_endpoint, _virtualHost, _exchange, queueDeclareResult.QueueName, string.Join(',', topics));
}

public void Acknowledge(MessageBase message)
Expand Down
210 changes: 210 additions & 0 deletions src/Plugins/RabbitMQ/Tests/Unit/RabbitMqMessageSubscriberServiceTest.cs
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using Monai.Deploy.Messaging.Messages;
using Moq;
using RabbitMQ.Client;
using RabbitMQ.Client.Exceptions;
using Xunit;

namespace Monai.Deploy.Messaging.RabbitMQ.Tests.Unit
Expand Down Expand Up @@ -165,6 +166,215 @@ await Task.Run(() =>
Assert.Equal(message.Body, s_messageReceived.Body);
}

[Fact(DisplayName = "Subscribes to a topic and previously created dead-letter queue is down")]
public void SubscribesToATopicAndDeadLetterQueueIsDown()
{
_options.Value.SubscriberSettings.Add(ConfigurationKeys.EndPoint, "endpoint");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.Username, "username");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.Password, "password");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.VirtualHost, "virtual-host");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.Exchange, "exchange");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.DeadLetterExchange, "exchange-dead-letter");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.DeliveryLimit, "3");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.RequeueDelay, "30");

var jsonMessage = new JsonMessage<string>("hello world", Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "1");
var message = jsonMessage.ToMessage();

var creationTime = DateTimeOffset.FromUnixTimeSeconds(new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds());

var basicProperties = new Mock<IBasicProperties>();
basicProperties.SetupGet(p => p.MessageId).Returns(jsonMessage.MessageId);
basicProperties.SetupGet(p => p.AppId).Returns(jsonMessage.ApplicationId);
basicProperties.SetupGet(p => p.ContentType).Returns(jsonMessage.ContentType);
basicProperties.SetupGet(p => p.CorrelationId).Returns(jsonMessage.CorrelationId);
basicProperties.SetupGet(p => p.Headers["CreationDateTime"]).Returns(Encoding.UTF8.GetBytes(creationTime.ToString("o", CultureInfo.InvariantCulture)));

basicProperties.SetupGet(p => p.Type).Returns("topic");
basicProperties.SetupGet(p => p.Timestamp).Returns(new AmqpTimestamp(creationTime.ToUnixTimeSeconds()));

_model.Setup(p => p.QueueDeclare(
"queue",
It.IsAny<bool>(),
It.IsAny<bool>(),
It.IsAny<bool>(),
It.IsAny<IDictionary<string, object>>()))
.Returns(new QueueDeclareOk("queue-name", 1, 1));

var exception = new OperationInterruptedException(new ShutdownEventArgs(ShutdownInitiator.Peer, 404,
"NOT_FOUND - home node '[email protected]' of durable queue 'md.workflow.request-dead-letter' in vhost 'monaideploy' is down or inaccessible"));

_model.Setup(p => p.QueueDeclare(
"queue-dead-letter",
It.IsAny<bool>(),
It.IsAny<bool>(),
It.IsAny<bool>(),
It.IsAny<IDictionary<string, object>>()))
.Throws(exception);

_model.Setup(p => p.QueueBind(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<IDictionary<string, object>>()));
_model.Setup(p => p.BasicQos(
It.IsAny<uint>(),
It.IsAny<ushort>(),
It.IsAny<bool>()));
_model.Setup(p => p.BasicConsume(
It.IsAny<string>(),
It.IsAny<bool>(),
It.IsAny<string>(),
It.IsAny<bool>(),
It.IsAny<bool>(),
It.IsAny<IDictionary<string, object>>(),
It.IsAny<IBasicConsumer>()))
.Callback<string, bool, string, bool, bool, IDictionary<string, object>, IBasicConsumer>(
(queue, autoAck, tag, noLocal, exclusive, args, consumer) =>
{
consumer.HandleBasicDeliver(tag, Convert.ToUInt64(jsonMessage.DeliveryTag, CultureInfo.InvariantCulture), false, "exchange", "topic", basicProperties.Object, new ReadOnlyMemory<byte>(message.Body));
});

var service = new RabbitMQMessageSubscriberService(_options, _logger.Object, _connectionFactory.Object);

service.Subscribe("topic", "queue", (args) =>
{
Assert.Equal(message.ApplicationId, args.Message.ApplicationId);
Assert.Equal(message.ContentType, args.Message.ContentType);
Assert.Equal(message.MessageId, args.Message.MessageId);
Assert.Equal(creationTime.ToUniversalTime(), args.Message.CreationDateTime.ToUniversalTime());
Assert.Equal(message.DeliveryTag, args.Message.DeliveryTag);
Assert.Equal("topic", args.Message.MessageDescription);
Assert.Equal(message.MessageId, args.Message.MessageId);
Assert.Equal(message.Body, args.Message.Body);
});

service.SubscribeAsync("topic", "queue", async (args) =>
{
await Task.Run(() =>
{
s_messageReceived = args.Message;
service.Acknowledge(args.Message);
}).ConfigureAwait(false);
});

// wait for it to pick up meassage
Task.Delay(500).GetAwaiter().GetResult();

Assert.NotNull(s_messageReceived);
Assert.Equal(message.ApplicationId, s_messageReceived.ApplicationId);
Assert.Equal(message.ContentType, s_messageReceived.ContentType);
Assert.Equal(message.MessageId, s_messageReceived.MessageId);
Assert.Equal(creationTime.ToUniversalTime(), s_messageReceived.CreationDateTime.ToUniversalTime());
Assert.Equal(message.DeliveryTag, s_messageReceived.DeliveryTag);
Assert.Equal("topic", s_messageReceived.MessageDescription);
Assert.Equal(message.MessageId, s_messageReceived.MessageId);
Assert.Equal(message.Body, s_messageReceived.Body);
}

[Fact(DisplayName = "Subscribes to a topic and dead-letter queue subscription fails with generic exception")]
public void SubscribesToATopicAndDeadLetterQueueSubscriptionFailsWithGenericException()
{
_options.Value.SubscriberSettings.Add(ConfigurationKeys.EndPoint, "endpoint");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.Username, "username");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.Password, "password");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.VirtualHost, "virtual-host");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.Exchange, "exchange");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.DeadLetterExchange, "exchange-dead-letter");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.DeliveryLimit, "3");
_options.Value.SubscriberSettings.Add(ConfigurationKeys.RequeueDelay, "30");

var jsonMessage = new JsonMessage<string>("hello world", Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), "1");
var message = jsonMessage.ToMessage();

var creationTime = DateTimeOffset.FromUnixTimeSeconds(new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds());

var basicProperties = new Mock<IBasicProperties>();
basicProperties.SetupGet(p => p.MessageId).Returns(jsonMessage.MessageId);
basicProperties.SetupGet(p => p.AppId).Returns(jsonMessage.ApplicationId);
basicProperties.SetupGet(p => p.ContentType).Returns(jsonMessage.ContentType);
basicProperties.SetupGet(p => p.CorrelationId).Returns(jsonMessage.CorrelationId);
basicProperties.SetupGet(p => p.Headers["CreationDateTime"]).Returns(Encoding.UTF8.GetBytes(creationTime.ToString("o", CultureInfo.InvariantCulture)));

basicProperties.SetupGet(p => p.Type).Returns("topic");
basicProperties.SetupGet(p => p.Timestamp).Returns(new AmqpTimestamp(creationTime.ToUnixTimeSeconds()));

_model.Setup(p => p.QueueDeclare(
"queue",
It.IsAny<bool>(),
It.IsAny<bool>(),
It.IsAny<bool>(),
It.IsAny<IDictionary<string, object>>()))
.Returns(new QueueDeclareOk("queue-name", 1, 1));

var exception = new OperationInterruptedException(new ShutdownEventArgs(ShutdownInitiator.Application, 500,
"Something else went wrong"));

_model.Setup(p => p.QueueDeclare(
"queue-dead-letter",
It.IsAny<bool>(),
It.IsAny<bool>(),
It.IsAny<bool>(),
It.IsAny<IDictionary<string, object>>()))
.Throws(exception);

_model.Setup(p => p.QueueBind(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<IDictionary<string, object>>()));
_model.Setup(p => p.BasicQos(
It.IsAny<uint>(),
It.IsAny<ushort>(),
It.IsAny<bool>()));
_model.Setup(p => p.BasicConsume(
It.IsAny<string>(),
It.IsAny<bool>(),
It.IsAny<string>(),
It.IsAny<bool>(),
It.IsAny<bool>(),
It.IsAny<IDictionary<string, object>>(),
It.IsAny<IBasicConsumer>()))
.Callback<string, bool, string, bool, bool, IDictionary<string, object>, IBasicConsumer>(
(queue, autoAck, tag, noLocal, exclusive, args, consumer) =>
{
consumer.HandleBasicDeliver(tag, Convert.ToUInt64(jsonMessage.DeliveryTag, CultureInfo.InvariantCulture), false, "exchange", "topic", basicProperties.Object, new ReadOnlyMemory<byte>(message.Body));
});

var service = new RabbitMQMessageSubscriberService(_options, _logger.Object, _connectionFactory.Object);

var act = () =>
{
service.Subscribe("topic", "queue", (args) =>
{
Assert.Equal(message.ApplicationId, args.Message.ApplicationId);
Assert.Equal(message.ContentType, args.Message.ContentType);
Assert.Equal(message.MessageId, args.Message.MessageId);
Assert.Equal(creationTime.ToUniversalTime(), args.Message.CreationDateTime.ToUniversalTime());
Assert.Equal(message.DeliveryTag, args.Message.DeliveryTag);
Assert.Equal("topic", args.Message.MessageDescription);
Assert.Equal(message.MessageId, args.Message.MessageId);
Assert.Equal(message.Body, args.Message.Body);

});
};
Assert.Throws<OperationInterruptedException>(act);

var asyncAct = () =>
{
service.SubscribeAsync("topic", "queue", async (args) =>
{
await Task.Run(() =>
{
s_messageReceived = args.Message;
service.Acknowledge(args.Message);
}).ConfigureAwait(false);

});
};
Assert.Throws<OperationInterruptedException>(asyncAct);
}

[Fact(DisplayName = "Acknowledge a message")]
public void AcknowledgeAMessage()
{
Expand Down

0 comments on commit 42a89ba

Please sign in to comment.