public void EventDataBatchIsSafeToDispose() { var size = 1024; var store = new List <EventData> { new EventData(new BinaryData(Array.Empty <byte>())), new EventData(new BinaryData(Array.Empty <byte>())) }; var options = new CreateBatchOptions { MaximumSizeInBytes = 2048 }; var batch = EventHubsModelFactory.EventDataBatch(size, store, options, _ => false); Assert.That(() => batch.Dispose(), Throws.Nothing); }
/// <summary> /// Initializes a new instance of the <see cref="AmqpMessageBatch"/> class. /// </summary> /// /// <param name="options">The set of options to apply to the batch.</param> /// public AmqpMessageBatch(CreateBatchOptions options) { Argument.AssertNotNull(options, nameof(options)); Argument.AssertNotNull(options.MaximumSizeInBytes, nameof(options.MaximumSizeInBytes)); Options = options; MaximumSizeInBytes = options.MaximumSizeInBytes.Value; // Initialize the size by reserving space for the batch envelope. using AmqpMessage envelope = AmqpMessageConverter.BatchSBMessagesAsAmqpMessage(Enumerable.Empty <ServiceBusMessage>()); _sizeBytes = envelope.SerializedMessageSize; }
public async Task SendInvokesTheTransportProducerWithABatch() { var batchOptions = new CreateBatchOptions { PartitionKey = "testKey" }; var batch = new EventDataBatch(new MockTransportBatch(), batchOptions.ToSendOptions()); var transportProducer = new ObservableTransportProducerMock(); var producer = new EventHubProducerClient(new MockConnection(() => transportProducer)); await producer.SendAsync(batch); Assert.That(transportProducer.SendBatchCalledWith, Is.SameAs(batch), "The batch should be the same instance."); }
public async Task CreateBatchDefaultsBatchOptions() { var expectedOptions = new CreateBatchOptions(); var transportProducer = new ObservableTransportProducerMock(); var producer = new EventHubProducerClient(new MockConnection(() => transportProducer)); await producer.CreateBatchAsync(); Assert.That(transportProducer.CreateBatchCalledWith, Is.Not.Null, "The batch creation should have passed options."); Assert.That(transportProducer.CreateBatchCalledWith, Is.Not.SameAs(expectedOptions), "The options should have been cloned."); Assert.That(transportProducer.CreateBatchCalledWith.PartitionKey, Is.EqualTo(expectedOptions.PartitionKey), "The partition key should match."); Assert.That(transportProducer.CreateBatchCalledWith.MaximumSizeInBytes, Is.EqualTo(expectedOptions.MaximumSizeInBytes), "The maximum size should match."); }
public async Task ProducerSequencesBatches() { await using (EventHubScope scope = await EventHubScope.CreateAsync(2)) { var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName); var options = new EventHubProducerClientOptions { EnableIdempotentPartitions = true }; await using var producer = new EventHubProducerClient(connectionString, options); var cancellationSource = new CancellationTokenSource(); cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit); var partition = (await producer.GetPartitionIdsAsync(cancellationSource.Token)).Last(); var batchOptions = new CreateBatchOptions { PartitionId = partition }; var partitionProperties = await producer.GetPartitionPublishingPropertiesAsync(partition); var eventSequenceNumber = partitionProperties.LastPublishedSequenceNumber; using var firstBatch = await producer.CreateBatchAsync(batchOptions, cancellationSource.Token); firstBatch.TryAdd(EventGenerator.CreateEvents(1).First()); firstBatch.TryAdd(EventGenerator.CreateEvents(1).First()); firstBatch.TryAdd(EventGenerator.CreateEvents(1).First()); using var secondBatch = await producer.CreateBatchAsync(batchOptions, cancellationSource.Token); secondBatch.TryAdd(EventGenerator.CreateEvents(1).First()); secondBatch.TryAdd(EventGenerator.CreateEvents(1).First()); secondBatch.TryAdd(EventGenerator.CreateEvents(1).First()); secondBatch.TryAdd(EventGenerator.CreateEvents(1).First()); Assert.That(firstBatch.StartingPublishedSequenceNumber.HasValue, Is.False, "Batches should start out as unpublished with no sequence number, the first batch was incorrect."); Assert.That(secondBatch.StartingPublishedSequenceNumber.HasValue, Is.False, "Batches should start out as unpublished with no sequence number, the second batch was incorrect."); await producer.SendAsync(firstBatch, cancellationSource.Token); await producer.SendAsync(secondBatch, cancellationSource.Token); Assert.That(firstBatch.StartingPublishedSequenceNumber.HasValue, "Batches should be sequenced after publishing, the first batch was incorrect."); Assert.That(firstBatch.StartingPublishedSequenceNumber, Is.EqualTo(eventSequenceNumber + 1), "Batches should be sequenced after publishing, the first batch was incorrect."); Assert.That(secondBatch.StartingPublishedSequenceNumber.HasValue, "Batches should be sequenced after publishing, the second batch was incorrect."); Assert.That(secondBatch.StartingPublishedSequenceNumber, Is.EqualTo(eventSequenceNumber + 1 + firstBatch.Count), "Batches should be sequenced after publishing, the second batch was incorrect."); } }
public void DisposeCleansUpBatchMessages() { var currentIndex = -1; var options = new CreateBatchOptions { MaximumSizeInBytes = 5000 }; var eventMessages = new AmqpMessage[5]; var mockEnvelope = new Mock <AmqpMessage>(); var mockConverter = new InjectableMockConverter { CreateBatchFromMessagesHandler = (_e, _p) => mockEnvelope.Object, CreateMessageFromEventHandler = (_e, _p) => eventMessages[++currentIndex] }; mockEnvelope .Setup(message => message.SerializedMessageSize) .Returns(0); for (var index = 0; index < eventMessages.Length; ++index) { eventMessages[index] = AmqpMessage.Create(new FramingData { Value = new ArraySegment <byte>(new byte[] { 0x66 }) }); } // Add the messages to the batch; all should be accepted. var batch = new AmqpEventBatch(mockConverter, options, default); for (var index = 0; index < eventMessages.Length; ++index) { Assert.That(batch.TryAdd(new EventData(new byte[0])), Is.True, $"The addition for index: { index } should fit and be accepted."); } // Validate that the AMQP messages have not been disposed. for (var index = 0; index < eventMessages.Length; ++index) { Assert.That(() => eventMessages[index].ThrowIfDisposed(), Throws.Nothing, $"The message at index: { index } should not have been disposed."); } // Dispose the batch and verify that the messages held by the batch have been disposed. batch.Dispose(); for (var index = 0; index < eventMessages.Length; ++index) { Assert.That(() => eventMessages[index].ThrowIfDisposed(), Throws.InstanceOf <ObjectDisposedException>(), $"The message at index: { index } should have been disposed."); } }
public void AsEnumerableReturnsTheEvents() { var currentIndex = -1; var maximumSize = 5000; var options = new CreateBatchOptions { MaximumSizeInBytes = maximumSize }; var eventMessages = new AmqpMessage[5]; var batchEvents = new EventData[5]; var mockEnvelope = new Mock <AmqpMessage>(); var mockConverter = new InjectableMockConverter { CreateBatchFromEventsHandler = (_e, _p) => mockEnvelope.Object, CreateMessageFromEventHandler = (_e, _p) => eventMessages[++currentIndex] }; mockEnvelope .Setup(message => message.SerializedMessageSize) .Returns(0); for (var index = 0; index < eventMessages.Length; ++index) { var message = new Mock <AmqpMessage>(); message.Setup(msg => msg.SerializedMessageSize).Returns(50); eventMessages[index] = message.Object; } var batch = new AmqpEventBatch(mockConverter, options, default); for (var index = 0; index < eventMessages.Length; ++index) { batchEvents[index] = new EventData(new byte[0]); batch.TryAdd(batchEvents[index]); } IEnumerable <EventData> batchEnumerable = batch.AsEnumerable <EventData>(); Assert.That(batchEnumerable, Is.Not.Null, "The batch enumerable should have been populated."); var batchEnumerableList = batchEnumerable.ToList(); Assert.That(batchEnumerableList.Count, Is.EqualTo(batch.Count), "The wrong number of events was in the enumerable."); for (var index = 0; index < batchEvents.Length; ++index) { Assert.That(batchEnumerableList.Contains(batchEvents[index]), $"The event at index: { index } was not in the enumerable."); } }
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."); }
/// <summary> /// Performs the tasks needed to initialize and set up the environment for the test scenario. /// This setup will take place once for each instance, running after the global setup has /// completed. /// </summary> /// public override async Task SetupAsync() { await base.SetupAsync(); _batchOptions = await CreateBatchOptions(s_producer).ConfigureAwait(false); // Publish an empty event to force the connection and link to be established. using var batch = await s_producer.CreateBatchAsync(_batchOptions).ConfigureAwait(false); if (!batch.TryAdd(new EventData(Array.Empty <byte>()))) { throw new InvalidOperationException("The empty event could not be added to the batch during global setup."); } await s_producer.SendAsync(batch).ConfigureAwait(false); }
/// <summary> /// Initializes a new instance of the <see cref="AmqpEventBatch"/> class. /// </summary> /// /// <param name="messageConverter">The converter to use for translating <see cref="EventData" /> into the corresponding AMQP message.</param> /// <param name="options">The set of options to apply to the batch.</param> /// public AmqpEventBatch(AmqpMessageConverter messageConverter, CreateBatchOptions options) { Argument.AssertNotNull(messageConverter, nameof(messageConverter)); Argument.AssertNotNull(options, nameof(options)); Argument.AssertNotNull(options.MaximumSizeInBytes, nameof(options.MaximumSizeInBytes)); MessageConverter = messageConverter; Options = options; MaximumSizeInBytes = options.MaximumSizeInBytes.Value; // Initialize the size by reserving space for the batch envelope. using AmqpMessage envelope = messageConverter.CreateBatchFromEvents(Enumerable.Empty <EventData>(), options.PartitionKey); _sizeBytes = envelope.SerializedMessageSize; }
public void ConstructorSetsTheMaximumSize() { var maximumSize = 9943; var options = new CreateBatchOptions { MaximumSizeInBytes = maximumSize }; var mockConverter = new InjectableMockConverter { CreateBatchFromEventsHandler = (_e, _p) => Mock.Of <AmqpMessage>() }; var batch = new AmqpEventBatch(mockConverter, options, default); Assert.That(batch.MaximumSizeInBytes, Is.EqualTo(maximumSize)); }
public async Task PartitionId() { await using var scope = await EventHubScope.CreateAsync(1); #region Snippet:EventHubs_Sample04_PartitionId #if SNIPPET var connectionString = "<< CONNECTION STRING FOR THE EVENT HUBS NAMESPACE >>"; var eventHubName = "<< NAME OF THE EVENT HUB >>"; #else var connectionString = EventHubsTestEnvironment.Instance.EventHubsConnectionString; var eventHubName = scope.EventHubName; #endif var producer = new EventHubProducerClient(connectionString, eventHubName); try { string firstPartition = (await producer.GetPartitionIdsAsync()).First(); var batchOptions = new CreateBatchOptions { PartitionId = firstPartition }; using var eventBatch = await producer.CreateBatchAsync(batchOptions); for (var index = 0; index < 5; ++index) { var eventBody = new BinaryData($"Event #{ index }"); var eventData = new EventData(eventBody); if (!eventBatch.TryAdd(eventData)) { throw new Exception($"The event at { index } could not be added."); } } await producer.SendAsync(eventBatch); } finally { await producer.CloseAsync(); } #endregion }
public void TryAddHonorStatefulFeatures(byte activeFeatures) { var maximumSize = 50; var batchEnvelopeSize = 0; var capturedSequence = default(int?); var capturedGroupId = default(long?); var capturedOwnerLevel = default(short?); var options = new CreateBatchOptions { MaximumSizeInBytes = maximumSize }; var mockEnvelope = new Mock <AmqpMessage>(); var mockEvent = new Mock <AmqpMessage>(); var mockConverter = new InjectableMockConverter { CreateBatchFromEventsHandler = (_e, _p) => mockEnvelope.Object, CreateMessageFromEventHandler = (_e, _p) => { capturedSequence = _e.PendingPublishSequenceNumber; capturedGroupId = _e.PendingProducerGroupId; capturedOwnerLevel = _e.PendingProducerOwnerLevel; return(mockEvent.Object); } }; mockEnvelope .Setup(message => message.SerializedMessageSize) .Returns(batchEnvelopeSize); mockEvent .Setup(message => message.SerializedMessageSize) .Returns(maximumSize); var batch = new AmqpEventBatch(mockConverter, options, (TransportProducerFeatures)activeFeatures); batch.TryAdd(EventGenerator.CreateEvents(1).Single()); NullConstraint generateConstraint() => ((TransportProducerFeatures)activeFeatures == TransportProducerFeatures.None) ? Is.Null : Is.Not.Null; Assert.That(capturedSequence, generateConstraint(), "The sequence was not set as expected."); Assert.That(capturedGroupId, generateConstraint(), "The group identifier was not set as expected."); Assert.That(capturedOwnerLevel, generateConstraint(), "The owner level was not set as expected."); }
public void TryAddAcceptEventsUntilTheMaximumSizeIsReached() { var currentIndex = -1; var maximumSize = 50; var options = new CreateBatchOptions { MaximumSizeInBytes = maximumSize }; var eventMessages = new AmqpMessage[5]; var mockEnvelope = new Mock <AmqpMessage>(); var mockConverter = new InjectableMockConverter { CreateBatchFromEventsHandler = (_e, _p) => mockEnvelope.Object, CreateMessageFromEventHandler = (_e, _p) => eventMessages[++currentIndex] }; mockEnvelope .Setup(message => message.SerializedMessageSize) .Returns(0); // Fill the set of messages with ones that should fit, reserving the last spot // for one that will deterministically be rejected. for (var index = 0; index < eventMessages.Length; ++index) { var size = (index == eventMessages.Length - 1) ? maximumSize : (maximumSize / eventMessages.Length) - 8; var message = new Mock <AmqpMessage>(); message.Setup(msg => msg.SerializedMessageSize).Returns(size); eventMessages[index] = message.Object; } var batch = new AmqpEventBatch(mockConverter, options, default); for (var index = 0; index < eventMessages.Length; ++index) { if (index == eventMessages.Length - 1) { Assert.That(batch.TryAdd(new EventData(new byte[0])), Is.False, "The final addition should not fit in the available space."); } else { Assert.That(batch.TryAdd(new EventData(new byte[0])), Is.True, $"The addition for index: { index } should fit and be accepted."); } } }
public async Task ProducerCanPublishBatchesAfterAnException() { await using (EventHubScope scope = await EventHubScope.CreateAsync(1)) { var cancellationSource = new CancellationTokenSource(); cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit); var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName); var options = new EventHubProducerClientOptions { EnableIdempotentPartitions = true }; await using var producer = new EventHubProducerClient(connectionString, options); var partition = (await producer.GetPartitionIdsAsync()).First(); var batchOptions = new CreateBatchOptions { PartitionId = partition }; // Publish a batch to validate that the initial publish works. using var firstBatch = await producer.CreateBatchAsync(batchOptions, cancellationSource.Token); firstBatch.TryAdd(EventGenerator.CreateEvents(1).First()); Assert.That(async() => await producer.SendAsync(firstBatch, cancellationSource.Token), Throws.Nothing, "The first publishing operation was not successful."); // Publish an event too large to succeed; this will force the producer to deal with an exception, which should // update idempotent state. var producerId = (await producer.GetPartitionPublishingPropertiesAsync(partition, cancellationSource.Token)).ProducerGroupId; using var badBatch = EventHubsModelFactory.EventDataBatch(firstBatch.MaximumSizeInBytes + 1000, new List <EventData>(new[] { new EventData(EventGenerator.CreateRandomBody(firstBatch.MaximumSizeInBytes + 1000)) }), new CreateBatchOptions { PartitionId = partition }); Assert.That(async() => await producer.SendAsync(badBatch, cancellationSource.Token), Throws.InstanceOf <EventHubsException>(), "The attempt to publish a too-large event should fail."); // Publish a second batch of events; this will prove that the producer recovered from the exception. using var secondBatch = await producer.CreateBatchAsync(batchOptions, cancellationSource.Token); secondBatch.TryAdd(EventGenerator.CreateEvents(1).First()); secondBatch.TryAdd(EventGenerator.CreateEvents(1).First()); Assert.That(async() => await producer.SendAsync(secondBatch, cancellationSource.Token), Throws.Nothing, "The second publishing operation was not successful."); var newProducerId = (await producer.GetPartitionPublishingPropertiesAsync(partition, cancellationSource.Token)).ProducerGroupId; Assert.That(newProducerId, Is.Not.Null, "The producer group identifier should have a value."); Assert.That(newProducerId, Is.Not.EqualTo(producerId), "The producer group identifier should have been updated after the exception."); } }
/// <summary> /// Creates a size-constraint batch to which <see cref="ServiceBusMessage" /> may be added using a try-based pattern. If a message would /// exceed the maximum allowable size of the batch, the batch will not allow adding the message and signal that scenario using its /// return value. /// /// Because messages that would violate the size constraint cannot be added, publishing a batch will not trigger an exception when /// attempting to send the message to the Queue/Topic. /// </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="ServiceBusMessageBatch" /> with the requested <paramref name="options"/>.</returns> /// public override async ValueTask <TransportMessageBatch> CreateBatchAsync( CreateBatchOptions options, CancellationToken cancellationToken) { TransportMessageBatch messageBatch = null; Task createBatchTask = _retryPolicy.RunOperation(async(timeout) => { messageBatch = await CreateBatchInternalAsync( options, timeout).ConfigureAwait(false); }, _connectionScope, cancellationToken); await createBatchTask.ConfigureAwait(false); return(messageBatch); }
public static async Task Run([IoTHubTrigger("messages/events", Connection = "IOT_HUB_CONN")] EventData message, ILogger log) { log.LogInformation($"C# IoT Hub trigger function processed a message: {Encoding.UTF8.GetString(message.Body.Array)}"); if (message.Properties.Keys.Contains("enterpriseid") == false) { return; } var connectionString = Environment.GetEnvironmentVariable("EVENT_HUB_CONN"); var eventHubNameString = Environment.GetEnvironmentVariable("EVENT_HUB_NAME"); if (eventProducer == null) { eventProducer = new EventHubProducerClient(connectionString); } foreach (var prop in message.Properties) { log.LogInformation($"{prop.Key} - {prop.Value}"); } var partitionKeyString = message.Properties["enterpriseid"] as string; var batchOptions = new CreateBatchOptions() { PartitionKey = partitionKeyString }; var eventDataBatch = await eventProducer.CreateBatchAsync(batchOptions); var partitionKeyName = Environment.GetEnvironmentVariable("PARTITION_KEY_NAME"); var ed = new AZEH.EventData(message.Body); ed.Properties.Add(partitionKeyName, partitionKeyString); foreach (var prop in message.Properties) { ed.Properties.Add(prop.Key, prop.Value); } eventDataBatch.TryAdd(ed); await eventProducer.SendAsync(eventDataBatch); }
public void EventDataBatchInitializesProperties() { var size = 1024; var store = new List <EventData> { new EventData(new BinaryData(Array.Empty <byte>())), new EventData(new BinaryData(Array.Empty <byte>())) }; var options = new CreateBatchOptions { MaximumSizeInBytes = 2048 }; var batch = EventHubsModelFactory.EventDataBatch(size, store, options); Assert.That(batch, Is.Not.Null, "The batch should have been created."); Assert.That(batch.SizeInBytes, Is.EqualTo(size), "The batch size should have been set."); Assert.That(batch.MaximumSizeInBytes, Is.EqualTo(options.MaximumSizeInBytes), "The maximum batch size should have been set."); Assert.That(batch.Count, Is.EqualTo(store.Count), "The batch count should reflect the count of the backing store."); Assert.That(batch.AsReadOnlyCollection <EventData>(), Is.EquivalentTo(store), "The batch enumerable should reflect the events in the backing store."); }
public void ToSendOptionsTranslatesProperly() { var options = new CreateBatchOptions { PartitionId = "0", PartitionKey = "some_partition_123", MaximumSizeInBytes = (int.MaxValue + 122L) }; var sendOptions = options.ToSendOptions(); Assert.That(sendOptions, Is.Not.Null, "The send options should not be null."); Assert.That(sendOptions, Is.TypeOf <SendEventOptions>(), "The send options should be a SendEventOptions instance."); Assert.That(sendOptions, Is.Not.SameAs(options), "The send options should not the same reference as the options."); Assert.That(sendOptions.PartitionId, Is.EqualTo(options.PartitionId), "The partition identifier of the send options should match."); Assert.That(sendOptions.PartitionKey, Is.EqualTo(options.PartitionKey), "The partition key of the send options should match."); }
public void CloneProducesACopy() { var options = new CreateBatchOptions { PartitionId = "0", PartitionKey = "some_partition_123", MaximumSizeInBytes = (int.MaxValue + 122L) }; CreateBatchOptions clone = options.Clone(); Assert.That(clone, Is.Not.Null, "The clone should not be null."); Assert.That(clone, Is.TypeOf <CreateBatchOptions>(), "The clone should be a CreateBatchOptions instance."); Assert.That(clone, Is.Not.SameAs(options), "The clone should not the same reference as the options."); Assert.That(clone.PartitionId, Is.EqualTo(options.PartitionId), "The partition identifier of the clone should match."); Assert.That(clone.PartitionKey, Is.EqualTo(options.PartitionKey), "The partition key of the clone should match."); Assert.That(clone.MaximumSizeInBytes, Is.EqualTo(options.MaximumSizeInBytes), "The maximum size should match."); }
public async Task CustomBatchSize() { await using var scope = await EventHubScope.CreateAsync(1); #region Snippet:EventHubs_Sample04_CustomBatchSize #if SNIPPET var connectionString = "<< CONNECTION STRING FOR THE EVENT HUBS NAMESPACE >>"; var eventHubName = "<< NAME OF THE EVENT HUB >>"; #else var connectionString = EventHubsTestEnvironment.Instance.EventHubsConnectionString; var eventHubName = scope.EventHubName; #endif var producer = new EventHubProducerClient(connectionString, eventHubName); try { var batchOptions = new CreateBatchOptions { MaximumSizeInBytes = 350 }; using EventDataBatch eventBatch = await producer.CreateBatchAsync(batchOptions); for (var index = 0; index < 5; ++index) { var eventData = new EventData($"Event #{ index }"); if (!eventBatch.TryAdd(eventData)) { throw new Exception($"The event at { index } could not be added."); } } await producer.SendAsync(eventBatch); } finally { await producer.CloseAsync(); } #endregion }
public void AsEnumerableValidatesTheTypeParameter() { var options = new CreateBatchOptions { MaximumSizeInBytes = 5000 }; var mockEnvelope = new Mock <AmqpMessage>(); var mockConverter = new InjectableMockConverter { CreateBatchFromEventsHandler = (_e, _p) => mockEnvelope.Object }; mockEnvelope .Setup(message => message.SerializedMessageSize) .Returns(0); var batch = new AmqpEventBatch(mockConverter, options, default); Assert.That(() => batch.AsEnumerable <AmqpMessage>(), Throws.InstanceOf <FormatException>()); }
/// <summary> /// Runs the sample using the specified Event Hubs connection information. /// </summary> /// /// <param name="connectionString">The connection string for the Event Hubs namespace that the sample should target.</param> /// <param name="eventHubName">The name of the Event Hub, sometimes known as its path, that she sample should run against.</param> /// public async Task RunAsync(string connectionString, string eventHubName) { // We will start by creating a producer client using its default set of options. await using (var producerClient = new EventHubProducerClient(connectionString, eventHubName)) { // To ensure that we request a valid partition, we'll need to read the metadata for the Event Hub. We will // select the first available partition. string firstPartition = (await producerClient.GetPartitionIdsAsync()).First(); // When publishing events, it may be desirable to request that the Event Hubs service place a batch on a specific partition, // for organization and processing. For example, you may have designated one partition of your Event Hub as being responsible // for all of your telemetry-related events. // // This can be accomplished by setting the identifier of the desired partition when creating the batch. It is important to note // that if you are using a partition identifier, you may not also specify a partition key; they are mutually exclusive. // // We will publish a small batch of events based on simple sentences. // To choose a partition identifier, you will need to create a custom set of batch options. var batchOptions = new CreateBatchOptions { PartitionId = firstPartition }; using EventDataBatch eventBatch = await producerClient.CreateBatchAsync(batchOptions); eventBatch.TryAdd(new EventData(Encoding.UTF8.GetBytes("Hello, Event Hubs!"))); eventBatch.TryAdd(new EventData(Encoding.UTF8.GetBytes("Goodbye, Event Hubs!"))); await producerClient.SendAsync(eventBatch); Console.WriteLine("The event batch has been published."); } // At this point, our client has passed its "using" scope and has safely been disposed of. We // have no further obligations. Console.WriteLine(); }
/// <summary> /// Runs the sample using the specified Event Hubs connection information. /// </summary> /// /// <param name="connectionString">The connection string for the Event Hubs namespace that the sample should target.</param> /// <param name="eventHubName">The name of the Event Hub, sometimes known as its path, that she sample should run against.</param> /// public async Task RunAsync(string connectionString, string eventHubName) { // We will start by creating a producer client using its default set of options. await using (var producerClient = new EventHubProducerClient(connectionString, eventHubName)) { // When publishing events, it may be desirable to request that the Event Hubs service keep the different // event batches together on the same partition. This can be accomplished by setting a // partition key when publishing the batch. // // The partition key is NOT the identifier of a specific partition. Rather, it is an arbitrary piece of string data // that Event Hubs uses as the basis to compute a hash value. Event Hubs will associate the hash value with a specific // partition, ensuring that any events published with the same partition key are routed to the same partition. // // Note that there is no means of accurately predicting which partition will be associated with a given partition key; // we can only be assured that it will be a consistent choice of partition. If you have a need to understand which // exact partition an event is published to, you will need to use an Event Hub producer associated with that partition. // // We will publish a small batch of events based on simple sentences. // To choose a partition key, you will need to create a custom set of batch options. var batchOptions = new CreateBatchOptions { PartitionKey = "Any Value Will Do..." }; using EventDataBatch eventBatch = await producerClient.CreateBatchAsync(batchOptions); eventBatch.TryAdd(new EventData(Encoding.UTF8.GetBytes("Hello, Event Hubs!"))); eventBatch.TryAdd(new EventData(Encoding.UTF8.GetBytes("Goodbye, Event Hubs!"))); await producerClient.SendAsync(eventBatch); Console.WriteLine("The event batch has been published."); } // At this point, our client has passed its "using" scope and has safely been disposed of. We // have no further obligations. Console.WriteLine(); }
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."); }
/// <summary> /// Initializes a new instance of the <see cref="AmqpEventBatch"/> class. /// </summary> /// /// <param name="messageConverter">The converter to use for translating <see cref="EventData" /> into the corresponding AMQP message.</param> /// <param name="options">The set of options to apply to the batch.</param> /// <param name="activeFeatures">The flags specifying the set of special transport features have been opted-into.</param> /// public AmqpEventBatch(AmqpMessageConverter messageConverter, CreateBatchOptions options, TransportProducerFeatures activeFeatures) { Argument.AssertNotNull(messageConverter, nameof(messageConverter)); Argument.AssertNotNull(options, nameof(options)); Argument.AssertNotNull(options.MaximumSizeInBytes, nameof(options.MaximumSizeInBytes)); MessageConverter = messageConverter; Options = options; MaximumSizeInBytes = options.MaximumSizeInBytes.Value; ActiveFeatures = activeFeatures; // Initialize the size by reserving space for the batch envelope. At this point, the // set of batch events is empty, so the message returned will only represent the envelope. using AmqpMessage envelope = messageConverter.CreateBatchFromEvents(BatchEvents, options.PartitionKey); ReservedSize = envelope.SerializedMessageSize; _sizeBytes = ReservedSize; }
public async Task PartitionKey() { #region Snippet:EventHubs_Sample04_PartitionKey var connectionString = "<< CONNECTION STRING FOR THE EVENT HUBS NAMESPACE >>"; var eventHubName = "<< NAME OF THE EVENT HUB >>"; /*@@*/ /*@@*/ connectionString = EventHubsTestEnvironment.Instance.EventHubsConnectionString; /*@@*/ eventHubName = _scope.EventHubName; var producer = new EventHubProducerClient(connectionString, eventHubName); try { var batchOptions = new CreateBatchOptions { PartitionKey = "Any Value Will Do..." }; using var eventBatch = await producer.CreateBatchAsync(batchOptions); for (var index = 0; index < 5; ++index) { var eventBody = new BinaryData($"Event #{ index }"); var eventData = new EventData(eventBody); if (!eventBatch.TryAdd(eventData)) { throw new Exception($"The event at { index } could not be added."); } } await producer.SendAsync(eventBatch); } finally { await producer.CloseAsync(); } #endregion }
public void ResetBatchSequencingRemovesPublishingProperties() { var currentIndex = -1; var removeCount = 0; var options = new CreateBatchOptions { MaximumSizeInBytes = 5000 }; var eventMessages = new AmqpMessage[5]; var mockEnvelope = new Mock <AmqpMessage>(); var mockConverter = new InjectableMockConverter { CreateBatchFromMessagesHandler = (_e, _p) => mockEnvelope.Object, CreateMessageFromEventHandler = (_e, _p) => eventMessages[++currentIndex], RemovePublishingPropertiesFromAmqpMessageHandler = (_m) => ++ removeCount }; mockEnvelope .Setup(message => message.SerializedMessageSize) .Returns(0); for (var index = 0; index < eventMessages.Length; ++index) { eventMessages[index] = AmqpMessage.Create(new FramingData { Value = new ArraySegment <byte>(new byte[] { 0x66 }) }); } // Add the messages to the batch; all should be accepted. var batch = new AmqpEventBatch(mockConverter, options, default); for (var index = 0; index < eventMessages.Length; ++index) { Assert.That(batch.TryAdd(new EventData(new byte[0])), Is.True, $"The addition for index: { index } should fit and be accepted."); } // Sequence the batch and validate the final state. batch.ResetBatchSequencing(); Assert.That(removeCount, Is.EqualTo(eventMessages.Length), "The publishing properties for each event should have been removed."); }
public async Task ProducerManagesConcurrencyWhenPublishingBatches() { await using (EventHubScope scope = await EventHubScope.CreateAsync(1)) { var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName); var options = new EventHubProducerClientOptions { EnableIdempotentPartitions = true }; await using var producer = new EventHubProducerClient(connectionString, options); var cancellationSource = new CancellationTokenSource(); cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit); var partition = (await producer.GetPartitionIdsAsync(cancellationSource.Token)).First(); var batchOptions = new CreateBatchOptions { PartitionId = partition }; async Task sendBatch(int delayMilliseconds) { await Task.Delay(delayMilliseconds); using var batch = await producer.CreateBatchAsync(batchOptions, cancellationSource.Token); batch.TryAdd(EventGenerator.CreateEvents(1).First()); batch.TryAdd(EventGenerator.CreateEvents(1).First()); batch.TryAdd(EventGenerator.CreateEvents(1).First()); await producer.SendAsync(batch, cancellationSource.Token); } var pendingSends = Task.WhenAll( sendBatch(100), sendBatch(50), sendBatch(0) ); Assert.That(async() => await pendingSends, Throws.Nothing); } }
/// <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> /// public override async ValueTask <TransportEventBatch> CreateBatchAsync(CreateBatchOptions options, CancellationToken cancellationToken) { Argument.AssertNotNull(options, nameof(options)); // Ensure that maximum message size has been determined; this depends on the underlying // AMQP link, so if not set, requesting the link will ensure that it is populated. if (!MaximumMessageSize.HasValue) { await SendLink.GetOrCreateAsync(RetryPolicy.CalculateTryTimeout(0)).ConfigureAwait(false); } // Ensure that there was a maximum size populated; if none was provided, // default to the maximum size allowed by the link. options.MaximumSizeInBytes ??= MaximumMessageSize; Argument.AssertInRange(options.MaximumSizeInBytes.Value, EventHubProducerClient.MinimumBatchSizeLimit, MaximumMessageSize.Value, nameof(options.MaximumSizeInBytes)); return(new AmqpEventBatch(MessageConverter, options)); }