|
| 1 | +// SPDX-FileCopyrightText: © 2021-2022 MONAI Consortium |
| 2 | +// SPDX-License-Identifier: Apache License 2.0 |
| 3 | + |
| 4 | +using System.Globalization; |
| 5 | +using System.Text; |
| 6 | +using Amazon.S3; |
| 7 | +using Amazon.SQS; |
| 8 | +using Amazon.SQS.ExtendedClient; |
| 9 | +using Amazon.SQS.Model; |
| 10 | +using Ardalis.GuardClauses; |
| 11 | +using Microsoft.Extensions.Logging; |
| 12 | +using Microsoft.Extensions.Options; |
| 13 | +using Monai.Deploy.Messaging.Configuration; |
| 14 | + |
| 15 | +namespace Monai.Deploy.Messaging.SQS |
| 16 | +{ |
| 17 | + public class SQSMessagePublisherService : IMessageBrokerPublisherService |
| 18 | + { |
| 19 | + private const int PersistentDeliveryMode = 2; |
| 20 | + |
| 21 | + private readonly ILogger<SQSMessagePublisherService> _logger; |
| 22 | + private readonly string? _accessKey; |
| 23 | + private readonly string? _accessToken; |
| 24 | + private readonly string _environmentId = string.Empty; |
| 25 | + private bool _disposedValue; |
| 26 | + |
| 27 | + |
| 28 | + public string Name => "AWS SQS Publisher"; |
| 29 | + private readonly string _queueName; |
| 30 | + private readonly string _bucketName; |
| 31 | + private readonly AmazonSQSClient? _sqsClient; |
| 32 | + private readonly AmazonS3Client? _s3Client; |
| 33 | + private readonly AmazonSQSExtendedClient? _sqSExtendedClient; |
| 34 | + |
| 35 | + public SQSMessagePublisherService(IOptions<MessageBrokerServiceConfiguration> options, |
| 36 | + ILogger<SQSMessagePublisherService> logger) |
| 37 | + { |
| 38 | + Guard.Against.Null(options, nameof(options)); |
| 39 | + |
| 40 | + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
| 41 | + |
| 42 | + var configuration = options.Value; |
| 43 | + ValidateConfiguration(configuration); |
| 44 | + |
| 45 | + |
| 46 | + //This 2 config entries are mandatory. |
| 47 | + _queueName = configuration.PublisherSettings[SQSConfigurationKeys.WorkflowRequestQueue]; |
| 48 | + _bucketName = configuration.PublisherSettings[SQSConfigurationKeys.BucketName]; |
| 49 | + |
| 50 | + |
| 51 | + if (configuration.PublisherSettings.ContainsKey(SQSConfigurationKeys.AccessKey)) |
| 52 | + { |
| 53 | + _logger.LogInformation("accessKey found in configuration."); |
| 54 | + _accessKey = configuration.PublisherSettings[SQSConfigurationKeys.AccessKey]; |
| 55 | + } |
| 56 | + |
| 57 | + |
| 58 | + if (configuration.PublisherSettings.ContainsKey(SQSConfigurationKeys.AccessToken)) |
| 59 | + { |
| 60 | + _logger.LogInformation("accessToken found in configuration."); |
| 61 | + _accessToken = configuration.PublisherSettings[SQSConfigurationKeys.AccessToken]; |
| 62 | + } |
| 63 | + |
| 64 | + if (configuration.PublisherSettings.ContainsKey(SQSConfigurationKeys.Envid)) |
| 65 | + _environmentId = configuration.PublisherSettings[SQSConfigurationKeys.Envid]; |
| 66 | + |
| 67 | + try |
| 68 | + { |
| 69 | + _logger.ConnectingToSQS(Name); |
| 70 | + |
| 71 | + if (!(_accessKey is null) && !(_accessToken is null)) |
| 72 | + { |
| 73 | + _logger.LogInformation("Assuming IAM user as found in the configuration file."); |
| 74 | + _sqsClient = new AmazonSQSClient(_accessKey, _accessToken); |
| 75 | + _s3Client = new AmazonS3Client(_accessKey, _accessToken); |
| 76 | + } |
| 77 | + else |
| 78 | + { |
| 79 | + _logger.LogInformation("Attempting to assume local AWS credentials."); |
| 80 | + _sqsClient = new AmazonSQSClient(); |
| 81 | + _s3Client = new AmazonS3Client(); |
| 82 | + } |
| 83 | + |
| 84 | + _sqSExtendedClient = new AmazonSQSExtendedClient(_sqsClient, |
| 85 | + new ExtendedClientConfiguration().WithLargePayloadSupportEnabled(_s3Client, _bucketName)); |
| 86 | + |
| 87 | + |
| 88 | + |
| 89 | + } |
| 90 | + catch (Amazon.SQS.AmazonSQSException Ex) |
| 91 | + { |
| 92 | + _logger.ConnectingToSQSError(Name, Ex); |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + private void ValidateConfiguration(MessageBrokerServiceConfiguration configuration) |
| 97 | + { |
| 98 | + Guard.Against.Null(configuration, nameof(configuration)); |
| 99 | + Guard.Against.Null(configuration.PublisherSettings, nameof(configuration.PublisherSettings)); |
| 100 | + |
| 101 | + foreach (var key in ConfigurationKeys.PublisherRequiredKeys) |
| 102 | + { |
| 103 | + if (!configuration.PublisherSettings.ContainsKey(key)) |
| 104 | + { |
| 105 | + throw new ConfigurationException($"{Name} is missing configuration for {key}."); |
| 106 | + } |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + public Task Publish(string topic, Monai.Deploy.Messaging.Messages.Message message) |
| 111 | + { |
| 112 | + |
| 113 | + Guard.Against.NullOrWhiteSpace(topic, nameof(topic)); |
| 114 | + Guard.Against.Null(message, nameof(message)); |
| 115 | + |
| 116 | + |
| 117 | + using var loggerScope = _logger.BeginScope(string.Format(CultureInfo.InvariantCulture, Log.LoggingScopeMessageApplication, message.MessageId, message.ApplicationId)); |
| 118 | + _logger.PublishingToSQS(topic, message.MessageId); |
| 119 | + var sendMessageRequest = new SendMessageRequest(); |
| 120 | + |
| 121 | + Dictionary<string, MessageAttributeValue> MessageAttributes = new Dictionary<string, MessageAttributeValue>(); |
| 122 | + MessageAttributeValue messageIdAttribute = new MessageAttributeValue(); |
| 123 | + messageIdAttribute.DataType = "String"; |
| 124 | + messageIdAttribute.StringValue = message.MessageId; |
| 125 | + MessageAttributes.Add("MessageId", messageIdAttribute); |
| 126 | + |
| 127 | + MessageAttributeValue ContentTypeAttribute = new MessageAttributeValue(); |
| 128 | + ContentTypeAttribute.DataType = "String"; |
| 129 | + ContentTypeAttribute.StringValue = message.ContentType; |
| 130 | + MessageAttributes.Add("ContentType", ContentTypeAttribute); |
| 131 | + |
| 132 | + |
| 133 | + MessageAttributeValue ApplicationIdAttribute = new MessageAttributeValue(); |
| 134 | + ApplicationIdAttribute.DataType = "String"; |
| 135 | + ApplicationIdAttribute.StringValue = message.MessageId; |
| 136 | + MessageAttributes.Add("ApplicationId", ApplicationIdAttribute); |
| 137 | + |
| 138 | + sendMessageRequest.MessageAttributes = MessageAttributes; |
| 139 | + |
| 140 | + |
| 141 | + Console.WriteLine("Message information : "); |
| 142 | + Console.WriteLine(message); |
| 143 | + Console.WriteLine(message.Body); |
| 144 | + Console.WriteLine(message.Body.Length); |
| 145 | + |
| 146 | + |
| 147 | + string queueName = QueueFormatter.FormatQueueName(_environmentId, _queueName, topic); |
| 148 | + _logger.LogDebug($"Attempting to create or subscribe to {queueName}"); |
| 149 | + |
| 150 | + var queueAttributes = new Dictionary<string, string>(); |
| 151 | + |
| 152 | + queueAttributes.Add("KmsMasterKeyId", "alias/aws/sqs"); |
| 153 | + var request = new CreateQueueRequest |
| 154 | + { |
| 155 | + Attributes = queueAttributes, |
| 156 | + QueueName = queueName |
| 157 | + }; |
| 158 | + |
| 159 | + CreateQueueResponse createQueueResponse = new CreateQueueResponse(); |
| 160 | + try |
| 161 | + { |
| 162 | + createQueueResponse = _sqSExtendedClient.CreateQueueAsync(request).Result; |
| 163 | + } |
| 164 | + catch (Exception ex) |
| 165 | + { |
| 166 | + _logger.LogDebug($"The queue could not be created or subscribed to: {ex.Message}"); |
| 167 | + } |
| 168 | + |
| 169 | + sendMessageRequest.QueueUrl = createQueueResponse.QueueUrl; |
| 170 | + |
| 171 | + |
| 172 | + |
| 173 | + sendMessageRequest.MessageBody = Encoding.UTF8.GetString(message.Body, 0, message.Body.Length); |
| 174 | + |
| 175 | + try |
| 176 | + { |
| 177 | + SendMessageResponse sqsresp = _sqSExtendedClient.SendMessageAsync(sendMessageRequest).Result; |
| 178 | + } |
| 179 | + catch(Exception e) |
| 180 | + { |
| 181 | + _logger.LogError($"The message could not be posted to the queue {queueName} : \n {e.Message}"); |
| 182 | + } |
| 183 | + |
| 184 | + |
| 185 | + return Task.CompletedTask; |
| 186 | + } |
| 187 | + |
| 188 | + |
| 189 | + protected virtual void Dispose(bool disposing) |
| 190 | + { |
| 191 | + if (!_disposedValue) |
| 192 | + { |
| 193 | + if (disposing) |
| 194 | + { |
| 195 | + // Dispose any managed objects |
| 196 | + } |
| 197 | + |
| 198 | + _disposedValue = true; |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + public void Dispose() |
| 203 | + { |
| 204 | + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method |
| 205 | + Dispose(disposing: true); |
| 206 | + GC.SuppressFinalize(this); |
| 207 | + } |
| 208 | + } |
| 209 | +} |
0 commit comments