/// <summary>
        ///   Initializes a new instance of the <see cref="AmqpProducer"/> class.
        /// </summary>
        ///
        /// <param name="eventHubName">The name of the Event Hub to which events will be published.</param>
        /// <param name="partitionId">The identifier of the Event Hub partition to which it is bound; if unbound, <c>null</c>.</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="requestedFeatures">The flags specifying the set of special transport features that should be opted-into.</param>
        /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</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 AmqpProducer(string eventHubName,
                            string partitionId,
                            AmqpConnectionScope connectionScope,
                            AmqpMessageConverter messageConverter,
                            EventHubsRetryPolicy retryPolicy,
                            TransportProducerFeatures requestedFeatures = TransportProducerFeatures.None,
                            PartitionPublishingOptions partitionOptions = null)
        {
            Argument.AssertNotNullOrEmpty(eventHubName, nameof(eventHubName));
            Argument.AssertNotNull(connectionScope, nameof(connectionScope));
            Argument.AssertNotNull(messageConverter, nameof(messageConverter));
            Argument.AssertNotNull(retryPolicy, nameof(retryPolicy));

            EventHubName     = eventHubName;
            PartitionId      = partitionId;
            RetryPolicy      = retryPolicy;
            ConnectionScope  = connectionScope;
            MessageConverter = messageConverter;
            ActiveFeatures   = requestedFeatures;
            ActiveOptions    = partitionOptions?.Clone() ?? new PartitionPublishingOptions();

            SendLink = new FaultTolerantAmqpObject <SendingAmqpLink>(
                timeout => CreateLinkAndEnsureProducerStateAsync(partitionId, ActiveOptions, timeout, CancellationToken.None),
                link =>
            {
                link.Session?.SafeClose();
                link.SafeClose();
            });
        }
Example #2
0
 /// <summary>
 ///   Creates a producer strongly aligned with the active protocol and transport,
 ///   responsible for publishing <see cref="EventData" /> to the Event Hub.
 /// </summary>
 ///
 /// <param name="partitionId">The identifier of the partition to which the transport producer should be bound; if <c>null</c>, the producer is unbound.</param>
 /// <param name="requestedFeatures">The flags specifying the set of special transport features that should be opted-into.</param>
 /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param>
 /// <param name="retryPolicy">The policy which governs retry behavior and try timeouts.</param>
 ///
 /// <returns>A <see cref="TransportProducer"/> configured in the requested manner.</returns>
 ///
 internal virtual TransportProducer CreateTransportProducer(string partitionId,
                                                            TransportProducerFeatures requestedFeatures,
                                                            PartitionPublishingOptions partitionOptions,
                                                            EventHubsRetryPolicy retryPolicy)
 {
     Argument.AssertNotNull(retryPolicy, nameof(retryPolicy));
     return(InnerClient.CreateProducer(partitionId, requestedFeatures, partitionOptions, retryPolicy));
 }
        public void GetPublishingOptionsOrDefaultForPartitionReturnsTheOptionsWhenThePartitionIsFound()
        {
            var partitionId = "12";
            var expectedPartitionOptions = new PartitionPublishingOptions {
                ProducerGroupId = 1
            };

            var options = new EventHubProducerClientOptions();

            options.PartitionOptions.Add(partitionId, expectedPartitionOptions);

            Assert.That(options.GetPublishingOptionsOrDefaultForPartition(partitionId), Is.SameAs(expectedPartitionOptions));
        }
        public void ToCoreOptionsProducesACopy()
        {
            var options = new PartitionPublishingOptions
            {
                OwnerLevel             = 3,
                ProducerGroupId        = 99,
                StartingSequenceNumber = 42
            };

            var clone = options.ToCoreOptions();

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

            Assert.That(clone.OwnerLevel, Is.EqualTo(options.OwnerLevel), "The owner level should have been copied.");
            Assert.That(clone.ProducerGroupId, Is.EqualTo(options.ProducerGroupId), "The producer group identifier should have been copied.");
            Assert.That(clone.StartingSequenceNumber, Is.EqualTo(options.StartingSequenceNumber), "The starting sequence number should have been copied.");
        }
Example #5
0
        /// <summary>
        ///   Creates a producer strongly aligned with the active protocol and transport,
        ///   responsible for publishing <see cref="EventData" /> to the Event Hub.
        /// </summary>
        ///
        /// <param name="partitionId">The identifier of the partition to which the transport producer should be bound; if <c>null</c>, the producer is unbound.</param>
        /// <param name="requestedFeatures">The flags specifying the set of special transport features that should be opted-into.</param>
        /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param>
        /// <param name="retryPolicy">The policy which governs retry behavior and try timeouts.</param>
        ///
        /// <returns>A <see cref="TransportProducer"/> configured in the requested manner.</returns>
        ///
        public override TransportProducer CreateProducer(string partitionId,
                                                         TransportProducerFeatures requestedFeatures,
                                                         PartitionPublishingOptions partitionOptions,
                                                         EventHubsRetryPolicy retryPolicy)
        {
            Argument.AssertNotClosed(_closed, nameof(AmqpClient));

            return(new AmqpProducer
                   (
                       EventHubName,
                       partitionId,
                       ConnectionScope,
                       MessageConverter,
                       retryPolicy,
                       requestedFeatures,
                       partitionOptions
                   ));
        }
