public async Task CreateBatchAsyncRespectsTheMaximumSizeWhenProvided() { var expectedMaximumSize = 512; var options = new CreateBatchOptions { MaximumSizeInBytes = expectedMaximumSize }; var retryPolicy = new BasicRetryPolicy(new RetryOptions { TryTimeout = TimeSpan.FromSeconds(17) }); var producer = new Mock <AmqpProducer>("aHub", null, Mock.Of <AmqpConnectionScope>(), new AmqpMessageConverter(), retryPolicy) { CallBase = true }; producer .Protected() .Setup <Task <SendingAmqpLink> >("CreateLinkAndEnsureProducerStateAsync", ItExpr.IsAny <string>(), ItExpr.IsAny <TimeSpan>(), ItExpr.IsAny <CancellationToken>()) .Callback(() => SetMaximumMessageSize(producer.Object, expectedMaximumSize + 27)) .Returns(Task.FromResult(new SendingAmqpLink(new AmqpLinkSettings()))) .Verifiable(); using TransportEventBatch batch = await producer.Object.CreateBatchAsync(options, default); Assert.That(options.MaximumSizeInBytes, Is.EqualTo(expectedMaximumSize)); }
public async Task SendBatchRespectsTheCancellationTokenIfSetWhenCalled() { var expectedMaximumSize = 512; var options = new CreateBatchOptions(); var retryPolicy = new BasicRetryPolicy(new RetryOptions { TryTimeout = TimeSpan.FromSeconds(17) }); var producer = new Mock <AmqpProducer>("aHub", null, Mock.Of <AmqpConnectionScope>(), new AmqpMessageConverter(), retryPolicy) { CallBase = true }; producer .Protected() .Setup <Task <SendingAmqpLink> >("CreateLinkAndEnsureProducerStateAsync", ItExpr.IsAny <string>(), ItExpr.IsAny <TimeSpan>(), ItExpr.IsAny <CancellationToken>()) .Callback(() => SetMaximumMessageSize(producer.Object, expectedMaximumSize)) .Returns(Task.FromResult(new SendingAmqpLink(new AmqpLinkSettings()))); using TransportEventBatch batch = await producer.Object.CreateBatchAsync(options, default); using CancellationTokenSource cancellationSource = new CancellationTokenSource(); cancellationSource.Cancel(); Assert.That(async() => await producer.Object.SendAsync(new EventDataBatch(batch, options), cancellationSource.Token), Throws.InstanceOf <TaskCanceledException>()); }
public async Task CreateBatchAsyncBuildsAnAmqpEventBatchWithTheOptions() { var options = new CreateBatchOptions { MaximumSizeInBytes = 512 }; var retryPolicy = new BasicRetryPolicy(new RetryOptions { TryTimeout = TimeSpan.FromSeconds(17) }); var producer = new Mock <AmqpProducer>("aHub", null, Mock.Of <AmqpConnectionScope>(), new AmqpMessageConverter(), retryPolicy) { CallBase = true }; producer .Protected() .Setup <Task <SendingAmqpLink> >("CreateLinkAndEnsureProducerStateAsync", ItExpr.IsAny <string>(), ItExpr.IsAny <TimeSpan>(), ItExpr.IsAny <CancellationToken>()) .Callback(() => SetMaximumMessageSize(producer.Object, options.MaximumSizeInBytes.Value + 982)) .Returns(Task.FromResult(new SendingAmqpLink(new AmqpLinkSettings()))) .Verifiable(); using TransportEventBatch batch = await producer.Object.CreateBatchAsync(options, default); Assert.That(batch, Is.Not.Null, "The created batch should be populated."); Assert.That(batch, Is.InstanceOf <AmqpEventBatch>(), $"The created batch should be an { nameof(AmqpEventBatch) }."); Assert.That(GetEventBatchOptions((AmqpEventBatch)batch), Is.SameAs(options), "The provided options should have been used."); }
public async Task SendBatchEnsuresNotClosed() { var expectedMaximumSize = 512; var options = new CreateBatchOptions { MaximumSizeInBytes = null }; var retryPolicy = new BasicRetryPolicy(new EventHubsRetryOptions { TryTimeout = TimeSpan.FromSeconds(17) }); var producer = new Mock <AmqpProducer>("aHub", null, Mock.Of <AmqpConnectionScope>(), new AmqpMessageConverter(), retryPolicy) { CallBase = true }; producer .Protected() .Setup <Task <SendingAmqpLink> >("CreateLinkAndEnsureProducerStateAsync", ItExpr.IsAny <string>(), ItExpr.IsAny <TimeSpan>(), ItExpr.IsAny <CancellationToken>()) .Callback(() => SetMaximumMessageSize(producer.Object, expectedMaximumSize)) .Returns(Task.FromResult(new SendingAmqpLink(new AmqpLinkSettings()))); using TransportEventBatch batch = await producer.Object.CreateBatchAsync(options, default); await producer.Object.CloseAsync(CancellationToken.None); Assert.That(async() => await producer.Object.SendAsync(new EventDataBatch(batch, new SendEventOptions()), CancellationToken.None), Throws.InstanceOf <EventHubsException>().And.Property(nameof(EventHubsException.Reason)).EqualTo(EventHubsException.FailureReason.ClientClosed)); }
/// <summary> /// Initializes a new instance of the <see cref="EventDataBatch"/> class. /// </summary> /// /// <param name="transportBatch">The transport-specific batch responsible for performing the batch operations.</param> /// <param name="sendOptions">The set of options that should be used when publishing the batch.</param> /// /// <remarks> /// As an internal type, this class performs only basic sanity checks against its arguments. It /// is assumed that callers are trusted and have performed deep validation. /// /// Any parameters passed are assumed to be owned by this instance and safe to mutate or dispose; /// creation of clones or otherwise protecting the parameters is assumed to be the purview of the /// caller. /// </remarks> /// internal EventDataBatch(TransportEventBatch transportBatch, SendOptions sendOptions) { Guard.ArgumentNotNull(nameof(transportBatch), transportBatch); Guard.ArgumentNotNull(nameof(sendOptions), sendOptions); InnerBatch = transportBatch; SendOptions = sendOptions; }
/// <summary> /// Initializes a new instance of the <see cref="EventDataBatch"/> class. /// </summary> /// /// <param name="transportBatch">The transport-specific batch responsible for performing the batch operations.</param> /// <param name="sendOptions">The set of options that should be used when publishing the batch.</param> /// /// <remarks> /// As an internal type, this class performs only basic sanity checks against its arguments. It /// is assumed that callers are trusted and have performed deep validation. /// /// Any parameters passed are assumed to be owned by this instance and safe to mutate or dispose; /// creation of clones or otherwise protecting the parameters is assumed to be the purview of the /// caller. /// </remarks> /// internal EventDataBatch(TransportEventBatch transportBatch, SendEventOptions sendOptions) { Argument.AssertNotNull(transportBatch, nameof(transportBatch)); Argument.AssertNotNull(sendOptions, nameof(sendOptions)); InnerBatch = transportBatch; SendOptions = sendOptions; }
/// <summary> /// Creates a size-constraint batch to which <see cref="EventData" /> may be added using a try-based pattern. If an event would /// exceed the maximum allowable size of the batch, the batch will not allow adding the event and signal that scenario using its /// return value. /// /// Because events that would violate the size constraint cannot be added, publishing a batch will not trigger an exception when /// attempting to send the events to the Event Hubs service. /// </summary> /// /// <param name="options">The set of options to consider when creating this batch.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param> /// /// <returns>An <see cref="EventDataBatch" /> with the requested <paramref name="options"/>.</returns> /// /// <seealso cref="CreateBatchAsync(CreateBatchOptions, CancellationToken)" /> /// public virtual async ValueTask <EventDataBatch> CreateBatchAsync(CreateBatchOptions options, CancellationToken cancellationToken = default) { options = options?.Clone() ?? new CreateBatchOptions(); AssertSinglePartitionReference(options.PartitionId, options.PartitionKey); TransportEventBatch transportBatch = await GatewayProducer.CreateBatchAsync(options, cancellationToken).ConfigureAwait(false); return(new EventDataBatch(transportBatch, options)); }
/// <summary> /// Creates a size-constraint batch to which <see cref="EventData" /> may be added using a try-based pattern. If an event would /// exceed the maximum allowable size of the batch, the batch will not allow adding the event and signal that scenario using its /// return value. /// /// Because events that would violate the size constraint cannot be added, publishing a batch will not trigger an exception when /// attempting to send the events to the Event Hubs service. /// </summary> /// /// <param name="options">The set of options to consider when creating this batch.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken" /> instance to signal the request to cancel the operation.</param> /// /// <returns>An <see cref="EventDataBatch" /> with the requested <paramref name="options"/>.</returns> /// /// <seealso cref="CreateBatchAsync(CreateBatchOptions, CancellationToken)" /> /// public virtual async ValueTask <EventDataBatch> CreateBatchAsync(CreateBatchOptions options, CancellationToken cancellationToken = default) { options = options?.Clone() ?? new CreateBatchOptions(); AssertSinglePartitionReference(options.PartitionId, options.PartitionKey); TransportEventBatch transportBatch = await PartitionProducerPool.EventHubProducer.CreateBatchAsync(options, cancellationToken).ConfigureAwait(false); return(new EventDataBatch(transportBatch, FullyQualifiedNamespace, EventHubName, options.ToSendOptions())); }
/// <summary> /// Initializes a new instance of the <see cref="EventDataBatch"/> class. /// </summary> /// /// <param name="transportBatch">The transport-specific batch responsible for performing the batch operations.</param> /// <param name="sendOptions">The set of options that should be used when publishing the batch.</param> /// /// <remarks> /// As an internal type, this class performs only basic sanity checks against its arguments. It /// is assumed that callers are trusted and have performed deep validation. /// /// Any parameters passed are assumed to be owned by this instance and safe to mutate or dispose; /// creation of clones or otherwise protecting the parameters is assumed to be the purview of the /// caller. /// </remarks> /// internal EventDataBatch(TransportEventBatch transportBatch, SendOptions sendOptions) { Guard.ArgumentNotNull(nameof(transportBatch), transportBatch); Guard.ArgumentNotNull(nameof(sendOptions), sendOptions); InnerBatch = transportBatch; SendOptions = sendOptions; _clientDiagnostics = new ClientDiagnostics(isActivityEnabled: true); }
/// <summary> /// Initializes a new instance of the <see cref="EventDataBatch"/> class. /// </summary> /// /// <param name="transportBatch">The transport-specific batch responsible for performing the batch operations.</param> /// <param name="fullyQualifiedNamespace">The fully qualified Event Hubs namespace to use for instrumentation.</param> /// <param name="eventHubName">The name of the specific Event Hub to associate the events with during instrumentation.</param> /// <param name="sendOptions">The set of options that should be used when publishing the batch.</param> /// /// <remarks> /// As an internal type, this class performs only basic sanity checks against its arguments. It /// is assumed that callers are trusted and have performed deep validation. /// /// Any parameters passed are assumed to be owned by this instance and safe to mutate or dispose; /// creation of clones or otherwise protecting the parameters is assumed to be the purview of the /// caller. /// </remarks> /// internal EventDataBatch(TransportEventBatch transportBatch, string fullyQualifiedNamespace, string eventHubName, SendEventOptions sendOptions) { Argument.AssertNotNull(transportBatch, nameof(transportBatch)); Argument.AssertNotNullOrEmpty(fullyQualifiedNamespace, nameof(fullyQualifiedNamespace)); Argument.AssertNotNullOrEmpty(eventHubName, nameof(eventHubName)); Argument.AssertNotNull(sendOptions, nameof(sendOptions)); InnerBatch = transportBatch; FullyQualifiedNamespace = fullyQualifiedNamespace; EventHubName = eventHubName; SendOptions = sendOptions; }
public async Task SendBatchCreatesTheAmqpMessageFromTheBatch(string partitonKey) { var messageFactory = default(Func <AmqpMessage>); var expectedMaximumSize = 512; var options = new CreateBatchOptions { PartitionKey = partitonKey }; var retryPolicy = new BasicRetryPolicy(new RetryOptions { TryTimeout = TimeSpan.FromSeconds(17) }); var producer = new Mock <AmqpProducer>("aHub", null, Mock.Of <AmqpConnectionScope>(), new AmqpMessageConverter(), retryPolicy) { CallBase = true }; producer .Protected() .Setup <Task <SendingAmqpLink> >("CreateLinkAndEnsureProducerStateAsync", ItExpr.IsAny <string>(), ItExpr.IsAny <TimeSpan>(), ItExpr.IsAny <CancellationToken>()) .Callback(() => SetMaximumMessageSize(producer.Object, expectedMaximumSize)) .Returns(Task.FromResult(new SendingAmqpLink(new AmqpLinkSettings()))); producer .Protected() .Setup <Task>("SendAsync", ItExpr.IsAny <Func <AmqpMessage> >(), ItExpr.IsAny <string>(), ItExpr.IsAny <CancellationToken>()) .Callback <Func <AmqpMessage>, string, CancellationToken>((factory, key, token) => messageFactory = factory) .Returns(Task.CompletedTask); using TransportEventBatch transportBatch = await producer.Object.CreateBatchAsync(options, default); using var batch = new EventDataBatch(transportBatch, options); batch.TryAdd(new EventData(new byte[] { 0x15 })); await producer.Object.SendAsync(batch, CancellationToken.None); Assert.That(messageFactory, Is.Not.Null, "The batch message factory should have been set."); using var batchMessage = new AmqpMessageConverter().CreateBatchFromMessages(batch.AsEnumerable <AmqpMessage>(), partitonKey); using var factoryMessage = messageFactory(); Assert.That(factoryMessage.SerializedMessageSize, Is.EqualTo(batchMessage.SerializedMessageSize), "The serialized size of the messages should match."); }
public async Task SendBatchDoesNotDisposeTheEventsInTheSourceBatch() { var expectedMaximumSize = 512; var options = new CreateBatchOptions { MaximumSizeInBytes = null }; var retryPolicy = new BasicRetryPolicy(new RetryOptions { TryTimeout = TimeSpan.FromSeconds(17) }); var producer = new Mock <AmqpProducer>("aHub", null, Mock.Of <AmqpConnectionScope>(), new AmqpMessageConverter(), retryPolicy) { CallBase = true }; producer .Protected() .Setup <Task <SendingAmqpLink> >("CreateLinkAndEnsureProducerStateAsync", ItExpr.IsAny <string>(), ItExpr.IsAny <TimeSpan>(), ItExpr.IsAny <CancellationToken>()) .Callback(() => SetMaximumMessageSize(producer.Object, expectedMaximumSize)) .Returns(Task.FromResult(new SendingAmqpLink(new AmqpLinkSettings()))); producer .Protected() .Setup <Task>("SendAsync", ItExpr.IsAny <Func <AmqpMessage> >(), ItExpr.IsAny <string>(), ItExpr.IsAny <CancellationToken>()) .Returns(Task.CompletedTask); using TransportEventBatch transportBatch = await producer.Object.CreateBatchAsync(options, default); using var batch = new EventDataBatch(transportBatch, options); batch.TryAdd(new EventData(new byte[] { 0x15 })); await producer.Object.SendAsync(batch, CancellationToken.None); Assert.That(batch, Is.Not.Null, "The batch should not have been set to null."); Assert.That(() => batch.AsEnumerable <AmqpMessage>().Single().ThrowIfDisposed(), Throws.Nothing, "The events within the source batch should not have been disposed."); }
public async Task SendBatchUsesThePartitionKey() { var expectedMaximumSize = 512; var expectedPartitionKey = "some key"; var options = new CreateBatchOptions { PartitionKey = expectedPartitionKey }; var retryPolicy = new BasicRetryPolicy(new RetryOptions { TryTimeout = TimeSpan.FromSeconds(17) }); var producer = new Mock <AmqpProducer>("aHub", null, Mock.Of <AmqpConnectionScope>(), new AmqpMessageConverter(), retryPolicy) { CallBase = true }; producer .Protected() .Setup <Task <SendingAmqpLink> >("CreateLinkAndEnsureProducerStateAsync", ItExpr.IsAny <string>(), ItExpr.IsAny <TimeSpan>(), ItExpr.IsAny <CancellationToken>()) .Callback(() => SetMaximumMessageSize(producer.Object, expectedMaximumSize)) .Returns(Task.FromResult(new SendingAmqpLink(new AmqpLinkSettings()))); producer .Protected() .Setup <Task>("SendAsync", ItExpr.IsAny <Func <AmqpMessage> >(), ItExpr.Is <string>(value => value == expectedPartitionKey), ItExpr.IsAny <CancellationToken>()) .Returns(Task.CompletedTask) .Verifiable(); using TransportEventBatch batch = await producer.Object.CreateBatchAsync(options, default); await producer.Object.SendAsync(new EventDataBatch(batch, options), CancellationToken.None); producer.VerifyAll(); }
public async Task CreateBatchAsyncEnsuresMaximumMessageSizeIsPopulated() { var retryPolicy = new BasicRetryPolicy(new RetryOptions { TryTimeout = TimeSpan.FromSeconds(17) }); var producer = new Mock <AmqpProducer>("aHub", null, Mock.Of <AmqpConnectionScope>(), new AmqpMessageConverter(), retryPolicy) { CallBase = true }; producer .Protected() .Setup <Task <SendingAmqpLink> >("CreateLinkAndEnsureProducerStateAsync", ItExpr.IsAny <string>(), ItExpr.IsAny <TimeSpan>(), ItExpr.IsAny <CancellationToken>()) .Callback(() => SetMaximumMessageSize(producer.Object, 512)) .Returns(Task.FromResult(new SendingAmqpLink(new AmqpLinkSettings()))) .Verifiable(); using TransportEventBatch batch = await producer.Object.CreateBatchAsync(new CreateBatchOptions (), default); producer.VerifyAll(); }