public void CreateConsumerCreatesDefaultWhenOptionsAreNotSet()
        {
            var retryOptions = new RetryOptions
            {
                MaximumRetries = 99,
                MaximumDelay   = TimeSpan.FromHours(72),
                Delay          = TimeSpan.FromSeconds(27)
            };

            var expectedOptions = new EventHubConsumerClientOptions
            {
                OwnerLevel    = 251,
                PrefetchCount = 600,
                RetryOptions  = retryOptions,
                DefaultMaximumReceiveWaitTime = TimeSpan.FromSeconds(123)
            };

            var clientOptions         = new EventHubConnectionOptions();
            var expectedConsumerGroup = "SomeGroup";
            var expectedPartition     = "56767";
            var expectedPosition      = EventPosition.FromSequenceNumber(123);
            var connectionString      = "Endpoint=value.com;SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]";
            var mockClient            = new ReadableOptionsMock(connectionString, clientOptions);

            mockClient.CreateTransportConsumer(expectedConsumerGroup, expectedPartition, expectedPosition, expectedOptions);
            EventHubConsumerClientOptions actualOptions = mockClient.ConsumerOptions;

            Assert.That(actualOptions, Is.Not.Null, "The consumer options should have been set.");
            Assert.That(actualOptions, Is.Not.SameAs(expectedOptions), "A clone of the options should have been made.");
            Assert.That(actualOptions.OwnerLevel, Is.EqualTo(expectedOptions.OwnerLevel), "The owner levels should match.");
            Assert.That(actualOptions.PrefetchCount, Is.EqualTo(expectedOptions.PrefetchCount), "The prefetch counts should match.");
            Assert.That(actualOptions.RetryOptions.IsEquivalentTo(expectedOptions.RetryOptions), Is.True, "The retries should match.");
            Assert.That(actualOptions.MaximumReceiveWaitTimeOrDefault, Is.EqualTo(expectedOptions.MaximumReceiveWaitTimeOrDefault), "The wait times should match.");
        }
Beispiel #2
0
        public void CreateConsumerInvokesTheTransportClient()
        {
            var transportClient = new ObservableTransportClientMock();
            var client          = new InjectableTransportClientMock(transportClient, "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath=fake");
            var expectedOptions = new EventHubConsumerClientOptions {
                RetryOptions = new RetryOptions {
                    MaximumRetries = 67
                }
            };
            var expectedPosition      = EventPosition.FromOffset(65);
            var expectedPartition     = "2123";
            var expectedConsumerGroup = EventHubConsumerClient.DefaultConsumerGroupName;

            client.CreateTransportConsumer(expectedConsumerGroup, expectedPartition, expectedPosition, expectedOptions);
            (var actualConsumerGroup, var actualPartition, EventPosition actualPosition, EventHubConsumerClientOptions actualOptions) = transportClient.CreateConsumerCalledWith;

            Assert.That(actualPartition, Is.EqualTo(expectedPartition), "The partition should have been passed.");
            Assert.That(actualConsumerGroup, Is.EqualTo(expectedConsumerGroup), "The consumer groups should match.");
            Assert.That(actualPosition.Offset, Is.EqualTo(expectedPosition.Offset), "The event position to receive should match.");
            Assert.That(actualOptions, Is.Not.Null, "The consumer options should have been set.");
            Assert.That(actualPosition.Offset, Is.EqualTo(expectedPosition.Offset), "The event position to receive should match.");
            Assert.That(actualOptions.OwnerLevel, Is.EqualTo(expectedOptions.OwnerLevel), "The owner levels should match.");
            Assert.That(actualOptions.Identifier, Is.EqualTo(expectedOptions.Identifier), "The identifiers should match.");
            Assert.That(actualOptions.PrefetchCount, Is.EqualTo(expectedOptions.PrefetchCount), "The prefetch counts should match.");
            Assert.That(actualOptions.MaximumReceiveWaitTimeOrDefault, Is.EqualTo(expectedOptions.MaximumReceiveWaitTimeOrDefault), "The wait times should match.");
        }