Example #6
0
        /// <summary>
        ///   Initializes a new instance of the <see cref="AmqpProducer"/> class.
        /// </summary>
        ///
        /// <param name="eventHubName">The name of the Event Hub to which events will be published.</param>
        /// <param name="partitionId">The identifier of the Event Hub partition to which it is bound; if unbound, <c>null</c>.</param>
        /// <param name="producerIdentifier">The identifier to associate with the consumer; if <c>null</c> or <see cref="string.Empty" />, a random identifier will be generated.</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="requestedFeatures">The flags specifying the set of special transport features that should be opted-into.</param>
        /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</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 AmqpProducer(string eventHubName,
                            string partitionId,
                            string producerIdentifier,
                            AmqpConnectionScope connectionScope,
                            AmqpMessageConverter messageConverter,
                            EventHubsRetryPolicy retryPolicy,
                            TransportProducerFeatures requestedFeatures = TransportProducerFeatures.None,
                            PartitionPublishingOptions partitionOptions = null)
        {
            Argument.AssertNotNullOrEmpty(eventHubName, nameof(eventHubName));
            Argument.AssertNotNull(connectionScope, nameof(connectionScope));
            Argument.AssertNotNull(messageConverter, nameof(messageConverter));
            Argument.AssertNotNull(retryPolicy, nameof(retryPolicy));

            if (string.IsNullOrEmpty(producerIdentifier))
            {
                producerIdentifier = Guid.NewGuid().ToString();
            }

            EventHubName     = eventHubName;
            PartitionId      = partitionId;
            Identifier       = producerIdentifier;
            RetryPolicy      = retryPolicy;
            ConnectionScope  = connectionScope;
            MessageConverter = messageConverter;
            ActiveFeatures   = requestedFeatures;
            ActiveOptions    = partitionOptions?.Clone() ?? new PartitionPublishingOptions();

            SendLink = new FaultTolerantAmqpObject <SendingAmqpLink>(
                timeout => CreateLinkAndEnsureProducerStateAsync(partitionId, producerIdentifier, ActiveOptions, timeout, CancellationToken.None),
                link =>
            {
                link.Session?.SafeClose();
                link.SafeClose();
                EventHubsEventSource.Log.FaultTolerantAmqpObjectClose(nameof(SendingAmqpLink), "", EventHubName, "", PartitionId, link.TerminalException?.Message);
            });
        }
        /// <summary>
        ///   Creates the AMQP link to be used for producer-related operations and ensures
        ///   that the corresponding state for the producer has been updated based on the link
        ///   configuration.
        /// </summary>
        ///
        /// <param name="partitionId">The identifier of the Event Hub partition to which it is bound; if unbound, <c>null</c>.</param>
        /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param>
        /// <param name="timeout">The timeout to apply when creating the link.</param>
        /// <param name="cancellationToken">The cancellation token to consider when creating the link.</param>
        ///
        /// <returns>The AMQP link to use for producer-related operations.</returns>
        ///
        /// <remarks>
        ///   This method will modify class-level state, setting those attributes that depend on the AMQP
        ///   link configuration.  There exists a benign race condition in doing so, as there may be multiple
        ///   concurrent callers.  In this case, the attributes may be set multiple times but the resulting
        ///   value will be the same.
        /// </remarks>
        ///
        protected virtual async Task <SendingAmqpLink> CreateLinkAndEnsureProducerStateAsync(string partitionId,
                                                                                             PartitionPublishingOptions partitionOptions,
                                                                                             TimeSpan timeout,
                                                                                             CancellationToken cancellationToken)
        {
            var link = default(SendingAmqpLink);

            try
            {
                link = await ConnectionScope.OpenProducerLinkAsync(partitionId, ActiveFeatures, partitionOptions, timeout, cancellationToken).ConfigureAwait(false);

                if (!MaximumMessageSize.HasValue)
                {
                    // This delay is necessary to prevent the link from causing issues for subsequent
                    // operations after creating a batch.  Without it, operations using the link consistently
                    // timeout.  The length of the delay does not appear significant, just the act of introducing
                    // an asynchronous delay.
                    //
                    // For consistency the value used by the legacy Event Hubs client has been brought forward and
                    // used here.

                    await Task.Delay(15, cancellationToken).ConfigureAwait(false);

                    MaximumMessageSize = (long)link.Settings.MaxMessageSize;
                }

                if (InitializedPartitionProperties == null)
                {
                    var producerGroup = link.ExtractSettingPropertyValueOrDefault(AmqpProperty.ProducerGroupId, default(long?));
                    var ownerLevel    = link.ExtractSettingPropertyValueOrDefault(AmqpProperty.ProducerOwnerLevel, default(short?));
                    var sequence      = link.ExtractSettingPropertyValueOrDefault(AmqpProperty.ProducerSequenceNumber, default(int?));

                    // Once the properties are initialized, clear the starting sequence number to ensure that the current
                    // sequence tracked by the service is used should the link need to be recreated; this avoids the need for
                    // the transport producer to have awareness of the sequence numbers of events being sent.

                    InitializedPartitionProperties          = new PartitionPublishingProperties(false, producerGroup, ownerLevel, sequence);
                    partitionOptions.StartingSequenceNumber = null;
                }
            }
            catch (Exception ex)
            {
                ExceptionDispatchInfo.Capture(ex.TranslateConnectionCloseDuringLinkCreationException(EventHubName)).Throw();
            }

            return(link);
        }
 internal override TransportProducer CreateTransportProducer(string partitionId,
                                                             TransportProducerFeatures requestedFeatures,
                                                             PartitionPublishingOptions partitionOptions,
                                                             EventHubsRetryPolicy retryPolicy) => TransportProducerFactory();
