/// <summary> /// Creates an Event Hub consumer responsible for reading <see cref="EventData" /> from a specific Event Hub partition, /// and as a member 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> /// <param name="defaultRetryPolicy">The default retry policy to use if no retry options were specified in the <paramref name="consumerOptions" />.</param> /// /// <returns>An Event Hub consumer configured in the requested manner.</returns> /// public override EventHubConsumer CreateConsumer(string consumerGroup, string partitionId, EventPosition eventPosition, EventHubConsumerOptions consumerOptions, EventHubRetryPolicy defaultRetryPolicy) { Argument.AssertNotClosed(_closed, nameof(AmqpEventHubClient)); LastEnqueuedEventProperties lastEnqueuedEventProperties = consumerOptions.TrackLastEnqueuedEventInformation ? new LastEnqueuedEventProperties(EventHubName, partitionId) : null; EventHubRetryPolicy retryPolicy = defaultRetryPolicy ?? _retryPolicy; var transportConsumer = new AmqpEventHubConsumer ( EventHubName, consumerGroup, partitionId, eventPosition, consumerOptions, ConnectionScope, MessageConverter, retryPolicy, lastEnqueuedEventProperties ); return(new EventHubConsumer(transportConsumer, EventHubName, consumerGroup, partitionId, eventPosition, consumerOptions, retryPolicy)); }
/// <summary> /// Initializes a new instance of the <see cref="AmqpEventHubConsumer"/> 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> /// <param name="lastEnqueuedEventProperties">The set of properties for the last event enqueued in a partition; if not requested in the consumer options, it is expected that this is <c>null</c>.</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 AmqpEventHubConsumer(string eventHubName, string consumerGroup, string partitionId, EventPosition eventPosition, EventHubConsumerOptions consumerOptions, AmqpConnectionScope connectionScope, AmqpMessageConverter messageConverter, EventHubRetryPolicy retryPolicy, LastEnqueuedEventProperties lastEnqueuedEventProperties) : base(lastEnqueuedEventProperties) { Argument.AssertNotNullOrEmpty(eventHubName, nameof(eventHubName)); Argument.AssertNotNullOrEmpty(consumerGroup, nameof(consumerGroup)); Argument.AssertNotNullOrEmpty(partitionId, nameof(partitionId)); Argument.AssertNotNull(eventPosition, nameof(EventPosition)); Argument.AssertNotNull(consumerOptions, nameof(EventHubConsumerOptions)); Argument.AssertNotNull(connectionScope, nameof(connectionScope)); Argument.AssertNotNull(messageConverter, nameof(messageConverter)); Argument.AssertNotNull(retryPolicy, nameof(retryPolicy)); EventHubName = eventHubName; ConsumerGroup = consumerGroup; PartitionId = partitionId; ConnectionScope = connectionScope; MessageConverter = messageConverter; ReceiveLink = new FaultTolerantAmqpObject <ReceivingAmqpLink>(timeout => ConnectionScope.OpenConsumerLinkAsync(consumerGroup, partitionId, eventPosition, consumerOptions, timeout, CancellationToken.None), link => link.SafeClose()); _retryPolicy = retryPolicy; _tryTimeout = retryPolicy.CalculateTryTimeout(0); }
public async Task ReceiverIsConstructedCorrectly() { var eventHub = "eventHub"; var consumerGroup = "$TestThing"; var partition = "123"; var position = EventPosition.FromEnqueuedTime(DateTimeOffset.Parse("2015-10-25T12:00:00Z")); var priority = 8765; var identifier = "ThisIsAnAwesomeConsumer!"; var partitionMetrics = new LastEnqueuedEventProperties(eventHub, partition); var retryPolicy = Mock.Of <EventHubRetryPolicy>(); var mock = new ObservableReceiverMock(new ClientMock(), consumerGroup, partition, TrackOne.EventPosition.FromEnqueuedTime(position.EnqueuedTime.Value.UtcDateTime), priority, new ReceiverOptions { Identifier = identifier, EnableReceiverRuntimeMetric = true }); var consumer = new TrackOneEventHubConsumer(_ => mock, retryPolicy, partitionMetrics); // Invoke an operation to force the consumer to be lazily instantiated. Otherwise, // construction does not happen. await consumer.ReceiveAsync(0, TimeSpan.Zero, default); Assert.That(mock.ConstructedWith.ConsumerGroup, Is.EqualTo(consumerGroup), "The consumer group should match."); Assert.That(mock.ConstructedWith.Partition, Is.EqualTo(partition), "The partition should match."); Assert.That(TrackOneComparer.IsEventPositionEquivalent(mock.ConstructedWith.Position, position), Is.True, "The starting event position should match."); Assert.That(mock.ConstructedWith.Priority, Is.EqualTo(priority), "The owner level should match."); Assert.That(mock.ConstructedWith.Options.Identifier, Is.EqualTo(identifier), "The consumer identifier should match."); Assert.That(mock.ConstructedWith.Options.EnableReceiverRuntimeMetric, Is.True, "The receiver metrics should be enabled when set in the options."); var consumerRetry = GetRetryPolicy(consumer); Assert.That(consumerRetry, Is.SameAs(retryPolicy), "The consumer retry instance should match."); }
public async Task ReadPartitionTrackLastEnqueued() { await using var scope = await EventHubScope.CreateAsync(1); #region Snippet:EventHubs_Sample05_ReadPartitionTrackLastEnqueued 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 consumer = new EventHubConsumerClient( consumerGroup, connectionString, eventHubName); try { using CancellationTokenSource cancellationSource = new CancellationTokenSource(); cancellationSource.CancelAfter(TimeSpan.FromSeconds(30)); string firstPartition = (await consumer.GetPartitionIdsAsync(cancellationSource.Token)).First(); EventPosition startingPosition = EventPosition.Earliest; var options = new ReadEventOptions { TrackLastEnqueuedEventProperties = true }; await foreach (PartitionEvent partitionEvent in consumer.ReadEventsFromPartitionAsync( firstPartition, startingPosition, options, cancellationSource.Token)) { LastEnqueuedEventProperties properties = partitionEvent.Partition.ReadLastEnqueuedEventProperties(); Debug.WriteLine($"Partition: { partitionEvent.Partition.PartitionId }"); Debug.WriteLine($"\tThe last sequence number is: { properties.SequenceNumber }"); Debug.WriteLine($"\tThe last offset is: { properties.Offset }"); Debug.WriteLine($"\tThe last enqueued time is: { properties.EnqueuedTime }, in UTC."); Debug.WriteLine($"\tThe information was updated at: { properties.LastReceivedTime }, in UTC."); } } catch (TaskCanceledException) { // This is expected if the cancellation token is // signaled. } finally { await consumer.CloseAsync(); } #endregion }
public void GetHashCodeReturnsDifferentValuesForDifferentMembers() { var now = DateTimeOffset.UtcNow; var first = new LastEnqueuedEventProperties(lastSequenceNumber: 333, lastOffset: 888, lastEnqueuedTime: DateTimeOffset.Parse("2015-10-27T12:00:00Z"), lastReceivedTime: now); var second = new LastEnqueuedEventProperties(lastSequenceNumber: 555, lastOffset: 777, lastEnqueuedTime: DateTimeOffset.Parse("2015-10-27T12:00:00Z"), lastReceivedTime: now); Assert.That(first.GetHashCode(), Is.Not.EqualTo(second.GetHashCode())); }
/// <summary> /// Creates a consumer responsible for reading <see cref="EventData" /> from a specific Event Hub partition, /// and as a member 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> /// <param name="defaultRetryPolicy">The default retry policy to use if no retry options were specified in the <paramref name="consumerOptions" />.</param> /// /// <returns>An Event Hub consumer configured in the requested manner.</returns> /// public override EventHubConsumer CreateConsumer(string consumerGroup, string partitionId, EventPosition eventPosition, EventHubConsumerOptions consumerOptions, EventHubRetryPolicy defaultRetryPolicy) { TrackOne.PartitionReceiver CreateReceiverFactory(EventHubRetryPolicy activeRetryPolicy) { var position = new TrackOne.EventPosition { IsInclusive = eventPosition.IsInclusive, Offset = eventPosition.Offset, SequenceNumber = eventPosition.SequenceNumber, EnqueuedTimeUtc = eventPosition.EnqueuedTime?.UtcDateTime }; var trackOneOptions = new TrackOne.ReceiverOptions { Identifier = consumerOptions.Identifier, EnableReceiverRuntimeMetric = consumerOptions.TrackLastEnqueuedEventInformation }; PartitionReceiver consumer; if (consumerOptions.OwnerLevel.HasValue) { consumer = TrackOneClient.CreateEpochReceiver(consumerGroup, partitionId, position, consumerOptions.OwnerLevel.Value, trackOneOptions); } else { consumer = TrackOneClient.CreateReceiver(consumerGroup, partitionId, position, trackOneOptions); } consumer.RetryPolicy = new TrackOneRetryPolicy(activeRetryPolicy); return(consumer); } EventHubRetryPolicy initialRetryPolicy = (consumerOptions.RetryOptions != null) ? new BasicRetryPolicy(consumerOptions.RetryOptions) : defaultRetryPolicy; LastEnqueuedEventProperties partitionMetrics = (consumerOptions.TrackLastEnqueuedEventInformation) ? new LastEnqueuedEventProperties(EventHubName, partitionId) : null; return(new EventHubConsumer ( new TrackOneEventHubConsumer(CreateReceiverFactory, initialRetryPolicy, partitionMetrics), TrackOneClient.EventHubName, partitionId, consumerGroup, eventPosition, consumerOptions, initialRetryPolicy )); }
/// <summary> /// Initializes a new instance of the <see cref="TrackOneEventHubConsumer"/> class. /// </summary> /// /// <param name="trackOneReceiverFactory">A delegate that can be used for creation of the <see cref="TrackOne.PartitionReceiver" /> to which operations are delegated to.</param> /// <param name="retryPolicy">The retry policy to use when creating the <see cref="TrackOne.PartitionReceiver" />.</param> /// <param name="lastEnqueuedEventProperties">The set of properties for the last event enqueued in a partition; if not requested in the consumer options, it is expected that this is <c>null</c>.</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 TrackOneEventHubConsumer(Func <EventHubRetryPolicy, TrackOne.PartitionReceiver> trackOneReceiverFactory, EventHubRetryPolicy retryPolicy, LastEnqueuedEventProperties lastEnqueuedEventProperties) : base(lastEnqueuedEventProperties) { Guard.ArgumentNotNull(nameof(trackOneReceiverFactory), trackOneReceiverFactory); Guard.ArgumentNotNull(nameof(retryPolicy), retryPolicy); _retryPolicy = retryPolicy; _trackOneReceiver = new Lazy <TrackOne.PartitionReceiver>(() => trackOneReceiverFactory(_retryPolicy), LazyThreadSafetyMode.ExecutionAndPublication); }
public void DifferentLastReceiveTimesAreNotEqual() { var now = DateTimeOffset.UtcNow; var first = new LastEnqueuedEventProperties(lastSequenceNumber: 123, lastOffset: 887, lastEnqueuedTime: DateTimeOffset.Parse("2015-10-27T12:00:00Z"), lastReceivedTime: now); var second = new LastEnqueuedEventProperties(lastSequenceNumber: 123, lastOffset: 887, lastEnqueuedTime: DateTimeOffset.Parse("2015-10-27T12:00:00Z"), lastReceivedTime: now.AddHours(1)); Assert.That(first.Equals((object)second), Is.False, "The default Equals comparison is incorrect."); Assert.That(first.Equals(second), Is.False, "The IEquatable comparison is incorrect."); Assert.That((first == second), Is.False, "The == operator comparison is incorrect."); Assert.That((first != second), Is.True, "The != operator comparison is incorrect."); }
public void ToStringReflectsTheState() { var offset = 123; var sequence = 778; var enqueued = DateTimeOffset.Now.AddHours(1); var received = DateTimeOffset.Now.AddHours(7); var properties = new LastEnqueuedEventProperties(sequence, offset, enqueued, received); var toStringValue = properties.ToString(); Assert.That(toStringValue, Contains.Substring($"[{ offset }]"), "The offset should be represented."); Assert.That(toStringValue, Contains.Substring($"[{ sequence }]"), "The sequence number should be represented."); Assert.That(toStringValue, Contains.Substring($"[{ enqueued }]"), "The enqueued time should be represented."); Assert.That(toStringValue, Contains.Substring($"[{ received }]"), "The received time should be represented."); }
public async Task ReadLastEnqueuedEventPropertiesReadsPropertiesWhenThePartitionIsOwned() { using var processorCancellation = new CancellationTokenSource(); using var cancellationSource = new CancellationTokenSource(); cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit); var partitionId = "27"; var partitionIds = new[] { "0", partitionId }; var ownedPartitions = new List <string>(); var lastEventProperties = new LastEnqueuedEventProperties(1234, 9876, DateTimeOffset.Parse("2015-10-27T00:00:00Z"), DateTimeOffset.Parse("2012-03-04T08:30:00Z")); var completionSource = new TaskCompletionSource <bool>(TaskCreationOptions.RunContinuationsAsynchronously); var options = new EventProcessorOptions { LoadBalancingUpdateInterval = TimeSpan.FromMinutes(5), TrackLastEnqueuedEventProperties = true }; var mockLoadBalancer = new Mock <PartitionLoadBalancer>(); var mockConnection = new Mock <EventHubConnection>(); var mockProcessor = new Mock <MinimalProcessorMock>(65, "consumerGroup", "namespace", "eventHub", Mock.Of <TokenCredential>(), options, mockLoadBalancer.Object) { CallBase = true }; mockLoadBalancer .SetupGet(processor => processor.OwnedPartitionIds) .Returns(ownedPartitions); mockLoadBalancer .Setup(lb => lb.RunLoadBalancingAsync(partitionIds, It.IsAny <CancellationToken>())) .Callback(() => { GetActivePartitionProcessors(mockProcessor.Object).TryAdd( partitionId, new EventProcessor <EventProcessorPartition> .PartitionProcessor(Task.Delay(Timeout.Infinite, processorCancellation.Token), new EventProcessorPartition { PartitionId = partitionId }, () => lastEventProperties, processorCancellation) ); ownedPartitions.Add(partitionId); completionSource.TrySetResult(true); }) .Returns(() => default);
/// <summary> /// Initializes a new instance of the <see cref="PartitionContext"/> class. /// </summary> /// /// <param name="partitionId">The identifier of the Event Hub partition this context is associated with.</param> /// <param name="lastEnqueuedEventProperties">The set of properties to be returned when <see cref="PartitionContext.ReadLastEnqueuedEventProperties" /> is invoked.</param> /// public static PartitionContext PartitionContext(string partitionId, LastEnqueuedEventProperties lastEnqueuedEventProperties = default) => new FactoryPartitionContext(partitionId, lastEnqueuedEventProperties);
/// <summary> /// Initializes a new instance of the <see cref="FactoryPartitionContext"/> class. /// </summary> /// /// <param name="partitionId">The identifier of the Event Hub partition this context is associated with.</param> /// <param name="lastEnqueuedEventProperties">The set of properties to be returned when <see cref="ReadLastEnqueuedEventProperties" /> is invoked.</param> /// internal FactoryPartitionContext(string partitionId, LastEnqueuedEventProperties lastEnqueuedEventProperties) : base(partitionId) =>
/// <summary> /// Initializes a new instance of the <see cref="Consumer.PartitionContext"/> class. /// </summary> /// /// <param name="fullyQualifiedNamespace">The fully qualified Event Hubs namespace this context is associated with.</param> /// <param name="eventHubName">The name of the Event Hub partition this context is associated with.</param> /// <param name="consumerGroup">The name of the consumer group this context is associated with.</param> /// <param name="partitionId">The identifier of the Event Hub partition this context is associated with.</param> /// <param name="lastEnqueuedEventProperties">The set of properties to be returned when <see cref="PartitionContext.ReadLastEnqueuedEventProperties" /> is invoked.</param> /// public static PartitionContext PartitionContext(string fullyQualifiedNamespace, string eventHubName, string consumerGroup, string partitionId, LastEnqueuedEventProperties lastEnqueuedEventProperties = default) => new FactoryPartitionContext(fullyQualifiedNamespace, eventHubName, consumerGroup, partitionId, lastEnqueuedEventProperties);
/// <summary> /// Initializes a new instance of the <see cref="FactoryPartitionContext"/> class. /// </summary> /// /// <param name="fullyQualifiedNamespace">The fully qualified Event Hubs namespace this context is associated with.</param> /// <param name="eventHubName">The name of the Event Hub partition this context is associated with.</param> /// <param name="consumerGroup">The name of the consumer group this context is associated with.</param> /// <param name="partitionId">The identifier of the Event Hub partition this context is associated with.</param> /// <param name="lastEnqueuedEventProperties">The set of properties to be returned when <see cref="ReadLastEnqueuedEventProperties" /> is invoked.</param> /// internal FactoryPartitionContext(string fullyQualifiedNamespace, string eventHubName, string consumerGroup, string partitionId, LastEnqueuedEventProperties lastEnqueuedEventProperties) : base(fullyQualifiedNamespace, eventHubName, consumerGroup, partitionId) =>
/// <summary> /// Initializes a new instance of the <see cref="TransportEventHubConsumer"/> class. /// </summary> /// /// <param name="lastEnqueuedEventProperties">The set of properties for the last event enqueued in a partition.</param> /// protected TransportEventHubConsumer(LastEnqueuedEventProperties lastEnqueuedEventProperties = null) { LastEnqueuedEventInformation = lastEnqueuedEventProperties; }