Beispiel #3
0
        public async Task ConfigureConsumerRetryWithFullOptions()
        {
            #region Snippet:EventHubs_Sample02_ConsumerRetryWithFullOptions

            var connectionString = "<< CONNECTION STRING FOR THE EVENT HUBS NAMESPACE >>";
            var eventHubName     = "<< NAME OF THE EVENT HUB >>";
            var consumerGroup    = EventHubConsumerClient.DefaultConsumerGroupName;
            /*@@*/
            /*@@*/ connectionString = EventHubsTestEnvironment.Instance.EventHubsConnectionString;
            /*@@*/ eventHubName     = _scope.EventHubName;

            var consumerOptions = new EventHubConsumerClientOptions
            {
                RetryOptions = new EventHubsRetryOptions
                {
                    Mode           = EventHubsRetryMode.Exponential,
                    MaximumRetries = 5,
                    Delay          = TimeSpan.FromMilliseconds(800),
                    MaximumDelay   = TimeSpan.FromSeconds(10)
                }
            };

            var consumer = new EventHubConsumerClient(
                consumerGroup,
                connectionString,
                eventHubName,
                consumerOptions);

            #endregion

            using var cancellationSource = new CancellationTokenSource();
            cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit);

            await consumer.CloseAsync(cancellationSource.Token).IgnoreExceptions();
        }
        /// <summary>
        ///   Initializes a new instance of the <see cref="AmqpConsumer"/> class.
        /// </summary>
        ///
        /// <param name="eventHubName">The name of the Event Hub from which events will be consumed.</param>
        /// <param name="consumerGroup">The name of the consumer group this consumer is associated with.  Events are read in the context of this group.</param>
        /// <param name="partitionId">The identifier of the Event Hub partition from which events will be received.</param>
        /// <param name="consumerOptions">The set of active options for the consumer that will make use of the link.</param>
        /// <param name="eventPosition">The position of the event in the partition where the consumer should begin reading.</param>
        /// <param name="connectionScope">The AMQP connection context for operations .</param>
        /// <param name="messageConverter">The converter to use for translating between AMQP messages and client types.</param>
        /// <param name="retryPolicy">The retry policy to consider when an operation fails.</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>
        ///
        public AmqpConsumer(string eventHubName,
                            string consumerGroup,
                            string partitionId,
                            EventPosition eventPosition,
                            EventHubConsumerClientOptions consumerOptions,
                            AmqpConnectionScope connectionScope,
                            AmqpMessageConverter messageConverter,
                            EventHubsRetryPolicy retryPolicy)
        {
            Argument.AssertNotNullOrEmpty(eventHubName, nameof(eventHubName));
            Argument.AssertNotNullOrEmpty(consumerGroup, nameof(consumerGroup));
            Argument.AssertNotNullOrEmpty(partitionId, nameof(partitionId));
            Argument.AssertNotNull(consumerOptions, nameof(EventHubConsumerClientOptions));
            Argument.AssertNotNull(connectionScope, nameof(connectionScope));
            Argument.AssertNotNull(messageConverter, nameof(messageConverter));
            Argument.AssertNotNull(retryPolicy, nameof(retryPolicy));

            EventHubName     = eventHubName;
            ConsumerGroup    = consumerGroup;
            PartitionId      = partitionId;
            Options          = consumerOptions;
            ConnectionScope  = connectionScope;
            RetryPolicy      = retryPolicy;
            MessageConverter = messageConverter;
            ReceiveLink      = new FaultTolerantAmqpObject <ReceivingAmqpLink>(timeout => ConnectionScope.OpenConsumerLinkAsync(consumerGroup, partitionId, eventPosition, consumerOptions, timeout, CancellationToken.None), link => link.SafeClose());
        }
