コード例 #1
0
        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);
        }
コード例 #2
0
        /// <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;
        }
コード例 #3
0
        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.");
        }
コード例 #4
0
        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.");
        }
コード例 #5
0
        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.");
            }
        }
コード例 #6
0
        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.");
            }
        }
コード例 #7
0
        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.");
            }
        }
コード例 #8
0
        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.");
        }
コード例 #9
0
        /// <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);
        }
コード例 #10
0
        /// <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;
        }
コード例 #11
0
        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));
        }
コード例 #12
0
        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
        }
コード例 #13
0
        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.");
        }
コード例 #14
0
        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.");
                }
            }
        }
コード例 #15
0
        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.");
            }
        }
コード例 #16
0
        /// <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);
        }
コード例 #17
0
        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);
        }
コード例 #18
0
        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.");
        }
コード例 #19
0
        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.");
        }
コード例 #20
0
        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.");
        }
コード例 #21
0
        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
        }
コード例 #22
0
        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>());
        }
コード例 #23
0
        /// <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();
        }
コード例 #25
0
        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.");
        }
コード例 #26
0
        /// <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;
        }
コード例 #27
0
        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
        }
コード例 #28
0
        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.");
        }
コード例 #29
0
        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);
            }
        }
コード例 #30
0
        /// <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));
        }