Example #9
0
 /// <summary>
 ///   Creates a producer strongly aligned with the active protocol and transport,
 ///   responsible for publishing <see cref="EventData" /> to the Event Hub.
 /// </summary>
 ///
 /// <param name="partitionId">The identifier of the partition to which the transport producer should be bound; if <c>null</c>, the producer is unbound.</param>
 /// <param name="requestedFeatures">The flags specifying the set of special transport features that should be opted-into.</param>
 /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param>
 /// <param name="retryPolicy">The policy which governs retry behavior and try timeouts.</param>
 ///
 /// <returns>A <see cref="TransportProducer"/> configured in the requested manner.</returns>
 ///
 public abstract TransportProducer CreateProducer(string partitionId,
                                                  TransportProducerFeatures requestedFeatures,
                                                  PartitionPublishingOptions partitionOptions,
                                                  EventHubsRetryPolicy retryPolicy);
Example #10
0
        /// <summary>
        ///   Creates the AMQP link to be used for producer-related operations and ensures
        ///   that the corresponding state for the producer has been updated based on the link
        ///   configuration.
        /// </summary>
        ///
        /// <param name="partitionId">The identifier of the Event Hub partition to which it is bound; if unbound, <c>null</c>.</param>
        /// <param name="partitionOptions">The set of options, if any, that should be considered when initializing the producer.</param>
        /// <param name="timeout">The timeout to apply when creating the link.</param>
        /// <param name="cancellationToken">The cancellation token to consider when creating the link.</param>
        ///
        /// <returns>The AMQP link to use for producer-related operations.</returns>
        ///
        /// <remarks>
        ///   This method will modify class-level state, setting those attributes that depend on the AMQP
        ///   link configuration.  There exists a benign race condition in doing so, as there may be multiple
        ///   concurrent callers.  In this case, the attributes may be set multiple times but the resulting
        ///   value will be the same.
        /// </remarks>
        ///
        protected virtual async Task <SendingAmqpLink> CreateLinkAndEnsureProducerStateAsync(string partitionId,
                                                                                             PartitionPublishingOptions partitionOptions,
                                                                                             TimeSpan timeout,
                                                                                             CancellationToken cancellationToken)
        {
            var link = default(SendingAmqpLink);

            try
            {
                link = await ConnectionScope.OpenProducerLinkAsync(partitionId, timeout, cancellationToken).ConfigureAwait(false);

                if (!MaximumMessageSize.HasValue)
                {
                    // This delay is necessary to prevent the link from causing issues for subsequent
                    // operations after creating a batch.  Without it, operations using the link consistently
                    // timeout.  The length of the delay does not appear significant, just the act of introducing
                    // an asynchronous delay.
                    //
                    // For consistency the value used by the legacy Event Hubs client has been brought forward and
                    // used here.

                    await Task.Delay(15, cancellationToken).ConfigureAwait(false);

                    MaximumMessageSize = (long)link.Settings.MaxMessageSize;
                }

                if (InitializedPartitionProperties == null)
                {
                    if ((ActiveFeatures & TransportProducerFeatures.IdempotentPublishing) != 0)
                    {
                        throw new NotImplementedException(nameof(CreateLinkAndEnsureProducerStateAsync));
                    }
                    else
                    {
                        InitializedPartitionProperties = new PartitionPublishingProperties(false, null, null, null);
                    }
                }
            }
            catch (Exception ex)
            {
                ExceptionDispatchInfo.Capture(ex.TranslateConnectionCloseDuringLinkCreationException(EventHubName)).Throw();
            }

            return(link);
        }