Beispiel #5
0
        public async Task ConfigureConsumerRetryByProperty()
        {
            #region Snippet:EventHubs_Sample02_ConsumerRetryByProperty

            var connectionString = "<< CONNECTION STRING FOR THE EVENT HUBS NAMESPACE >>";
            var eventHubName     = "<< NAME OF THE EVENT HUB >>";
            var consumerGroup    = EventHubConsumerClient.DefaultConsumerGroupName;
            /*@@*/
            /*@@*/ connectionString = EventHubsTestEnvironment.Instance.EventHubsConnectionString;
            /*@@*/ eventHubName     = _scope.EventHubName;

            var consumerOptions = new EventHubConsumerClientOptions();
            consumerOptions.RetryOptions.Mode           = EventHubsRetryMode.Fixed;
            consumerOptions.RetryOptions.MaximumRetries = 5;

            var consumer = new EventHubConsumerClient(
                consumerGroup,
                connectionString,
                eventHubName,
                consumerOptions);

            #endregion

            using var cancellationSource = new CancellationTokenSource();
            cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit);

            await consumer.CloseAsync(cancellationSource.Token).IgnoreExceptions();
        }
        /// <summary>
        ///   Opens an AMQP link for use with consumer operations.
        /// </summary>
        ///
        /// <param name="consumerGroup">The name of the consumer group in the context of which events should be received.</param>
        /// <param name="partitionId">The identifier of the Event Hub partition from which events should be received.</param>
        /// <param name="eventPosition">The position of the event in the partition where the link should be filtered to.</param>
        /// <param name="consumerOptions">The set of active options for the consumer that will make use of the link.</param>
        /// <param name="timeout">The timeout to apply when creating the link.</param>
        /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param>
        ///
        /// <returns>A link for use with consumer operations.</returns>
        ///
        public virtual async Task <ReceivingAmqpLink> OpenConsumerLinkAsync(string consumerGroup,
                                                                            string partitionId,
                                                                            EventPosition eventPosition,
                                                                            EventHubConsumerClientOptions consumerOptions,
                                                                            TimeSpan timeout,
                                                                            CancellationToken cancellationToken)
        {
            Argument.AssertNotNullOrEmpty(consumerGroup, nameof(consumerGroup));
            Argument.AssertNotNullOrEmpty(partitionId, nameof(partitionId));
            Argument.AssertNotNull(eventPosition, nameof(eventPosition));
            Argument.AssertNotNull(consumerOptions, nameof(consumerOptions));

            cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();

            var stopWatch        = Stopwatch.StartNew();
            var consumerEndpoint = new Uri(ServiceEndpoint, string.Format(ConsumerPathSuffixMask, EventHubName, consumerGroup, partitionId));

            var connection = await ActiveConnection.GetOrCreateAsync(timeout).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();

            var link = await CreateReceivingLinkAsync(connection, consumerEndpoint, eventPosition, consumerOptions, timeout.CalculateRemaining(stopWatch.Elapsed), cancellationToken).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();

            await OpenAmqpObjectAsync(link, timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();

            stopWatch.Stop();
            return(link);
        }
        public void CloneProducesACopy()
        {
            var options = new EventHubConsumerClientOptions
            {
                OwnerLevel = 99,
                TrackLastEnqueuedEventInformation = false,
                RetryOptions = new RetryOptions {
                    Mode = RetryMode.Fixed
                },
                ConnectionOptions = new EventHubConnectionOptions {
                    TransportType = TransportType.AmqpWebSockets
                },
                DefaultMaximumReceiveWaitTime = TimeSpan.FromMinutes(65)
            };

            EventHubConsumerClientOptions clone = options.Clone();

            Assert.That(clone, Is.Not.Null, "The clone should not be null.");

            Assert.That(clone.OwnerLevel, Is.EqualTo(options.OwnerLevel), "The owner level of the clone should match.");
            Assert.That(clone.TrackLastEnqueuedEventInformation, Is.EqualTo(options.TrackLastEnqueuedEventInformation), "The tracking of last event information of the clone should match.");
            Assert.That(clone.DefaultMaximumReceiveWaitTime, Is.EqualTo(options.DefaultMaximumReceiveWaitTime), "The default maximum wait time of the clone should match.");
            Assert.That(clone.ConnectionOptions.TransportType, Is.EqualTo(options.ConnectionOptions.TransportType), "The connection options of the clone should copy properties.");
            Assert.That(clone.ConnectionOptions, Is.Not.SameAs(options.ConnectionOptions), "The connection options of the clone should be a copy, not the same instance.");
            Assert.That(clone.RetryOptions.IsEquivalentTo(options.RetryOptions), Is.True, "The retry options of the clone should be considered equal.");
            Assert.That(clone.RetryOptions, Is.Not.SameAs(options.RetryOptions), "The retry options of the clone should be a copy, not the same instance.");
        }
        public void DefaultMaximumReceiveWaitTimeUsesNormalizedValueIfNotSpecified(int?noTimeoutValue)
        {
            var      options      = new EventHubConsumerClientOptions();
            TimeSpan?timeoutValue = (noTimeoutValue.HasValue) ? TimeSpan.Zero : (TimeSpan?)null;

            options.DefaultMaximumReceiveWaitTime = timeoutValue;
            Assert.That(options.DefaultMaximumReceiveWaitTime, Is.EqualTo(timeoutValue), "The value supplied by the caller should be preserved.");
            Assert.That(options.MaximumReceiveWaitTimeOrDefault, Is.Null, "The maximum wait value should be normalized to null internally.");
        }
Beispiel #9
0
        public void ReceiveAsyncRespectsTheRetryPolicy(RetryOptions retryOptions)
        {
            var eventHub      = "eventHubName";
            var consumerGroup = "$DEFAULT";
            var partition     = "3";
            var eventPosition = EventPosition.FromOffset(123);
            var options       = new EventHubConsumerClientOptions {
                Identifier = "OMG!"
            };
            var tokenValue         = "123ABC";
            var retryPolicy        = new BasicRetryPolicy(retryOptions);
            var retriableException = new EventHubsException(true, "Test");
            var mockConverter      = new Mock <AmqpMessageConverter>();
            var mockCredential     = new Mock <TokenCredential>();
            var mockScope          = new Mock <AmqpConnectionScope>();

            using var cancellationSource = new CancellationTokenSource();

            mockCredential
            .Setup(credential => credential.GetTokenAsync(It.IsAny <TokenRequestContext>(), It.Is <CancellationToken>(value => value == cancellationSource.Token)))
            .Returns(new ValueTask <AccessToken>(new AccessToken(tokenValue, DateTimeOffset.MaxValue)));

            mockScope
            .Setup(scope => scope.OpenConsumerLinkAsync(
                       It.IsAny <string>(),
                       It.IsAny <string>(),
                       It.IsAny <EventPosition>(),
                       It.IsAny <EventHubConsumerClientOptions>(),
                       It.IsAny <TimeSpan>(),
                       It.IsAny <CancellationToken>()))
            .Throws(retriableException);

            var consumer = new AmqpConsumer(eventHub, consumerGroup, partition, eventPosition, options, mockScope.Object, Mock.Of <AmqpMessageConverter>(), retryPolicy);

            Assert.That(async() => await consumer.ReceiveAsync(100, null, cancellationSource.Token), Throws.InstanceOf(retriableException.GetType()));

            mockScope
            .Verify(scope => scope.OpenConsumerLinkAsync(
                        It.Is <string>(value => value == consumerGroup),
                        It.Is <string>(value => value == partition),
                        It.Is <EventPosition>(value => value == eventPosition),
                        It.Is <EventHubConsumerClientOptions>(value => value == options),
                        It.IsAny <TimeSpan>(),
                        It.IsAny <CancellationToken>()),
                    Times.Exactly(1 + retryOptions.MaximumRetries));
        }
        public void CreateConsumerCreatesDefaultWhenNoOptionsArePassed()
        {
            var expectedOptions       = new EventHubConsumerClientOptions();
            var expectedConsumerGroup = EventHubConsumerClient.DefaultConsumerGroupName;
            var expectedPartition     = "56767";
            var expectedPosition      = EventPosition.FromEnqueuedTime(DateTimeOffset.Parse("2015-10-27T12:00:00Z"));
            var connectionString      = "Endpoint=value.com;SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]";
            var mockClient            = new ReadableOptionsMock(connectionString, new EventHubConnectionOptions());

            mockClient.CreateTransportConsumer(expectedConsumerGroup, expectedPartition, expectedPosition);
            EventHubConsumerClientOptions actualOptions = mockClient.ConsumerOptions;

            Assert.That(actualOptions, Is.Not.Null, "The consumer options should have been set.");
            Assert.That(actualOptions.OwnerLevel, Is.EqualTo(expectedOptions.OwnerLevel), "The owner levels should match.");
            Assert.That(actualOptions.PrefetchCount, Is.EqualTo(expectedOptions.PrefetchCount), "The prefetch counts should match.");
            Assert.That(actualOptions.RetryOptions.IsEquivalentTo(expectedOptions.RetryOptions), Is.True, "The retries should match.");
            Assert.That(actualOptions.MaximumReceiveWaitTimeOrDefault, Is.EqualTo(expectedOptions.MaximumReceiveWaitTimeOrDefault), "The wait times should match.");
        }
        /// <summary>
        ///   Creates an <see cref="EventHubConsumerClient" /> to use for mock processing.
        /// </summary>
        ///
        /// <param name="consumerGroup">The consumer group to associate with the consumer client.</param>
        /// <param name="connection">The connection to use for the consumer client.</param>
        /// <param name="options">The options to use for configuring the consumer client.</param>
        ///
        /// <returns>An <see cref="EventHubConsumerClient" /> with the requested configuration.</returns>
        ///
        internal override EventHubConsumerClient CreateConsumer(string consumerGroup,
                                                                EventHubConnection connection,
                                                                EventHubConsumerClientOptions options)
        {
            var mockConsumer = new Mock <EventHubConsumerClient>();

            mockConsumer
            .Setup(m => m.ReadEventsFromPartitionAsync(It.IsAny <string>(), It.IsAny <EventPosition>(), It.IsAny <ReadEventOptions>(), It.IsAny <CancellationToken>()))
            .Returns <string, EventPosition, ReadEventOptions, CancellationToken>((partitionId, eventPosition, options, token) =>
            {
                return(CreateReadEventsFromPartitionResponse(partitionId));
            });

            mockConsumer
            .Setup(m => m.GetPartitionIdsAsync(It.IsAny <CancellationToken>()))
            .Returns(Task.FromResult(EventPipeline.Keys.ToArray()));
            return(mockConsumer.Object);
        }
        public void ReceiveAsyncValidatesTheMaximumMessageCount(int count)
        {
            var eventHub           = "eventHubName";
            var consumerGroup      = "$DEFAULT";
            var partition          = "3";
            var eventPosition      = EventPosition.FromOffset(123);
            var options            = new EventHubConsumerClientOptions();
            var retryPolicy        = new BasicRetryPolicy(new RetryOptions());
            var retriableException = new EventHubsException(true, "Test");
            var mockConverter      = new Mock <AmqpMessageConverter>();
            var mockCredential     = new Mock <TokenCredential>();
            var mockScope          = new Mock <AmqpConnectionScope>();

            using var cancellationSource = new CancellationTokenSource();

            var consumer = new AmqpConsumer(eventHub, consumerGroup, partition, eventPosition, options, mockScope.Object, Mock.Of <AmqpMessageConverter>(), retryPolicy);

            Assert.That(async() => await consumer.ReceiveAsync(count, null, cancellationSource.Token), Throws.InstanceOf <ArgumentException>());
        }
Beispiel #13
0
        /// <summary>
        ///   Creates a consumer strongly aligned with the active protocol and transport, responsible
        ///   for reading <see cref="EventData" /> from a specific Event Hub partition, in the context
        ///   of a specific consumer group.
        ///
        ///   A consumer may be exclusive, which asserts ownership over the partition for the consumer
        ///   group to ensure that only one consumer from that group is reading the from the partition.
        ///   These exclusive consumers are sometimes referred to as "Epoch Consumers."
        ///
        ///   A consumer may also be non-exclusive, allowing multiple consumers from the same consumer
        ///   group to be actively reading events from the partition.  These non-exclusive consumers are
        ///   sometimes referred to as "Non-epoch Consumers."
        ///
        ///   Designating a consumer as exclusive may be specified in the <paramref name="consumerOptions" />.
        ///   By default, consumers are created as non-exclusive.
        /// </summary>
        ///
        /// <param name="consumerGroup">The name of the consumer group this consumer is associated with.  Events are read in the context of this group.</param>
        /// <param name="partitionId">The identifier of the Event Hub partition from which events will be received.</param>
        /// <param name="eventPosition">The position within the partition where the consumer should begin reading events.</param>
        /// <param name="consumerOptions">The set of options to apply when creating the consumer.</param>
        ///
        /// <returns>A <see cref="TransportConsumer" /> configured in the requested manner.</returns>
        ///
        public override TransportConsumer CreateConsumer(string consumerGroup,
                                                         string partitionId,
                                                         EventPosition eventPosition,
                                                         EventHubConsumerClientOptions consumerOptions)
        {
            Argument.AssertNotClosed(_closed, nameof(AmqpClient));

            return(new AmqpConsumer
                   (
                       EventHubName,
                       consumerGroup,
                       partitionId,
                       eventPosition,
                       consumerOptions,
                       ConnectionScope,
                       MessageConverter,
                       consumerOptions.RetryOptions.ToRetryPolicy()
                   ));
        }
        public void ReceiveAsyncValidatesConnectionClosed()
        {
            var endpoint       = new Uri("amqps://not.real.com");
            var eventHub       = "eventHubName";
            var consumerGroup  = "$DEFAULT";
            var partition      = "3";
            var eventPosition  = EventPosition.FromOffset(123);
            var options        = new EventHubConsumerClientOptions();
            var retryPolicy    = new BasicRetryPolicy(new EventHubsRetryOptions());
            var mockCredential = new EventHubTokenCredential(Mock.Of <TokenCredential>());

            var scope    = new AmqpConnectionScope(endpoint, endpoint, eventHub, mockCredential, EventHubsTransportType.AmqpTcp, null);
            var consumer = new AmqpConsumer(eventHub, consumerGroup, partition, eventPosition, true, false, null, null, null, scope, Mock.Of <AmqpMessageConverter>(), retryPolicy);

            scope.Dispose();

            Assert.That(async() => await consumer.ReceiveAsync(100, null, CancellationToken.None),
                        Throws.InstanceOf <EventHubsException>().And.Property(nameof(EventHubsException.Reason)).EqualTo(EventHubsException.FailureReason.ClientClosed));
        }
        public async Task ReceiveAsyncValidatesClosed()
        {
            var eventHub           = "eventHubName";
            var consumerGroup      = "$DEFAULT";
            var partition          = "3";
            var eventPosition      = EventPosition.FromOffset(123);
            var options            = new EventHubConsumerClientOptions();
            var retryPolicy        = new BasicRetryPolicy(new EventHubsRetryOptions());
            var retriableException = new EventHubsException(true, "Test");
            var mockConverter      = new Mock <AmqpMessageConverter>();
            var mockCredential     = new Mock <TokenCredential>();
            var mockScope          = new Mock <AmqpConnectionScope>();

            using var cancellationSource = new CancellationTokenSource();

            var consumer = new AmqpConsumer(eventHub, consumerGroup, partition, eventPosition, true, false, null, null, null, mockScope.Object, Mock.Of <AmqpMessageConverter>(), retryPolicy);
            await consumer.CloseAsync(cancellationSource.Token);

            Assert.That(async() => await consumer.ReceiveAsync(100, null, cancellationSource.Token), Throws.InstanceOf <EventHubsException>().And.Property(nameof(EventHubsException.Reason)).EqualTo(EventHubsException.FailureReason.ClientClosed));
        }
Beispiel #16
0
        public void CloneProducesACopy()
        {
            var options = new EventHubConsumerClientOptions
            {
                RetryOptions = new EventHubsRetryOptions {
                    Mode = EventHubsRetryMode.Fixed
                },
                ConnectionOptions = new EventHubConnectionOptions {
                    TransportType = EventHubsTransportType.AmqpWebSockets
                }
            };

            EventHubConsumerClientOptions clone = options.Clone();

            Assert.That(clone, Is.Not.Null, "The clone should not be null.");

            Assert.That(clone.ConnectionOptions.TransportType, Is.EqualTo(options.ConnectionOptions.TransportType), "The connection options of the clone should copy properties.");
            Assert.That(clone.ConnectionOptions, Is.Not.SameAs(options.ConnectionOptions), "The connection options of the clone should be a copy, not the same instance.");
            Assert.That(clone.RetryOptions.IsEquivalentTo(options.RetryOptions), Is.True, "The retry options of the clone should be considered equal.");
            Assert.That(clone.RetryOptions, Is.Not.SameAs(options.RetryOptions), "The retry options of the clone should be a copy, not the same instance.");
        }
Beispiel #17
0
        public void ReceiveAsyncRespectsTheCancellationTokenIfSetWhenCalled()
        {
            var eventHub      = "eventHubName";
            var consumerGroup = "$DEFAULT";
            var partition     = "3";
            var eventPosition = EventPosition.FromOffset(123);
            var options       = new EventHubConsumerClientOptions {
                Identifier = "OMG!"
            };
            var retryPolicy        = new BasicRetryPolicy(new RetryOptions());
            var retriableException = new EventHubsException(true, "Test");
            var mockConverter      = new Mock <AmqpMessageConverter>();
            var mockCredential     = new Mock <TokenCredential>();
            var mockScope          = new Mock <AmqpConnectionScope>();

            using var cancellationSource = new CancellationTokenSource();
            cancellationSource.Cancel();

            var consumer = new AmqpConsumer(eventHub, consumerGroup, partition, eventPosition, options, mockScope.Object, Mock.Of <AmqpMessageConverter>(), retryPolicy);

            Assert.That(async() => await consumer.ReceiveAsync(100, null, cancellationSource.Token), Throws.InstanceOf <TaskCanceledException>());
        }
        /// <summary>
        ///   Creates an AMQP link for use with receiving operations.
        /// </summary>
        ///
        /// <param name="connection">The active and opened AMQP connection to use for this link.</param>
        /// <param name="endpoint">The fully qualified endpoint to open the link for.</param>
        /// <param name="eventPosition">The position of the event in the partition where the link should be filtered to.</param>
        /// <param name="consumerOptions">The set of active options for the consumer that will make use of the link.</param>
        /// <param name="timeout">The timeout to apply when creating the link.</param>
        /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param>
        ///
        /// <returns>A link for use for operations related to receiving events.</returns>
        ///
        protected virtual async Task <ReceivingAmqpLink> CreateReceivingLinkAsync(AmqpConnection connection,
                                                                                  Uri endpoint,
                                                                                  EventPosition eventPosition,
                                                                                  EventHubConsumerClientOptions consumerOptions,
                                                                                  TimeSpan timeout,
                                                                                  CancellationToken cancellationToken)
        {
            Argument.AssertNotDisposed(IsDisposed, nameof(AmqpConnectionScope));
            cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();

            var session   = default(AmqpSession);
            var stopWatch = Stopwatch.StartNew();

            try
            {
                // Perform the initial authorization for the link.

                var authClaims        = new[] { EventHubsClaim.Listen };
                var authExpirationUtc = await RequestAuthorizationUsingCbsAsync(connection, TokenProvider, endpoint, endpoint.AbsoluteUri, endpoint.AbsoluteUri, authClaims, timeout.CalculateRemaining(stopWatch.Elapsed)).ConfigureAwait(false);

                cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();

                // Create and open the AMQP session associated with the link.

                var sessionSettings = new AmqpSessionSettings {
                    Properties = new Fields()
                };
                session = connection.CreateSession(sessionSettings);

                await OpenAmqpObjectAsync(session, timeout).ConfigureAwait(false);

                cancellationToken.ThrowIfCancellationRequested <TaskCanceledException>();

                // Create and open the link.

                var filters = new FilterSet();
                filters.Add(AmqpFilter.ConsumerFilterName, AmqpFilter.CreateConsumerFilter(AmqpFilter.BuildFilterExpression(eventPosition)));

                var linkSettings = new AmqpLinkSettings
                {
                    Role            = true,
                    TotalLinkCredit = (uint)consumerOptions.PrefetchCount,
                    AutoSendFlow    = consumerOptions.PrefetchCount > 0,
                    SettleType      = SettleMode.SettleOnSend,
                    Source          = new Source {
                        Address = endpoint.AbsolutePath, FilterSet = filters
                    },
                    Target = new Target {
                        Address = Guid.NewGuid().ToString()
                    }
                };

                linkSettings.AddProperty(AmqpProperty.EntityType, (int)AmqpProperty.Entity.ConsumerGroup);

                if (!string.IsNullOrEmpty(consumerOptions.Identifier))
                {
                    linkSettings.AddProperty(AmqpProperty.ConsumerIdentifier, consumerOptions.Identifier);
                }

                if (consumerOptions.OwnerLevel.HasValue)
                {
                    linkSettings.AddProperty(AmqpProperty.OwnerLevel, consumerOptions.OwnerLevel.Value);
                }

                if (consumerOptions.TrackLastEnqueuedEventInformation)
                {
                    linkSettings.DesiredCapabilities = new Multiple <AmqpSymbol>(new List <AmqpSymbol>
                    {
                        AmqpProperty.TrackLastEnqueuedEventInformation
                    });
                }

                var link = new ReceivingAmqpLink(linkSettings);
                linkSettings.LinkName = $"{ Id };{ connection.Identifier }:{ session.Identifier }:{ link.Identifier }";
                link.AttachTo(session);

                stopWatch.Stop();

                // Configure refresh for authorization of the link.

                var refreshTimer = default(Timer);

                var refreshHandler = CreateAuthorizationRefreshHandler
                                     (
                    connection,
                    link,
                    TokenProvider,
                    endpoint,
                    endpoint.AbsoluteUri,
                    endpoint.AbsoluteUri,
                    authClaims,
                    AuthorizationRefreshTimeout,
                    () => refreshTimer
                                     );

                refreshTimer = new Timer(refreshHandler, null, CalculateLinkAuthorizationRefreshInterval(authExpirationUtc), Timeout.InfiniteTimeSpan);

                // Track the link before returning it, so that it can be managed with the scope.

                BeginTrackingLinkAsActive(link, refreshTimer);
                return(link);
            }
            catch
            {
                // Aborting the session will perform any necessary cleanup of
                // the associated link as well.

                session?.Abort();
                throw;
            }
        }
Beispiel #19
0
        /// <summary>
        ///   Starts running a task responsible for receiving and processing events in the context of a specified partition.
        /// </summary>
        ///
        /// <param name="partitionId">The identifier of the Event Hub partition the task is associated with.  Events will be read only from this partition.</param>
        /// <param name="startingPosition">The position within the partition where the task should begin reading events.</param>
        /// <param name="maximumReceiveWaitTime">The maximum amount of time to wait to for an event to be available before emitting an empty item; if <c>null</c>, empty items will not be published.</param>
        /// <param name="retryOptions">The set of options to use for determining whether a failed operation should be retried and, if so, the amount of time to wait between retry attempts.</param>
        /// <param name="trackLastEnqueuedEventInformation">Indicates whether or not the task should request information on the last enqueued event on the partition associated with a given event, and track that information as events are received.</param>
        /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> instance to signal the request to cancel the operation.</param>
        ///
        /// <returns>The running task that is currently receiving and processing events in the context of the specified partition.</returns>
        ///
        protected virtual Task RunPartitionProcessingAsync(string partitionId,
                                                           EventPosition startingPosition,
                                                           TimeSpan?maximumReceiveWaitTime,
                                                           RetryOptions retryOptions,
                                                           bool trackLastEnqueuedEventInformation,
                                                           CancellationToken cancellationToken = default)
        {
            // TODO: should the retry options used here be the same for the abstract RetryPolicy property?

            Argument.AssertNotNullOrEmpty(partitionId, nameof(partitionId));
            Argument.AssertNotNull(retryOptions, nameof(retryOptions));

            return(Task.Run(async() =>
            {
                // TODO: should we double check if a previous run already exists and close it?  We have a race condition.  Maybe we should throw in case another task exists.

                var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
                var taskCancellationToken = cancellationSource.Token;

                ActivePartitionProcessorTokenSources[partitionId] = cancellationSource;

                // Context is set to default if operation fails.  This shouldn't fail unless the user tries processing
                // a partition they don't own.

                PartitionContexts.TryGetValue(partitionId, out var context);

                var options = new EventHubConsumerClientOptions
                {
                    RetryOptions = retryOptions,
                    TrackLastEnqueuedEventInformation = trackLastEnqueuedEventInformation
                };

                await using var connection = CreateConnection();

                await using (var consumer = new EventHubConsumerClient(ConsumerGroup, connection, options))
                {
                    await foreach (var partitionEvent in consumer.ReadEventsFromPartitionAsync(partitionId, startingPosition, maximumReceiveWaitTime, taskCancellationToken))
                    {
                        using DiagnosticScope diagnosticScope = EventDataInstrumentation.ClientDiagnostics.CreateScope(DiagnosticProperty.EventProcessorProcessingActivityName);
                        diagnosticScope.AddAttribute("kind", "server");

                        if (diagnosticScope.IsEnabled &&
                            partitionEvent.Data != null &&
                            EventDataInstrumentation.TryExtractDiagnosticId(partitionEvent.Data, out string diagnosticId))
                        {
                            diagnosticScope.AddLink(diagnosticId);
                        }

                        diagnosticScope.Start();

                        try
                        {
                            await ProcessEventAsync(partitionEvent, context).ConfigureAwait(false);
                        }
                        catch (Exception eventProcessingException)
                        {
                            diagnosticScope.Failed(eventProcessingException);
                            throw;
                        }
                    }
                }
            }));
        }
 /// <summary>
 ///   Creates a consumer strongly aligned with the active protocol and transport, responsible
 ///   for reading <see cref="EventData" /> from a specific Event Hub partition, in the context
 ///   of a specific consumer group.
 ///
 ///   A consumer may be exclusive, which asserts ownership over the partition for the consumer
 ///   group to ensure that only one consumer from that group is reading the from the partition.
 ///   These exclusive consumers are sometimes referred to as "Epoch Consumers."
 ///
 ///   A consumer may also be non-exclusive, allowing multiple consumers from the same consumer
 ///   group to be actively reading events from the partition.  These non-exclusive consumers are
 ///   sometimes referred to as "Non-epoch Consumers."
 ///
 ///   Designating a consumer as exclusive may be specified in the <paramref name="consumerOptions" />.
 ///   By default, consumers are created as non-exclusive.
 /// </summary>
 ///
 /// <param name="consumerGroup">The name of the consumer group this consumer is associated with.  Events are read in the context of this group.</param>
 /// <param name="partitionId">The identifier of the Event Hub partition from which events will be received.</param>
 /// <param name="eventPosition">The position within the partition where the consumer should begin reading events.</param>
 /// <param name="consumerOptions">The set of options to apply when creating the consumer.</param>
 ///
 /// <returns>A <see cref="TransportConsumer" /> configured in the requested manner.</returns>
 ///
 public abstract TransportConsumer CreateConsumer(string consumerGroup,
                                                  string partitionId,
                                                  EventPosition eventPosition,
                                                  EventHubConsumerClientOptions consumerOptions);