diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Processor/api/Azure.Messaging.EventHubs.Processor.netstandard2.0.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Processor/api/Azure.Messaging.EventHubs.Processor.netstandard2.0.cs
index a5aa8f6c0c14..ff8ed3c52970 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Processor/api/Azure.Messaging.EventHubs.Processor.netstandard2.0.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs.Processor/api/Azure.Messaging.EventHubs.Processor.netstandard2.0.cs
@@ -7,6 +7,7 @@ public EventProcessorClient(Azure.Storage.Blobs.BlobContainerClient checkpointSt
public EventProcessorClient(Azure.Storage.Blobs.BlobContainerClient checkpointStore, string consumerGroup, string connectionString, Azure.Messaging.EventHubs.EventProcessorClientOptions clientOptions) { }
public EventProcessorClient(Azure.Storage.Blobs.BlobContainerClient checkpointStore, string consumerGroup, string connectionString, string eventHubName) { }
public EventProcessorClient(Azure.Storage.Blobs.BlobContainerClient checkpointStore, string consumerGroup, string fullyQualifiedNamespace, string eventHubName, Azure.Core.TokenCredential credential, Azure.Messaging.EventHubs.EventProcessorClientOptions clientOptions = null) { }
+ public EventProcessorClient(Azure.Storage.Blobs.BlobContainerClient checkpointStore, string consumerGroup, string fullyQualifiedNamespace, string eventHubName, Azure.Messaging.EventHubs.EventHubsSharedAccessKeyCredential credential, Azure.Messaging.EventHubs.EventProcessorClientOptions clientOptions = null) { }
public EventProcessorClient(Azure.Storage.Blobs.BlobContainerClient checkpointStore, string consumerGroup, string connectionString, string eventHubName, Azure.Messaging.EventHubs.EventProcessorClientOptions clientOptions) { }
public new string ConsumerGroup { get { throw null; } }
public new string EventHubName { get { throw null; } }
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Processor/src/EventProcessorClient.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Processor/src/EventProcessorClient.cs
index ae44438dc2eb..3ad9d1f90e2b 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Processor/src/EventProcessorClient.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs.Processor/src/EventProcessorClient.cs
@@ -405,6 +405,33 @@ public EventProcessorClient(BlobContainerClient checkpointStore,
StorageManager = CreateStorageManager(checkpointStore);
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The client responsible for persisting checkpoints and processor state to durable storage. The associated container is expected to exist.
+ /// The name of the consumer group this processor is associated with. Events are read in the context of this group.
+ /// The fully qualified Event Hubs namespace to connect to. This is likely to be similar to {yournamespace}.servicebus.windows.net.
+ /// The name of the specific Event Hub to associate the processor with.
+ /// The Event Hubs shared access key credential to use for authorization. Access controls may be specified by the Event Hubs namespace or the requested Event Hub, depending on Azure configuration.
+ /// The set of options to use for this processor.
+ ///
+ ///
+ /// The container associated with the is expected to exist; the
+ /// does not assume the ability to manage the storage account and is safe to run without permission to manage the storage account.
+ ///
+ ///
+ public EventProcessorClient(BlobContainerClient checkpointStore,
+ string consumerGroup,
+ string fullyQualifiedNamespace,
+ string eventHubName,
+ EventHubsSharedAccessKeyCredential credential,
+ EventProcessorClientOptions clientOptions = default) : base((clientOptions ?? DefaultClientOptions).CacheEventCount, consumerGroup, fullyQualifiedNamespace, eventHubName, credential, CreateOptions(clientOptions))
+ {
+ Argument.AssertNotNull(checkpointStore, nameof(checkpointStore));
+ StorageManager = CreateStorageManager(checkpointStore);
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -432,6 +459,36 @@ public EventProcessorClient(BlobContainerClient checkpointStore,
StorageManager = CreateStorageManager(checkpointStore);
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Responsible for creation of checkpoints and for ownership claim.
+ /// The name of the consumer group this processor is associated with. Events are read in the context of this group.
+ /// The fully qualified Event Hubs namespace to connect to. This is likely to be similar to {yournamespace}.servicebus.windows.net.
+ /// The name of the specific Event Hub to associate the processor with.
+ /// The maximum number of events that will be read from the Event Hubs service and held in a local memory cache when reading is active and events are being emitted to an enumerator for processing.
+ /// A shared access key credential to satisfy base class requirements; this credential may not be null but will only be used in the case that has not been overridden.
+ /// The set of options to use for this processor.
+ ///
+ ///
+ /// This constructor is intended only to support functional testing and mocking; it should not be used for production scenarios.
+ ///
+ ///
+ internal EventProcessorClient(StorageManager storageManager,
+ string consumerGroup,
+ string fullyQualifiedNamespace,
+ string eventHubName,
+ int cacheEventCount,
+ EventHubsSharedAccessKeyCredential credential,
+ EventProcessorOptions clientOptions) : base(cacheEventCount, consumerGroup, fullyQualifiedNamespace, eventHubName, credential, clientOptions)
+ {
+ Argument.AssertNotNull(storageManager, nameof(storageManager));
+
+ DefaultStartingPosition = (clientOptions?.DefaultStartingPosition ?? DefaultStartingPosition);
+ StorageManager = storageManager;
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Processor/tests/Processor/EventProcessorClientLiveTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Processor/tests/Processor/EventProcessorClientLiveTests.cs
index 387e77496a25..cf513a24b803 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Processor/tests/Processor/EventProcessorClientLiveTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs.Processor/tests/Processor/EventProcessorClientLiveTests.cs
@@ -134,6 +134,56 @@ public async Task EventsCanBeReadByOneProcessorClientUsingAnIdentityCredential()
}
}
+ ///
+ /// Verifies that the can read a set of published events.
+ ///
+ ///
+ [Test]
+ public async Task EventsCanBeReadByOneProcessorClientUsingTheSharedKeyCredential()
+ {
+ // Setup the environment.
+
+ await using EventHubScope scope = await EventHubScope.CreateAsync(2);
+ var connectionString = EventHubsTestEnvironment.Instance.BuildConnectionStringForEventHub(scope.EventHubName);
+
+ using var cancellationSource = new CancellationTokenSource();
+ cancellationSource.CancelAfter(EventHubsTestEnvironment.Instance.TestExecutionTimeLimit);
+
+ // Send a set of events.
+
+ var sourceEvents = EventGenerator.CreateEvents(50).ToList();
+ var sentCount = await SendEvents(connectionString, sourceEvents, cancellationSource.Token);
+
+ Assert.That(sentCount, Is.EqualTo(sourceEvents.Count), "Not all of the source events were sent.");
+
+ // Attempt to read back the events.
+
+ var processedEvents = new ConcurrentDictionary();
+ var completionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var options = new EventProcessorOptions { LoadBalancingUpdateInterval = TimeSpan.FromMilliseconds(250) };
+ var processor = CreateProcessorWithSharedAccessKey(scope.ConsumerGroups.First(), scope.EventHubName, options: options);
+
+ processor.ProcessErrorAsync += CreateAssertingErrorHandler();
+ processor.ProcessEventAsync += CreateEventTrackingHandler(sentCount, processedEvents, completionSource, cancellationSource.Token);
+
+ await processor.StartProcessingAsync(cancellationSource.Token);
+
+ await Task.WhenAny(completionSource.Task, Task.Delay(Timeout.Infinite, cancellationSource.Token));
+ Assert.That(cancellationSource.IsCancellationRequested, Is.False, $"The cancellation token should not have been signaled. { processedEvents.Count } events were processed.");
+
+ await processor.StopProcessingAsync(cancellationSource.Token);
+ cancellationSource.Cancel();
+
+ // Validate the events that were processed.
+
+ foreach (var sourceEvent in sourceEvents)
+ {
+ var sourceId = sourceEvent.Properties[EventGenerator.IdPropertyName].ToString();
+ Assert.That(processedEvents.TryGetValue(sourceId, out var processedEvent), Is.True, $"The event with custom identifier [{ sourceId }] was not processed." );
+ Assert.That(sourceEvent.IsEquivalentTo(processedEvent), $"The event with custom identifier [{ sourceId }] did not match the corresponding processed event.");
+ }
+ }
+
///
/// Verifies that the can read a set of published events.
///
@@ -477,6 +527,29 @@ private EventProcessorClient CreateProcessorWithIdentity(string consumerGroup,
return new TestEventProcessorClient(storageManager, consumerGroup, EventHubsTestEnvironment.Instance.FullyQualifiedNamespace, eventHubName, credential, createConnection, options);
}
+ ///
+ /// Creates an that uses mock storage and
+ /// a connection based on an identity credential.
+ ///
+ ///
+ /// The consumer group for the processor.
+ /// The name of the Event Hub for the processor.
+ /// The set of client options to pass.
+ ///
+ /// The processor instance.
+ ///
+ private EventProcessorClient CreateProcessorWithSharedAccessKey(string consumerGroup,
+ string eventHubName,
+ StorageManager storageManager = default,
+ EventProcessorOptions options = default)
+ {
+ var credential = new EventHubsSharedAccessKeyCredential(EventHubsTestEnvironment.Instance.SharedAccessKeyName, EventHubsTestEnvironment.Instance.SharedAccessKey);
+ EventHubConnection createConnection() => new EventHubConnection(EventHubsTestEnvironment.Instance.FullyQualifiedNamespace, eventHubName, credential);
+
+ storageManager ??= new InMemoryStorageManager(_=> {});
+ return new TestEventProcessorClient(storageManager, consumerGroup, EventHubsTestEnvironment.Instance.FullyQualifiedNamespace, eventHubName, credential, createConnection, options);
+ }
+
///
/// Sends a set of events using a new producer to do so.
///
@@ -575,6 +648,17 @@ public class TestEventProcessorClient : EventProcessorClient
{
private readonly Func InjectedConnectionFactory;
+ internal TestEventProcessorClient(StorageManager storageManager,
+ string consumerGroup,
+ string fullyQualifiedNamespace,
+ string eventHubName,
+ EventHubsSharedAccessKeyCredential credential,
+ Func connectionFactory,
+ EventProcessorOptions options) : base(storageManager, consumerGroup, fullyQualifiedNamespace, eventHubName, 100, credential, options)
+ {
+ InjectedConnectionFactory = connectionFactory;
+ }
+
internal TestEventProcessorClient(StorageManager storageManager,
string consumerGroup,
string fullyQualifiedNamespace,
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Processor/tests/Processor/EventProcessorClientTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Processor/tests/Processor/EventProcessorClientTests.cs
index 1dce55fe8e86..053781ca4f96 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Processor/tests/Processor/EventProcessorClientTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs.Processor/tests/Processor/EventProcessorClientTests.cs
@@ -37,7 +37,8 @@ public class EventProcessorClientTests
public void ConstructorsValidateTheConsumerGroup(string consumerGroup)
{
Assert.That(() => new EventProcessorClient(Mock.Of(), consumerGroup, "dummyConnection", new EventProcessorClientOptions()), Throws.InstanceOf(), "The connection string constructor should validate the consumer group.");
- Assert.That(() => new EventProcessorClient(Mock.Of(), consumerGroup, "dummyNamespace", "dummyEventHub", Mock.Of(), new EventProcessorClientOptions()), Throws.InstanceOf(), "The namespace constructor should validate the consumer group.");
+ Assert.That(() => new EventProcessorClient(Mock.Of(), consumerGroup, "dummyNamespace", "dummyEventHub", Mock.Of(), new EventProcessorClientOptions()), Throws.InstanceOf(), "The token credential constructor should validate the consumer group.");
+ Assert.That(() => new EventProcessorClient(Mock.Of(), consumerGroup, "dummyNamespace", "dummyEventHub", new EventHubsSharedAccessKeyCredential("key", "value"), new EventProcessorClientOptions()), Throws.InstanceOf(), "The shared key credential constructor should validate the consumer group.");
}
///
@@ -51,7 +52,8 @@ public void ConstructorsValidateTheBlobContainerClient()
var fakeConnection = "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath=fake";
Assert.That(() => new EventProcessorClient(null, "consumerGroup", fakeConnection, new EventProcessorClientOptions()), Throws.InstanceOf(), "The connection string constructor should validate the blob container client.");
- Assert.That(() => new EventProcessorClient(null, "consumerGroup", "dummyNamespace", "dummyEventHub", Mock.Of(), new EventProcessorClientOptions()), Throws.InstanceOf(), "The namespace constructor should validate the blob container client.");
+ Assert.That(() => new EventProcessorClient(null, "consumerGroup", "dummyNamespace", "dummyEventHub", Mock.Of(), new EventProcessorClientOptions()), Throws.InstanceOf(), "The token credential constructor should validate the blob container client.");
+ Assert.That(() => new EventProcessorClient(null, "consumerGroup", "dummyNamespace", "dummyEventHub", new EventHubsSharedAccessKeyCredential("key", "value"), new EventProcessorClientOptions()), Throws.InstanceOf(), "The shared key credential constructor should validate the blob container client.");
}
///
@@ -77,7 +79,8 @@ public void ConstructorsValidateTheConnectionString(string connectionString)
[TestCase("http://namspace.servciebus.windows.com")]
public void ConstructorValidatesTheNamespace(string constructorArgument)
{
- Assert.That(() => new EventProcessorClient(Mock.Of(), EventHubConsumerClient.DefaultConsumerGroupName, constructorArgument, "dummy", Mock.Of()), Throws.InstanceOf());
+ Assert.That(() => new EventProcessorClient(Mock.Of(), EventHubConsumerClient.DefaultConsumerGroupName, constructorArgument, "dummy", Mock.Of()), Throws.InstanceOf(), "The token credential should validate.");
+ Assert.That(() => new EventProcessorClient(Mock.Of(), EventHubConsumerClient.DefaultConsumerGroupName, constructorArgument, "dummy", new EventHubsSharedAccessKeyCredential("key", "value")), Throws.InstanceOf(), "The shared key credential should validate.");
}
///
@@ -89,7 +92,8 @@ public void ConstructorValidatesTheNamespace(string constructorArgument)
[TestCase("")]
public void ConstructorValidatesTheEventHub(string constructorArgument)
{
- Assert.That(() => new EventProcessorClient(Mock.Of(), EventHubConsumerClient.DefaultConsumerGroupName, "namespace", constructorArgument, Mock.Of()), Throws.InstanceOf());
+ Assert.That(() => new EventProcessorClient(Mock.Of(), EventHubConsumerClient.DefaultConsumerGroupName, "namespace", constructorArgument, Mock.Of()), Throws.InstanceOf(), "The token credential should validate.");
+ Assert.That(() => new EventProcessorClient(Mock.Of(), EventHubConsumerClient.DefaultConsumerGroupName, "namespace", constructorArgument, new EventHubsSharedAccessKeyCredential("key", "value")), Throws.InstanceOf(), "The shared key credential should validate.");
}
///
@@ -99,7 +103,8 @@ public void ConstructorValidatesTheEventHub(string constructorArgument)
[Test]
public void ConstructorValidatesTheCredential()
{
- Assert.That(() => new EventProcessorClient(Mock.Of(), EventHubConsumerClient.DefaultConsumerGroupName, "namespace", "hubName", default(TokenCredential)), Throws.ArgumentNullException);
+ Assert.That(() => new EventProcessorClient(Mock.Of(), EventHubConsumerClient.DefaultConsumerGroupName, "namespace", "hubName", default(TokenCredential)), Throws.ArgumentNullException, "The token credential should validate.");
+ Assert.That(() => new EventProcessorClient(Mock.Of(), EventHubConsumerClient.DefaultConsumerGroupName, "namespace", "hubName", default(EventHubsSharedAccessKeyCredential)), Throws.ArgumentNullException, "The shared key credential should validate.");
}
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/EventHubSharedKeyCredential.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/EventHubSharedKeyCredential.cs
deleted file mode 100755
index 64ccedc1acdc..000000000000
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/EventHubSharedKeyCredential.cs
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using Azure.Core;
-using Azure.Messaging.EventHubs.Authorization;
-
-namespace Azure.Messaging.EventHubs
-{
- ///
- /// Provides a credential based on a shared access signature for a given
- /// Event Hub instance.
- ///
- ///
- ///
- ///
- internal sealed class EventHubSharedKeyCredential : TokenCredential
- {
- ///
- /// The name of the shared access key to be used for authorization, as
- /// reported by the Azure portal.
- ///
- ///
- private string SharedAccessKeyName { get; set; }
-
- ///
- /// The value of the shared access key to be used for authorization, as
- /// reported by the Azure portal.
- ///
- ///
- private string SharedAccessKey { get; set; }
-
- ///
- /// A reference to a corresponding SharedAccessSignatureCredential.
- ///
- ///
- private SharedAccessSignatureCredential SharedAccessSignatureCredential { get; set; }
-
- ///
- /// Initializes a new instance of the class.
- ///
- ///
- /// The name of the shared access key to be used for authorization, as reported by the Azure portal.
- /// The value of the shared access key to be used for authorization, as reported by the Azure portal.
- ///
- public EventHubSharedKeyCredential(string sharedAccessKeyName,
- string sharedAccessKey)
- {
- Argument.AssertNotNullOrEmpty(sharedAccessKeyName, nameof(sharedAccessKeyName));
- Argument.AssertNotNullOrEmpty(sharedAccessKey, nameof(sharedAccessKey));
-
- SharedAccessKeyName = sharedAccessKeyName;
- SharedAccessKey = sharedAccessKey;
- }
-
- ///
- /// Retrieves the token that represents the shared access signature credential, for
- /// use in authorization against an Event Hub.
- ///
- ///
- /// The details of the authentication request.
- /// The token used to request cancellation of the operation.
- ///
- /// The token representing the shared access signature for this credential.
- ///
- public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) => throw new InvalidOperationException(Resources.SharedKeyCredentialCannotGenerateTokens);
-
- ///
- /// Retrieves the token that represents the shared access signature credential, for
- /// use in authorization against an Event Hub.
- ///
- ///
- /// The details of the authentication request.
- /// The token used to request cancellation of the operation.
- ///
- /// The token representing the shared access signature for this credential.
- ///
- public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) => throw new InvalidOperationException(Resources.SharedKeyCredentialCannotGenerateTokens);
-
- ///
- /// Allows the rotation of Shared Access Signatures.
- ///
- ///
- /// The name of the shared access key that the signature should be based on.
- /// The value of the shared access key for the signature.
- ///
- public void UpdateSharedAccessKey(string keyName,
- string keyValue)
- {
- Argument.AssertNotNullOrEmpty(keyName, nameof(keyName));
- Argument.AssertNotNullOrEmpty(keyValue, nameof(keyValue));
-
- SharedAccessKeyName = keyName;
- SharedAccessKey = keyValue;
-
- SharedAccessSignatureCredential?.UpdateSharedAccessKey(keyName, keyValue);
- }
-
- ///
- /// Coverts to shared access signature credential.
- /// It retains a reference to the generated SharedAccessSignatureCredential.
- ///
- ///
- /// The Event Hubs resource to which the token is intended to serve as authorization.
- /// The duration that the signature should be considered valid; if not specified, a default will be assumed.
- ///
- /// A new based on the requested shared access key.
- ///
- internal SharedAccessSignatureCredential AsSharedAccessSignatureCredential(string eventHubResource,
- TimeSpan? signatureValidityDuration = default)
- {
- SharedAccessSignatureCredential = new SharedAccessSignatureCredential(new SharedAccessSignature(eventHubResource, SharedAccessKeyName, SharedAccessKey, signatureValidityDuration));
-
- return SharedAccessSignatureCredential;
- }
- }
-}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/EventHubTokenCredential.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/EventHubTokenCredential.cs
old mode 100755
new mode 100644
index 0da9234d4590..b6a7f72a6699
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/EventHubTokenCredential.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/EventHubTokenCredential.cs
@@ -56,8 +56,7 @@ public EventHubTokenCredential(TokenCredential tokenCredential,
Resource = eventHubResource;
IsSharedAccessSignatureCredential =
- (tokenCredential is EventHubSharedKeyCredential)
- || (tokenCredential is SharedAccessSignatureCredential)
+ (tokenCredential is SharedAccessSignatureCredential)
|| ((tokenCredential as EventHubTokenCredential)?.IsSharedAccessSignatureCredential == true);
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/SharedAccessSignatureCredential.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/SharedAccessSignatureCredential.cs
index 49cc4106e8f5..4029e64a8851 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/SharedAccessSignatureCredential.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Authorization/SharedAccessSignatureCredential.cs
@@ -90,7 +90,7 @@ public override ValueTask GetTokenAsync(TokenRequestContext request
CancellationToken cancellationToken) => new ValueTask(GetToken(requestContext, cancellationToken));
///
- /// It creates a new shared signature using the key name and the key value passed as
+ /// Creates a new shared signature using the key name and the key value passed as
/// input allowing credentials rotation. A call will not extend the signature duration.
///
///
@@ -108,5 +108,19 @@ internal void UpdateSharedAccessKey(string keyName, string keyValue)
SharedAccessSignature.SignatureExpiration);
}
}
+
+ ///
+ /// Creates a new shared signature allowing credentials rotation.
+ ///
+ ///
+ /// The shared access signature that forms the basis of this security token.
+ ///
+ internal void UpdateSharedAccessSignature(string signature)
+ {
+ lock (SignatureSyncRoot)
+ {
+ SharedAccessSignature = new SharedAccessSignature(signature);
+ }
+ }
}
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringParser.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringParser.cs
deleted file mode 100755
index 3f366c3c3dda..000000000000
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringParser.cs
+++ /dev/null
@@ -1,174 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-using Azure.Core;
-
-namespace Azure.Messaging.EventHubs.Core
-{
- ///
- /// Allows for parsing Event Hubs connection strings.
- ///
- ///
- internal static class ConnectionStringParser
- {
- /// The token that identifies the endpoint address for the Event Hubs namespace.
- private const string EndpointToken = "Endpoint";
-
- /// The token that identifies the name of a specific Event Hub under the namespace.
- private const string EventHubNameToken = "EntityPath";
-
- /// The token that identifies the name of a shared access key.
- private const string SharedAccessKeyNameToken = "SharedAccessKeyName";
-
- /// The token that identifies the value of a shared access key.
- private const string SharedAccessKeyValueToken = "SharedAccessKey";
-
- /// The token that identifies the value of a shared access signature.
- private const string SharedAccessSignatureToken = "SharedAccessSignature";
-
- /// The character used to separate a token and its value in the connection string.
- private const char TokenValueSeparator = '=';
-
- /// The character used to mark the beginning of a new token/value pair in the connection string.
- private const char TokenValuePairDelimiter = ';';
-
- /// The name of the protocol used by an Event Hubs endpoint.
- private const string EventHubsEndpointSchemeName = "sb";
-
- /// The formatted protocol used by an Event Hubs endpoint.
- private static readonly string EventHubsEndpointScheme = $"{ EventHubsEndpointSchemeName }{ Uri.SchemeDelimiter }";
-
- ///
- /// Parses the specified Event Hubs connection string into its component properties.
- ///
- ///
- /// The connection string to parse.
- ///
- /// The component properties parsed from the connection string.
- ///
- ///
- ///
- public static ConnectionStringProperties Parse(string connectionString)
- {
- Argument.AssertNotNullOrEmpty(connectionString, nameof(connectionString));
-
- int tokenPositionModifier = (connectionString[0] == TokenValuePairDelimiter) ? 0 : 1;
- int lastPosition = 0;
- int currentPosition = 0;
- int valueStart;
-
- string slice;
- string token;
- string value;
-
- var parsedValues =
- (
- EndpointToken: default(UriBuilder),
- EventHubNameToken: default(string),
- SharedAccessKeyNameToken: default(string),
- SharedAccessKeyValueToken: default(string),
- SharedAccessSignatureToken: default(string)
- );
-
- while (currentPosition != -1)
- {
- // Slice the string into the next token/value pair.
-
- currentPosition = connectionString.IndexOf(TokenValuePairDelimiter, lastPosition + 1);
-
- if (currentPosition >= 0)
- {
- slice = connectionString.Substring(lastPosition, (currentPosition - lastPosition));
- }
- else
- {
- slice = connectionString.Substring(lastPosition);
- }
-
- // Break the token and value apart, if this is a legal pair.
-
- valueStart = slice.IndexOf(TokenValueSeparator);
-
- if (valueStart >= 0)
- {
- token = slice.Substring((1 - tokenPositionModifier), (valueStart - 1 + tokenPositionModifier));
- value = slice.Substring(valueStart + 1);
-
- // Guard against leading and trailing spaces, only trimming if there is a need.
-
- if ((!string.IsNullOrEmpty(token)) && (char.IsWhiteSpace(token[0])) || char.IsWhiteSpace(token[token.Length - 1]))
- {
- token = token.Trim();
- }
-
- if ((!string.IsNullOrEmpty(value)) && (char.IsWhiteSpace(value[0]) || char.IsWhiteSpace(value[value.Length - 1])))
- {
- value = value.Trim();
- }
-
- // If there was no value for a key, then consider the connection string to
- // be malformed.
-
- if (string.IsNullOrEmpty(value))
- {
- throw new FormatException(Resources.InvalidConnectionString);
- }
-
- // Compare the token against the known connection string properties and capture the
- // pair if they are a known attribute.
-
- if (string.Compare(EndpointToken, token, StringComparison.OrdinalIgnoreCase) == 0)
- {
- parsedValues.EndpointToken = new UriBuilder(value)
- {
- Scheme = EventHubsEndpointScheme,
- Port = -1
- };
-
- if ((string.Compare(parsedValues.EndpointToken.Scheme, EventHubsEndpointSchemeName, StringComparison.OrdinalIgnoreCase) != 0)
- || (Uri.CheckHostName(parsedValues.EndpointToken.Host) == UriHostNameType.Unknown))
- {
- throw new FormatException(Resources.InvalidConnectionString);
- }
- }
- else if (string.Compare(EventHubNameToken, token, StringComparison.OrdinalIgnoreCase) == 0)
- {
- parsedValues.EventHubNameToken = value;
- }
- else if (string.Compare(SharedAccessKeyNameToken, token, StringComparison.OrdinalIgnoreCase) == 0)
- {
- parsedValues.SharedAccessKeyNameToken = value;
- }
- else if (string.Compare(SharedAccessKeyValueToken, token, StringComparison.OrdinalIgnoreCase) == 0)
- {
- parsedValues.SharedAccessKeyValueToken = value;
- }
- else if (string.Compare(SharedAccessSignatureToken, token, StringComparison.OrdinalIgnoreCase) == 0)
- {
- parsedValues.SharedAccessSignatureToken = value;
- }
- }
- else if ((slice.Length != 1) || (slice[0] != TokenValuePairDelimiter))
- {
- // This wasn't a legal pair and it is not simply a trailing delimiter; consider
- // the connection string to be malformed.
-
- throw new FormatException(Resources.InvalidConnectionString);
- }
-
- tokenPositionModifier = 0;
- lastPosition = currentPosition;
- }
-
- return new ConnectionStringProperties
- (
- parsedValues.EndpointToken?.Uri,
- parsedValues.EventHubNameToken,
- parsedValues.SharedAccessKeyNameToken,
- parsedValues.SharedAccessKeyValueToken,
- parsedValues.SharedAccessSignatureToken
- );
- }
- }
-}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringProperties.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringProperties.cs
deleted file mode 100644
index f1e6401bfdfc..000000000000
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Core/ConnectionStringProperties.cs
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System;
-
-namespace Azure.Messaging.EventHubs.Core
-{
- ///
- /// The set of properties that comprise a connection string from the
- /// Azure portal.
- ///
- ///
- internal struct ConnectionStringProperties
- {
- ///
- /// The endpoint to be used for connecting to the Event Hubs namespace.
- ///
- ///
- /// The endpoint address, including protocol, from the connection string.
- ///
- public Uri Endpoint { get; }
-
- ///
- /// The name of the specific Event Hub instance under the associated Event Hubs namespace.
- ///
- ///
- public string EventHubName { get; }
-
- ///
- /// The name of the shared access key, either for the Event Hubs namespace
- /// or the Event Hub.
- ///
- ///
- public string SharedAccessKeyName { get; }
-
- ///
- /// The value of the shared access key, either for the Event Hubs namespace
- /// or the Event Hub.
- ///
- ///
- public string SharedAccessKey { get; }
-
- ///
- /// The value of the fully-formed shared access signature, either for the Event Hubs
- /// namespace or the Event Hub.
- ///
- ///
- public string SharedAccessSignature { get; }
-
- ///
- /// Initializes a new instance of the structure.
- ///
- ///
- /// The endpoint of the Event Hubs namespace.
- /// The name of the specific Event Hub under the namespace.
- /// The name of the shared access key, to use authorization.
- /// The shared access key to use for authorization.
- /// The precomputed shared access signature to use for authorization.
- ///
- public ConnectionStringProperties(Uri endpoint,
- string eventHubName,
- string sharedAccessKeyName,
- string sharedAccessKey,
- string sharedAccessSignature)
- {
- Endpoint = endpoint;
- EventHubName = eventHubName;
- SharedAccessKeyName = sharedAccessKeyName;
- SharedAccessKey = sharedAccessKey;
- SharedAccessSignature = sharedAccessSignature;
- }
-
- ///
- /// Performs the actions needed to validate the set of connection string properties for connecting to the
- /// Event Hubs service.
- ///
- ///
- /// The name of the Event Hub that was explicitly passed independent of the connection string, allowing easier use of a namespace-level connection string.
- /// The name of the argument associated with the connection string; to be used when raising variants.
- ///
- /// In the case that the properties violate an invariant or otherwise represent a combination that is not permissible, an appropriate exception will be thrown.
- ///
- public void Validate(string explicitEventHubName,
- string connectionStringArgumentName)
- {
- // The Event Hub name may only be specified in one of the possible forms, either as part of the
- // connection string or as a stand-alone parameter, but not both. If specified in both to the same
- // value, then do not consider this a failure.
-
- if ((!string.IsNullOrEmpty(explicitEventHubName))
- && (!string.IsNullOrEmpty(EventHubName))
- && (!string.Equals(explicitEventHubName, EventHubName, StringComparison.InvariantCultureIgnoreCase)))
- {
- throw new ArgumentException(Resources.OnlyOneEventHubNameMayBeSpecified, connectionStringArgumentName);
- }
-
- // The connection string may contain a precomputed shared access signature OR a shared key name and value,
- // but not both.
-
- if ((!string.IsNullOrEmpty(SharedAccessSignature))
- && ((!string.IsNullOrEmpty(SharedAccessKeyName)) || (!string.IsNullOrEmpty(SharedAccessKey))))
- {
- throw new ArgumentException(Resources.OnlyOneSharedAccessAuthorizationMayBeSpecified, connectionStringArgumentName);
- }
-
- // Ensure that each of the needed components are present for connecting.
-
- var hasSharedKey = ((!string.IsNullOrEmpty(SharedAccessKeyName)) && (!string.IsNullOrEmpty(SharedAccessKey)));
- var hasSharedSignature = (!string.IsNullOrEmpty(SharedAccessSignature));
-
- if (string.IsNullOrEmpty(Endpoint?.Host)
- || ((string.IsNullOrEmpty(explicitEventHubName)) && (string.IsNullOrEmpty(EventHubName)))
- || ((!hasSharedKey) && (!hasSharedSignature)))
- {
- throw new ArgumentException(Resources.MissingConnectionInformation, connectionStringArgumentName);
- }
- }
- }
-}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs
index 3777fb7e9710..d0e3034bac96 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.Designer.cs
@@ -286,6 +286,17 @@ internal static string InvalidSharedAccessSignature
}
}
+ ///
+ /// Looks up a localized string similar to The endpoint address could not be parsed; it was either malformed or not using the `sb://` scheme..
+ ///
+ internal static string InvalidEndpointAddress
+ {
+ get
+ {
+ return ResourceManager.GetString("InvalidEndpointAddress", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The time period may not be Zero or Infinite..
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx
index bd1f476eb16c..5ba5bb875a57 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx
+++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Resources.resx
@@ -159,6 +159,9 @@
The specified connection type, "{0}", is not recognized as valid in this context.
+
+ The endpoint address could not be parsed; it was either malformed or not using the `sb://` scheme.
+
The shared access signature could not be parsed; it was either malformed or incorrectly encoded.
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Testing/EventHubsTestEnvironment.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Testing/EventHubsTestEnvironment.cs
index 959dc33f6ce3..e96155181574 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Testing/EventHubsTestEnvironment.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/src/Testing/EventHubsTestEnvironment.cs
@@ -43,7 +43,7 @@ public sealed class EventHubsTestEnvironment: TestEnvironment
private readonly Lazy ActivePerTestExecutionLimit;
/// The connection string for the active Event Hubs namespace for this test run, lazily created.
- private readonly Lazy ParsedConnectionString;
+ private readonly Lazy ParsedConnectionString;
///
/// The shared instance of the to be used during test runs.
@@ -89,7 +89,7 @@ public sealed class EventHubsTestEnvironment: TestEnvironment
///
/// The fully qualified namespace, as contained within the associated connection string.
///
- public string FullyQualifiedNamespace => ParsedConnectionString.Value.Endpoint.Host;
+ public string FullyQualifiedNamespace => ParsedConnectionString.Value.FullyQualifiedNamespace;
///
/// The name of the Event Hub to use during Live tests.
@@ -139,7 +139,7 @@ public sealed class EventHubsTestEnvironment: TestEnvironment
///
private EventHubsTestEnvironment() : base("eventhub")
{
- ParsedConnectionString = new Lazy(() => ConnectionStringParser.Parse(EventHubsConnectionString), LazyThreadSafetyMode.ExecutionAndPublication);
+ ParsedConnectionString = new Lazy(() => EventHubsConnectionStringProperties.Parse(EventHubsConnectionString), LazyThreadSafetyMode.ExecutionAndPublication);
ActiveEventHubsNamespace = new Lazy(EnsureEventHubsNamespace, LazyThreadSafetyMode.ExecutionAndPublication);
ActivePerTestExecutionLimit = new Lazy(() =>
@@ -183,7 +183,7 @@ public string BuildConnectionStringWithSharedAccessSignature(string eventHubName
int validDurationMinutes = 30)
{
var signature = new SharedAccessSignature(signatureAudience, SharedAccessKeyName, SharedAccessKey, TimeSpan.FromMinutes(validDurationMinutes));
- return $"Endpoint={ ParsedConnectionString.Value.Endpoint };EntityPath={ eventHubName };SharedAccessSignature={ signature.Value }";
+ return $"Endpoint=sb://{ ParsedConnectionString.Value.FullyQualifiedNamespace };EntityPath={ eventHubName };SharedAccessSignature={ signature.Value }";
}
///
@@ -200,11 +200,11 @@ private NamespaceProperties EnsureEventHubsNamespace()
if (!string.IsNullOrEmpty(environmentConnectionString))
{
- var parsed = ConnectionStringParser.Parse(environmentConnectionString);
+ var parsed = EventHubsConnectionStringProperties.Parse(environmentConnectionString);
return new NamespaceProperties
(
- parsed.Endpoint.Host.Substring(0, parsed.Endpoint.Host.IndexOf('.')),
+ parsed.FullyQualifiedNamespace.Substring(0, parsed.FullyQualifiedNamespace.IndexOf('.')),
environmentConnectionString.Replace($";EntityPath={ parsed.EventHubName }", string.Empty),
shouldRemoveAtCompletion: false
);
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Authorization/EventHubTokenCredentialTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Authorization/EventHubTokenCredentialTests.cs
old mode 100755
new mode 100644
index 7de82da1f25c..52e76620f521
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Authorization/EventHubTokenCredentialTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Authorization/EventHubTokenCredentialTests.cs
@@ -32,8 +32,6 @@ public static IEnumerable
///
[Test]
- public void ShouldUpdateSharedAccessKey()
+ public void SharedAccessKeyCanBeUpdated()
{
var value = "TOkEn!";
var tokenExpiration = DateTimeOffset.UtcNow.Add(TimeSpan.FromSeconds(GetSignatureRefreshBuffer().TotalSeconds / 2));
@@ -195,6 +195,28 @@ public void ShouldUpdateSharedAccessKey()
Assert.That(newSignature.SignatureExpiration, Is.EqualTo(signature.SignatureExpiration));
}
+ ///
+ /// Verifies that a signature can be rotated without refreshing its validity.
+ ///
+ ///
+ [Test]
+ public void SharedAccesSignatureCanBeUpdated()
+ {
+ var tokenExpiration = TimeSpan.FromSeconds(GetSignatureRefreshBuffer().TotalSeconds / 2);
+ var signature = new SharedAccessSignature("hub-name", "keyName", "key", tokenExpiration);
+ var updatedSignature = new SharedAccessSignature("hub-name", "newKeyName", "newKey", tokenExpiration.Add(TimeSpan.FromMinutes(30)));
+ var credential = new SharedAccessSignatureCredential(signature);
+
+ credential.UpdateSharedAccessSignature(updatedSignature.Value);
+
+ var newSignature = GetSharedAccessSignature(credential);
+
+ Assert.That(newSignature.Value, Is.EqualTo(updatedSignature.Value));
+ Assert.That(newSignature.SharedAccessKeyName, Is.EqualTo(updatedSignature.SharedAccessKeyName));
+ Assert.That(newSignature.SharedAccessKey, Is.Null);
+ Assert.That(newSignature.SignatureExpiration, Is.EqualTo(updatedSignature.SignatureExpiration).Within(TimeSpan.FromSeconds(5)));
+ }
+
///
/// Converts a value to the corresponding Unix-style time stamp.
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Core/ConnectionStringPropertiesTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Core/ConnectionStringPropertiesTests.cs
deleted file mode 100644
index 390d9269eab4..000000000000
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Core/ConnectionStringPropertiesTests.cs
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using Azure.Messaging.EventHubs.Core;
-using NUnit.Framework;
-
-namespace Azure.Messaging.EventHubs.Tests
-{
- ///
- /// The suite of tests for the
- /// class.
- ///
- ///
- [TestFixture]
- public class ConnectionStringPropertiesTests
- {
- ///
- /// Verifies functionality of the
- /// method.
- ///
- ///
- [Test]
- [TestCase("SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]")]
- [TestCase("Endpoint=sb://value.com;SharedAccessKey=[value];EntityPath=[value]")]
- [TestCase("Endpoint=sb://value.com;SharedAccessKeyName=[value];EntityPath=[value]")]
- [TestCase("Endpoint=sb://value.com;SharedAccessKeyName=[value];SharedAccessKey=[value]")]
- [TestCase("HostName=value.azure-devices.net;SharedAccessKeyName=[value];SharedAccessKey=[value]")]
- [TestCase("HostName=value.azure-devices.net;SharedAccessKeyName=[value];SharedAccessKey=[value];EntityPath=[value]")]
- public void ValidateDetectsMissingConnectionStringInformation(string connectionString)
- {
- var properties = ConnectionStringParser.Parse(connectionString);
- Assert.That(() => properties.Validate(null, "Dummy"), Throws.ArgumentException.And.Message.StartsWith(Resources.MissingConnectionInformation));
- }
-
- ///
- /// Verifies functionality of the
- /// method.
- ///
- ///
- [Test]
- public void ValidateDetectsMultipleEventHubNames()
- {
- var eventHubName = "myHub";
- var fakeConnection = "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath=[unique_fake]";
- var properties = ConnectionStringParser.Parse(fakeConnection);
-
- Assert.That(() => properties.Validate(eventHubName, "Dummy"), Throws.ArgumentException.And.Message.StartsWith(Resources.OnlyOneEventHubNameMayBeSpecified));
- }
-
- ///
- /// Verifies functionality of the
- /// method.
- ///
- ///
- [Test]
- public void ValidateAllowsMultipleEventHubNamesIfEqual()
- {
- var eventHubName = "myHub";
- var fakeConnection = $"Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath={ eventHubName }";
- var properties = ConnectionStringParser.Parse(fakeConnection);
-
- Assert.That(() => properties.Validate(eventHubName, "dummy"), Throws.Nothing, "Validation should accept the same Event Hub in multiple places.");
- }
-
- ///
- /// Verifies functionality of the
- /// method.
- ///
- ///
- [Test]
- [TestCase("Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real];EntityPath=[unique_fake];SharedAccessSignature=[not_real]")]
- [TestCase("Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;EntityPath=[unique_fake];SharedAccessSignature=[not_real]")]
- [TestCase("Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKey=[not_real];EntityPath=[unique_fake];SharedAccessSignature=[not_real]")]
- public void ValidateDetectsMultipleAuthorizationCredentials(string connectionString)
- {
- var properties = ConnectionStringParser.Parse(connectionString);
- Assert.That(() => properties.Validate(null, "Dummy"), Throws.ArgumentException.And.Message.StartsWith(Resources.OnlyOneSharedAccessAuthorizationMayBeSpecified));
- }
-
- ///
- /// Verifies functionality of the
- /// method.
- ///
- ///
- [Test]
- public void ValidateAllowsSharedAccessKeyAuthorization()
- {
- var eventHubName = "myHub";
- var fakeConnection = "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessKeyName=DummyKey;SharedAccessKey=[not_real]";
- var properties = ConnectionStringParser.Parse(fakeConnection);
-
- Assert.That(() => properties.Validate(eventHubName, "dummy"), Throws.Nothing, "Validation should accept the shared access key authorization.");
- }
-
- ///
- /// Verifies functionality of the
- /// method.
- ///
- ///
- [Test]
- public void ValidateAllowsSharedAccessSignatureAuthorization()
- {
- var eventHubName = "myHub";
- var fakeConnection = "Endpoint=sb://not-real.servicebus.windows.net/;SharedAccessSignature=[not_real]";
- var properties = ConnectionStringParser.Parse(fakeConnection);
-
- Assert.That(() => properties.Validate(eventHubName, "dummy"), Throws.Nothing, "Validation should accept the shared access signature authorization.");
- }
- }
-}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/api/Azure.Messaging.EventHubs.netstandard2.0.cs b/sdk/eventhub/Azure.Messaging.EventHubs/api/Azure.Messaging.EventHubs.netstandard2.0.cs
index 8e469627fbfa..34074c03f942 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs/api/Azure.Messaging.EventHubs.netstandard2.0.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/api/Azure.Messaging.EventHubs.netstandard2.0.cs
@@ -34,6 +34,7 @@ public EventHubConnection(string connectionString, Azure.Messaging.EventHubs.Eve
public EventHubConnection(string connectionString, string eventHubName) { }
public EventHubConnection(string fullyQualifiedNamespace, string eventHubName, Azure.Core.TokenCredential credential, Azure.Messaging.EventHubs.EventHubConnectionOptions connectionOptions = null) { }
public EventHubConnection(string connectionString, string eventHubName, Azure.Messaging.EventHubs.EventHubConnectionOptions connectionOptions) { }
+ public EventHubConnection(string fullyQualifiedNamespace, string eventHubName, Azure.Messaging.EventHubs.EventHubsSharedAccessKeyCredential credential, Azure.Messaging.EventHubs.EventHubConnectionOptions connectionOptions = null) { }
public string EventHubName { get { throw null; } }
public string FullyQualifiedNamespace { get { throw null; } }
public bool IsClosed { get { throw null; } }
@@ -64,6 +65,29 @@ protected internal EventHubProperties(string name, System.DateTimeOffset created
public System.DateTimeOffset CreatedOn { get { throw null; } }
public string Name { get { throw null; } }
public string[] PartitionIds { get { throw null; } }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public override bool Equals(object obj) { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public override int GetHashCode() { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public override string ToString() { throw null; }
+ }
+ public partial class EventHubsConnectionStringProperties
+ {
+ public EventHubsConnectionStringProperties() { }
+ public System.Uri Endpoint { get { throw null; } }
+ public string EventHubName { get { throw null; } }
+ public string FullyQualifiedNamespace { get { throw null; } }
+ public string SharedAccessKey { get { throw null; } }
+ public string SharedAccessKeyName { get { throw null; } }
+ public string SharedAccessSignature { get { throw null; } }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public override bool Equals(object obj) { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public override int GetHashCode() { throw null; }
+ public static Azure.Messaging.EventHubs.EventHubsConnectionStringProperties Parse(string connectionString) { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public override string ToString() { throw null; }
}
public partial class EventHubsException : System.Exception
{
@@ -121,6 +145,22 @@ protected EventHubsRetryPolicy() { }
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public override string ToString() { throw null; }
}
+ public sealed partial class EventHubsSharedAccessKeyCredential
+ {
+ public EventHubsSharedAccessKeyCredential(string sharedAccessSignature) { }
+ public EventHubsSharedAccessKeyCredential(string sharedAccessKeyName, string sharedAccessKey) { }
+ public string SharedAccessKey { get { throw null; } }
+ public string SharedAccessKeyName { get { throw null; } }
+ public string SharedAccessSignature { get { throw null; } }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public override bool Equals(object obj) { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public override int GetHashCode() { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public override string ToString() { throw null; }
+ public void UpdateSharedAccessKey(string keyName, string keyValue) { }
+ public void UpdateSharedAccessSignature(string sharedAccessSignature) { }
+ }
public enum EventHubsTransportType
{
AmqpTcp = 0,
@@ -136,6 +176,12 @@ protected internal PartitionProperties(string eventHubName, string partitionId,
public long LastEnqueuedOffset { get { throw null; } }
public long LastEnqueuedSequenceNumber { get { throw null; } }
public System.DateTimeOffset LastEnqueuedTime { get { throw null; } }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public override bool Equals(object obj) { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public override int GetHashCode() { throw null; }
+ [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+ public override string ToString() { throw null; }
}
}
namespace Azure.Messaging.EventHubs.Consumer
@@ -150,6 +196,7 @@ public EventHubConsumerClient(string consumerGroup, string connectionString, Azu
public EventHubConsumerClient(string consumerGroup, string connectionString, string eventHubName) { }
public EventHubConsumerClient(string consumerGroup, string fullyQualifiedNamespace, string eventHubName, Azure.Core.TokenCredential credential, Azure.Messaging.EventHubs.Consumer.EventHubConsumerClientOptions clientOptions = null) { }
public EventHubConsumerClient(string consumerGroup, string connectionString, string eventHubName, Azure.Messaging.EventHubs.Consumer.EventHubConsumerClientOptions clientOptions) { }
+ public EventHubConsumerClient(string consumerGroup, string fullyQualifiedNamespace, string eventHubName, Azure.Messaging.EventHubs.EventHubsSharedAccessKeyCredential credential, Azure.Messaging.EventHubs.Consumer.EventHubConsumerClientOptions clientOptions = null) { }
public string ConsumerGroup { get { throw null; } }
public string EventHubName { get { throw null; } }
public string FullyQualifiedNamespace { get { throw null; } }
@@ -306,6 +353,7 @@ public EventProcessorPartitionOwnership() { }
protected EventProcessor() { }
protected EventProcessor(int eventBatchMaximumCount, string consumerGroup, string connectionString, Azure.Messaging.EventHubs.Primitives.EventProcessorOptions options = null) { }
protected EventProcessor(int eventBatchMaximumCount, string consumerGroup, string fullyQualifiedNamespace, string eventHubName, Azure.Core.TokenCredential credential, Azure.Messaging.EventHubs.Primitives.EventProcessorOptions options = null) { }
+ protected EventProcessor(int eventBatchMaximumCount, string consumerGroup, string fullyQualifiedNamespace, string eventHubName, Azure.Messaging.EventHubs.EventHubsSharedAccessKeyCredential credential, Azure.Messaging.EventHubs.Primitives.EventProcessorOptions options = null) { }
protected EventProcessor(int eventBatchMaximumCount, string consumerGroup, string connectionString, string eventHubName, Azure.Messaging.EventHubs.Primitives.EventProcessorOptions options = null) { }
public string ConsumerGroup { get { throw null; } }
public string EventHubName { get { throw null; } }
@@ -339,6 +387,7 @@ protected PartitionReceiver() { }
public PartitionReceiver(string consumerGroup, string partitionId, Azure.Messaging.EventHubs.Consumer.EventPosition eventPosition, Azure.Messaging.EventHubs.EventHubConnection connection, Azure.Messaging.EventHubs.Primitives.PartitionReceiverOptions options = null) { }
public PartitionReceiver(string consumerGroup, string partitionId, Azure.Messaging.EventHubs.Consumer.EventPosition eventPosition, string connectionString, Azure.Messaging.EventHubs.Primitives.PartitionReceiverOptions options = null) { }
public PartitionReceiver(string consumerGroup, string partitionId, Azure.Messaging.EventHubs.Consumer.EventPosition eventPosition, string fullyQualifiedNamespace, string eventHubName, Azure.Core.TokenCredential credential, Azure.Messaging.EventHubs.Primitives.PartitionReceiverOptions options = null) { }
+ public PartitionReceiver(string consumerGroup, string partitionId, Azure.Messaging.EventHubs.Consumer.EventPosition eventPosition, string fullyQualifiedNamespace, string eventHubName, Azure.Messaging.EventHubs.EventHubsSharedAccessKeyCredential credential, Azure.Messaging.EventHubs.Primitives.PartitionReceiverOptions options = null) { }
public PartitionReceiver(string consumerGroup, string partitionId, Azure.Messaging.EventHubs.Consumer.EventPosition eventPosition, string connectionString, string eventHubName, Azure.Messaging.EventHubs.Primitives.PartitionReceiverOptions options = null) { }
public string ConsumerGroup { get { throw null; } }
public string EventHubName { get { throw null; } }
@@ -458,6 +507,7 @@ public EventHubProducerClient(string connectionString) { }
public EventHubProducerClient(string connectionString, Azure.Messaging.EventHubs.Producer.EventHubProducerClientOptions clientOptions) { }
public EventHubProducerClient(string connectionString, string eventHubName) { }
public EventHubProducerClient(string fullyQualifiedNamespace, string eventHubName, Azure.Core.TokenCredential credential, Azure.Messaging.EventHubs.Producer.EventHubProducerClientOptions clientOptions = null) { }
+ public EventHubProducerClient(string fullyQualifiedNamespace, string eventHubName, Azure.Messaging.EventHubs.EventHubsSharedAccessKeyCredential credential, Azure.Messaging.EventHubs.Producer.EventHubProducerClientOptions clientOptions = null) { }
public EventHubProducerClient(string connectionString, string eventHubName, Azure.Messaging.EventHubs.Producer.EventHubProducerClientOptions clientOptions) { }
public string EventHubName { get { throw null; } }
public string FullyQualifiedNamespace { get { throw null; } }
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Authorization/EventHubsSharedAccessKeyCredential.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Authorization/EventHubsSharedAccessKeyCredential.cs
new file mode 100644
index 000000000000..3964ef651302
--- /dev/null
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Authorization/EventHubsSharedAccessKeyCredential.cs
@@ -0,0 +1,175 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.ComponentModel;
+using Azure.Core;
+using Azure.Messaging.EventHubs.Authorization;
+
+namespace Azure.Messaging.EventHubs
+{
+ ///
+ /// Provides a credential based on a shared access signature for a given
+ /// Event Hub instance.
+ ///
+ ///
+ public sealed class EventHubsSharedAccessKeyCredential
+ {
+ ///
+ /// The name of the shared access key to be used for authorization, as
+ /// reported by the Azure portal.
+ ///
+ ///
+ ///
+ /// This will only be populated when the credential is created using a shared key, not when created
+ /// using a precomputed shared access signature.
+ ///
+ ///
+ public string SharedAccessKeyName { get; private set; }
+
+ ///
+ /// The value of the shared access key to be used for authorization, as
+ /// reported by the Azure portal.
+ ///
+ ///
+ ///
+ /// This will only be populated when the credential is created using a shared key, not when created
+ /// using a precomputed shared access signature.
+ ///
+ ///
+ public string SharedAccessKey { get; private set; }
+
+ ///
+ /// The value of the precomputed shared access signature to be used for authorization.
+ ///
+ ///
+ ///
+ /// This will only be populated when the credential is created using a precomputed shared access signature, not when created
+ /// using a shared key.
+ ///
+ ///
+ public string SharedAccessSignature { get; private set; }
+
+ ///
+ /// A reference to a corresponding SharedAccessSignatureCredential.
+ ///
+ ///
+ private SharedAccessSignatureCredential SharedAccessSignatureCredential { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The name of the shared access key to be used for authorization, as reported by the Azure portal.
+ /// The value of the shared access key to be used for authorization, as reported by the Azure portal.
+ ///
+ public EventHubsSharedAccessKeyCredential(string sharedAccessKeyName,
+ string sharedAccessKey)
+ {
+ Argument.AssertNotNullOrEmpty(sharedAccessKeyName, nameof(sharedAccessKeyName));
+ Argument.AssertNotNullOrEmpty(sharedAccessKey, nameof(sharedAccessKey));
+
+ SharedAccessKeyName = sharedAccessKeyName;
+ SharedAccessKey = sharedAccessKey;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The shared access signature that forms the basis of this security token.
+ ///
+ public EventHubsSharedAccessKeyCredential(string sharedAccessSignature)
+ {
+ Argument.AssertNotNullOrEmpty(sharedAccessSignature, nameof(sharedAccessSignature));
+ SharedAccessSignature = sharedAccessSignature;
+ }
+
+ ///
+ /// Allows the rotation of Shared Access Signatures.
+ ///
+ ///
+ /// The name of the shared access key that the signature should be based on.
+ /// The value of the shared access key for the signature.
+ ///
+ public void UpdateSharedAccessKey(string keyName,
+ string keyValue)
+ {
+ Argument.AssertNotNullOrEmpty(keyName, nameof(keyName));
+ Argument.AssertNotNullOrEmpty(keyValue, nameof(keyValue));
+
+ SharedAccessKeyName = keyName;
+ SharedAccessKey = keyValue;
+ SharedAccessSignature = null;
+
+ SharedAccessSignatureCredential?.UpdateSharedAccessKey(keyName, keyValue);
+ }
+
+ ///
+ /// Allows the rotation of Shared Access Signatures.
+ ///
+ ///
+ /// The shared access signature that forms the basis of this security token.
+ ///
+ public void UpdateSharedAccessSignature(string sharedAccessSignature)
+ {
+ Argument.AssertNotNullOrEmpty(sharedAccessSignature, nameof(sharedAccessSignature));
+
+ SharedAccessSignature = sharedAccessSignature;
+ SharedAccessKeyName = null;
+ SharedAccessKey = null;
+
+ SharedAccessSignatureCredential?.UpdateSharedAccessSignature(sharedAccessSignature);
+ }
+
+ ///
+ /// Determines whether the specified is equal to this instance.
+ ///
+ ///
+ /// The to compare with this instance.
+ ///
+ /// true if the specified is equal to this instance; otherwise, false.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj) => base.Equals(obj);
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode() => base.GetHashCode();
+
+ ///
+ /// Converts the instance to string representation.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string ToString() => base.ToString();
+
+ ///
+ /// Coverts to shared access signature credential.
+ /// It retains a reference to the generated SharedAccessSignatureCredential.
+ ///
+ ///
+ /// The Event Hubs resource to which the token is intended to serve as authorization.
+ /// The duration that the signature should be considered valid; if not specified, a default will be assumed.
+ ///
+ /// A new based on the requested shared access key.
+ ///
+ internal SharedAccessSignatureCredential AsSharedAccessSignatureCredential(string eventHubResource,
+ TimeSpan? signatureValidityDuration = default)
+ {
+
+ SharedAccessSignatureCredential = string.IsNullOrEmpty(SharedAccessSignature)
+ ? new SharedAccessSignatureCredential(new SharedAccessSignature(eventHubResource, SharedAccessKeyName, SharedAccessKey, signatureValidityDuration))
+ : new SharedAccessSignatureCredential(new SharedAccessSignature(SharedAccessSignature));
+
+ return SharedAccessSignatureCredential;
+ }
+ }
+}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Consumer/EventHubConsumerClient.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Consumer/EventHubConsumerClient.cs
index fbf8e0808ea5..48a6f3e8fc5e 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Consumer/EventHubConsumerClient.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Consumer/EventHubConsumerClient.cs
@@ -215,6 +215,35 @@ public EventHubConsumerClient(string consumerGroup,
RetryPolicy = clientOptions.RetryOptions.ToRetryPolicy();
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The name of the consumer group this consumer is associated with. Events are read in the context of this group.
+ /// The fully qualified Event Hubs namespace to connect to. This is likely to be similar to {yournamespace}.servicebus.windows.net.
+ /// The name of the specific Event Hub to associate the consumer with.
+ /// The Event Hubs shared access key credential to use for authorization. Access controls may be specified by the Event Hubs namespace or the requested Event Hub, depending on Azure configuration.
+ /// A set of options to apply when configuring the consumer.
+ ///
+ public EventHubConsumerClient(string consumerGroup,
+ string fullyQualifiedNamespace,
+ string eventHubName,
+ EventHubsSharedAccessKeyCredential credential,
+ EventHubConsumerClientOptions clientOptions = default)
+ {
+ Argument.AssertNotNullOrEmpty(consumerGroup, nameof(consumerGroup));
+ Argument.AssertWellFormedEventHubsNamespace(fullyQualifiedNamespace, nameof(fullyQualifiedNamespace));
+ Argument.AssertNotNullOrEmpty(eventHubName, nameof(eventHubName));
+ Argument.AssertNotNull(credential, nameof(credential));
+
+ clientOptions = clientOptions?.Clone() ?? new EventHubConsumerClientOptions();
+
+ OwnsConnection = true;
+ Connection = new EventHubConnection(fullyQualifiedNamespace, eventHubName, credential, clientOptions.ConnectionOptions);
+ ConsumerGroup = consumerGroup;
+ RetryPolicy = clientOptions.RetryOptions.ToRetryPolicy();
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -258,6 +287,7 @@ public EventHubConsumerClient(string consumerGroup,
{
Argument.AssertNotNullOrEmpty(consumerGroup, nameof(consumerGroup));
Argument.AssertNotNull(connection, nameof(connection));
+
clientOptions = clientOptions?.Clone() ?? new EventHubConsumerClientOptions();
OwnsConnection = false;
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConnection.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConnection.cs
index 4c1ae8d7aded..32d3490e9184 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConnection.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubConnection.cs
@@ -27,6 +27,13 @@ namespace Azure.Messaging.EventHubs
///
public class EventHubConnection : IAsyncDisposable
{
+ ///
+ /// The default transport type to assume when forming credentials, when no
+ /// transport type was specified.
+ ///
+ ///
+ private static EventHubsTransportType DefaultCredentialTransportType { get; } = new EventHubConnectionOptions().TransportType;
+
///
/// The fully qualified Event Hubs namespace that the connection is associated with. This is likely
/// to be similar to {yournamespace}.servicebus.windows.net.
@@ -150,10 +157,10 @@ public EventHubConnection(string connectionString,
connectionOptions = connectionOptions?.Clone() ?? new EventHubConnectionOptions();
ValidateConnectionOptions(connectionOptions);
- var connectionStringProperties = ConnectionStringParser.Parse(connectionString);
+ var connectionStringProperties = EventHubsConnectionStringProperties.Parse(connectionString);
connectionStringProperties.Validate(eventHubName, nameof(connectionString));
- var fullyQualifiedNamespace = connectionStringProperties.Endpoint.Host;
+ var fullyQualifiedNamespace = connectionStringProperties.FullyQualifiedNamespace;
if (string.IsNullOrEmpty(eventHubName))
{
@@ -186,6 +193,25 @@ public EventHubConnection(string connectionString,
#pragma warning restore CA2214 // Do not call overridable methods in constructors.
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The fully qualified Event Hubs namespace to connect to. This is likely to be similar to {yournamespace}.servicebus.windows.net.
+ /// The name of the specific Event Hub to associate the connection with.
+ /// The to use for authorization. Access controls may be specified by the Event Hubs namespace or the requested Event Hub, depending on Azure configuration.
+ /// A set of options to apply when configuring the connection.
+ ///
+ public EventHubConnection(string fullyQualifiedNamespace,
+ string eventHubName,
+ EventHubsSharedAccessKeyCredential credential,
+ EventHubConnectionOptions connectionOptions = default) : this(fullyQualifiedNamespace,
+ eventHubName,
+ TranslateSharedKeyCredential(credential, fullyQualifiedNamespace, eventHubName, connectionOptions?.TransportType),
+ connectionOptions)
+ {
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -207,16 +233,6 @@ public EventHubConnection(string fullyQualifiedNamespace,
connectionOptions = connectionOptions?.Clone() ?? new EventHubConnectionOptions();
ValidateConnectionOptions(connectionOptions);
- switch (credential)
- {
- case SharedAccessSignatureCredential _:
- break;
-
- case EventHubSharedKeyCredential sharedKeyCredential:
- credential = sharedKeyCredential.AsSharedAccessSignatureCredential(BuildConnectionAudience(connectionOptions.TransportType, fullyQualifiedNamespace, eventHubName));
- break;
- }
-
var tokenCredential = new EventHubTokenCredential(credential, BuildConnectionAudience(connectionOptions.TransportType, fullyQualifiedNamespace, eventHubName));
FullyQualifiedNamespace = fullyQualifiedNamespace;
@@ -484,6 +500,30 @@ internal static string BuildConnectionAudience(EventHubsTransportType transportT
return builder.Uri.AbsoluteUri.ToLowerInvariant();
}
+ ///
+ /// Translates an into the equivalent shared access signature credential.
+ ///
+ ///
+ /// The credential to translate.
+ /// The fully qualified Event Hubs namespace being connected to.
+ /// The name of the Event Hub being connected to.
+ /// The type of transport being used for the connection.
+ ///
+ /// The which the was translated into.
+ ///
+ internal static SharedAccessSignatureCredential TranslateSharedKeyCredential(EventHubsSharedAccessKeyCredential credential,
+ string fullyQualifiedNamespace,
+ string eventHubName,
+ EventHubsTransportType? transportType)
+ {
+ if ((credential == null) || (string.IsNullOrEmpty(fullyQualifiedNamespace)) || (string.IsNullOrEmpty(eventHubName)))
+ {
+ return null;
+ }
+
+ return credential.AsSharedAccessSignatureCredential(BuildConnectionAudience(transportType ?? DefaultCredentialTransportType, fullyQualifiedNamespace, eventHubName));
+ }
+
///
/// Performs the actions needed to validate the associated
/// with this client.
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubProperties.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubProperties.cs
old mode 100755
new mode 100644
index edecca1d3525..14a132c29a27
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubProperties.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubProperties.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.ComponentModel;
namespace Azure.Messaging.EventHubs
{
@@ -46,5 +47,34 @@ protected internal EventHubProperties(string name,
CreatedOn = createdOn;
PartitionIds = partitionIds;
}
+
+ ///
+ /// Determines whether the specified is equal to this instance.
+ ///
+ ///
+ /// The to compare with this instance.
+ ///
+ /// true if the specified is equal to this instance; otherwise, false.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj) => base.Equals(obj);
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode() => base.GetHashCode();
+
+ ///
+ /// Converts the instance to string representation.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string ToString() => base.ToString();
}
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubsConnectionStringProperties.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubsConnectionStringProperties.cs
new file mode 100644
index 000000000000..1eff269ab057
--- /dev/null
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/EventHubsConnectionStringProperties.cs
@@ -0,0 +1,347 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.ComponentModel;
+using System.Text;
+using Azure.Core;
+
+namespace Azure.Messaging.EventHubs
+{
+ ///
+ /// The set of properties that comprise an Event Hubs connection string.
+ ///
+ ///
+ public class EventHubsConnectionStringProperties
+ {
+ /// The token that identifies the endpoint address for the Event Hubs namespace.
+ private const string EndpointToken = "Endpoint";
+
+ /// The token that identifies the name of a specific Event Hub under the namespace.
+ private const string EventHubNameToken = "EntityPath";
+
+ /// The token that identifies the name of a shared access key.
+ private const string SharedAccessKeyNameToken = "SharedAccessKeyName";
+
+ /// The token that identifies the value of a shared access key.
+ private const string SharedAccessKeyValueToken = "SharedAccessKey";
+
+ /// The token that identifies the value of a shared access signature.
+ private const string SharedAccessSignatureToken = "SharedAccessSignature";
+
+ /// The character used to separate a token and its value in the connection string.
+ private const char TokenValueSeparator = '=';
+
+ /// The character used to mark the beginning of a new token/value pair in the connection string.
+ private const char TokenValuePairDelimiter = ';';
+
+ /// The name of the protocol used by an Event Hubs endpoint.
+ private const string EventHubsEndpointSchemeName = "sb";
+
+ /// The formatted protocol used by an Event Hubs endpoint.
+ private static readonly string EventHubsEndpointScheme = $"{ EventHubsEndpointSchemeName }{ Uri.SchemeDelimiter }";
+
+ ///
+ /// The fully qualified Event Hubs namespace that the consumer is associated with. This is likely
+ /// to be similar to {yournamespace}.servicebus.windows.net.
+ ///
+ ///
+ /// The namespace of the Event Hub, as derived from the endpoint address of the connection string.
+ ///
+ public string FullyQualifiedNamespace => Endpoint?.Host;
+
+ ///
+ /// The endpoint to be used for connecting to the Event Hubs namespace.
+ ///
+ ///
+ /// The endpoint address, including protocol, from the connection string.
+ ///
+ public Uri Endpoint { get; internal set; }
+
+ ///
+ /// The name of the specific Event Hub instance under the associated Event Hubs namespace.
+ ///
+ ///
+ public string EventHubName { get; internal set; }
+
+ ///
+ /// The name of the shared access key, either for the Event Hubs namespace
+ /// or the Event Hub.
+ ///
+ ///
+ public string SharedAccessKeyName { get; internal set; }
+
+ ///
+ /// The value of the shared access key, either for the Event Hubs namespace
+ /// or the Event Hub.
+ ///
+ ///
+ public string SharedAccessKey { get; internal set; }
+
+ ///
+ /// The value of the fully-formed shared access signature, either for the Event Hubs
+ /// namespace or the Event Hub.
+ ///
+ ///
+ public string SharedAccessSignature { get; internal set; }
+
+ ///
+ /// Determines whether the specified is equal to this instance.
+ ///
+ ///
+ /// The to compare with this instance.
+ ///
+ /// true if the specified is equal to this instance; otherwise, false.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj) => base.Equals(obj);
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode() => base.GetHashCode();
+
+ ///
+ /// Converts the instance to string representation.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string ToString() => base.ToString();
+
+ ///
+ /// Creates an Event Hubs connection string based on this set of .
+ ///
+ ///
+ ///
+ /// A valid Event Hubs connection string; depending on the specified property information, this may
+ /// represent the namespace-level or Event Hub-level.
+ ///
+ ///
+ ///
+ internal string ToConnectionString()
+ {
+ Validate(null, null);
+
+ var endpointBuilder = new UriBuilder(Endpoint)
+ {
+ Scheme = EventHubsEndpointScheme,
+ Port = -1
+ };
+
+ if ((string.Compare(endpointBuilder.Scheme, EventHubsEndpointSchemeName, StringComparison.OrdinalIgnoreCase) != 0)
+ || (Uri.CheckHostName(endpointBuilder.Host) == UriHostNameType.Unknown))
+ {
+ throw new ArgumentException(Resources.InvalidEndpointAddress);
+ }
+
+ var builder = new StringBuilder()
+ .Append(EndpointToken)
+ .Append(TokenValueSeparator)
+ .Append(endpointBuilder.Uri.AbsoluteUri)
+ .Append(TokenValuePairDelimiter);
+
+ if (!string.IsNullOrEmpty(EventHubName))
+ {
+ builder
+ .Append(EventHubNameToken)
+ .Append(TokenValueSeparator)
+ .Append(EventHubName)
+ .Append(TokenValuePairDelimiter);
+ }
+
+ if (!string.IsNullOrEmpty(SharedAccessSignature))
+ {
+ builder
+ .Append(SharedAccessSignatureToken)
+ .Append(TokenValueSeparator)
+ .Append(SharedAccessSignature)
+ .Append(TokenValuePairDelimiter);
+ }
+ else
+ {
+ builder
+ .Append(SharedAccessKeyNameToken)
+ .Append(TokenValueSeparator)
+ .Append(SharedAccessKeyName)
+ .Append(TokenValuePairDelimiter)
+ .Append(SharedAccessKeyValueToken)
+ .Append(TokenValueSeparator)
+ .Append(SharedAccessKey)
+ .Append(TokenValuePairDelimiter);
+ }
+
+ return builder.ToString();
+ }
+
+ ///
+ /// Performs the actions needed to validate the set of connection string properties for connecting to the
+ /// Event Hubs service.
+ ///
+ ///
+ /// The name of the Event Hub that was explicitly passed independent of the connection string, allowing easier use of a namespace-level connection string.
+ /// The name of the argument associated with the connection string; to be used when raising variants.
+ ///
+ /// In the case that the properties violate an invariant or otherwise represent a combination that is not permissible, an appropriate exception will be thrown.
+ ///
+ internal void Validate(string explicitEventHubName,
+ string connectionStringArgumentName)
+ {
+ // The Event Hub name may only be specified in one of the possible forms, either as part of the
+ // connection string or as a stand-alone parameter, but not both. If specified in both to the same
+ // value, then do not consider this a failure.
+
+ if ((!string.IsNullOrEmpty(explicitEventHubName))
+ && (!string.IsNullOrEmpty(EventHubName))
+ && (!string.Equals(explicitEventHubName, EventHubName, StringComparison.InvariantCultureIgnoreCase)))
+ {
+ throw new ArgumentException(Resources.OnlyOneEventHubNameMayBeSpecified, connectionStringArgumentName);
+ }
+
+ // The connection string may contain a precomputed shared access signature OR a shared key name and value,
+ // but not both.
+
+ if ((!string.IsNullOrEmpty(SharedAccessSignature))
+ && ((!string.IsNullOrEmpty(SharedAccessKeyName)) || (!string.IsNullOrEmpty(SharedAccessKey))))
+ {
+ throw new ArgumentException(Resources.OnlyOneSharedAccessAuthorizationMayBeSpecified, connectionStringArgumentName);
+ }
+
+ // Ensure that each of the needed components are present for connecting.
+
+ var hasSharedKey = ((!string.IsNullOrEmpty(SharedAccessKeyName)) && (!string.IsNullOrEmpty(SharedAccessKey)));
+ var hasSharedSignature = (!string.IsNullOrEmpty(SharedAccessSignature));
+
+ if (string.IsNullOrEmpty(Endpoint?.Host)
+ || ((string.IsNullOrEmpty(explicitEventHubName)) && (string.IsNullOrEmpty(EventHubName)))
+ || ((!hasSharedKey) && (!hasSharedSignature)))
+ {
+ throw new ArgumentException(Resources.MissingConnectionInformation, connectionStringArgumentName);
+ }
+ }
+
+ ///
+ /// Parses the specified Event Hubs connection string into its component properties.
+ ///
+ ///
+ /// The connection string to parse.
+ ///
+ /// The component properties parsed from the connection string.
+ ///
+ /// The specified connection string was malformed and could not be parsed.
+ ///
+ public static EventHubsConnectionStringProperties Parse(string connectionString)
+ {
+ Argument.AssertNotNullOrEmpty(connectionString, nameof(connectionString));
+
+ var parsedValues = new EventHubsConnectionStringProperties();
+ var tokenPositionModifier = (connectionString[0] == TokenValuePairDelimiter) ? 0 : 1;
+ var lastPosition = 0;
+ var currentPosition = 0;
+
+ int valueStart;
+ string slice;
+ string token;
+ string value;
+
+ while (currentPosition != -1)
+ {
+ // Slice the string into the next token/value pair.
+
+ currentPosition = connectionString.IndexOf(TokenValuePairDelimiter, lastPosition + 1);
+
+ if (currentPosition >= 0)
+ {
+ slice = connectionString.Substring(lastPosition, (currentPosition - lastPosition));
+ }
+ else
+ {
+ slice = connectionString.Substring(lastPosition);
+ }
+
+ // Break the token and value apart, if this is a legal pair.
+
+ valueStart = slice.IndexOf(TokenValueSeparator);
+
+ if (valueStart >= 0)
+ {
+ token = slice.Substring((1 - tokenPositionModifier), (valueStart - 1 + tokenPositionModifier));
+ value = slice.Substring(valueStart + 1);
+
+ // Guard against leading and trailing spaces, only trimming if there is a need.
+
+ if ((!string.IsNullOrEmpty(token)) && (char.IsWhiteSpace(token[0])) || char.IsWhiteSpace(token[token.Length - 1]))
+ {
+ token = token.Trim();
+ }
+
+ if ((!string.IsNullOrEmpty(value)) && (char.IsWhiteSpace(value[0]) || char.IsWhiteSpace(value[value.Length - 1])))
+ {
+ value = value.Trim();
+ }
+
+ // If there was no value for a key, then consider the connection string to
+ // be malformed.
+
+ if (string.IsNullOrEmpty(value))
+ {
+ throw new FormatException(Resources.InvalidConnectionString);
+ }
+
+ // Compare the token against the known connection string properties and capture the
+ // pair if they are a known attribute.
+
+ if (string.Compare(EndpointToken, token, StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ var endpointBuilder = new UriBuilder(value)
+ {
+ Scheme = EventHubsEndpointScheme,
+ Port = -1
+ };
+
+ if ((string.Compare(endpointBuilder.Scheme, EventHubsEndpointSchemeName, StringComparison.OrdinalIgnoreCase) != 0)
+ || (Uri.CheckHostName(endpointBuilder.Host) == UriHostNameType.Unknown))
+ {
+ throw new FormatException(Resources.InvalidConnectionString);
+ }
+
+ parsedValues.Endpoint = endpointBuilder.Uri;
+ }
+ else if (string.Compare(EventHubNameToken, token, StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ parsedValues.EventHubName = value;
+ }
+ else if (string.Compare(SharedAccessKeyNameToken, token, StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ parsedValues.SharedAccessKeyName = value;
+ }
+ else if (string.Compare(SharedAccessKeyValueToken, token, StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ parsedValues.SharedAccessKey = value;
+ }
+ else if (string.Compare(SharedAccessSignatureToken, token, StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ parsedValues.SharedAccessSignature = value;
+ }
+ }
+ else if ((slice.Length != 1) || (slice[0] != TokenValuePairDelimiter))
+ {
+ // This wasn't a legal pair and it is not simply a trailing delimiter; consider
+ // the connection string to be malformed.
+
+ throw new FormatException(Resources.InvalidConnectionString);
+ }
+
+ tokenPositionModifier = 0;
+ lastPosition = currentPosition;
+ }
+
+ return parsedValues;
+ }
+ }
+}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/PartitionProperties.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/PartitionProperties.cs
old mode 100755
new mode 100644
index f0070e76140f..122cd4d023dd
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/PartitionProperties.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/PartitionProperties.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.ComponentModel;
namespace Azure.Messaging.EventHubs
{
@@ -84,5 +85,34 @@ protected internal PartitionProperties(string eventHubName,
LastEnqueuedTime = lastEnqueuedTime;
IsEmpty = isEmpty;
}
+
+ ///
+ /// Determines whether the specified is equal to this instance.
+ ///
+ ///
+ /// The to compare with this instance.
+ ///
+ /// true if the specified is equal to this instance; otherwise, false.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj) => base.Equals(obj);
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode() => base.GetHashCode();
+
+ ///
+ /// Converts the instance to string representation.
+ ///
+ ///
+ /// A that represents this instance.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override string ToString() => base.ToString();
}
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Primitives/EventProcessor{TPartition}.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Primitives/EventProcessor{TPartition}.cs
index ed7e82b0456f..0512721dcfb9 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Primitives/EventProcessor{TPartition}.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Primitives/EventProcessor{TPartition}.cs
@@ -243,10 +243,7 @@ internal EventProcessor(int eventBatchMaximumCount,
RetryPolicy = options.RetryOptions.ToRetryPolicy();
Options = options;
EventBatchMaximumCount = eventBatchMaximumCount;
-
-#pragma warning disable CA2214 // Do not call overridable methods in constructors. The virtual methods are internal and used for testing.
- LoadBalancer = loadBalancer ?? CreatePartitionLoadBalancer(CreateStorageManager(this), Identifier, ConsumerGroup, FullyQualifiedNamespace, EventHubName, options.PartitionOwnershipExpirationInterval, options.LoadBalancingUpdateInterval);
-#pragma warning restore CA2214 // Do not call overridable methods in constructors.
+ LoadBalancer = loadBalancer ?? new PartitionLoadBalancer(CreateStorageManager(this), Identifier, ConsumerGroup, FullyQualifiedNamespace, EventHubName, options.PartitionOwnershipExpirationInterval, options.LoadBalancingUpdateInterval);
}
///
@@ -306,7 +303,7 @@ protected EventProcessor(int eventBatchMaximumCount,
options = options?.Clone() ?? new EventProcessorOptions();
- var connectionStringProperties = ConnectionStringParser.Parse(connectionString);
+ var connectionStringProperties = EventHubsConnectionStringProperties.Parse(connectionString);
connectionStringProperties.Validate(eventHubName, nameof(connectionString));
ConnectionFactory = () => new EventHubConnection(connectionString, eventHubName, options.ConnectionOptions);
@@ -317,10 +314,44 @@ protected EventProcessor(int eventBatchMaximumCount,
RetryPolicy = options.RetryOptions.ToRetryPolicy();
Options = options;
EventBatchMaximumCount = eventBatchMaximumCount;
+ LoadBalancer = new PartitionLoadBalancer(CreateStorageManager(this), Identifier, ConsumerGroup, FullyQualifiedNamespace, EventHubName, options.PartitionOwnershipExpirationInterval, options.LoadBalancingUpdateInterval);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The desired number of events to include in a batch to be processed. This size is the maximum count in a batch; the actual count may be smaller, depending on whether events are available in the Event Hub.
+ /// The name of the consumer group the processor is associated with. Events are read in the context of this group.
+ /// The fully qualified Event Hubs namespace to connect to. This is likely to be similar to {yournamespace}.servicebus.windows.net.
+ /// The name of the specific Event Hub to associate the processor with.
+ /// The Event Hubs shared access key credential to use for authorization. Access controls may be specified by the Event Hubs namespace or the requested Event Hub, depending on Azure configuration.
+ /// The set of options to use for the processor.
+ ///
+ protected EventProcessor(int eventBatchMaximumCount,
+ string consumerGroup,
+ string fullyQualifiedNamespace,
+ string eventHubName,
+ EventHubsSharedAccessKeyCredential credential,
+ EventProcessorOptions options = default)
+ {
+ Argument.AssertInRange(eventBatchMaximumCount, 1, int.MaxValue, nameof(eventBatchMaximumCount));
+ Argument.AssertNotNullOrEmpty(consumerGroup, nameof(consumerGroup));
+ Argument.AssertWellFormedEventHubsNamespace(fullyQualifiedNamespace, nameof(fullyQualifiedNamespace));
+ Argument.AssertNotNullOrEmpty(eventHubName, nameof(eventHubName));
+ Argument.AssertNotNull(credential, nameof(credential));
-#pragma warning disable CA2214 // Do not call overridable methods in constructors. The virtual methods are internal and used for testing.
- LoadBalancer = CreatePartitionLoadBalancer(CreateStorageManager(this), Identifier, ConsumerGroup, FullyQualifiedNamespace, EventHubName, options.PartitionOwnershipExpirationInterval, options.LoadBalancingUpdateInterval);
-#pragma warning restore CA2214 // Do not call overridable methods in constructors
+ options = options?.Clone() ?? new EventProcessorOptions();
+
+ ConnectionFactory = () => new EventHubConnection(fullyQualifiedNamespace, eventHubName, credential, options.ConnectionOptions);
+ FullyQualifiedNamespace = fullyQualifiedNamespace;
+ EventHubName = eventHubName;
+ ConsumerGroup = consumerGroup;
+ Identifier = string.IsNullOrEmpty(options.Identifier) ? Guid.NewGuid().ToString() : options.Identifier;
+ RetryPolicy = options.RetryOptions.ToRetryPolicy();
+ Options = options;
+ EventBatchMaximumCount = eventBatchMaximumCount;
+ LoadBalancer = new PartitionLoadBalancer(CreateStorageManager(this), Identifier, ConsumerGroup, FullyQualifiedNamespace, EventHubName, options.PartitionOwnershipExpirationInterval, options.LoadBalancingUpdateInterval);
}
///
@@ -455,37 +486,6 @@ internal virtual TransportConsumer CreateConsumer(string consumerGroup,
EventProcessorOptions options) =>
connection.CreateTransportConsumer(consumerGroup, partitionId, eventPosition, options.RetryOptions.ToRetryPolicy(), options.TrackLastEnqueuedEventProperties, prefetchCount: (uint?)options.PrefetchCount, prefetchSizeInBytes: options.PrefetchSizeInBytes, ownerLevel: 0);
- ///
- /// Creates a to use for interacting with durable storage.
- ///
- ///
- /// The instance to associate with the storage manager.
- ///
- /// A with the requested configuration.
- ///
- internal virtual StorageManager CreateStorageManager(EventProcessor instance) => new DelegatingStorageManager(instance);
-
- ///
- /// Creates a for managing partition ownership for the event processor.
- ///
- ///
- /// Responsible for managing persistence of the partition ownership data.
- /// The identifier of the event processor associated with the load balancer.
- /// The name of the consumer group this load balancer is associated with.
- /// The fully qualified Event Hubs namespace that the processor is associated with.
- /// The name of the Event Hub that the processor is associated with.
- /// The minimum amount of time for an ownership to be considered expired without further updates.
- /// The minimum amount of time to be elapsed between two load balancing verifications.
- ///
- internal virtual PartitionLoadBalancer CreatePartitionLoadBalancer(StorageManager storageManager,
- string identifier,
- string consumerGroup,
- string fullyQualifiedNamespace,
- string eventHubName,
- TimeSpan ownershipExpiration,
- TimeSpan loadBalancingInterval) =>
- new PartitionLoadBalancer(storageManager, identifier, consumerGroup, fullyQualifiedNamespace, eventHubName, ownershipExpiration, loadBalancingInterval);
-
///
/// Performs the tasks needed to process a batch of events.
///
@@ -1517,6 +1517,16 @@ private Task InvokeOnProcessingErrorAsync(Exception exception,
string operationDescription,
CancellationToken cancellationToken) => Task.Run(() => OnProcessingErrorAsync(exception, partition, operationDescription, cancellationToken));
+ ///
+ /// Creates a to use for interacting with durable storage.
+ ///
+ ///
+ /// The instance to associate with the storage manager.
+ ///
+ /// A with the requested configuration.
+ ///
+ internal static StorageManager CreateStorageManager(EventProcessor instance) => new DelegatingStorageManager(instance);
+
///
/// A virtual instance that delegates calls to the
/// to which it is associated.
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Primitives/PartitionReceiver.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Primitives/PartitionReceiver.cs
index 2053ba9fb9ba..fef7fac9cb18 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Primitives/PartitionReceiver.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Primitives/PartitionReceiver.cs
@@ -202,6 +202,46 @@ public PartitionReceiver(string consumerGroup,
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The name of the consumer group this client is associated with. Events are read in the context of this group.
+ /// The identifier of the Event Hub partition from which events will be received.
+ /// The position within the partition where the client should begin reading events.
+ /// The fully qualified Event Hubs namespace to connect to. This is likely to be similar to {yournamespace}.servicebus.windows.net.
+ /// The name of the specific Event Hub to associate the client with.
+ /// The Event Hubs shared key credential to use for authorization. Access controls may be specified by the Event Hubs namespace or the requested Event Hub, depending on Azure configuration.
+ /// A set of options to apply when configuring the client.
+ ///
+ public PartitionReceiver(string consumerGroup,
+ string partitionId,
+ EventPosition eventPosition,
+ string fullyQualifiedNamespace,
+ string eventHubName,
+ EventHubsSharedAccessKeyCredential credential,
+ PartitionReceiverOptions options = default)
+ {
+ Argument.AssertNotNullOrEmpty(consumerGroup, nameof(consumerGroup));
+ Argument.AssertNotNullOrEmpty(partitionId, nameof(partitionId));
+ Argument.AssertWellFormedEventHubsNamespace(fullyQualifiedNamespace, nameof(fullyQualifiedNamespace));
+ Argument.AssertNotNullOrEmpty(eventHubName, nameof(eventHubName));
+ Argument.AssertNotNull(credential, nameof(credential));
+
+ options = options?.Clone() ?? new PartitionReceiverOptions();
+
+ Connection = new EventHubConnection(fullyQualifiedNamespace, eventHubName, credential, options.ConnectionOptions);
+ ConsumerGroup = consumerGroup;
+ PartitionId = partitionId;
+ InitialPosition = eventPosition;
+ DefaultMaximumWaitTime = options.DefaultMaximumReceiveWaitTime;
+ RetryPolicy = options.RetryOptions.ToRetryPolicy();
+
+#pragma warning disable CA2214 // Do not call overridable methods in constructors. This internal method is virtual for testing purposes.
+ InnerConsumer = CreateTransportConsumer(consumerGroup, partitionId, eventPosition, RetryPolicy, options);
+#pragma warning restore CA2214 // Do not call overridable methods in constructors.
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/src/Producer/EventHubProducerClient.cs b/sdk/eventhub/Azure.Messaging.EventHubs/src/Producer/EventHubProducerClient.cs
index 5a84a3301b06..d8e94e812f88 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs/src/Producer/EventHubProducerClient.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/src/Producer/EventHubProducerClient.cs
@@ -239,6 +239,44 @@ public EventHubProducerClient(string connectionString,
}
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The fully qualified Event Hubs namespace to connect to. This is likely to be similar to {yournamespace}.servicebus.windows.net.
+ /// The name of the specific Event Hub to associate the producer with.
+ /// The Event Hubs shared access key credential to use for authorization. Access controls may be specified by the Event Hubs namespace or the requested Event Hub, depending on Azure configuration.
+ /// A set of options to apply when configuring the producer.
+ ///
+ public EventHubProducerClient(string fullyQualifiedNamespace,
+ string eventHubName,
+ EventHubsSharedAccessKeyCredential credential,
+ EventHubProducerClientOptions clientOptions = default)
+ {
+ Argument.AssertWellFormedEventHubsNamespace(fullyQualifiedNamespace, nameof(fullyQualifiedNamespace));
+ Argument.AssertNotNullOrEmpty(eventHubName, nameof(eventHubName));
+ Argument.AssertNotNull(credential, nameof(credential));
+
+ clientOptions = clientOptions?.Clone() ?? new EventHubProducerClientOptions();
+
+ OwnsConnection = true;
+ Connection = new EventHubConnection(fullyQualifiedNamespace, eventHubName, credential, clientOptions.ConnectionOptions);
+ Options = clientOptions;
+ RetryPolicy = clientOptions.RetryOptions.ToRetryPolicy();
+
+ PartitionProducerPool = new TransportProducerPool(partitionId =>
+ Connection.CreateTransportProducer(
+ partitionId,
+ clientOptions.CreateFeatureFlags(),
+ Options.GetPublishingOptionsOrDefaultForPartition(partitionId),
+ RetryPolicy));
+
+ if (RequiresStatefulPartitions(clientOptions))
+ {
+ PartitionState = new ConcurrentDictionary();
+ }
+ }
+
///
/// Initializes a new instance of the class.
///
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Authorization/EventHubSharedKeyCredentialTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubsSharedAccessKeyCredentialTests.cs
old mode 100755
new mode 100644
similarity index 58%
rename from sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Authorization/EventHubSharedKeyCredentialTests.cs
rename to sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubsSharedAccessKeyCredentialTests.cs
index 099cc4eddb84..5da17e8517e9
--- a/sdk/eventhub/Azure.Messaging.EventHubs.Shared/tests/Authorization/EventHubSharedKeyCredentialTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Authorization/EventHubsSharedAccessKeyCredentialTests.cs
@@ -10,12 +10,12 @@
namespace Azure.Messaging.EventHubs.Tests
{
///
- /// The suite of tests for the
+ /// The suite of tests for the
/// class.
///
///
[TestFixture]
- public class EventHubSharedKeyCredentialTests
+ public class EventHubsSharedAccessKeyCredentialTests
{
///
/// Verifies functionality of the constructor.
@@ -26,7 +26,7 @@ public class EventHubSharedKeyCredentialTests
[TestCase("")]
public void ConstructorValidatesTheKeyName(string keyName)
{
- Assert.That(() => new EventHubSharedKeyCredential(keyName, "someKey"), Throws.InstanceOf());
+ Assert.That(() => new EventHubsSharedAccessKeyCredential(keyName, "someKey"), Throws.InstanceOf());
}
///
@@ -38,7 +38,7 @@ public void ConstructorValidatesTheKeyName(string keyName)
[TestCase("")]
public void ConstructorValidatesTheKeyValue(string keyValue)
{
- Assert.That(() => new EventHubSharedKeyCredential("someName", keyValue), Throws.InstanceOf());
+ Assert.That(() => new EventHubsSharedAccessKeyCredential("someName", keyValue), Throws.InstanceOf());
}
///
@@ -46,14 +46,27 @@ public void ConstructorValidatesTheKeyValue(string keyValue)
///
///
[Test]
- public void ConstructorValidatesInitializesProperties()
+ [TestCase(null)]
+ [TestCase("")]
+ public void ConstructorValidatesInitializesSharedAccessSignatureProperties(string signature)
+ {
+ Assert.That(() => new EventHubsSharedAccessKeyCredential(signature), Throws.InstanceOf());
+ }
+
+ ///
+ /// Verifies functionality of the constructor.
+ ///
+ ///
+ [Test]
+ public void ConstructorInitializesSharedKeyProperties()
{
var name = "KeyName";
var value = "KeyValue";
- var credential = new EventHubSharedKeyCredential(name, value);
- var initializedValue = GetSharedAccessKey(credential);
+ var credential = new EventHubsSharedAccessKeyCredential(name, value);
- Assert.That(initializedValue, Is.EqualTo(value), "The shared key should have been set.");
+ Assert.That(credential.SharedAccessKeyName, Is.EqualTo(name), "The shared key name should have been set.");
+ Assert.That(credential.SharedAccessKey, Is.EqualTo(value), "The shared key should have been set.");
+ Assert.That(credential.SharedAccessSignature, Is.Null, "The shared access signature should not have been set.");
}
///
@@ -61,9 +74,14 @@ public void ConstructorValidatesInitializesProperties()
///
///
[Test]
- public void GetTokenIsNotPermitted()
+ public void ConstructorInitializesSharedSignatureProperties()
{
- Assert.That(() => new EventHubSharedKeyCredential("key", "value").GetToken(new TokenRequestContext(new[] { "test" }), default), Throws.InvalidOperationException);
+ var signature = new SharedAccessSignature("RESOURCE", "keyname", "keyvalue").Value;
+ var credential = new EventHubsSharedAccessKeyCredential(signature);
+
+ Assert.That(credential.SharedAccessSignature, Is.EqualTo(signature), "The shared access signature name should have been set.");
+ Assert.That(credential.SharedAccessKeyName, Is.Null, "The shared key name should have been set.");
+ Assert.That(credential.SharedAccessKey, Is.Null, "The shared access key should not have been set.");
}
///
@@ -71,9 +89,52 @@ public void GetTokenIsNotPermitted()
///
///
[Test]
- public void GetTokenAsyncIsNotPermitted()
+ public void AsSharedAccessSignatureCredentialProducesTheExpectedCredentialForSharedKeys()
{
- Assert.That(async () => await (new EventHubSharedKeyCredential("key", "value").GetTokenAsync(new TokenRequestContext(new[] { "thing" }), default)), Throws.InvalidOperationException);
+ var resource = "amqps://some.hub.com/path";
+ var keyName = "sharedKey";
+ var keyValue = "keyValue";
+ var validSpan = TimeSpan.FromHours(4);
+ var signature = new SharedAccessSignature(resource, keyName, keyValue, validSpan);
+ var keyCredential = new EventHubsSharedAccessKeyCredential(keyName, keyValue);
+ var sasCredential = keyCredential.AsSharedAccessSignatureCredential(resource, validSpan);
+
+ Assert.That(sasCredential, Is.Not.Null, "A shared access signature credential should have been created.");
+
+ var credentialSignature = GetSharedAccessSignature(sasCredential);
+ Assert.That(credentialSignature, Is.Not.Null, "The SAS credential should contain a shared access signature.");
+ Assert.That(credentialSignature.Resource, Is.EqualTo(signature.Resource), "The resource should match.");
+ Assert.That(credentialSignature.SharedAccessKeyName, Is.EqualTo(signature.SharedAccessKeyName), "The shared access key name should match.");
+ Assert.That(credentialSignature.SharedAccessKey, Is.EqualTo(signature.SharedAccessKey), "The shared access key should match.");
+ Assert.That(credentialSignature.SignatureExpiration, Is.EqualTo(signature.SignatureExpiration).Within(TimeSpan.FromSeconds(5)), "The expiration should match.");
+ }
+
+ ///
+ /// The signature expiration will always be extended after calling AsSharedAccessSignatureCredential.
+ ///
+ ///
+ [Test]
+ public void AsSharedAccessSignatureCredentialShouldRefreshTokenValidityForSharedKeys()
+ {
+ var beforeResource = "amqps://before/path";
+ var afterResource = "amqps://after/path";
+ var beforeSpan = TimeSpan.FromHours(4);
+ var afterSpan = TimeSpan.FromHours(8);
+ var keyName = "keyName";
+ var keyValue = "keyValue";
+ var expectedSignature = new SharedAccessSignature(beforeResource, keyName, keyValue, beforeSpan);
+ var keyCredential = new EventHubsSharedAccessKeyCredential(keyName, keyValue);
+
+ SharedAccessSignatureCredential sasCredential = keyCredential.AsSharedAccessSignatureCredential(beforeResource, beforeSpan);
+ SharedAccessSignature beforeSignature = GetSharedAccessSignature(sasCredential);
+
+ Assert.That(beforeSignature.SignatureExpiration, Is.EqualTo(expectedSignature.SignatureExpiration).Within(TimeSpan.FromSeconds(5)), "The expiration should match.");
+
+ expectedSignature = new SharedAccessSignature(afterResource, keyName, keyValue, afterSpan);
+ sasCredential = keyCredential.AsSharedAccessSignatureCredential(afterResource, afterSpan);
+ SharedAccessSignature afterSignature = GetSharedAccessSignature(sasCredential);
+
+ Assert.That(afterSignature.SignatureExpiration, Is.EqualTo(expectedSignature.SignatureExpiration).Within(TimeSpan.FromSeconds(5)), "The expiration should match.");
}
///
@@ -81,14 +142,14 @@ public void GetTokenAsyncIsNotPermitted()
///
///
[Test]
- public void AsSharedAccessSignatureCredentialProducesTheExpectedCredential()
+ public void AsSharedAccessSignatureCredentialProducesTheExpectedCredentialForSharedAccessSignatures()
{
var resource = "amqps://some.hub.com/path";
var keyName = "sharedKey";
var keyValue = "keyValue";
var validSpan = TimeSpan.FromHours(4);
var signature = new SharedAccessSignature(resource, keyName, keyValue, validSpan);
- var keyCredential = new EventHubSharedKeyCredential(keyName, keyValue);
+ var keyCredential = new EventHubsSharedAccessKeyCredential(signature.Value);
var sasCredential = keyCredential.AsSharedAccessSignatureCredential(resource, validSpan);
Assert.That(sasCredential, Is.Not.Null, "A shared access signature credential should have been created.");
@@ -97,7 +158,7 @@ public void AsSharedAccessSignatureCredentialProducesTheExpectedCredential()
Assert.That(credentialSignature, Is.Not.Null, "The SAS credential should contain a shared access signature.");
Assert.That(credentialSignature.Resource, Is.EqualTo(signature.Resource), "The resource should match.");
Assert.That(credentialSignature.SharedAccessKeyName, Is.EqualTo(signature.SharedAccessKeyName), "The shared access key name should match.");
- Assert.That(credentialSignature.SharedAccessKey, Is.EqualTo(signature.SharedAccessKey), "The shared access key should match.");
+ Assert.That(credentialSignature.SharedAccessKey, Is.Null, "The shared access key should not be populated.");
Assert.That(credentialSignature.SignatureExpiration, Is.EqualTo(signature.SignatureExpiration).Within(TimeSpan.FromSeconds(5)), "The expiration should match.");
}
@@ -106,7 +167,7 @@ public void AsSharedAccessSignatureCredentialProducesTheExpectedCredential()
///
///
[Test]
- public void AsSharedAccessSignatureCredentialShouldRefreshTokenValidity()
+ public void AsSharedAccessSignatureCredentialShouldNotRefreshTokenValidityForSharedAccessSignatures()
{
var beforeResource = "amqps://before/path";
var afterResource = "amqps://after/path";
@@ -115,14 +176,13 @@ public void AsSharedAccessSignatureCredentialShouldRefreshTokenValidity()
var keyName = "keyName";
var keyValue = "keyValue";
var expectedSignature = new SharedAccessSignature(beforeResource, keyName, keyValue, beforeSpan);
- var keyCredential = new EventHubSharedKeyCredential(keyName, keyValue);
+ var keyCredential = new EventHubsSharedAccessKeyCredential(expectedSignature.Value);
SharedAccessSignatureCredential sasCredential = keyCredential.AsSharedAccessSignatureCredential(beforeResource, beforeSpan);
SharedAccessSignature beforeSignature = GetSharedAccessSignature(sasCredential);
Assert.That(beforeSignature.SignatureExpiration, Is.EqualTo(expectedSignature.SignatureExpiration).Within(TimeSpan.FromSeconds(5)), "The expiration should match.");
- expectedSignature = new SharedAccessSignature(afterResource, keyName, keyValue, afterSpan);
sasCredential = keyCredential.AsSharedAccessSignatureCredential(afterResource, afterSpan);
SharedAccessSignature afterSignature = GetSharedAccessSignature(sasCredential);
@@ -141,7 +201,7 @@ public void EventHubSharedKeyCredentialShouldHoldAReferenceToASharedAccessKey()
var span = TimeSpan.FromHours(4);
var keyName = "keyName";
var keyValue = "keyValue";
- var keyCredential = new EventHubSharedKeyCredential(keyName, keyValue);
+ var keyCredential = new EventHubsSharedAccessKeyCredential(keyName, keyValue);
SharedAccessSignatureCredential sasCredential = keyCredential.AsSharedAccessSignatureCredential(resource, span);
SharedAccessSignatureCredential wrappedCredential = GetSharedAccessSignatureCredential(keyCredential);
@@ -154,7 +214,7 @@ public void EventHubSharedKeyCredentialShouldHoldAReferenceToASharedAccessKey()
///
///
[Test]
- public void UpdateSharedAccessKeyShouldAllowToRefreshASharedAccessSignature()
+ public void UpdateSharedAccessKeyShouldAllowRefreshOfTheSharedAccessSignature()
{
var resource = "amqps://before/path";
var beforeKeyName = "beforeKeyName";
@@ -163,7 +223,7 @@ public void UpdateSharedAccessKeyShouldAllowToRefreshASharedAccessSignature()
var afterKeyValue = "afterKeyValue";
var validSpan = TimeSpan.FromHours(4);
var signature = new SharedAccessSignature(resource, beforeKeyName, beforeKeyValue, validSpan);
- var keyCredential = new EventHubSharedKeyCredential(beforeKeyName, beforeKeyValue);
+ var keyCredential = new EventHubsSharedAccessKeyCredential(beforeKeyName, beforeKeyValue);
// Needed to instantiate a SharedAccessSignatureCredential
var sasCredential = keyCredential.AsSharedAccessSignatureCredential(resource, validSpan);
@@ -181,13 +241,44 @@ public void UpdateSharedAccessKeyShouldAllowToRefreshASharedAccessSignature()
Assert.That(credentialSignature.SignatureExpiration, Is.EqualTo(signature.SignatureExpiration).Within(TimeSpan.FromSeconds(5)), "The expiration should match.");
}
+ ///
+ /// Verifies functionality of the constructor.
+ ///
+ ///
+ [Test]
+ public void UpdateSharedAccessSignatureShouldUpdateTheSharedAccessSignature()
+ {
+ var resource = "amqps://before/path";
+ var beforeKeyName = "beforeKeyName";
+ var afterKeyName = "afterKeyName";
+ var beforeKeyValue = "beforeKeyValue";
+ var afterKeyValue = "afterKeyValue";
+ var validSpan = TimeSpan.FromHours(4);
+ var signature = new SharedAccessSignature(resource, beforeKeyName, beforeKeyValue, validSpan.Add(TimeSpan.FromHours(2)));
+ var keyCredential = new EventHubsSharedAccessKeyCredential(signature.Value);
+ var sasCredential = keyCredential.AsSharedAccessSignatureCredential(resource, validSpan);
+
+ // Updates
+ var newSignature = new SharedAccessSignature(resource, afterKeyName, afterKeyValue, validSpan);
+ keyCredential.UpdateSharedAccessSignature(newSignature.Value);
+
+ Assert.That(sasCredential, Is.Not.Null, "A shared access signature credential should have been created.");
+
+ var credentialSignature = GetSharedAccessSignature(sasCredential);
+ Assert.That(credentialSignature, Is.Not.Null, "The SAS credential should contain a shared access signature.");
+ Assert.That(credentialSignature.Resource, Is.EqualTo(signature.Resource), "The resource should match.");
+ Assert.That(credentialSignature.SharedAccessKeyName, Is.EqualTo(afterKeyName), "The shared access key name should match.");
+ Assert.That(credentialSignature.SharedAccessKey, Is.Null, "The shared access key should not have been set.");
+ Assert.That(credentialSignature.SignatureExpiration, Is.EqualTo(newSignature.SignatureExpiration).Within(TimeSpan.FromSeconds(5)), "The expiration should match.");
+ }
+
///
/// A call to UpdateSharedAccessKey should change properties of the SharedAccessSignature
/// that it wraps like the SharedAccessKeyName or the SharedAccessKey.
///
///
[Test]
- public void UpdateSharedAccessKeyShouldAlwaysRefreshEventHubSharedKeyCredentialNameAndKey()
+ public void UpdateSharedAccessKeyShoulRefreshEventHubSharedKeyCredentialNameAndKey()
{
var resource = "amqps://before/path";
var validSpan = TimeSpan.FromHours(4);
@@ -195,14 +286,13 @@ public void UpdateSharedAccessKeyShouldAlwaysRefreshEventHubSharedKeyCredentialN
var afterKeyName = "afterKeyName";
var beforeKeyValue = "beforeKeyValue";
var afterKeyValue = "afterKeyValue";
- var keyCredential = new EventHubSharedKeyCredential(beforeKeyName, beforeKeyValue);
+ var keyCredential = new EventHubsSharedAccessKeyCredential(beforeKeyName, beforeKeyValue);
keyCredential.UpdateSharedAccessKey(afterKeyName, afterKeyValue);
- string keyName = GetSharedAccessKeyName(keyCredential);
- string key = GetSharedAccessKey(keyCredential);
+ var keyName = keyCredential.SharedAccessKeyName;
+ var key = keyCredential.SharedAccessKey;
- // Needed to instantiate a SharedAccessSignatureCredential
var sasCredential = keyCredential.AsSharedAccessSignatureCredential(resource, validSpan);
var credentialSignature = GetSharedAccessSignature(sasCredential);
@@ -218,7 +308,7 @@ public void UpdateSharedAccessKeyShouldAlwaysRefreshEventHubSharedKeyCredentialN
///
///
[Test]
- public void UpdateSharedAccessKeyShouldNotChangeOtherPropertiesOfASharedAccessSignature()
+ public void UpdateSharedAccessKeyShouldNotChangeOtherPropertiesForTheSharedAccessSignature()
{
var resource = "amqps://before/path";
var validSpan = TimeSpan.FromHours(4);
@@ -226,7 +316,7 @@ public void UpdateSharedAccessKeyShouldNotChangeOtherPropertiesOfASharedAccessSi
var afterKeyName = "afterKeyName";
var beforeKeyValue = "beforeKeyValue";
var afterKeyValue = "afterKeyValue";
- var keyCredential = new EventHubSharedKeyCredential(beforeKeyName, beforeKeyValue);
+ var keyCredential = new EventHubsSharedAccessKeyCredential(beforeKeyName, beforeKeyValue);
// Needed to instantiate a SharedAccessTokenCredential
var sasCredential = keyCredential.AsSharedAccessSignatureCredential(resource, validSpan);
@@ -240,34 +330,6 @@ public void UpdateSharedAccessKeyShouldNotChangeOtherPropertiesOfASharedAccessSi
Assert.That(credentialSignature.Resource, Is.EqualTo(resource), "The resource of a signature should not change when the credentials are rolled.");
}
- ///
- /// Retrieves the shared access key from the credential using its private accessor.
- ///
- ///
- /// The instance to retrieve the key from.
- ///
- /// The shared access key.
- ///
- private static string GetSharedAccessKey(EventHubSharedKeyCredential instance) =>
- (string)
- typeof(EventHubSharedKeyCredential)
- .GetProperty("SharedAccessKey", BindingFlags.Instance | BindingFlags.NonPublic)
- .GetValue(instance, null);
-
- ///
- /// Retrieves the shared access key from the credential using its private accessor.
- ///
- ///
- /// The instance to retrieve the key from.
- ///
- /// The shared access key.
- ///
- private static string GetSharedAccessKeyName(EventHubSharedKeyCredential instance) =>
- (string)
- typeof(EventHubSharedKeyCredential)
- .GetProperty("SharedAccessKeyName", BindingFlags.Instance | BindingFlags.NonPublic)
- .GetValue(instance, null);
-
///
/// Retrieves the shared access signature from the credential using its private accessor.
///
@@ -290,9 +352,9 @@ private static SharedAccessSignature GetSharedAccessSignature(SharedAccessSignat
///
/// The shared access key.
///
- private static SharedAccessSignatureCredential GetSharedAccessSignatureCredential(EventHubSharedKeyCredential instance) =>
+ private static SharedAccessSignatureCredential GetSharedAccessSignatureCredential(EventHubsSharedAccessKeyCredential instance) =>
(SharedAccessSignatureCredential)
- typeof(EventHubSharedKeyCredential)
+ typeof(EventHubsSharedAccessKeyCredential)
.GetProperty("SharedAccessSignatureCredential", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(instance, null);
}
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionLiveTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionLiveTests.cs
old mode 100755
new mode 100644
index 43b41702177c..d36f56756b3e
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionLiveTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionLiveTests.cs
@@ -100,7 +100,7 @@ public async Task ConnectionCanConnectToEventHubsUsingSharedKeyCredential()
{
await using (EventHubScope scope = await EventHubScope.CreateAsync(1))
{
- var credential = new EventHubSharedKeyCredential(EventHubsTestEnvironment.Instance.SharedAccessKeyName, EventHubsTestEnvironment.Instance.SharedAccessKey);
+ var credential = new EventHubsSharedAccessKeyCredential(EventHubsTestEnvironment.Instance.SharedAccessKeyName, EventHubsTestEnvironment.Instance.SharedAccessKey);
await using (var connection = new TestConnectionWithTransport(EventHubsTestEnvironment.Instance.FullyQualifiedNamespace, scope.EventHubName, credential))
{
@@ -369,6 +369,13 @@ public TestConnectionWithTransport(string fullyQualifiedNamespace,
{
}
+ public TestConnectionWithTransport(string fullyQualifiedNamespace,
+ string eventHubName,
+ EventHubsSharedAccessKeyCredential credential,
+ EventHubConnectionOptions connectionOptions = default) : base(fullyQualifiedNamespace, eventHubName, credential, connectionOptions)
+ {
+ }
+
public Task GetPropertiesAsync(CancellationToken cancellationToken = default) => base.GetPropertiesAsync(RetryPolicy, cancellationToken);
public Task GetPartitionIdsAsync(CancellationToken cancellationToken = default) => base.GetPartitionIdsAsync(RetryPolicy, cancellationToken);
public Task GetPartitionPropertiesAsync(string partitionId, CancellationToken cancellationToken = default) => base.GetPartitionPropertiesAsync(partitionId, RetryPolicy, cancellationToken);
diff --git a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionTests.cs b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionTests.cs
index 9aeb7e103c16..a826e70e1da4 100644
--- a/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionTests.cs
+++ b/sdk/eventhub/Azure.Messaging.EventHubs/tests/Connection/EventHubConnectionTests.cs
@@ -29,7 +29,7 @@ public class EventHubConnectionTests
/// Provides the invalid test cases for the constructor tests.
///
///
- public static IEnumerable