From c876a6e0c24a6b02113528e9bcf8d789e9b3c6c9 Mon Sep 17 00:00:00 2001 From: JamesBirdsall Date: Mon, 22 Jul 2019 17:33:27 -0700 Subject: [PATCH] Event Hubs RBAC support (#4498) --- .../microsoft-azure-eventhubs-eph/pom.xml | 10 +- .../AzureStorageCheckpointLeaseManager.java | 32 +- .../EventHubClientFactory.java | 94 +++ .../EventProcessorHost.java | 612 +++++++++++------- .../azure/eventprocessorhost/HostContext.java | 18 +- .../eventprocessorhost/PartitionManager.java | 2 +- .../eventprocessorhost/PartitionPump.java | 3 +- .../CheckpointManagerTest.java | 10 +- .../EPHConstructorTests.java | 9 +- .../eventprocessorhost/LeaseManagerTest.java | 9 +- .../PartitionManagerTest.java | 7 +- .../eventprocessorhost/PerTestSettings.java | 22 + .../RealEventHubUtilities.java | 4 +- .../azure/eventprocessorhost/Repros.java | 45 +- .../azure/eventprocessorhost/SmokeTest.java | 54 ++ .../azure/eventprocessorhost/TestBase.java | 23 +- .../pom.xml | 2 +- .../extensions/appender/EventHubsManager.java | 2 +- .../microsoft-azure-eventhubs/pom.xml | 28 +- .../AuthorizationFailedException.java | 2 +- .../AzureActiveDirectoryTokenProvider.java | 42 ++ .../eventhubs/ConnectionStringBuilder.java | 35 +- .../azure/eventhubs/ErrorContext.java | 4 +- .../microsoft/azure/eventhubs/EventData.java | 6 +- .../azure/eventhubs/EventHubClient.java | 70 +- .../eventhubs/EventHubClientOptions.java | 76 +++ .../azure/eventhubs/ITokenProvider.java | 11 + .../azure/eventhubs/JsonSecurityToken.java | 31 + .../ManagedIdentityTokenProvider.java | 26 + .../azure/eventhubs/PartitionSender.java | 2 +- .../eventhubs/QuotaExceededException.java | 3 +- .../azure/eventhubs/SecurityToken.java | 63 ++ .../impl/ActiveClientTokenManager.java | 2 +- .../azure/eventhubs/impl/AmqpUtil.java | 5 + .../azure/eventhubs/impl/CBSChannel.java | 44 +- .../azure/eventhubs/impl/ClientConstants.java | 4 +- .../eventhubs/impl/EventHubClientImpl.java | 123 ++-- .../azure/eventhubs/impl/MessageReceiver.java | 128 ++-- .../azure/eventhubs/impl/MessageSender.java | 78 ++- .../eventhubs/impl/MessagingFactory.java | 147 ++++- .../SharedAccessSignatureTokenProvider.java | 30 +- .../concurrency/ConcurrentReceiversTest.java | 4 +- .../concurrency/EventHubClientTest.java | 2 +- .../connstrbuilder/TransportTypeTest.java | 6 +- .../eventhubs/eventdata/BackCompatTest.java | 2 +- .../eventdata/EventDataBatchTest.java | 2 +- .../eventdata/InteropAmqpPropertiesTest.java | 2 +- .../eventdata/InteropEventBodyTest.java | 2 +- .../ClientEntityCreateTest.java | 12 +- .../MsgFactoryOpenCloseTest.java | 28 +- .../exceptioncontracts/ReactorFaultTest.java | 4 +- .../exceptioncontracts/ReceiverEpochTest.java | 2 +- .../SecurityExceptionsTest.java | 20 +- .../SendLargeMessageTest.java | 4 +- .../eventhubs/proxy/ProxySelectorTest.java | 2 +- .../azure/eventhubs/sendrecv/AadBase.java | 168 +++++ .../azure/eventhubs/sendrecv/AdalTest.java | 64 ++ .../sendrecv/EventDataBatchAPITest.java | 2 +- .../sendrecv/ManagedIdentityTest.java | 48 ++ .../azure/eventhubs/sendrecv/MsalTest.java | 74 +++ .../sendrecv/ReceiveParallelManualTest.java | 8 +- .../sendrecv/ReceivePumpEventHubTest.java | 2 +- .../azure/eventhubs/sendrecv/ReceiveTest.java | 2 +- .../sendrecv/ReceiverIdentifierTest.java | 2 +- .../sendrecv/ReceiverRuntimeMetricsTest.java | 2 +- .../sendrecv/RequestResponseTest.java | 10 +- .../azure/eventhubs/sendrecv/SendTest.java | 2 +- .../sendrecv/SetPrefetchCountTest.java | 2 +- sdk/eventhubs/pom.data.xml | 2 +- 69 files changed, 1814 insertions(+), 584 deletions(-) create mode 100644 sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/EventHubClientFactory.java create mode 100644 sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/AzureActiveDirectoryTokenProvider.java create mode 100644 sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClientOptions.java create mode 100644 sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ITokenProvider.java create mode 100644 sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/JsonSecurityToken.java create mode 100644 sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ManagedIdentityTokenProvider.java create mode 100644 sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/SecurityToken.java create mode 100644 sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/AadBase.java create mode 100644 sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/AdalTest.java create mode 100644 sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ManagedIdentityTest.java create mode 100644 sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/MsalTest.java diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/pom.xml b/sdk/eventhubs/microsoft-azure-eventhubs-eph/pom.xml index fad46c57eae75..61dd300dd013b 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/pom.xml +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/pom.xml @@ -7,14 +7,14 @@ com.microsoft.azure azure-eventhubs-clients - 2.3.1 + 3.0.0 ../pom.data.xml 4.0.0 com.microsoft.azure azure-eventhubs-eph - 2.5.1 + 3.0.0 Microsoft Azure SDK for Event Hubs Event Processor Host(EPH) EPH is built on top of the Azure Event Hubs Client and provides a number of features not present in that lower layer @@ -45,6 +45,12 @@ com.google.code.gson gson + + com.microsoft.azure + msal4j + 0.4.0-preview + test + diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/AzureStorageCheckpointLeaseManager.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/AzureStorageCheckpointLeaseManager.java index e44b6d2ccdf62..6261138e5deac 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/AzureStorageCheckpointLeaseManager.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/AzureStorageCheckpointLeaseManager.java @@ -6,6 +6,7 @@ import com.google.gson.Gson; import com.microsoft.azure.storage.AccessCondition; import com.microsoft.azure.storage.CloudStorageAccount; +import com.microsoft.azure.storage.StorageCredentials; import com.microsoft.azure.storage.StorageErrorCodeStrings; import com.microsoft.azure.storage.StorageException; import com.microsoft.azure.storage.StorageExtendedErrorInformation; @@ -46,6 +47,7 @@ class AzureStorageCheckpointLeaseManager implements ICheckpointManager, ILeaseMa private static final String METADATA_OWNER_NAME = "OWNINGHOST"; private final String storageConnectionString; + private final StorageCredentials storageCredentials; private final String storageBlobPrefix; private final BlobRequestOptions leaseOperationOptions = new BlobRequestOptions(); private final BlobRequestOptions checkpointOperationOptions = new BlobRequestOptions(); @@ -59,16 +61,30 @@ class AzureStorageCheckpointLeaseManager implements ICheckpointManager, ILeaseMa private Hashtable latestCheckpoint = new Hashtable(); - AzureStorageCheckpointLeaseManager(String storageConnectionString, String storageContainerName) { - this(storageConnectionString, storageContainerName, ""); - } - AzureStorageCheckpointLeaseManager(String storageConnectionString, String storageContainerName, String storageBlobPrefix) { if ((storageConnectionString == null) || storageConnectionString.trim().isEmpty()) { throw new IllegalArgumentException("Provide valid Azure Storage connection string when using Azure Storage"); } this.storageConnectionString = storageConnectionString; + this.storageCredentials = null; + + if ((storageContainerName != null) && storageContainerName.trim().isEmpty()) { + throw new IllegalArgumentException("Azure Storage container name must be a valid container name or null to use the default"); + } + this.storageContainerName = storageContainerName; + // Convert all-whitespace prefix to empty string. Convert null prefix to empty string. + // Then the rest of the code only has one case to worry about. + this.storageBlobPrefix = (storageBlobPrefix != null) ? storageBlobPrefix.trim() : ""; + } + + AzureStorageCheckpointLeaseManager(StorageCredentials storageCredentials, String storageContainerName, String storageBlobPrefix) { + if (storageCredentials == null) { + throw new IllegalArgumentException("Provide valid Azure Storage credentials when using Azure Storage"); + } + this.storageConnectionString = null; + this.storageCredentials = storageCredentials; + if ((storageContainerName != null) && storageContainerName.trim().isEmpty()) { throw new IllegalArgumentException("Azure Storage container name must be a valid container name or null to use the default"); } @@ -102,7 +118,13 @@ void initialize(HostContext hostContext) throws InvalidKeyException, URISyntaxEx + "Must be from 3 to 63 characters long."); } - this.storageClient = CloudStorageAccount.parse(this.storageConnectionString).createCloudBlobClient(); + CloudStorageAccount storageAccount = null; + if (this.storageConnectionString != null) { + storageAccount = CloudStorageAccount.parse(this.storageConnectionString); + } else { + storageAccount = new CloudStorageAccount(this.storageCredentials); + } + this.storageClient = storageAccount.createCloudBlobClient(); this.eventHubContainer = this.storageClient.getContainerReference(this.storageContainerName); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/EventHubClientFactory.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/EventHubClientFactory.java new file mode 100644 index 0000000000000..e91a5d13dc96f --- /dev/null +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/EventHubClientFactory.java @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.azure.eventprocessorhost; + +import java.io.IOException; +import java.net.URI; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; + +import com.microsoft.azure.eventhubs.AzureActiveDirectoryTokenProvider; +import com.microsoft.azure.eventhubs.EventHubClient; +import com.microsoft.azure.eventhubs.EventHubClientOptions; +import com.microsoft.azure.eventhubs.EventHubException; +import com.microsoft.azure.eventhubs.ITokenProvider; +import com.microsoft.azure.eventhubs.RetryPolicy; + +abstract class EventHubClientFactory { + protected ScheduledExecutorService executor; + + protected final EventHubClientOptions options; + + public EventHubClientFactory(final RetryPolicy retryPolicy) { + this((new EventHubClientOptions()).setRetryPolicy(retryPolicy)); + } + + public EventHubClientFactory(final EventHubClientOptions options) { + this.options = options; + } + + public void setExecutor(ScheduledExecutorService executor) { + this.executor = executor; + } + + abstract CompletableFuture createEventHubClient() throws EventHubException, IOException; + + static class EHCFWithConnectionString extends EventHubClientFactory { + private final String eventHubConnectionString; + + public EHCFWithConnectionString(final String eventHubConnectionString, + final RetryPolicy retryPolicy) { + super(retryPolicy); + this.eventHubConnectionString = eventHubConnectionString; + } + + public CompletableFuture createEventHubClient() throws EventHubException, IOException { + return EventHubClient.createFromConnectionString(this.eventHubConnectionString, this.options.getRetryPolicy(), this.executor); + } + } + + static class EHCFWithAuthCallback extends EventHubClientFactory { + private final URI endpoint; + private final String eventHubPath; + private final AzureActiveDirectoryTokenProvider.AuthenticationCallback authCallback; + private final String authority; + + public EHCFWithAuthCallback(final URI endpoint, + final String eventHubPath, + final AzureActiveDirectoryTokenProvider.AuthenticationCallback authCallback, + final String authority, + final EventHubClientOptions options) { + super(options); + this.endpoint = endpoint; + this.eventHubPath = eventHubPath; + this.authCallback = authCallback; + this.authority = authority; + } + + public CompletableFuture createEventHubClient() throws EventHubException, IOException { + return EventHubClient.createWithAzureActiveDirectory(this.endpoint, + this.eventHubPath, this.authCallback, this.authority, this.executor, this.options); + } + } + + static class EHCFWithTokenProvider extends EventHubClientFactory { + private final URI endpoint; + private final String eventHubPath; + private final ITokenProvider tokenProvider; + + public EHCFWithTokenProvider(final URI endpoint, + final String eventHubPath, + final ITokenProvider tokenProvider, + final EventHubClientOptions options) { + super(options); + this.endpoint = endpoint; + this.eventHubPath = eventHubPath; + this.tokenProvider = tokenProvider; + } + + public CompletableFuture createEventHubClient() throws EventHubException, IOException { + return EventHubClient.createWithTokenProvider(this.endpoint, this.eventHubPath, this.tokenProvider, this.executor, this.options); + } + } +} diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/EventProcessorHost.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/EventProcessorHost.java index c9eaf61e266dc..5fc9acbd1e71b 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/EventProcessorHost.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/EventProcessorHost.java @@ -3,15 +3,26 @@ package com.microsoft.azure.eventprocessorhost; +import com.microsoft.azure.eventhubs.AzureActiveDirectoryTokenProvider; +import com.microsoft.azure.eventhubs.AzureActiveDirectoryTokenProvider.AuthenticationCallback; import com.microsoft.azure.eventhubs.ConnectionStringBuilder; +import com.microsoft.azure.eventhubs.EventHubClientOptions; +import com.microsoft.azure.eventhubs.ITokenProvider; import com.microsoft.azure.eventhubs.RetryPolicy; +import com.microsoft.azure.eventhubs.TransportType; +import com.microsoft.azure.eventhubs.impl.StringUtil; +import com.microsoft.azure.storage.StorageCredentials; import com.microsoft.azure.storage.StorageException; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.net.URI; import java.net.URISyntaxException; import java.security.InvalidKeyException; +import java.time.Duration; import java.util.Locale; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -39,244 +50,24 @@ public final class EventProcessorHost { private PartitionManager partitionManager; private PartitionManagerOptions partitionManagerOptions = null; - /** - * Create a new host instance to process events from an Event Hub. - *

- * Since Event Hubs are generally used for scale-out, high-traffic scenarios, in most scenarios there will - * be only one host instances per process, and the processes will be run on separate machines. Besides scale, this also - * provides isolation: one process or machine crashing will not take out multiple host instances. However, it is - * supported to run multiple host instances on one machine, or even within one process, for development and testing. - *

- * The hostName parameter is a name for this event processor host, which must be unique among all event processor host instances - * receiving from this event hub+consumer group combination: the unique name is used to distinguish which event processor host - * instance owns the lease for a given partition. An easy way to generate a unique hostName which also includes - * other information is to call EventProcessorHost.createHostName("mystring"). - *

- * This overload of the constructor uses the built-in lease and checkpoint managers. The - * Azure Storage account specified by the storageConnectionString parameter is used by the built-in - * managers to record leases and checkpoints, in the specified container. - *

- * The Event Hub connection string may be conveniently constructed using the ConnectionStringBuilder class - * from the Java Event Hub client. - * - * @param hostName A name for this event processor host. See method notes. - * @param eventHubPath Specifies the Event Hub to receive events from. - * @param consumerGroupName The name of the consumer group to use when receiving from the Event Hub. - * @param eventHubConnectionString Connection string for the Event Hub to receive from. - * @param storageConnectionString Connection string for the Azure Storage account to use for persisting leases and checkpoints. - * @param storageContainerName Azure Storage container name for use by built-in lease and checkpoint manager. - */ - public EventProcessorHost( - final String hostName, - final String eventHubPath, - final String consumerGroupName, - final String eventHubConnectionString, - final String storageConnectionString, - final String storageContainerName) { - this(hostName, eventHubPath, consumerGroupName, eventHubConnectionString, storageConnectionString, storageContainerName, (ScheduledExecutorService) null); - } - - /** - * Create a new host to process events from an Event Hub. - *

- * This overload adds an argument to specify a user-provided thread pool. The number of partitions in the - * target event hub and the number of host instances should be considered when choosing the size of the thread pool: - * how many partitions is one instance expected to own under normal circumstances? One thread per partition should - * provide good performance, while being able to support more partitions adequately if a host instance fails and its - * partitions must be redistributed. - * - * @param hostName A name for this event processor host. See method notes. - * @param eventHubPath Specifies the Event Hub to receive events from. - * @param consumerGroupName The name of the consumer group to use when receiving from the Event Hub. - * @param eventHubConnectionString Connection string for the Event Hub to receive from. - * @param storageConnectionString Connection string for the Azure Storage account to use for persisting leases and checkpoints. - * @param storageContainerName Azure Storage container name for use by built-in lease and checkpoint manager. - * @param executorService User-supplied thread executor, or null to use EventProcessorHost-internal executor. - */ - public EventProcessorHost( - final String hostName, - final String eventHubPath, - final String consumerGroupName, - final String eventHubConnectionString, - final String storageConnectionString, - final String storageContainerName, - final ScheduledExecutorService executorService) { - this(hostName, eventHubPath, consumerGroupName, eventHubConnectionString, storageConnectionString, storageContainerName, (String) null, executorService); - } - - /** - * Create a new host to process events from an Event Hub. - *

- * This overload adds an argument to specify a prefix used by the built-in lease manager when naming blobs in Azure Storage. - * - * @param hostName A name for this event processor host. See method notes. - * @param eventHubPath Specifies the Event Hub to receive events from. - * @param consumerGroupName The name of the consumer group to use when receiving from the Event Hub. - * @param eventHubConnectionString Connection string for the Event Hub to receive from. - * @param storageConnectionString Connection string for the Azure Storage account to use for persisting leases and checkpoints. - * @param storageContainerName Azure Storage container name for use by built-in lease and checkpoint manager. - * @param storageBlobPrefix Prefix used when naming blobs within the storage container. - */ - public EventProcessorHost( - final String hostName, - final String eventHubPath, - final String consumerGroupName, - final String eventHubConnectionString, - final String storageConnectionString, - final String storageContainerName, - final String storageBlobPrefix) { - this(hostName, eventHubPath, consumerGroupName, eventHubConnectionString, storageConnectionString, storageContainerName, storageBlobPrefix, - (ScheduledExecutorService) null); - } - - /** - * Create a new host to process events from an Event Hub. - *

- * This overload allows the caller to specify both a user-supplied thread pool and - * a prefix used by the built-in lease manager when naming blobs in Azure Storage. - * - * @param hostName A name for this event processor host. See method notes. - * @param eventHubPath Specifies the Event Hub to receive events from. - * @param consumerGroupName The name of the consumer group to use when receiving from the Event Hub. - * @param eventHubConnectionString Connection string for the Event Hub to receive from. - * @param storageConnectionString Connection string for the Azure Storage account to use for persisting leases and checkpoints. - * @param storageContainerName Azure Storage container name for use by built-in lease and checkpoint manager. - * @param storageBlobPrefix Prefix used when naming blobs within the storage container. - * @param executorService User-supplied thread executor, or null to use EventProcessorHost-internal executor. - */ - public EventProcessorHost( - final String hostName, - final String eventHubPath, - final String consumerGroupName, - final String eventHubConnectionString, - final String storageConnectionString, - final String storageContainerName, - final String storageBlobPrefix, - final ScheduledExecutorService executorService) { - // Would like to check storageConnectionString and storageContainerName here but can't, because Java doesn't allow statements before - // calling another constructor. storageBlobPrefix is allowed to be null or empty, doesn't need checking. - this(hostName, eventHubPath, consumerGroupName, eventHubConnectionString, - new AzureStorageCheckpointLeaseManager(storageConnectionString, storageContainerName, storageBlobPrefix), executorService); - this.initializeLeaseManager = true; - this.partitionManagerOptions = new AzureStoragePartitionManagerOptions(); - } - - // Because Java won't let you do ANYTHING before calling another constructor. In particular, you can't - // new up an object and pass it as TWO parameters of the other constructor. private EventProcessorHost( final String hostName, final String eventHubPath, final String consumerGroupName, - final String eventHubConnectionString, - final AzureStorageCheckpointLeaseManager combinedManager, - final ScheduledExecutorService executorService) { - this(hostName, eventHubPath, consumerGroupName, eventHubConnectionString, combinedManager, combinedManager, executorService, null); - } - - /** - * Create a new host to process events from an Event Hub. - *

- * This overload allows the caller to provide their own lease and checkpoint managers to replace the built-in - * ones based on Azure Storage. - * - * @param hostName A name for this event processor host. See method notes. - * @param eventHubPath Specifies the Event Hub to receive events from. - * @param consumerGroupName The name of the consumer group to use when receiving from the Event Hub. - * @param eventHubConnectionString Connection string for the Event Hub to receive from. - * @param checkpointManager Implementation of ICheckpointManager, to be replacement checkpoint manager. - * @param leaseManager Implementation of ILeaseManager, to be replacement lease manager. - */ - public EventProcessorHost( - final String hostName, - final String eventHubPath, - final String consumerGroupName, - final String eventHubConnectionString, - ICheckpointManager checkpointManager, - ILeaseManager leaseManager) { - this(hostName, eventHubPath, consumerGroupName, eventHubConnectionString, checkpointManager, leaseManager, null, null); - } - - /** - * Create a new host to process events from an Event Hub. - *

- * This overload allows the caller to provide their own lease and checkpoint managers to replace the built-in - * ones based on Azure Storage, and to provide an executor service and a retry policy for communications with the event hub. - * - * @param hostName A name for this event processor host. See method notes. - * @param eventHubPath Specifies the Event Hub to receive events from. - * @param consumerGroupName The name of the consumer group to use when receiving from the Event Hub. - * @param eventHubConnectionString Connection string for the Event Hub to receive from. - * @param checkpointManager Implementation of ICheckpointManager, to be replacement checkpoint manager. - * @param leaseManager Implementation of ILeaseManager, to be replacement lease manager. - * @param executorService User-supplied thread executor, or null to use EventProcessorHost-internal executor. - * @param retryPolicy Retry policy governing communications with the event hub. - */ - public EventProcessorHost( - final String hostName, - final String eventHubPath, - final String consumerGroupName, - final String eventHubConnectionString, + final EventHubClientFactory eventHubClientFactory, ICheckpointManager checkpointManager, ILeaseManager leaseManager, - ScheduledExecutorService executorService, - RetryPolicy retryPolicy) { - if ((hostName == null) || hostName.isEmpty()) { - throw new IllegalArgumentException("hostName argument must not be null or empty string"); - } - - // eventHubPath is allowed to be null or empty if it is provided in the connection string. That will be checked later. - if ((consumerGroupName == null) || consumerGroupName.isEmpty()) { - throw new IllegalArgumentException("consumerGroupName argument must not be null or empty"); - } - - if ((eventHubConnectionString == null) || eventHubConnectionString.isEmpty()) { - throw new IllegalArgumentException("eventHubConnectionString argument must not be null or empty"); - } - - // The event hub path must appear in at least one of the eventHubPath argument or the connection string. - // If it appears in both, then it must be the same in both. If it appears in only one, populate the other. - ConnectionStringBuilder providedCSB = new ConnectionStringBuilder(eventHubConnectionString); - String extractedEntityPath = providedCSB.getEventHubName(); - String effectiveEventHubPath = eventHubPath; - String effectiveEventHubConnectionString = eventHubConnectionString; - if ((effectiveEventHubPath != null) && !effectiveEventHubPath.isEmpty()) { - if (extractedEntityPath != null) { - if (effectiveEventHubPath.compareTo(extractedEntityPath) != 0) { - throw new IllegalArgumentException("Provided EventHub path in eventHubPath parameter conflicts with the path in provided EventHub connection string"); - } - // else they are the same and that's fine - } else { - // There is no entity path in the connection string, so put it there. - ConnectionStringBuilder rebuildCSB = new ConnectionStringBuilder() - .setEndpoint(providedCSB.getEndpoint()) - .setEventHubName(effectiveEventHubPath) - .setSasKeyName(providedCSB.getSasKeyName()) - .setSasKey(providedCSB.getSasKey()); - rebuildCSB.setOperationTimeout(providedCSB.getOperationTimeout()); - effectiveEventHubConnectionString = rebuildCSB.toString(); - } + final boolean initializeLeaseManager, + ScheduledExecutorService executorService) { + this.initializeLeaseManager = initializeLeaseManager; + if (this.initializeLeaseManager) { + this.partitionManagerOptions = new AzureStoragePartitionManagerOptions(); } else { - if ((extractedEntityPath != null) && !extractedEntityPath.isEmpty()) { - effectiveEventHubPath = extractedEntityPath; - } else { - throw new IllegalArgumentException("Provide EventHub entity path in either eventHubPath argument or in eventHubConnectionString"); - } + // Using user-supplied implementation. + // Establish generic defaults in case the user doesn't provide an options object. + this.partitionManagerOptions = new PartitionManagerOptions(); } - if (checkpointManager == null) { - throw new IllegalArgumentException("Must provide an object which implements ICheckpointManager"); - } - if (leaseManager == null) { - throw new IllegalArgumentException("Must provide an object which implements ILeaseManager"); - } - - // executorService argument is allowed to be null, that is the indication to use an internal threadpool. - - // Normally will not be null because we're using the AzureStorage implementation. - // If it is null, we're using user-supplied implementation. Establish generic defaults - // in case the user doesn't provide an options object. - this.partitionManagerOptions = new PartitionManagerOptions(); - if (executorService != null) { // User has supplied an ExecutorService, so use that. this.weOwnExecutor = false; @@ -285,12 +76,13 @@ public EventProcessorHost( this.weOwnExecutor = true; this.executorService = Executors.newScheduledThreadPool( this.executorServicePoolSize, - new EventProcessorHostThreadPoolFactory(hostName, effectiveEventHubPath, consumerGroupName)); + new EventProcessorHostThreadPoolFactory(hostName, eventHubPath, consumerGroupName)); } + eventHubClientFactory.setExecutor(this.executorService); this.hostContext = new HostContext(this.executorService, this, hostName, - effectiveEventHubPath, consumerGroupName, effectiveEventHubConnectionString, retryPolicy, + eventHubPath, consumerGroupName, eventHubClientFactory, leaseManager, checkpointManager); this.partitionManager = new PartitionManager(hostContext); @@ -571,4 +363,362 @@ public void uncaughtException(Thread t, Throwable e) { } } } + + /** + * Builder class to create EventProcessorHost instances. + *

+ * To use, start with: EventProcessorHost.EventProcessorHostBuilder.newBuilder(...) + * Then either use the built-in Azure Storage-based lease and checkpoint managers, or user implementations. + * Then either supply an Event Hub connection string or use Azure Active Directory (AAD) authentication. + * If using AAD auth, either provide a callback or an ITokenProvider + * Finally, set various optional values as desired, then call build() to get an EventProcessorHost instance. + */ + public static class EventProcessorHostBuilder { + /** + * The process of building starts here, with arguments that are always required. + *

+ * The hostName parameter is a name for this EventProcessorHost instance, which must be unique among + * all instances consuming from the same Event Hub and consumer group. The name must be unique because + * it is used to distinguish which instance owns the lease for a given partition of the event hub. An + * easy way to generate a unique host name is to call EventProcessorHost.createHostName("mystring"). + * + * @param hostName a name for this host instance. See method notes. + * @param consumerGroupName the consumer group on the Event Hub + * @return interface for setting the lease and checkpoint managers + */ + public static ManagerStep newBuilder(final String hostName, final String consumerGroupName) { + return new Steps(hostName, consumerGroupName); + } + + private EventProcessorHostBuilder() { + } + + public static interface ManagerStep { + /** + * Use the built-in Azure Storage-based lease and checkpoint managers. + * + * @param storageConnectionString connection string for an Azure Storage account + * @param storageContainerName name for the blob container within the Storage account + * @param storageBlobPrefix prefix for the names of the blobs within the blob container, can be empty or null + * @return interface for setting the Event Hub connection info and auth + */ + AuthStep useAzureStorageCheckpointLeaseManager(String storageConnectionString, String storageContainerName, String storageBlobPrefix); + + /** + * Use the built-in Azure Storage-based lease and checkpoint managers. + * + * @param storageCredentials credentials for an Azure Storage account, such as an AAD token + * @param storageContainerName name for the blob container within the Storage account + * @param storageBlobPrefix prefix for the names of the blobs within the blob container, can be empty or null + * @return interface for setting the Event Hub connection info and auth + */ + AuthStep useAzureStorageCheckpointLeaseManager(StorageCredentials storageCredentials, String storageContainerName, String storageBlobPrefix); + + /** + * Use user-implemented lease and checkpoint managers. + * + * @param checkpointManager user-supplied implementation of {@link ICheckpointManager} + * @param leaseManager user-supplied implementation of {@link ILeaseManager} + * @return interface for setting the Event Hub connection info and auth + */ + AuthStep useUserCheckpointAndLeaseManagers(ICheckpointManager checkpointManager, ILeaseManager leaseManager); + } + + public static interface AuthStep { + /** + * Azure Portal can provide a connection string with auth information that applies only to one + * individual Event Hub. In that case, the connection string contains the name of the Event Hub. + * + * @param eventHubConnectionString Event Hub connection string (which contains the name of the Event Hub) + * @return interface for setting optional values + */ + OptionalStep useEventHubConnectionString(String eventHubConnectionString); + + /** + * Azure Portal can provide a connection string with auth information that applies to the entire + * namespace instead of an individual Event Hub. Use this overload with such a connection string, + * which requires you to specify the name of the Event Hub separately. + * + * @param eventHubConnectionString Event Hub connection string (which does not contain the name of the Event Hub) + * @param eventHubPath name of the Event Hub + * @return interface for setting optional values + */ + OptionalStep useEventHubConnectionString(String eventHubConnectionString, String eventHubPath); + + /** + * When using AAD auth, call this method to specify the Event Hub, then add AAD-based auth information in the next step. + * + * @param endpoint URI of the Event Hub namespace + * @param eventHubPath name of the Event Hub + * @return interface for setting AAD auth info + */ + AADAuthStep useAADAuthentication(URI endpoint, String eventHubPath); + } + + public static interface AADAuthStep { + /** + * Provide a callback which will be called when a token is needed. See {@link AzureActiveDirectoryTokenProvider} + * + * @param authCallback the callback + * @param authority AAD authority string which will be passed to the callback. Used for national cloud support. + * @return interface for setting optional values + */ + OptionalStep useAuthenticationCallback(AuthenticationCallback authCallback, String authority); + + /** + * Provide a user-implemented token provider which will be called when a token is needed. + * + * @param tokenProvider user implementation of ITokenProvider + * @return interface for setting optional values + */ + OptionalStep useTokenProvider(ITokenProvider tokenProvider); + } + + public static interface OptionalStep { + /** + * Event Processor Host runs tasks on the supplied threadpool, or creates an internal one. + * @param executor threadpool, or null to use an internal one + * @return interface for setting optional values + */ + OptionalStep setExecutor(ScheduledExecutorService executor); + + /** + * {@link RetryPolicy} for Event Hubs operations. Event Processor Host uses RetryPolicy.getDefault() + * if none is supplied. + * + * @param retryPolicy desired retry policy + * @return interface for setting optional values + */ + OptionalStep setRetryPolicy(RetryPolicy retryPolicy); + + /** + * {@link TransportType} for connections to the Event Hubs service. Defaults to TransportType.AMQP. + * The transport type can also be set in the Event Hub connection string. The value set here will + * override the value in the connection string, if any. + * + * @param transportType desired transport type + * @return interface for setting optional values + */ + OptionalStep setTransportType(TransportType transportType); + + /** + * The timeout for Event Hubs operations. Defaults to MessagingFactory.DefaultOperationTimeout. + * The timeout can also be set in the Event Hub connection string. The value set here will override + * the value in the connection string, if any. + * + * @param operationTimeout desired timeout + * @return interface for setting optional values + */ + OptionalStep setOperationTimeout(Duration operationTimeout); + + /** + * After setting all desired optional values, call this method to build an EventProcessorHost instance. + * + * @return new EventProcessorHost instance + */ + EventProcessorHost build(); + } + + private static class Steps implements ManagerStep, AuthStep, AADAuthStep, OptionalStep { + private final String hostName; + private final String consumerGroupName; + + // OptionalStep + private ScheduledExecutorService executor = null; + private RetryPolicy retryPolicy = null; + private TransportType transportType = null; + private Duration operationTimeout = null; + + // Auth steps + private String eventHubConnectionString = null; // group 1 + private String eventHubPath = null; // optional for group 1, required for groups 2-3 + private URI endpoint = null; // groups 2-3 + private AuthenticationCallback authCallback = null; // group 2 + private String authority = null; // group 2 + private ITokenProvider tokenProvider = null; // group 3 + + // ManagerStep + private ICheckpointManager checkpointManager; + private ILeaseManager leaseManager; + private boolean initializeManagers = false; + + + public Steps(final String hostName, final String consumerGroupName) { + if (StringUtil.isNullOrWhiteSpace(hostName) || StringUtil.isNullOrWhiteSpace(consumerGroupName)) { + throw new IllegalArgumentException("hostName and consumerGroupName cannot be null or empty"); + } + + this.hostName = hostName; + this.consumerGroupName = consumerGroupName; + } + + @Override + public OptionalStep setExecutor(final ScheduledExecutorService executor) { + // executor is allowed to be null, causes EPH to create and use an internal one + this.executor = executor; + return this; + } + + @Override + public OptionalStep setRetryPolicy(final RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + return this; + } + + @Override + public OptionalStep setTransportType(final TransportType transportType) { + Objects.requireNonNull(transportType); + + this.transportType = transportType; + return this; + } + + @Override + public OptionalStep setOperationTimeout(final Duration operationTimeout) { + Objects.requireNonNull(operationTimeout); + + this.operationTimeout = operationTimeout; + return this; + } + + @Override + public OptionalStep useAuthenticationCallback(final AuthenticationCallback authCallback, final String authority) { + Objects.requireNonNull(authCallback); + if (StringUtil.isNullOrWhiteSpace(authority)) { + throw new IllegalArgumentException("authority cannot be null or empty"); + } + + this.authCallback = authCallback; + this.authority = authority; + return this; + } + + @Override + public OptionalStep useTokenProvider(final ITokenProvider tokenProvider) { + Objects.requireNonNull(tokenProvider); + + this.tokenProvider = tokenProvider; + return this; + } + + @Override + public OptionalStep useEventHubConnectionString(final String eventHubConnectionString) { + return useEventHubConnectionString(eventHubConnectionString, null); + } + + @Override + public OptionalStep useEventHubConnectionString(final String eventHubConnectionString, final String eventHubPath) { + if (StringUtil.isNullOrWhiteSpace(eventHubConnectionString)) { + throw new IllegalArgumentException("eventHubConnectionString cannot be null or empty"); + } + if ((eventHubPath != null) && StringUtil.isNullOrWhiteSpace(eventHubPath)) { + throw new IllegalArgumentException("eventHubPath cannot be empty. Use null if the connection string already contains the path."); + } + + this.eventHubConnectionString = eventHubConnectionString; + this.eventHubPath = eventHubPath; + return this; + } + + @Override + public AADAuthStep useAADAuthentication(final URI endpoint, final String eventHubPath) { + Objects.requireNonNull(endpoint); + if (StringUtil.isNullOrWhiteSpace(eventHubPath)) { + throw new IllegalArgumentException("eventHubPath cannot be null or empty"); + } + + this.endpoint = endpoint; + this.eventHubPath = eventHubPath; + return this; + } + + @Override + public AuthStep useAzureStorageCheckpointLeaseManager(final String storageConnectionString, + final String storageContainerName, final String storageBlobPrefix) { + AzureStorageCheckpointLeaseManager mgr = new AzureStorageCheckpointLeaseManager(storageConnectionString, storageContainerName, storageBlobPrefix); + this.initializeManagers = true; + return useUserCheckpointAndLeaseManagers(mgr, mgr); + } + + @Override + public AuthStep useAzureStorageCheckpointLeaseManager(final StorageCredentials storageCredentials, + final String storageContainerName, final String storageBlobPrefix) { + AzureStorageCheckpointLeaseManager mgr = new AzureStorageCheckpointLeaseManager(storageCredentials, storageContainerName, storageBlobPrefix); + this.initializeManagers = true; + return useUserCheckpointAndLeaseManagers(mgr, mgr); + } + + @Override + public AuthStep useUserCheckpointAndLeaseManagers(final ICheckpointManager checkpointManager, + final ILeaseManager leaseManager) { + Objects.requireNonNull(checkpointManager); + Objects.requireNonNull(leaseManager); + + this.checkpointManager = checkpointManager; + this.leaseManager = leaseManager; + return this; + } + + @Override + public EventProcessorHost build() { + // One of these conditions MUST be true. Can't get to the OptionalStep interface where build() is available + // without setting one of the auth options. + EventHubClientFactory ehcFactory = null; + if (this.eventHubConnectionString != null) { + normalizeConnectionStringAndEventHubPath(); + ehcFactory = new EventHubClientFactory.EHCFWithConnectionString(this.eventHubConnectionString, this.retryPolicy); + } else if (this.authCallback != null) { + ehcFactory = new EventHubClientFactory.EHCFWithAuthCallback(this.endpoint, this.eventHubPath, + this.authCallback, this.authority, packOptions()); + } else if (this.tokenProvider != null) { + ehcFactory = new EventHubClientFactory.EHCFWithTokenProvider(this.endpoint, this.eventHubPath, this.tokenProvider, packOptions()); + } + return new EventProcessorHost(this.hostName, + this.eventHubPath, + this.consumerGroupName, + ehcFactory, + this.checkpointManager, + this.leaseManager, + this.initializeManagers, + this.executor); + } + + private EventHubClientOptions packOptions() { + return (new EventHubClientOptions()).setOperationTimeout(this.operationTimeout).setRetryPolicy(this.retryPolicy).setTransportType(this.transportType); + } + + private void normalizeConnectionStringAndEventHubPath() { + // The event hub path must appear in at least one of the eventHubPath argument or the connection string. + // If it appears in both, then it must be the same in both. If it appears in only one, populate the other. + ConnectionStringBuilder csb = new ConnectionStringBuilder(this.eventHubConnectionString); + String extractedEntityPath = csb.getEventHubName(); + if ((this.eventHubPath != null) && !this.eventHubPath.isEmpty()) { + if (extractedEntityPath != null) { + if (this.eventHubPath.compareTo(extractedEntityPath) != 0) { + throw new IllegalArgumentException("Provided EventHub path in eventHubPath parameter conflicts with the path in provided EventHub connection string"); + } + // else they are the same and that's fine + } else { + // There is no entity path in the connection string, so put it there. + csb.setEventHubName(this.eventHubPath); + } + } else { + if ((extractedEntityPath != null) && !extractedEntityPath.isEmpty()) { + this.eventHubPath = extractedEntityPath; + } else { + throw new IllegalArgumentException("Provide EventHub entity path in either eventHubPath argument or in eventHubConnectionString"); + } + } + + if (this.transportType != null) { + csb.setTransportType(this.transportType); + } + if (this.operationTimeout != null) { + csb.setOperationTimeout(this.operationTimeout); + } + + this.eventHubConnectionString = csb.toString(); + } + } + } } diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/HostContext.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/HostContext.java index 1998a782e97ae..26b7cc0285ed3 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/HostContext.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/HostContext.java @@ -3,8 +3,6 @@ package com.microsoft.azure.eventprocessorhost; -import com.microsoft.azure.eventhubs.RetryPolicy; - import java.util.concurrent.ScheduledExecutorService; final class HostContext { @@ -18,8 +16,7 @@ final class HostContext { private final String eventHubPath; private final String consumerGroupName; - private final String eventHubConnectionString; - private final RetryPolicy retryPolicy; + private final EventHubClientFactory eventHubClientFactory; private final ILeaseManager leaseManager; private final ICheckpointManager checkpointManager; @@ -33,7 +30,7 @@ final class HostContext { HostContext(ScheduledExecutorService executor, EventProcessorHost host, String hostName, - String eventHubPath, String consumerGroupName, String eventHubConnectionString, RetryPolicy retryPolicy, + String eventHubPath, String consumerGroupName, EventHubClientFactory eventHubClientFactory, ILeaseManager leaseManager, ICheckpointManager checkpointManager) { this.executor = executor; @@ -42,8 +39,7 @@ final class HostContext { this.eventHubPath = eventHubPath; this.consumerGroupName = consumerGroupName; - this.eventHubConnectionString = eventHubConnectionString; - this.retryPolicy = retryPolicy; + this.eventHubClientFactory = eventHubClientFactory; this.leaseManager = leaseManager; this.checkpointManager = checkpointManager; @@ -65,12 +61,8 @@ String getConsumerGroupName() { return this.consumerGroupName; } - String getEventHubConnectionString() { - return this.eventHubConnectionString; - } - - RetryPolicy getRetryPolicy() { - return this.retryPolicy; + EventHubClientFactory getEventHubClientFactory() { + return this.eventHubClientFactory; } ILeaseManager getLeaseManager() { diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/PartitionManager.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/PartitionManager.java index 8f419a35e6e28..96d6ede652814 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/PartitionManager.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/PartitionManager.java @@ -47,7 +47,7 @@ CompletableFuture cachePartitionIds() { final CompletableFuture cleanupFuture = new CompletableFuture(); // Stage 0A: get EventHubClient for the event hub - retval = EventHubClient.create(this.hostContext.getEventHubConnectionString(), this.hostContext.getRetryPolicy(), this.hostContext.getExecutor()) + retval = this.hostContext.getEventHubClientFactory().createEventHubClient() // Stage 0B: set up a way to close the EventHubClient when we're done .thenApplyAsync((ehClient) -> { final EventHubClient saveForCleanupClient = ehClient; diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/PartitionPump.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/PartitionPump.java index d2e16d1e3c097..ec049c63d511d 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/PartitionPump.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/main/java/com/microsoft/azure/eventprocessorhost/PartitionPump.java @@ -175,8 +175,7 @@ private CompletableFuture openClients() { CompletableFuture startOpeningFuture = null; try { - startOpeningFuture = EventHubClient.create(this.hostContext.getEventHubConnectionString(), - this.hostContext.getRetryPolicy(), this.hostContext.getExecutor()); + startOpeningFuture = this.hostContext.getEventHubClientFactory().createEventHubClient(); } catch (EventHubException | IOException e2) { // Marking startOpeningFuture as completed exceptionally will cause all the // following stages to fall through except stage 1 which will report the error. diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/CheckpointManagerTest.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/CheckpointManagerTest.java index 838a17c5344a9..669aebb50c045 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/CheckpointManagerTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/CheckpointManagerTest.java @@ -232,15 +232,17 @@ private void setupOneManager(boolean useAzureStorage, int index, String suffix, } else { TestBase.logInfo("Container name: " + containerName); String azureStorageConnectionString = TestUtilities.getStorageConnectionString(); - AzureStorageCheckpointLeaseManager azMgr = new AzureStorageCheckpointLeaseManager(azureStorageConnectionString, containerName); + AzureStorageCheckpointLeaseManager azMgr = new AzureStorageCheckpointLeaseManager(azureStorageConnectionString, containerName, null); leaseMgr = azMgr; checkpointMgr = azMgr; } // Host name needs to be unique per host so use index. Event hub should be the same for all hosts in a test, so use the supplied suffix. - EventProcessorHost host = new EventProcessorHost("dummyHost" + String.valueOf(index), RealEventHubUtilities.SYNTACTICALLY_CORRECT_DUMMY_EVENT_HUB_PATH + suffix, - EventHubClient.DEFAULT_CONSUMER_GROUP_NAME, RealEventHubUtilities.SYNTACTICALLY_CORRECT_DUMMY_CONNECTION_STRING + suffix, checkpointMgr, leaseMgr); - + EventProcessorHost host = EventProcessorHost.EventProcessorHostBuilder.newBuilder("dummyHost" + String.valueOf(index), EventHubClient.DEFAULT_CONSUMER_GROUP_NAME) + .useUserCheckpointAndLeaseManagers(checkpointMgr, leaseMgr) + .useEventHubConnectionString(RealEventHubUtilities.SYNTACTICALLY_CORRECT_DUMMY_CONNECTION_STRING + suffix, + RealEventHubUtilities.SYNTACTICALLY_CORRECT_DUMMY_EVENT_HUB_PATH + suffix) + .build(); try { if (!useAzureStorage) { diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/EPHConstructorTests.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/EPHConstructorTests.java index 3ae2c7b3f6e81..51070c03f3ef4 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/EPHConstructorTests.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/EPHConstructorTests.java @@ -38,7 +38,8 @@ public void missingEventHubPathTest() throws Exception { settings.inEventHubDoesNotExist = true; settings.inoutEPHConstructorArgs.dummyStorageConnection(); - settings.inoutEPHConstructorArgs.setEHPath("", PerTestSettings.EPHConstructorArgs.EH_PATH_OVERRIDE_AND_REPLACE); + settings.inoutEPHConstructorArgs.removePathFromEHConnection(); + settings.inoutEPHConstructorArgs.setEHPath(null, PerTestSettings.EPHConstructorArgs.EH_PATH_OVERRIDE); try { settings = testSetup(settings); @@ -191,7 +192,7 @@ public void ehPathOnlyInConnStringTest() throws Exception { settings.inEventHubDoesNotExist = true; settings.inoutEPHConstructorArgs.dummyStorageConnection(); - settings.inoutEPHConstructorArgs.setEHPath("", PerTestSettings.EPHConstructorArgs.EH_PATH_OVERRIDE); + settings.inoutEPHConstructorArgs.setEHPath(null, PerTestSettings.EPHConstructorArgs.EH_PATH_OVERRIDE); try { settings = testSetup(settings); @@ -212,7 +213,7 @@ public void nullCheckpointManagerTest() throws Exception { try { settings = testSetup(settings); fail("No exception occurred"); - } catch (IllegalArgumentException e) { + } catch (NullPointerException e) { TestBase.logInfo("Got expected exception"); } finally { testFinish(settings, NO_CHECKS); @@ -231,7 +232,7 @@ public void nullLeaseManagerTest() throws Exception { try { settings = testSetup(settings); fail("No exception occurred"); - } catch (IllegalArgumentException e) { + } catch (NullPointerException e) { TestBase.logInfo("Got expected exception"); } finally { testFinish(settings, NO_CHECKS); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/LeaseManagerTest.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/LeaseManagerTest.java index d424606afe7bf..c819b0fee2cb6 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/LeaseManagerTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/LeaseManagerTest.java @@ -281,14 +281,17 @@ private void setupOneManager(boolean useAzureStorage, int index, String suffix, } else { TestBase.logInfo("Container name: " + containerName); String azureStorageConnectionString = TestUtilities.getStorageConnectionString(); - AzureStorageCheckpointLeaseManager azMgr = new AzureStorageCheckpointLeaseManager(azureStorageConnectionString, containerName); + AzureStorageCheckpointLeaseManager azMgr = new AzureStorageCheckpointLeaseManager(azureStorageConnectionString, containerName, null); leaseMgr = azMgr; checkpointMgr = azMgr; } // Host name needs to be unique per host so use index. Event hub should be the same for all hosts in a test, so use the supplied suffix. - EventProcessorHost host = new EventProcessorHost("dummyHost" + String.valueOf(index), RealEventHubUtilities.SYNTACTICALLY_CORRECT_DUMMY_EVENT_HUB_PATH + suffix, - EventHubClient.DEFAULT_CONSUMER_GROUP_NAME, RealEventHubUtilities.SYNTACTICALLY_CORRECT_DUMMY_CONNECTION_STRING + suffix, checkpointMgr, leaseMgr); + EventProcessorHost host = EventProcessorHost.EventProcessorHostBuilder.newBuilder("dummyHost" + String.valueOf(index), EventHubClient.DEFAULT_CONSUMER_GROUP_NAME) + .useUserCheckpointAndLeaseManagers(checkpointMgr, leaseMgr) + .useEventHubConnectionString(RealEventHubUtilities.SYNTACTICALLY_CORRECT_DUMMY_CONNECTION_STRING + suffix, + RealEventHubUtilities.SYNTACTICALLY_CORRECT_DUMMY_EVENT_HUB_PATH + suffix) + .build(); try { if (!useAzureStorage) { diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/PartitionManagerTest.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/PartitionManagerTest.java index fcce41773e7b9..42fdc30bffa1c 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/PartitionManagerTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/PartitionManagerTest.java @@ -327,8 +327,11 @@ private void setup(int hostCount, int partitionCount, long latency, int threads) if (threads > 0) { threadpool = Executors.newScheduledThreadPool(threads); } - this.hosts[i] = new EventProcessorHost("dummyHost" + String.valueOf(i), "NOTREAL", EventHubClient.DEFAULT_CONSUMER_GROUP_NAME, - RealEventHubUtilities.SYNTACTICALLY_CORRECT_DUMMY_CONNECTION_STRING, cm, lm, threadpool, null); + this.hosts[i] = EventProcessorHost.EventProcessorHostBuilder.newBuilder("dummyHost" + String.valueOf(i), EventHubClient.DEFAULT_CONSUMER_GROUP_NAME) + .useUserCheckpointAndLeaseManagers(cm, lm) + .useEventHubConnectionString(RealEventHubUtilities.SYNTACTICALLY_CORRECT_DUMMY_CONNECTION_STRING, "NOTREAL") + .setExecutor(threadpool) + .build(); lm.initialize(this.hosts[i].getHostContext()); lm.setLatency(latency); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/PerTestSettings.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/PerTestSettings.java index 1ad68b3950f5a..7172e8a8e4a52 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/PerTestSettings.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/PerTestSettings.java @@ -6,6 +6,8 @@ import java.util.ArrayList; import java.util.concurrent.ScheduledExecutorService; +import com.microsoft.azure.eventhubs.AzureActiveDirectoryTokenProvider; + public class PerTestSettings { // In-out properties: may be set before test setup and then changed by setup. final EPHConstructorArgs inoutEPHConstructorArgs; @@ -60,6 +62,7 @@ class EPHConstructorArgs { static final int LEASE_MANAGER_OVERRIDE = 0x0800; static final int EXPLICIT_MANAGER = CHECKPOINT_MANAGER_OVERRIDE | LEASE_MANAGER_OVERRIDE; static final int TELLTALE_ON_TIMEOUT = 0x1000; + static final int AUTH_CALLBACK = 0x2000; private int flags; @@ -67,6 +70,8 @@ class EPHConstructorArgs { private String ehPath; private String consumerGroupName; private String ehConnection; + private AzureActiveDirectoryTokenProvider.AuthenticationCallback authCallback; + private String authAuthority; private String storageConnection; private String storageContainerName; private String storageBlobPrefix; @@ -81,6 +86,8 @@ class EPHConstructorArgs { this.ehPath = null; this.consumerGroupName = null; this.ehConnection = null; + this.authCallback = null; + this.authAuthority = null; this.storageConnection = null; this.storageContainerName = null; this.storageBlobPrefix = null; @@ -136,6 +143,21 @@ void setEHConnection(String ehConnection) { this.ehConnection = ehConnection; this.flags |= EH_CONNECTION_OVERRIDE; } + + AzureActiveDirectoryTokenProvider.AuthenticationCallback getAuthCallback() { + return this.authCallback; + } + + String getAuthAuthority() { + return this.authAuthority; + } + + void setAuthCallback(AzureActiveDirectoryTokenProvider.AuthenticationCallback authCallback, String authAuthority) + { + this.authCallback = authCallback; + this.authAuthority = authAuthority; + this.flags |= AUTH_CALLBACK; + } String getStorageConnection() { return this.storageConnection; diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/RealEventHubUtilities.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/RealEventHubUtilities.java index b3174c613ea79..492b9080fdb85 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/RealEventHubUtilities.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/RealEventHubUtilities.java @@ -37,7 +37,7 @@ ArrayList setup(boolean skipIfFakeEH, int fakePartitions) throws EventHu ArrayList partitionIds = setupWithoutSenders(skipIfFakeEH, fakePartitions); // EventHubClient is source of all senders - this.client = EventHubClient.createSync(this.hubConnectionString.toString(), TestUtilities.EXECUTOR_SERVICE); + this.client = EventHubClient.createFromConnectionStringSync(this.hubConnectionString.toString(), TestUtilities.EXECUTOR_SERVICE); return partitionIds; } @@ -132,7 +132,7 @@ ArrayList getPartitionIdsForTest() throws EventHubException, IOException this.cachedPartitionIds = new ArrayList(); ehCacheCheck(true); - EventHubClient idClient = EventHubClient.createSync(this.hubConnectionString.toString(), TestUtilities.EXECUTOR_SERVICE); + EventHubClient idClient = EventHubClient.createFromConnectionStringSync(this.hubConnectionString.toString(), TestUtilities.EXECUTOR_SERVICE); try { EventHubRuntimeInformation info = idClient.getRuntimeInformation().get(); String[] ids = info.getPartitionIds(); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/Repros.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/Repros.java index 8d0c6dcf9b401..9c072cd18d4d7 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/Repros.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/Repros.java @@ -42,17 +42,19 @@ public void conflictingHosts() throws Exception { PrefabGeneralErrorHandler general1 = new PrefabGeneralErrorHandler(); PrefabProcessorFactory factory1 = new PrefabProcessorFactory(telltale, doCheckpointing, doMarker); - EventProcessorHost host1 = new EventProcessorHost(conflictingName, utils.getConnectionString(true).getEventHubName(), - utils.getConsumerGroup(), utils.getConnectionString(true).toString(), - TestUtilities.getStorageConnectionString(), storageName); + EventProcessorHost host1 = EventProcessorHost.EventProcessorHostBuilder.newBuilder(conflictingName, utils.getConsumerGroup()) + .useAzureStorageCheckpointLeaseManager(TestUtilities.getStorageConnectionString(), storageName, null) + .useEventHubConnectionString(utils.getConnectionString(true).toString(), utils.getConnectionString(true).getEventHubName()) + .build(); EventProcessorOptions options1 = EventProcessorOptions.getDefaultOptions(); options1.setExceptionNotification(general1); PrefabGeneralErrorHandler general2 = new PrefabGeneralErrorHandler(); PrefabProcessorFactory factory2 = new PrefabProcessorFactory(telltale, doCheckpointing, doMarker); - EventProcessorHost host2 = new EventProcessorHost(conflictingName, utils.getConnectionString(true).getEventHubName(), - utils.getConsumerGroup(), utils.getConnectionString(true).toString(), - TestUtilities.getStorageConnectionString(), storageName); + EventProcessorHost host2 = EventProcessorHost.EventProcessorHostBuilder.newBuilder(conflictingName, utils.getConsumerGroup()) + .useAzureStorageCheckpointLeaseManager(TestUtilities.getStorageConnectionString(), storageName, null) + .useEventHubConnectionString(utils.getConnectionString(true).toString(), utils.getConnectionString(true).getEventHubName()) + .build(); EventProcessorOptions options2 = EventProcessorOptions.getDefaultOptions(); options2.setExceptionNotification(general2); @@ -79,9 +81,11 @@ public void infiniteReceive() throws Exception { PrefabProcessorFactory factory = new PrefabProcessorFactory("never match", PrefabEventProcessor.CheckpointChoices.CKP_NONE, true, false); InMemoryCheckpointManager checkpointer = new InMemoryCheckpointManager(); InMemoryLeaseManager leaser = new InMemoryLeaseManager(); - EventProcessorHost host = new EventProcessorHost("infiniteReceive-1", utils.getConnectionString(true).getEventHubName(), - utils.getConsumerGroup(), utils.getConnectionString(true).toString(), - checkpointer, leaser, Executors.newScheduledThreadPool(16), null); + EventProcessorHost host = EventProcessorHost.EventProcessorHostBuilder.newBuilder("infiniteReceive-1", utils.getConsumerGroup()) + .useUserCheckpointAndLeaseManagers(checkpointer, leaser) + .useEventHubConnectionString(utils.getConnectionString(true).toString(), utils.getConnectionString(true).getEventHubName()) + .setExecutor(Executors.newScheduledThreadPool(16)) + .build(); checkpointer.initialize(host.getHostContext()); leaser.initialize(host.getHostContext()); @@ -113,17 +117,19 @@ public void infiniteReceive2Hosts() throws Exception { PrefabGeneralErrorHandler general1 = new PrefabGeneralErrorHandler(); PrefabProcessorFactory factory1 = new PrefabProcessorFactory("never match", PrefabEventProcessor.CheckpointChoices.CKP_NONE, true, false); - EventProcessorHost host1 = new EventProcessorHost("infiniteReceive2Hosts-1", utils.getConnectionString(true).getEventHubName(), - utils.getConsumerGroup(), utils.getConnectionString(true).toString(), - TestUtilities.getStorageConnectionString(), storageName); + EventProcessorHost host1 = EventProcessorHost.EventProcessorHostBuilder.newBuilder("infiniteReceive2Hosts-1", utils.getConsumerGroup()) + .useAzureStorageCheckpointLeaseManager(TestUtilities.getStorageConnectionString(), storageName, null) + .useEventHubConnectionString(utils.getConnectionString(true).toString(), utils.getConnectionString(true).getEventHubName()) + .build(); EventProcessorOptions options1 = EventProcessorOptions.getDefaultOptions(); options1.setExceptionNotification(general1); PrefabGeneralErrorHandler general2 = new PrefabGeneralErrorHandler(); PrefabProcessorFactory factory2 = new PrefabProcessorFactory("never match", PrefabEventProcessor.CheckpointChoices.CKP_NONE, true, false); - EventProcessorHost host2 = new EventProcessorHost("infiniteReceive2Hosts-2", utils.getConnectionString(true).getEventHubName(), - utils.getConsumerGroup(), utils.getConnectionString(true).toString(), - TestUtilities.getStorageConnectionString(), storageName); + EventProcessorHost host2 = EventProcessorHost.EventProcessorHostBuilder.newBuilder("infiniteReceive2Hosts-1", utils.getConsumerGroup()) + .useAzureStorageCheckpointLeaseManager(TestUtilities.getStorageConnectionString(), storageName, null) + .useEventHubConnectionString(utils.getConnectionString(true).toString(), utils.getConnectionString(true).getEventHubName()) + .build(); EventProcessorOptions options2 = EventProcessorOptions.getDefaultOptions(); options2.setExceptionNotification(general2); @@ -173,9 +179,10 @@ public void infiniteReceive2Hosts() throws Exception { host2 = null; } else { factory2 = new PrefabProcessorFactory("never match", PrefabEventProcessor.CheckpointChoices.CKP_NONE, true, false); - host2 = new EventProcessorHost("infiniteReceive2Hosts-2", utils.getConnectionString(true).getEventHubName(), - utils.getConsumerGroup(), utils.getConnectionString(true).toString(), - TestUtilities.getStorageConnectionString(), storageName); + host2 = EventProcessorHost.EventProcessorHostBuilder.newBuilder("infiniteReceive2Hosts-1", utils.getConsumerGroup()) + .useAzureStorageCheckpointLeaseManager(TestUtilities.getStorageConnectionString(), storageName, null) + .useEventHubConnectionString(utils.getConnectionString(true).toString(), utils.getConnectionString(true).getEventHubName()) + .build(); options2 = EventProcessorOptions.getDefaultOptions(); options2.setExceptionNotification(general2); @@ -229,7 +236,7 @@ public void rawEpochStealing() throws Exception { System.out.println("\nParked: " + parkedCount + " SELECTING: " + selectingList); System.out.println("Client " + clientSerialNumber + " starting"); - EventHubClient client = EventHubClient.createSync(utils.getConnectionString(true).toString(), TestUtilities.EXECUTOR_SERVICE); + EventHubClient client = EventHubClient.createFromConnectionStringSync(utils.getConnectionString(true).toString(), TestUtilities.EXECUTOR_SERVICE); PartitionReceiver receiver = client.createReceiver(utils.getConsumerGroup(), "0", EventPosition.fromStartOfStream()).get(); //client.createEpochReceiver(utils.getConsumerGroup(), "0", PartitionReceiver.START_OF_STREAM, 1).get(); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/SmokeTest.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/SmokeTest.java index 1fc9b86b2eed5..704b920b51ca9 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/SmokeTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/SmokeTest.java @@ -3,11 +3,20 @@ package com.microsoft.azure.eventprocessorhost; +import com.microsoft.aad.msal4j.ClientCredentialParameters; +import com.microsoft.aad.msal4j.ClientSecret; +import com.microsoft.aad.msal4j.ConfidentialClientApplication; +import com.microsoft.aad.msal4j.IAuthenticationResult; +import com.microsoft.azure.eventhubs.AzureActiveDirectoryTokenProvider; import com.microsoft.azure.eventhubs.EventPosition; + import org.junit.Assert; import org.junit.Test; import java.time.Instant; +import java.util.Collections; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.Executors; @@ -22,6 +31,51 @@ public void sendRecv1MsgTest() throws Exception { testFinish(settings, SmokeTest.ANY_NONZERO_COUNT); } + + /** + * This JUnit test case is all commented out by default because it can only be run with special setup. + * It extracts the namespace (endpoint) and event hub name from the connection string in the environment variable + * which all test cases use, but it assumes that the namespace (or event hub) has been set up with special permissions. + * Within the AAD directory indicated by "authority", there is a registered application with id "clientId" and a secret + * "clientSecret". This application has been granted the "Azure Event Hubs Data Owner" role on the namespace or + * event hub. + */ + //@Test + public void sendRecv1MsgAADTest() throws Exception { + PerTestSettings settings = new PerTestSettings("SendRecv1MsgAAD"); + AzureActiveDirectoryTokenProvider.AuthenticationCallback authCallback = new MsalAuthCallback(); + String authAuthority = "https://login.windows.net/replaceWithTenantIdGuid"; + settings.inoutEPHConstructorArgs.setAuthCallback(authCallback, authAuthority); + settings = testSetup(settings); + + settings.outUtils.sendToAny(settings.outTelltale); + waitForTelltale(settings); + + testFinish(settings, SmokeTest.ANY_NONZERO_COUNT); + } + + private class MsalAuthCallback implements AzureActiveDirectoryTokenProvider.AuthenticationCallback { + final private String clientId = "replaceWithClientIdGuid"; + final private String clientSecret = "replaceWithClientSecret"; + + @Override + public CompletableFuture acquireToken(String audience, String authority, Object state) { + try { + ConfidentialClientApplication app = ConfidentialClientApplication.builder(this.clientId, new ClientSecret(this.clientSecret)) + .authority(authority) + .build(); + + ClientCredentialParameters parameters = ClientCredentialParameters.builder(Collections.singleton(audience + ".default")).build(); + + IAuthenticationResult result = app.acquireToken(parameters).get(); + + return CompletableFuture.completedFuture(result.accessToken()); + } + catch (Exception e) { + throw new CompletionException(e); + } + } + } @Test public void receiverRuntimeMetricsTest() throws Exception { diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/TestBase.java b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/TestBase.java index a7447e5392e8d..69451871e2660 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/TestBase.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-eph/src/test/java/com/microsoft/azure/eventprocessorhost/TestBase.java @@ -6,6 +6,8 @@ import com.microsoft.azure.eventhubs.ConnectionStringBuilder; import com.microsoft.azure.eventhubs.EventHubClient; import com.microsoft.azure.eventhubs.EventHubException; +import com.microsoft.azure.eventprocessorhost.EventProcessorHost.EventProcessorHostBuilder.AuthStep; +import com.microsoft.azure.eventprocessorhost.EventProcessorHost.EventProcessorHostBuilder.OptionalStep; import org.junit.After; import org.junit.AfterClass; @@ -151,15 +153,17 @@ PerTestSettings testSetup(PerTestSettings settings) throws Exception { settings.inOptions.setExceptionNotification(settings.outGeneralErrorHandler); if (settings.inoutEPHConstructorArgs.useExplicitManagers()) { - ICheckpointManager effectiveCheckpointMananger = settings.inoutEPHConstructorArgs.isFlagSet(PerTestSettings.EPHConstructorArgs.CHECKPOINT_MANAGER_OVERRIDE) + ICheckpointManager effectiveCheckpointManager = settings.inoutEPHConstructorArgs.isFlagSet(PerTestSettings.EPHConstructorArgs.CHECKPOINT_MANAGER_OVERRIDE) ? settings.inoutEPHConstructorArgs.getCheckpointMananger() : new BogusCheckpointMananger(); ILeaseManager effectiveLeaseManager = settings.inoutEPHConstructorArgs.isFlagSet(PerTestSettings.EPHConstructorArgs.LEASE_MANAGER_OVERRIDE) ? settings.inoutEPHConstructorArgs.getLeaseManager() : new BogusLeaseManager(); - settings.outHost = new EventProcessorHost(effectiveHostName, effectiveEntityPath, effectiveConsumerGroup, effectiveConnectionString, - effectiveCheckpointMananger, effectiveLeaseManager, effectiveExecutor, null); + settings.outHost = EventProcessorHost.EventProcessorHostBuilder.newBuilder(effectiveHostName, effectiveConsumerGroup) + .useUserCheckpointAndLeaseManagers(effectiveCheckpointManager, effectiveLeaseManager) + .useEventHubConnectionString(effectiveConnectionString, effectiveEntityPath) + .setExecutor(effectiveExecutor).build(); } else { String effectiveStorageConnectionString = settings.inoutEPHConstructorArgs.isFlagSet(PerTestSettings.EPHConstructorArgs.STORAGE_CONNECTION_OVERRIDE) ? settings.inoutEPHConstructorArgs.getStorageConnection() @@ -179,8 +183,17 @@ PerTestSettings testSetup(PerTestSettings settings) throws Exception { ? settings.inoutEPHConstructorArgs.getStorageBlobPrefix() : null; - settings.outHost = new EventProcessorHost(effectiveHostName, effectiveEntityPath, effectiveConsumerGroup, effectiveConnectionString, - effectiveStorageConnectionString, effectiveStorageContainerName, effectiveBlobPrefix, effectiveExecutor); + AuthStep intermediate = EventProcessorHost.EventProcessorHostBuilder.newBuilder(effectiveHostName, effectiveConsumerGroup) + .useAzureStorageCheckpointLeaseManager(effectiveStorageConnectionString, effectiveStorageContainerName, effectiveBlobPrefix); + OptionalStep almostDone = null; + if (settings.inoutEPHConstructorArgs.isFlagSet(PerTestSettings.EPHConstructorArgs.AUTH_CALLBACK)) { + ConnectionStringBuilder csb = new ConnectionStringBuilder(effectiveConnectionString); + almostDone = intermediate.useAADAuthentication(csb.getEndpoint(), effectiveEntityPath) + .useAuthenticationCallback(settings.inoutEPHConstructorArgs.getAuthCallback(), settings.inoutEPHConstructorArgs.getAuthAuthority()); + } else { + almostDone = intermediate.useEventHubConnectionString(effectiveConnectionString, effectiveEntityPath); + } + settings.outHost = almostDone.setExecutor(effectiveExecutor).build(); } if (!settings.inEventHubDoesNotExist) { diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-extensions/pom.xml b/sdk/eventhubs/microsoft-azure-eventhubs-extensions/pom.xml index 5faee62b351f4..9dcf397d18ddc 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-extensions/pom.xml +++ b/sdk/eventhubs/microsoft-azure-eventhubs-extensions/pom.xml @@ -7,7 +7,7 @@ com.microsoft.azure azure-eventhubs-clients - 2.3.1 + 3.0.0 ../pom.data.xml diff --git a/sdk/eventhubs/microsoft-azure-eventhubs-extensions/src/main/java/com/microsoft/azure/eventhubs/extensions/appender/EventHubsManager.java b/sdk/eventhubs/microsoft-azure-eventhubs-extensions/src/main/java/com/microsoft/azure/eventhubs/extensions/appender/EventHubsManager.java index c4c9a9e6ad3e8..aa22b28b12004 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs-extensions/src/main/java/com/microsoft/azure/eventhubs/extensions/appender/EventHubsManager.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs-extensions/src/main/java/com/microsoft/azure/eventhubs/extensions/appender/EventHubsManager.java @@ -43,6 +43,6 @@ public void send(final Iterable messages) throws EventHubException { } public void startup() throws EventHubException, IOException { - this.eventHubSender = EventHubClient.createSync(this.eventHubConnectionString, EXECUTOR_SERVICE); + this.eventHubSender = EventHubClient.createFromConnectionStringSync(this.eventHubConnectionString, EXECUTOR_SERVICE); } } diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/pom.xml b/sdk/eventhubs/microsoft-azure-eventhubs/pom.xml index dc90cc557ca52..0be6b248879b6 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/pom.xml +++ b/sdk/eventhubs/microsoft-azure-eventhubs/pom.xml @@ -7,7 +7,7 @@ com.microsoft.azure azure-eventhubs-clients - 2.3.1 + 3.0.0 ../pom.data.xml @@ -30,4 +30,30 @@ scm:git:https://github.com/Azure/azure-sdk-for-java + + + com.microsoft.azure + azure-client-authentication + ${client-runtime.version} + compile + + + com.microsoft.azure + msal4j + 0.4.0-preview + test + + + com.microsoft.azure + adal4j + ${adal4j.version} + test + + + com.nimbusds + nimbus-jose-jwt + 6.0.1 + + + diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/AuthorizationFailedException.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/AuthorizationFailedException.java index 3905f043321c1..436f5fd9b69c7 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/AuthorizationFailedException.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/AuthorizationFailedException.java @@ -8,7 +8,7 @@ /** * Authorization failed exception is thrown when error is encountered during authorizing user's permission to run the intended operations. * When encountered this exception user should check whether the token/key provided in the connection string (e.g. one passed to - * {@link EventHubClient#create(String, ScheduledExecutorService)}) is valid, and has correct execution right for the intended operations (e.g. + * {@link EventHubClient#createFromConnectionString(String, ScheduledExecutorService)}) is valid, and has correct execution right for the intended operations (e.g. * Receive call will need Listen claim associated with the key/token). * * @see http://go.microsoft.com/fwlink/?LinkId=761101 diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/AzureActiveDirectoryTokenProvider.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/AzureActiveDirectoryTokenProvider.java new file mode 100644 index 0000000000000..5247aebf7cf19 --- /dev/null +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/AzureActiveDirectoryTokenProvider.java @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.azure.eventhubs; + +import java.text.ParseException; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import com.microsoft.azure.eventhubs.impl.ClientConstants; + +public final class AzureActiveDirectoryTokenProvider implements ITokenProvider { + private final AuthenticationCallback authCallback; + private final String authority; + private final Object authCallbackState; + + public AzureActiveDirectoryTokenProvider( + final AuthenticationCallback authenticationCallback, + final String authority, + final Object state) { + this.authCallbackState = state; + this.authority = authority; + this.authCallback = authenticationCallback; + } + + @Override + public CompletableFuture getToken(String resource, Duration timeout) { + return this.authCallback.acquireToken(ClientConstants.EVENTHUBS_AUDIENCE, this.authority, this.authCallbackState) + .thenApply((rawToken) -> { + try { + return new JsonSecurityToken(rawToken, resource); + } catch (ParseException e) { + throw new CompletionException(e); + } + }); + } + + public interface AuthenticationCallback { + CompletableFuture acquireToken(final String audience, final String authority, final Object state); + } +} diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ConnectionStringBuilder.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ConnectionStringBuilder.java index d203026ddec2a..0118a4058f8f9 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ConnectionStringBuilder.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ConnectionStringBuilder.java @@ -61,10 +61,13 @@ public final class ConnectionStringBuilder { static final String SHARED_ACCESS_KEY_CONFIG_NAME = "SharedAccessKey"; static final String SHARED_ACCESS_SIGNATURE_CONFIG_NAME = "SharedAccessSignature"; static final String TRANSPORT_TYPE_CONFIG_NAME = "TransportType"; + static final String AUTHENTICATION_CONFIG_NAME = "Authentication"; + + static public final String MANAGED_IDENTITY_AUTHENTICATION = "Managed Identity"; private static final String ALL_KEY_ENUMERATE_REGEX = "(" + HOST_NAME_CONFIG_NAME + "|" + ENDPOINT_CONFIG_NAME + "|" + SHARED_ACCESS_KEY_NANE_CONFIG_NAME + "|" + SHARED_ACCESS_KEY_CONFIG_NAME + "|" + SHARED_ACCESS_SIGNATURE_CONFIG_NAME + "|" + ENTITY_PATH_CONFIG_NAME + "|" + OPERATION_TIMEOUT_CONFIG_NAME - + "|" + TRANSPORT_TYPE_CONFIG_NAME + ")"; + + "|" + TRANSPORT_TYPE_CONFIG_NAME + "|" + AUTHENTICATION_CONFIG_NAME + ")"; private static final String KEYS_WITH_DELIMITERS_REGEX = KEY_VALUE_PAIR_DELIMITER + ALL_KEY_ENUMERATE_REGEX + KEY_VALUE_SEPARATOR; @@ -76,6 +79,7 @@ public final class ConnectionStringBuilder { private String sharedAccessSignature; private Duration operationTimeout; private TransportType transportType; + private String authentication; /** * Creates an empty {@link ConnectionStringBuilder}. At minimum, a namespace name, an entity path, SAS key name, and SAS key @@ -278,6 +282,26 @@ public ConnectionStringBuilder setTransportType(final TransportType transportTyp this.transportType = transportType; return this; } + + /** + * Get the authentication type in the Connection String. + * + * @return authentication + */ + public String getAuthentication() { + return this.authentication; + } + + /** + * Set the authentication type in the Connection String. The only valid values are "Managed Identity" or null. + * + * @param authentication + * @return the {@link ConnectionStringBuilder} instance being set. + */ + public ConnectionStringBuilder setAuthentication(final String authentication) { + this.authentication = authentication; + return this; + } /** * Returns an inter-operable connection string that can be used to connect to EventHubs instances. @@ -321,6 +345,11 @@ public String toString() { connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", TRANSPORT_TYPE_CONFIG_NAME, KEY_VALUE_SEPARATOR, this.transportType.toString(), KEY_VALUE_PAIR_DELIMITER)); } + + if (!StringUtil.isNullOrWhiteSpace(this.authentication)) { + connectionStringBuilder.append(String.format(Locale.US, "%s%s%s%s", AUTHENTICATION_CONFIG_NAME, + KEY_VALUE_SEPARATOR, this.authentication, KEY_VALUE_PAIR_DELIMITER)); + } connectionStringBuilder.deleteCharAt(connectionStringBuilder.length() - 1); return connectionStringBuilder.toString(); @@ -409,7 +438,9 @@ private void parseConnectionString(final String connectionString) { String.format(Locale.US, "Invalid value specified for property '%s' in the ConnectionString.", TRANSPORT_TYPE_CONFIG_NAME), exception); } - } else { + } else if (key.equalsIgnoreCase(AUTHENTICATION_CONFIG_NAME)) { + this.authentication = values[valueIndex]; + } else { throw new IllegalConnectionStringFormatException( String.format(Locale.US, "Illegal connection string parameter name: %s", key)); } diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ErrorContext.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ErrorContext.java index ebf4ecaf30377..ef55c00f1f3a0 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ErrorContext.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ErrorContext.java @@ -9,7 +9,9 @@ import java.util.Locale; public abstract class ErrorContext implements Serializable { - private final String namespaceName; + private static final long serialVersionUID = -841174412304936908L; + + private final String namespaceName; protected ErrorContext(final String namespaceName) { this.namespaceName = namespaceName; diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventData.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventData.java index e2091f2977e63..128eccf75f7c0 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventData.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventData.java @@ -50,7 +50,7 @@ public interface EventData extends Serializable, Comparable { * * @param data the actual payload of data in bytes to be Sent to EventHubs. * @return EventData the created {@link EventData} to send to EventHubs. - * @see EventHubClient#create(String, ScheduledExecutorService) + * @see EventHubClient#createFromConnectionString(String, ScheduledExecutorService) */ static EventData create(final byte[] data) { return new EventDataImpl(data); @@ -74,7 +74,7 @@ static EventData create(final byte[] data) { * @param offset Offset in the byte[] to read from ; inclusive index * @param length length of the byte[] to be read, starting from offset * @return EventData the created {@link EventData} to send to EventHubs. - * @see EventHubClient#create(String, ScheduledExecutorService) + * @see EventHubClient#createFromConnectionString(String, ScheduledExecutorService) */ static EventData create(final byte[] data, final int offset, final int length) { return new EventDataImpl(data, offset, length); @@ -96,7 +96,7 @@ static EventData create(final byte[] data, final int offset, final int length) { * * @param buffer ByteBuffer which references the payload of the Event to be sent to EventHubs * @return EventData the created {@link EventData} to send to EventHubs. - * @see EventHubClient#create(String, ScheduledExecutorService) + * @see EventHubClient#createFromConnectionString(String, ScheduledExecutorService) */ static EventData create(final ByteBuffer buffer) { return new EventDataImpl(buffer); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClient.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClient.java index ea49239ed07e7..3d11e7ee95d33 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClient.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClient.java @@ -7,6 +7,7 @@ import com.microsoft.azure.eventhubs.impl.ExceptionUtil; import java.io.IOException; +import java.net.URI; import java.nio.channels.UnresolvedAddressException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledExecutorService; @@ -14,14 +15,14 @@ /** * Anchor class - all EventHub client operations STARTS here. * - * @see EventHubClient#create(String, ScheduledExecutorService) + * @see EventHubClient#createFromConnectionString(String, ScheduledExecutorService) */ public interface EventHubClient { String DEFAULT_CONSUMER_GROUP_NAME = "$Default"; /** - * Synchronous version of {@link #create(String, ScheduledExecutorService)}. + * Synchronous version of {@link #createFromConnectionString(String, ScheduledExecutorService)}. * * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. * @param executor An {@link ScheduledExecutorService} to run all tasks performed by {@link EventHubClient}. @@ -29,13 +30,13 @@ public interface EventHubClient { * @throws EventHubException If Service Bus service encountered problems during connection creation. * @throws IOException If the underlying Proton-J layer encounter network errors. */ - static EventHubClient createSync(final String connectionString, final ScheduledExecutorService executor) + static EventHubClient createFromConnectionStringSync(final String connectionString, final ScheduledExecutorService executor) throws EventHubException, IOException { - return EventHubClient.createSync(connectionString, null, executor); + return createFromConnectionStringSync(connectionString, null, executor); } /** - * Synchronous version of {@link #create(String, ScheduledExecutorService)}. + * Synchronous version of {@link #createFromConnectionString(String, ScheduledExecutorService)}. * * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. * @param retryPolicy A custom {@link RetryPolicy} to be used when communicating with EventHub. @@ -44,9 +45,9 @@ static EventHubClient createSync(final String connectionString, final ScheduledE * @throws EventHubException If Service Bus service encountered problems during connection creation. * @throws IOException If the underlying Proton-J layer encounter network errors. */ - static EventHubClient createSync(final String connectionString, final RetryPolicy retryPolicy, final ScheduledExecutorService executor) + static EventHubClient createFromConnectionStringSync(final String connectionString, final RetryPolicy retryPolicy, final ScheduledExecutorService executor) throws EventHubException, IOException { - return ExceptionUtil.syncWithIOException(() -> create(connectionString, retryPolicy, executor).get()); + return ExceptionUtil.syncWithIOException(() -> createFromConnectionString(connectionString, retryPolicy, executor).get()); } /** @@ -60,9 +61,9 @@ static EventHubClient createSync(final String connectionString, final RetryPolic * @throws EventHubException If Service Bus service encountered problems during connection creation. * @throws IOException If the underlying Proton-J layer encounter network errors. */ - static CompletableFuture create(final String connectionString, final ScheduledExecutorService executor) + static CompletableFuture createFromConnectionString(final String connectionString, final ScheduledExecutorService executor) throws EventHubException, IOException { - return EventHubClient.create(connectionString, null, executor); + return createFromConnectionString(connectionString, null, executor); } /** @@ -77,12 +78,61 @@ static CompletableFuture create(final String connectionString, f * @throws EventHubException If Service Bus service encountered problems during connection creation. * @throws IOException If the underlying Proton-J layer encounter network errors. */ - static CompletableFuture create( + static CompletableFuture createFromConnectionString( final String connectionString, final RetryPolicy retryPolicy, final ScheduledExecutorService executor) throws EventHubException, IOException { return EventHubClientImpl.create(connectionString, retryPolicy, executor); } + /** + * Factory method to create an instance of {@link EventHubClient} using the supplied namespace endpoint address, eventhub name and authentication mechanism. + * In a normal scenario (when re-direct is not enabled) - one EventHubClient instance maps to one Connection to the Azure ServiceBus EventHubs service. + *

The {@link EventHubClient} created from this method creates a Sender instance internally, which is used by the {@link #send(EventData)} methods. + * + * @param endpointAddress namespace level endpoint. This needs to be in the format of scheme://fullyQualifiedServiceBusNamespaceEndpointName + * @param eventHubName EventHub name + * @param authCallback A callback which returns a JSON Web Token obtained from AAD. + * @param authority Address of the AAD authority to issue the token. + * @param executor An {@link ScheduledExecutorService} to run all tasks performed by {@link EventHubClient}. + * @param options Options {@link EventHubClientOptions} for creating the client. Uses all defaults if null. + * @return EventHubClient which can be used to create Senders and Receivers to EventHub + * @throws EventHubException If the EventHubs service encountered problems during connection creation. + * @throws IOException If the underlying Proton-J layer encounter network errors. + */ + public static CompletableFuture createWithAzureActiveDirectory( + final URI endpointAddress, + final String eventHubName, + final AzureActiveDirectoryTokenProvider.AuthenticationCallback authCallback, + final String authority, + final ScheduledExecutorService executor, + final EventHubClientOptions options) throws EventHubException, IOException { + ITokenProvider tokenProvider = new AzureActiveDirectoryTokenProvider(authCallback, authority, null); + return createWithTokenProvider(endpointAddress, eventHubName, tokenProvider, executor, options); + } + + /** + * Factory method to create an instance of {@link EventHubClient} using the supplied namespace endpoint address, eventhub name and authentication mechanism. + * In a normal scenario (when re-direct is not enabled) - one EventHubClient instance maps to one Connection to the Azure ServiceBus EventHubs service. + *

The {@link EventHubClient} created from this method creates a Sender instance internally, which is used by the {@link #send(EventData)} methods. + * + * @param endpointAddress namespace level endpoint. This needs to be in the format of scheme://fullyQualifiedServiceBusNamespaceEndpointName + * @param eventHubName EventHub name + * @param tokenProvider The {@link ITokenProvider} implementation to be used to authenticate + * @param executor An {@link ScheduledExecutorService} to run all tasks performed by {@link EventHubClient}. + * @param options Options {@link EventHubClientOptions} for creating the client. Uses all defaults if null. + * @return EventHubClient which can be used to create Senders and Receivers to EventHub + * @throws EventHubException If the EventHubs service encountered problems during connection creation. + * @throws IOException If the underlying Proton-J layer encounter network errors. + */ + public static CompletableFuture createWithTokenProvider( + final URI endpointAddress, + final String eventHubName, + final ITokenProvider tokenProvider, + final ScheduledExecutorService executor, + final EventHubClientOptions options) throws EventHubException, IOException { + return EventHubClientImpl.create(endpointAddress, eventHubName, tokenProvider, executor, options); + } + /** * @return the name of the Event Hub this client is connected to. */ diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClientOptions.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClientOptions.java new file mode 100644 index 0000000000000..562a0ae40cba2 --- /dev/null +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/EventHubClientOptions.java @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.azure.eventhubs; + +import java.time.Duration; + +/** + * Convenient container for options for creating an {@link EventHubClient} + * All options default to not specified (null) + */ +public class EventHubClientOptions { + private Duration operationTimeout = null; + private TransportType transportType = null; + private RetryPolicy retryPolicy = null; + + /** + * Create with all defaults + */ + public EventHubClientOptions() { + } + + /** + * Set the operation timeout. + * @param operationTimeout new operation timeout, null to unset any previous value + * @return this options object + */ + public EventHubClientOptions setOperationTimeout(Duration operationTimeout) { + this.operationTimeout = operationTimeout; + return this; + } + + /** + * Get the operation timeout. + * @return operation timeout or null if not set + */ + public Duration getOperationTimeout() { + return this.operationTimeout; + } + + /** + * Set the {@link TransportType} for the connection to the Event Hubs service + * @param transportType new transport type, null to unset any previous value + * @return this options object + */ + public EventHubClientOptions setTransportType(TransportType transportType) { + this.transportType = transportType; + return this; + } + + /** + * Get the transport type + * @return {@link TransportType} or null if not set + */ + public TransportType getTransportType() { + return this.transportType; + } + + /** + * Set the {@link RetryPolicy} for operations + * @param retryPolicy new retry policy, null to unset any previous value + * @return this options object + */ + public EventHubClientOptions setRetryPolicy(RetryPolicy retryPolicy) { + this.retryPolicy = retryPolicy; + return this; + } + + /** + * Get the retry policy + * @return {@link RetryPolicy} or null if not set + */ + public RetryPolicy getRetryPolicy() { + return this.retryPolicy; + } +} diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ITokenProvider.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ITokenProvider.java new file mode 100644 index 0000000000000..21c668711cccc --- /dev/null +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ITokenProvider.java @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.azure.eventhubs; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +public interface ITokenProvider { + CompletableFuture getToken(final String resource, final Duration timeout); +} diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/JsonSecurityToken.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/JsonSecurityToken.java new file mode 100644 index 0000000000000..8f45de59393ae --- /dev/null +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/JsonSecurityToken.java @@ -0,0 +1,31 @@ +package com.microsoft.azure.eventhubs; + +import java.text.ParseException; +import java.util.Date; + +import com.microsoft.azure.eventhubs.impl.ClientConstants; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.JWTParser; + +/** + * Extend SecurityToken with some specifics for a JSon Web Token + * + */ +public class JsonSecurityToken extends SecurityToken { + /** + * Construct from a raw JWT. + * @param rawToken the JWT token data + * @param audience audience of the token + * @throws ParseException if the token cannot be parsed + */ + public JsonSecurityToken(final String rawToken, final String audience) throws ParseException { + super(rawToken, GetExpirationDateTimeUtcFromToken(rawToken), audience, ClientConstants.JWT_TOKEN_TYPE); + } + + static Date GetExpirationDateTimeUtcFromToken(final String token) throws ParseException { + JWT jwt = JWTParser.parse(token); + JWTClaimsSet claims = jwt.getJWTClaimsSet(); + return claims.getExpirationTime(); + } +} diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ManagedIdentityTokenProvider.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ManagedIdentityTokenProvider.java new file mode 100644 index 0000000000000..c8e4b3d324d25 --- /dev/null +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/ManagedIdentityTokenProvider.java @@ -0,0 +1,26 @@ +package com.microsoft.azure.eventhubs; + +import java.io.IOException; +import java.text.ParseException; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import com.microsoft.azure.credentials.MSICredentials; +import com.microsoft.azure.eventhubs.impl.ClientConstants; + +public class ManagedIdentityTokenProvider implements ITokenProvider { + final static MSICredentials credentials = new MSICredentials(); + + @Override + public CompletableFuture getToken(final String resource, final Duration timeout) { + return CompletableFuture.supplyAsync(() -> { + try { + String rawToken = ManagedIdentityTokenProvider.credentials.getToken(ClientConstants.EVENTHUBS_AUDIENCE); + return new JsonSecurityToken(rawToken, resource); + } catch (IOException | ParseException e) { + throw new CompletionException(e); + } + }); + } +} diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionSender.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionSender.java index c191c9881698f..3ae79cecdb481 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionSender.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/PartitionSender.java @@ -13,7 +13,7 @@ * if you do not care about sending events to specific partitions. Instead, use {@link EventHubClient#send} method. * * @see EventHubClient#createPartitionSender(String) - * @see EventHubClient#create(String, ScheduledExecutorService) + * @see EventHubClient#createFromConnectionString(String, ScheduledExecutorService) */ public interface PartitionSender { diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/QuotaExceededException.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/QuotaExceededException.java index e0ff9b50d376d..10533ea5c753a 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/QuotaExceededException.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/QuotaExceededException.java @@ -4,8 +4,9 @@ package com.microsoft.azure.eventhubs; public class QuotaExceededException extends EventHubException { + private static final long serialVersionUID = 1L; - public QuotaExceededException(String message) { + public QuotaExceededException(String message) { super(false, message); } diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/SecurityToken.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/SecurityToken.java new file mode 100644 index 0000000000000..7ee3293bb4eb1 --- /dev/null +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/SecurityToken.java @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.azure.eventhubs; + +import java.util.Date; + +/** + * A generic representation of a security token. + * + */ +public class SecurityToken { + private final String tokenType; + private final String token; + private final Date validTo; + private final String audience; + + /** + * Constructor. + * @param token the actual token data + * @param validTo expiration date/time of the token + * @param audience audience of the token + * @param tokenType type of the token + */ + public SecurityToken(final String token, final Date validTo, final String audience, final String tokenType) { + this.tokenType = tokenType; + this.token = token; + this.validTo = validTo; + this.audience = audience; + } + + /** + * Get the type of the token. + * @return token type + */ + public String getTokenType() { + return this.tokenType; + } + + /** + * Get the token itself. + * @return token + */ + public String getToken() { + return this.token; + } + + /** + * Get the expiration date/time of the token. + * @return expiration + */ + public Date validTo() { + return this.validTo; + } + + /** + * Get the audience of the token. + * @return audience + */ + public String getAudience() { + return this.audience; + } +} diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/ActiveClientTokenManager.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/ActiveClientTokenManager.java index 63a20902717f9..4c4bcd8844203 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/ActiveClientTokenManager.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/ActiveClientTokenManager.java @@ -19,7 +19,7 @@ final class ActiveClientTokenManager { private final Duration tokenRefreshInterval; private final SchedulerProvider schedulerProvider; private final Timer timerScheduler; - private CompletableFuture timer; + private CompletableFuture timer; ActiveClientTokenManager( final ClientEntity clientEntity, diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/AmqpUtil.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/AmqpUtil.java index e4b1e9a6e2ffc..ec97dc294ccbb 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/AmqpUtil.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/AmqpUtil.java @@ -11,6 +11,7 @@ import org.apache.qpid.proton.amqp.messaging.MessageAnnotations; import org.apache.qpid.proton.message.Message; +import java.util.Date; import java.util.Locale; public final class AmqpUtil { @@ -121,6 +122,10 @@ private static int sizeof(Object obj) { return Double.BYTES; } + if (obj instanceof Date) { + return 32; + } + throw new IllegalArgumentException(String.format(Locale.US, "Encoding Type: %s is not supported", obj.getClass())); } } diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/CBSChannel.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/CBSChannel.java index 4dcfcd6c5c380..c7e3f073715c5 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/CBSChannel.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/CBSChannel.java @@ -8,18 +8,24 @@ import org.apache.qpid.proton.amqp.messaging.ApplicationProperties; import org.apache.qpid.proton.message.Message; +import com.microsoft.azure.eventhubs.SecurityToken; + import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; final class CBSChannel { - + final ScheduledExecutorService executor; final FaultTolerantObject innerChannel; CBSChannel( final SessionProvider sessionProvider, final AmqpConnection connection, - final String clientId) { - + final String clientId, + final ScheduledExecutorService executor) { + this.executor = executor; RequestResponseCloser closer = new RequestResponseCloser(); this.innerChannel = new FaultTolerantObject<>( new RequestResponseOpener(sessionProvider, clientId, "cbs-session", "cbs", ClientConstants.CBS_ADDRESS, connection), @@ -29,27 +35,47 @@ final class CBSChannel { public void sendToken( final ReactorDispatcher dispatcher, - final String token, + final CompletableFuture tokenFuture, final String tokenAudience, - final OperationResult sendTokenCallback) { + final OperationResult sendTokenCallback, + final Consumer errorCallback) { + tokenFuture.thenAcceptAsync((token) -> { + innerSendToken(dispatcher, token, tokenAudience, sendTokenCallback); + }, this.executor) + .whenCompleteAsync((empty, exception) -> { + // TODO: whenCompleteAsync presents a Throwable. But many of the error callbacks expect + // an Exception. For now, do a cast here. Will we ever actually get an error that is + // not an Exception? + if ((exception != null) && (exception instanceof Exception)) { + errorCallback.accept((Exception)exception); + } + }, this.executor); + } + private void innerSendToken( + final ReactorDispatcher dispatcher, + final SecurityToken token, + final String tokenAudience, + final OperationResult sendTokenCallback) { final Message request = Proton.message(); final Map properties = new HashMap<>(); properties.put(ClientConstants.PUT_TOKEN_OPERATION, ClientConstants.PUT_TOKEN_OPERATION_VALUE); - properties.put(ClientConstants.PUT_TOKEN_TYPE, ClientConstants.SAS_TOKEN_TYPE); + properties.put(ClientConstants.PUT_TOKEN_TYPE, token.getTokenType()); + properties.put(ClientConstants.PUT_TOKEN_EXPIRY, token.validTo()); properties.put(ClientConstants.PUT_TOKEN_AUDIENCE, tokenAudience); + final ApplicationProperties applicationProperties = new ApplicationProperties(properties); request.setApplicationProperties(applicationProperties); - request.setBody(new AmqpValue(token)); + request.setBody(new AmqpValue(token.getToken())); final MessageOperationResult messageOperation = new MessageOperationResult(response -> sendTokenCallback.onComplete(null), sendTokenCallback::onError); final OperationResultBase operation = new OperationResultBase<>( result -> result.request(request, messageOperation), sendTokenCallback::onError); - + this.innerChannel.runOnOpenedObject(dispatcher, operation); } - + public void close( final ReactorDispatcher reactorDispatcher, final OperationResult closeCallback) { diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/ClientConstants.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/ClientConstants.java index 0cd1b70972cf1..497d72f0cb05e 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/ClientConstants.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/ClientConstants.java @@ -37,7 +37,7 @@ public final class ClientConstants { public static final String NO_RETRY = "NoRetry"; public static final String DEFAULT_RETRY = "Default"; public static final String PRODUCT_NAME = "MSJavaClient"; - public static final String CURRENT_JAVACLIENT_VERSION = "2.3.1"; + public static final String CURRENT_JAVACLIENT_VERSION = "3.0.0"; public static final String PLATFORM_INFO = getPlatformInfo(); public static final String FRAMEWORK_INFO = getFrameworkInfo(); public static final String CBS_ADDRESS = "$cbs"; @@ -45,10 +45,12 @@ public final class ClientConstants { public static final String PUT_TOKEN_OPERATION_VALUE = "put-token"; public static final String PUT_TOKEN_TYPE = "type"; public static final String SAS_TOKEN_TYPE = "servicebus.windows.net:sastoken"; + public static final String JWT_TOKEN_TYPE = "jwt"; public static final String PUT_TOKEN_AUDIENCE = "name"; public static final String PUT_TOKEN_EXPIRY = "expiration"; public static final String PUT_TOKEN_STATUS_CODE = "status-code"; public static final String PUT_TOKEN_STATUS_DESCRIPTION = "status-description"; + public final static String EVENTHUBS_AUDIENCE = "https://eventhubs.azure.net/"; public static final String MANAGEMENT_ADDRESS = "$management"; public static final String MANAGEMENT_EVENTHUB_ENTITY_TYPE = AmqpConstants.VENDOR + ":eventhub"; public static final String MANAGEMENT_PARTITION_ENTITY_TYPE = AmqpConstants.VENDOR + ":partition"; diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/EventHubClientImpl.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/EventHubClientImpl.java index a352172bb1dc4..511ae19728c54 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/EventHubClientImpl.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/EventHubClientImpl.java @@ -8,24 +8,27 @@ import com.microsoft.azure.eventhubs.EventData; import com.microsoft.azure.eventhubs.EventDataBatch; import com.microsoft.azure.eventhubs.EventHubClient; +import com.microsoft.azure.eventhubs.EventHubClientOptions; import com.microsoft.azure.eventhubs.EventHubException; import com.microsoft.azure.eventhubs.EventHubRuntimeInformation; import com.microsoft.azure.eventhubs.EventPosition; +import com.microsoft.azure.eventhubs.ITokenProvider; import com.microsoft.azure.eventhubs.OperationCancelledException; import com.microsoft.azure.eventhubs.PartitionReceiver; import com.microsoft.azure.eventhubs.PartitionRuntimeInformation; import com.microsoft.azure.eventhubs.PartitionSender; import com.microsoft.azure.eventhubs.ReceiverOptions; import com.microsoft.azure.eventhubs.RetryPolicy; +import com.microsoft.azure.eventhubs.impl.MessagingFactory.MessagingFactoryBuilder; import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; +import java.net.URI; import java.time.Duration; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; @@ -49,18 +52,23 @@ public final class EventHubClientImpl extends ClientEntity implements EventHubCl private CompletableFuture createSender; - private EventHubClientImpl(final ConnectionStringBuilder connectionString, final ScheduledExecutorService executor) { + private EventHubClientImpl(final String eventHubName, final ScheduledExecutorService executor) { super(StringUtil.getRandomString("EC"), null, executor); - this.eventHubName = connectionString.getEventHubName(); + this.eventHubName = eventHubName; this.senderCreateSync = new Object(); } public static CompletableFuture create( final String connectionString, final RetryPolicy retryPolicy, final ScheduledExecutorService executor) throws IOException { + if (StringUtil.isNullOrWhiteSpace(connectionString)) { + throw new IllegalArgumentException("Connection string cannot be null or empty"); + } + Objects.requireNonNull(executor, "Executor cannot be null"); + final ConnectionStringBuilder connStr = new ConnectionStringBuilder(connectionString); - final EventHubClientImpl eventHubClient = new EventHubClientImpl(connStr, executor); + final EventHubClientImpl eventHubClient = new EventHubClientImpl(connStr.getEventHubName(), executor); return MessagingFactory.createFromConnectionString(connectionString, retryPolicy, executor) .thenApplyAsync(new Function() { @@ -73,6 +81,38 @@ public EventHubClient apply(MessagingFactory factory) { }, executor); } + public static CompletableFuture create( + final URI endpoint, + final String eventHubName, + final ITokenProvider tokenProvider, + final ScheduledExecutorService executor, + final EventHubClientOptions options) throws IOException { + if (StringUtil.isNullOrWhiteSpace(endpoint.getHost())) { + throw new IllegalArgumentException("Endpoint must contain a hostname"); + } + if (StringUtil.isNullOrWhiteSpace(eventHubName)) { + throw new IllegalArgumentException("Event hub name cannot be null or empty"); + } + Objects.requireNonNull(tokenProvider, "Token provider cannot be null"); + + final EventHubClientImpl eventHubClient = new EventHubClientImpl(eventHubName, executor); + final MessagingFactoryBuilder builder = new MessagingFactoryBuilder(endpoint.getHost(), tokenProvider, executor); + if (options != null) { + builder.setOperationTimeout(options.getOperationTimeout()).setTransportType(options.getTransportType()). + setRetryPolicy(options.getRetryPolicy()); + } + + return builder.build() + .thenApplyAsync(new Function() { + @Override + public EventHubClient apply(MessagingFactory factory) { + eventHubClient.underlyingFactory = factory; + eventHubClient.timer = new Timer(factory); + return eventHubClient; + } + }, executor); + } + public String getEventHubName() { return eventHubName; } @@ -250,38 +290,24 @@ public void accept(MessageSender a) { @Override public CompletableFuture getRuntimeInformation() { - CompletableFuture future1 = null; - throwIfClosed(); Map request = new HashMap(); request.put(ClientConstants.MANAGEMENT_ENTITY_TYPE_KEY, ClientConstants.MANAGEMENT_EVENTHUB_ENTITY_TYPE); request.put(ClientConstants.MANAGEMENT_ENTITY_NAME_KEY, this.eventHubName); request.put(ClientConstants.MANAGEMENT_OPERATION_KEY, ClientConstants.READ_OPERATION_VALUE); - future1 = this.addManagementToken(request); - - if (future1 == null) { - future1 = managementWithRetry(request).thenComposeAsync(new Function, CompletableFuture>() { - @Override - public CompletableFuture apply(Map rawdata) { - CompletableFuture future2 = new CompletableFuture(); - future2.complete(new EventHubRuntimeInformation( + return addManagementToken(request).thenComposeAsync((requestWithToken) -> managementWithRetry(requestWithToken), this.executor). + thenApplyAsync((rawdata) -> { + return new EventHubRuntimeInformation( (String) rawdata.get(ClientConstants.MANAGEMENT_ENTITY_NAME_KEY), ((Date) rawdata.get(ClientConstants.MANAGEMENT_RESULT_CREATED_AT)).toInstant(), (int) rawdata.get(ClientConstants.MANAGEMENT_RESULT_PARTITION_COUNT), - (String[]) rawdata.get(ClientConstants.MANAGEMENT_RESULT_PARTITION_IDS))); - return future2; - } - }, this.executor); - } - - return future1; + (String[]) rawdata.get(ClientConstants.MANAGEMENT_RESULT_PARTITION_IDS)); + }, this.executor); } @Override public CompletableFuture getPartitionRuntimeInformation(String partitionId) { - CompletableFuture future1 = null; - throwIfClosed(); Map request = new HashMap(); @@ -289,40 +315,25 @@ public CompletableFuture getPartitionRuntimeInforma request.put(ClientConstants.MANAGEMENT_ENTITY_NAME_KEY, this.eventHubName); request.put(ClientConstants.MANAGEMENT_PARTITION_NAME_KEY, partitionId); request.put(ClientConstants.MANAGEMENT_OPERATION_KEY, ClientConstants.READ_OPERATION_VALUE); - future1 = this.addManagementToken(request); - - if (future1 == null) { - future1 = managementWithRetry(request).thenComposeAsync(new Function, CompletableFuture>() { - @Override - public CompletableFuture apply(Map rawData) { - CompletableFuture future2 = new CompletableFuture(); - future2.complete(new PartitionRuntimeInformation( - (String) rawData.get(ClientConstants.MANAGEMENT_ENTITY_NAME_KEY), - (String) rawData.get(ClientConstants.MANAGEMENT_PARTITION_NAME_KEY), - (long) rawData.get(ClientConstants.MANAGEMENT_RESULT_BEGIN_SEQUENCE_NUMBER), - (long) rawData.get(ClientConstants.MANAGEMENT_RESULT_LAST_ENQUEUED_SEQUENCE_NUMBER), - (String) rawData.get(ClientConstants.MANAGEMENT_RESULT_LAST_ENQUEUED_OFFSET), - ((Date) rawData.get(ClientConstants.MANAGEMENT_RESULT_LAST_ENQUEUED_TIME_UTC)).toInstant(), - (boolean) rawData.get(ClientConstants.MANAGEMENT_RESULT_PARTITION_IS_EMPTY))); - return future2; - } - }, this.executor); - } - - return future1; + return addManagementToken(request).thenComposeAsync((requestWithToken) -> managementWithRetry(requestWithToken), this.executor). + thenApplyAsync((rawdata) -> { + return new PartitionRuntimeInformation( + (String) rawdata.get(ClientConstants.MANAGEMENT_ENTITY_NAME_KEY), + (String) rawdata.get(ClientConstants.MANAGEMENT_PARTITION_NAME_KEY), + (long) rawdata.get(ClientConstants.MANAGEMENT_RESULT_BEGIN_SEQUENCE_NUMBER), + (long) rawdata.get(ClientConstants.MANAGEMENT_RESULT_LAST_ENQUEUED_SEQUENCE_NUMBER), + (String) rawdata.get(ClientConstants.MANAGEMENT_RESULT_LAST_ENQUEUED_OFFSET), + ((Date) rawdata.get(ClientConstants.MANAGEMENT_RESULT_LAST_ENQUEUED_TIME_UTC)).toInstant(), + (boolean) rawdata.get(ClientConstants.MANAGEMENT_RESULT_PARTITION_IS_EMPTY)); + }, this.executor); } - private CompletableFuture addManagementToken(Map request) { - CompletableFuture retval = null; - try { - String audience = String.format(Locale.US, "amqp://%s/%s", this.underlyingFactory.getHostName(), this.eventHubName); - String token = this.underlyingFactory.getTokenProvider().getToken(audience, ClientConstants.TOKEN_REFRESH_INTERVAL); - request.put(ClientConstants.MANAGEMENT_SECURITY_TOKEN_KEY, token); - } catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) { - retval = new CompletableFuture(); - retval.completeExceptionally(e); - } - return retval; + private CompletableFuture> addManagementToken(Map request) { + String audience = String.format(Locale.US, "amqp://%s/%s", this.underlyingFactory.getHostName(), this.eventHubName); + return this.underlyingFactory.getTokenProvider().getToken(audience, ClientConstants.TOKEN_REFRESH_INTERVAL).thenApplyAsync((securityToken) -> { + request.put(ClientConstants.MANAGEMENT_SECURITY_TOKEN_KEY, securityToken.getToken()); + return request; + }, this.executor); } private CompletableFuture> managementWithRetry(Map request) { diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/MessageReceiver.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/MessageReceiver.java index 6557093895179..d2e160f2e1b8e 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/MessageReceiver.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/MessageReceiver.java @@ -24,8 +24,6 @@ import org.slf4j.LoggerFactory; import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.time.Instant; import java.time.ZonedDateTime; @@ -77,7 +75,7 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver, private volatile CompletableFuture closeTimer; private int prefetchCount; private Exception lastKnownLinkError; - private String linkCreationTime; + private String linkCreationTime; // Used when looking at Java dumps, do not remove. private MessageReceiver(final MessagingFactory factory, final String name, @@ -149,40 +147,39 @@ public void run() { new Runnable() { @Override public void run() { - try { - underlyingFactory.getCBSChannel().sendToken( - underlyingFactory.getReactorDispatcher(), - underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY), - tokenAudience, - new OperationResult() { - @Override - public void onComplete(Void result) { - if (TRACE_LOGGER.isDebugEnabled()) { - TRACE_LOGGER.debug( - String.format(Locale.US, - "clientId[%s], path[%s], linkName[%s] - token renewed", - getClientId(), receivePath, getReceiveLinkName())); - } + underlyingFactory.getCBSChannel().sendToken( + underlyingFactory.getReactorDispatcher(), + underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY), + tokenAudience, + new OperationResult() { + @Override + public void onComplete(Void result) { + if (TRACE_LOGGER.isDebugEnabled()) { + TRACE_LOGGER.debug( + String.format(Locale.US, + "clientId[%s], path[%s], linkName[%s] - token renewed", + getClientId(), receivePath, getReceiveLinkName())); } - - @Override - public void onError(Exception error) { - if (TRACE_LOGGER.isInfoEnabled()) { - TRACE_LOGGER.info( - String.format(Locale.US, - "clientId[%s], path[%s], linkName[%s], tokenRenewalFailure[%s]", - getClientId(), receivePath, getReceiveLinkName(), error.getMessage())); - } + } + + @Override + public void onError(Exception error) { + if (TRACE_LOGGER.isInfoEnabled()) { + TRACE_LOGGER.info( + String.format(Locale.US, + "clientId[%s], path[%s], linkName[%s], tokenRenewalFailure[%s]", + getClientId(), receivePath, getReceiveLinkName(), error.getMessage())); } - }); - } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | RuntimeException exception) { - if (TRACE_LOGGER.isInfoEnabled()) { - TRACE_LOGGER.info( - String.format(Locale.US, - "clientId[%s], path[%s], linkName[%s], tokenRenewalScheduleFailure[%s]", - getClientId(), receivePath, getReceiveLinkName(), exception.getMessage())); - } - } + } + }, + (exception) -> { + if (TRACE_LOGGER.isInfoEnabled()) { + TRACE_LOGGER.info( + String.format(Locale.US, + "clientId[%s], path[%s], linkName[%s], tokenRenewalScheduleFailure[%s]", + getClientId(), receivePath, getReceiveLinkName(), exception.getMessage())); + } + }); } }, ClientConstants.TOKEN_REFRESH_INTERVAL, @@ -578,41 +575,40 @@ public void accept(ErrorCondition t, Exception u) { } }; - try { - this.underlyingFactory.getCBSChannel().sendToken( - this.underlyingFactory.getReactorDispatcher(), - this.underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY), - tokenAudience, - new OperationResult() { - @Override - public void onComplete(Void result) { - if (MessageReceiver.this.getIsClosingOrClosed()) { - return; - } - underlyingFactory.getSession( - receivePath, - onSessionOpen, - onSessionOpenFailed); + this.underlyingFactory.getCBSChannel().sendToken( + this.underlyingFactory.getReactorDispatcher(), + this.underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY), + tokenAudience, + new OperationResult() { + @Override + public void onComplete(Void result) { + if (MessageReceiver.this.getIsClosingOrClosed()) { + return; } + underlyingFactory.getSession( + receivePath, + onSessionOpen, + onSessionOpenFailed); + } - @Override - public void onError(Exception error) { - final Exception completionException; - if (error != null && error instanceof AmqpException) { - completionException = ExceptionUtil.toException(((AmqpException) error).getError()); - if (completionException != error && completionException.getCause() == null) { - completionException.initCause(error); - } - } else { - completionException = error; + @Override + public void onError(Exception error) { + final Exception completionException; + if (error != null && error instanceof AmqpException) { + completionException = ExceptionUtil.toException(((AmqpException) error).getError()); + if (completionException != error && completionException.getCause() == null) { + completionException.initCause(error); } - - MessageReceiver.this.onError(completionException); + } else { + completionException = error; } - }); - } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | RuntimeException exception) { - MessageReceiver.this.onError(exception); - } + + MessageReceiver.this.onError(completionException); + } + }, + (exception) -> { + MessageReceiver.this.onError(exception); + }); } // CONTRACT: message should be delivered to the caller of MessageReceiver.receive() only via Poll on prefetchqueue diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/MessageSender.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/MessageSender.java index 248643813aab7..bde8756daf474 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/MessageSender.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/MessageSender.java @@ -35,8 +35,6 @@ import java.io.IOException; import java.io.Serializable; import java.nio.BufferOverflowException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.time.Instant; import java.time.ZonedDateTime; @@ -120,7 +118,6 @@ public void onEvent() { new Runnable() { @Override public void run() { - try { underlyingFactory.getCBSChannel().sendToken( underlyingFactory.getReactorDispatcher(), underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY), @@ -143,15 +140,15 @@ public void onError(Exception error) { getClientId(), sendPath, getSendLinkName(), error.getMessage())); } } + }, + (exception) -> { + if (TRACE_LOGGER.isWarnEnabled()) { + TRACE_LOGGER.warn(String.format(Locale.US, + "clientId[%s], path[%s], linkName[%s] - tokenRenewalScheduleFailure[%s]", + getClientId(), sendPath, getSendLinkName(), exception.getMessage())); + } }); - } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | RuntimeException exception) { - if (TRACE_LOGGER.isWarnEnabled()) { - TRACE_LOGGER.warn(String.format(Locale.US, - "clientId[%s], path[%s], linkName[%s] - tokenRenewalScheduleFailure[%s]", - getClientId(), sendPath, getSendLinkName(), exception.getMessage())); - } } - } }, ClientConstants.TOKEN_REFRESH_INTERVAL, this.underlyingFactory); @@ -701,41 +698,40 @@ public void accept(ErrorCondition t, Exception u) { } }; - try { - this.underlyingFactory.getCBSChannel().sendToken( - this.underlyingFactory.getReactorDispatcher(), - this.underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY), - tokenAudience, - new OperationResult() { - @Override - public void onComplete(Void result) { - if (MessageSender.this.getIsClosingOrClosed()) { - return; - } - underlyingFactory.getSession( - sendPath, - onSessionOpen, - onSessionOpenError); + this.underlyingFactory.getCBSChannel().sendToken( + this.underlyingFactory.getReactorDispatcher(), + this.underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY), + tokenAudience, + new OperationResult() { + @Override + public void onComplete(Void result) { + if (MessageSender.this.getIsClosingOrClosed()) { + return; } + underlyingFactory.getSession( + sendPath, + onSessionOpen, + onSessionOpenError); + } - @Override - public void onError(Exception error) { - final Exception completionException; - if (error != null && error instanceof AmqpException) { - completionException = ExceptionUtil.toException(((AmqpException) error).getError()); - if (completionException != error && completionException.getCause() == null) { - completionException.initCause(error); - } - } else { - completionException = error; + @Override + public void onError(Exception error) { + final Exception completionException; + if (error != null && error instanceof AmqpException) { + completionException = ExceptionUtil.toException(((AmqpException) error).getError()); + if (completionException != error && completionException.getCause() == null) { + completionException.initCause(error); } - - MessageSender.this.onError(completionException); + } else { + completionException = error; } - }); - } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | RuntimeException exception) { - MessageSender.this.onError(exception); - } + + MessageSender.this.onError(completionException); + } + }, + (exception) -> { + MessageSender.this.onError(exception); + }); } private void scheduleLinkOpenTimeout(TimeoutTracker timeout) { diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/MessagingFactory.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/MessagingFactory.java index 2509288948c0b..74b6996e33210 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/MessagingFactory.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/MessagingFactory.java @@ -7,9 +7,13 @@ import com.microsoft.azure.eventhubs.CommunicationException; import com.microsoft.azure.eventhubs.ConnectionStringBuilder; import com.microsoft.azure.eventhubs.EventHubException; +import com.microsoft.azure.eventhubs.ITokenProvider; +import com.microsoft.azure.eventhubs.ManagedIdentityTokenProvider; import com.microsoft.azure.eventhubs.OperationCancelledException; import com.microsoft.azure.eventhubs.RetryPolicy; import com.microsoft.azure.eventhubs.TimeoutException; +import com.microsoft.azure.eventhubs.TransportType; + import org.apache.qpid.proton.amqp.Symbol; import org.apache.qpid.proton.amqp.transport.ErrorCondition; import org.apache.qpid.proton.engine.BaseHandler; @@ -31,6 +35,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.RejectedExecutionException; @@ -54,7 +59,7 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti private final Object reactorLock; private final Object cbsChannelCreateLock; private final Object mgmtChannelCreateLock; - private final SharedAccessSignatureTokenProvider tokenProvider; + private final ITokenProvider tokenProvider; private final ReactorFactory reactorFactory; private Reactor reactor; @@ -67,39 +72,51 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti private CompletableFuture open; private CompletableFuture openTimer; private CompletableFuture closeTimer; - private String reactorCreationTime; + private String reactorCreationTime; // used when looking at Java dumps, do not remove - MessagingFactory(final ConnectionStringBuilder builder, + MessagingFactory(final String hostname, + final Duration operationTimeout, + final TransportType transportType, + final ITokenProvider tokenProvider, final RetryPolicy retryPolicy, final ScheduledExecutorService executor, final ReactorFactory reactorFactory) { super(StringUtil.getRandomString("MF"), null, executor); - this.hostName = builder.getEndpoint().getHost(); + if (StringUtil.isNullOrWhiteSpace(hostname)) { + throw new IllegalArgumentException("Endpoint hostname cannot be null or empty"); + } + Objects.requireNonNull(operationTimeout, "Operation timeout cannot be null"); + Objects.requireNonNull(transportType, "Transport type cannot be null"); + Objects.requireNonNull(tokenProvider, "Token provider cannot be null"); + Objects.requireNonNull(retryPolicy, "Retry policy cannot be null"); + Objects.requireNonNull(executor, "Executor cannot be null"); + Objects.requireNonNull(reactorFactory, "Reactor factory cannot be null"); + + this.hostName = hostname; this.reactorFactory = reactorFactory; - this.operationTimeout = builder.getOperationTimeout(); + this.operationTimeout = operationTimeout; this.retryPolicy = retryPolicy; + this.connectionHandler = ConnectionHandler.create(transportType, this, this.getClientId()); + this.tokenProvider = tokenProvider; + this.registeredLinks = new LinkedList<>(); this.reactorLock = new Object(); - this.connectionHandler = ConnectionHandler.create(builder.getTransportType(), this, this.getClientId()); this.cbsChannelCreateLock = new Object(); this.mgmtChannelCreateLock = new Object(); - this.tokenProvider = builder.getSharedAccessSignature() == null - ? new SharedAccessSignatureTokenProvider(builder.getSasKeyName(), builder.getSasKey()) - : new SharedAccessSignatureTokenProvider(builder.getSharedAccessSignature()); this.closeTask = new CompletableFuture<>(); } public static CompletableFuture createFromConnectionString(final String connectionString, final ScheduledExecutorService executor) throws IOException { - return createFromConnectionString(connectionString, RetryPolicy.getDefault(), executor); + return createFromConnectionString(connectionString, null, executor); } public static CompletableFuture createFromConnectionString( final String connectionString, final RetryPolicy retryPolicy, final ScheduledExecutorService executor) throws IOException { - return createFromConnectionString(connectionString, retryPolicy, executor, new ReactorFactory()); + return createFromConnectionString(connectionString, retryPolicy, executor, null); } public static CompletableFuture createFromConnectionString( @@ -107,12 +124,90 @@ public static CompletableFuture createFromConnectionString( final RetryPolicy retryPolicy, final ScheduledExecutorService executor, final ReactorFactory reactorFactory) throws IOException { - final ConnectionStringBuilder builder = new ConnectionStringBuilder(connectionString); - final MessagingFactory messagingFactory = new MessagingFactory(builder, - (retryPolicy != null) ? retryPolicy : RetryPolicy.getDefault(), - executor, - reactorFactory); + final ConnectionStringBuilder csb = new ConnectionStringBuilder(connectionString); + ITokenProvider tokenProvider = null; + if (!StringUtil.isNullOrWhiteSpace(csb.getSharedAccessSignature())) { + tokenProvider = new SharedAccessSignatureTokenProvider(csb.getSharedAccessSignature()); + } else if (!StringUtil.isNullOrWhiteSpace(csb.getSasKey())) { + tokenProvider = new SharedAccessSignatureTokenProvider(csb.getSasKeyName(), csb.getSasKey()); + } else if ((csb.getAuthentication() != null) && csb.getAuthentication().equalsIgnoreCase(ConnectionStringBuilder.MANAGED_IDENTITY_AUTHENTICATION)) { + tokenProvider = new ManagedIdentityTokenProvider(); + } else { + throw new IllegalArgumentException("Connection string must specify a Shared Access Signature, Shared Access Key, or Managed Identity"); + } + + final MessagingFactoryBuilder builder = new MessagingFactoryBuilder(csb.getEndpoint().getHost(), tokenProvider, executor). + setOperationTimeout(csb.getOperationTimeout()). + setTransportType(csb.getTransportType()). + setRetryPolicy(retryPolicy). + setReactorFactory(reactorFactory); + return builder.build(); + } + + public static class MessagingFactoryBuilder { + // These parameters must always be specified by the caller + private final String hostname; + private final ITokenProvider tokenProvider; + private final ScheduledExecutorService executor; + + // Optional parameters with defaults + private Duration operationTimeout = DefaultOperationTimeout; + private TransportType transportType = TransportType.AMQP; + private RetryPolicy retryPolicy = RetryPolicy.getDefault(); + private ReactorFactory reactorFactory = new ReactorFactory(); + + public MessagingFactoryBuilder(final String hostname, final ITokenProvider tokenProvider, final ScheduledExecutorService executor) { + if (StringUtil.isNullOrWhiteSpace(hostname)) { + throw new IllegalArgumentException("Endpoint hostname cannot be null or empty"); + } + this.hostname = hostname; + + this.tokenProvider = Objects.requireNonNull(tokenProvider); + this.executor = Objects.requireNonNull(executor); + } + + public MessagingFactoryBuilder setOperationTimeout(Duration operationTimeout) { + if (operationTimeout != null) { + this.operationTimeout = operationTimeout; + } + return this; + } + + public MessagingFactoryBuilder setTransportType(TransportType transportType) { + if (transportType != null) { + this.transportType = transportType; + } + return this; + } + + public MessagingFactoryBuilder setRetryPolicy(RetryPolicy retryPolicy) { + if (retryPolicy != null) { + this.retryPolicy = retryPolicy; + } + return this; + } + + public MessagingFactoryBuilder setReactorFactory(ReactorFactory reactorFactory) { + if (reactorFactory != null) { + this.reactorFactory = reactorFactory; + } + return this; + } + + public CompletableFuture build() throws IOException { + final MessagingFactory messagingFactory = new MessagingFactory(this.hostname, + this.operationTimeout, + this.transportType, + this.tokenProvider, + this.retryPolicy, + this.executor, + this.reactorFactory); + return MessagingFactory.factoryStartup(messagingFactory); + } + } + private static CompletableFuture factoryStartup(MessagingFactory messagingFactory) throws IOException + { messagingFactory.createConnection(); final Timer timer = new Timer(messagingFactory); @@ -130,18 +225,18 @@ public void run() { // if scheduling messagingfactory openTimer fails - notify user and stop messagingFactory.openTimer.handleAsync( - (unUsed, exception) -> { - if (exception != null && !(exception instanceof CancellationException)) { - messagingFactory.open.completeExceptionally(exception); - messagingFactory.getReactor().stop(); - } + (unUsed, exception) -> { + if (exception != null && !(exception instanceof CancellationException)) { + messagingFactory.open.completeExceptionally(exception); + messagingFactory.getReactor().stop(); + } - return null; - }, messagingFactory.executor); + return null; + }, messagingFactory.executor); return messagingFactory.open; } - + @Override public String getHostName() { return this.hostName; @@ -159,7 +254,7 @@ public ReactorDispatcher getReactorDispatcher() { } } - public SharedAccessSignatureTokenProvider getTokenProvider() { + public ITokenProvider getTokenProvider() { return this.tokenProvider; } @@ -184,7 +279,7 @@ private void startReactor(final ReactorHandler reactorHandler) throws IOExceptio public CBSChannel getCBSChannel() { synchronized (this.cbsChannelCreateLock) { if (this.cbsChannel == null) { - this.cbsChannel = new CBSChannel(this, this, this.getClientId()); + this.cbsChannel = new CBSChannel(this, this, this.getClientId(), this.executor); } } diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/SharedAccessSignatureTokenProvider.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/SharedAccessSignatureTokenProvider.java index e5b412bc8a3a8..544ffe2b0ed53 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/SharedAccessSignatureTokenProvider.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/main/java/com/microsoft/azure/eventhubs/impl/SharedAccessSignatureTokenProvider.java @@ -5,6 +5,10 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; + +import com.microsoft.azure.eventhubs.ITokenProvider; +import com.microsoft.azure.eventhubs.SecurityToken; + import java.io.IOException; import java.net.URLEncoder; import java.security.InvalidKeyException; @@ -12,11 +16,13 @@ import java.time.Duration; import java.time.Instant; import java.util.Base64; +import java.util.Date; import java.util.Locale; +import java.util.concurrent.CompletableFuture; import static java.nio.charset.StandardCharsets.UTF_8; -public class SharedAccessSignatureTokenProvider { +public class SharedAccessSignatureTokenProvider implements ITokenProvider { final String keyName; final String sharedAccessKey; final String sharedAccessSignature; @@ -29,7 +35,7 @@ public class SharedAccessSignatureTokenProvider { this.sharedAccessSignature = null; } - public SharedAccessSignatureTokenProvider(final String sharedAccessSignature) { + SharedAccessSignatureTokenProvider(final String sharedAccessSignature) { this.keyName = null; this.sharedAccessKey = null; this.sharedAccessSignature = sharedAccessSignature; @@ -77,9 +83,21 @@ public static String generateSharedAccessSignature( URLEncoder.encode(keyName, utf8Encoding)); } - public String getToken(final String resource, final Duration tokenTimeToLive) throws IOException, InvalidKeyException, NoSuchAlgorithmException { - return this.sharedAccessSignature == null - ? generateSharedAccessSignature(this.keyName, this.sharedAccessKey, resource, tokenTimeToLive) - : this.sharedAccessSignature; + public CompletableFuture getToken(final String resource, final Duration tokenTimeToLive) { + final CompletableFuture result = new CompletableFuture<>(); + + if (this.sharedAccessSignature == null) { + try { + // Generation is nonblocking so there is no need to run it async. + final String token = generateSharedAccessSignature(this.keyName, this.sharedAccessKey, resource, ClientConstants.TOKEN_VALIDITY); + result.complete(new SecurityToken(token, Date.from(Instant.now().plus(ClientConstants.TOKEN_VALIDITY)), resource, ClientConstants.SAS_TOKEN_TYPE)); + } catch (NoSuchAlgorithmException|IOException|InvalidKeyException e) { + result.completeExceptionally(e); + } + } else { + result.complete(new SecurityToken(this.sharedAccessSignature, Date.from(Instant.now().plus(ClientConstants.TOKEN_VALIDITY)), resource, ClientConstants.SAS_TOKEN_TYPE)); + } + + return result; } } diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/concurrency/ConcurrentReceiversTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/concurrency/ConcurrentReceiversTest.java index b8fc071c9930c..2e9e4ea189717 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/concurrency/ConcurrentReceiversTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/concurrency/ConcurrentReceiversTest.java @@ -41,7 +41,7 @@ public class ConcurrentReceiversTest extends ApiTestBase { public static void initialize() throws InterruptedException, ExecutionException, EventHubException, IOException { connStr = TestContext.getConnectionString(); - sender = EventHubClient.create(connStr.toString(), TestContext.EXECUTOR_SERVICE).get(); + sender = EventHubClient.createFromConnectionString(connStr.toString(), TestContext.EXECUTOR_SERVICE).get(); partitionCount = sender.getRuntimeInformation().get().getPartitionCount(); receivers = new PartitionReceiver[partitionCount]; consumerGroupName = TestContext.getConsumerGroupName(); @@ -56,7 +56,7 @@ public static void cleanup() throws EventHubException { @Test() public void testParallelCreationOfReceivers() throws EventHubException, IOException, InterruptedException, ExecutionException, TimeoutException { - ehClient = EventHubClient.createSync(connStr.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connStr.toString(), TestContext.EXECUTOR_SERVICE); ReceiveAtleastOneEventValidator[] counter = new ReceiveAtleastOneEventValidator[partitionCount]; @SuppressWarnings("unchecked") CompletableFuture[] validationSignals = new CompletableFuture[partitionCount]; diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/concurrency/EventHubClientTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/concurrency/EventHubClientTest.java index d60ffbd782449..e30f7dc402af9 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/concurrency/EventHubClientTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/concurrency/EventHubClientTest.java @@ -32,7 +32,7 @@ public void testParallelEventHubClients() throws Exception { try { ConnectionStringBuilder connectionString = TestContext.getConnectionString(); for (int i = 0; i < noOfClients; i++) { - createFutures[i] = EventHubClient.create(connectionString.toString(), executorService); + createFutures[i] = EventHubClient.createFromConnectionString(connectionString.toString(), executorService); } CompletableFuture.allOf(createFutures).get(); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/connstrbuilder/TransportTypeTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/connstrbuilder/TransportTypeTest.java index 1b6ce12cc4a80..115b907599f8a 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/connstrbuilder/TransportTypeTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/connstrbuilder/TransportTypeTest.java @@ -34,7 +34,7 @@ public void transportTypeAmqpCreatesConnectionWithPort5671() throws Exception { ConnectionStringBuilder builder = new ConnectionStringBuilder(TestContext.getConnectionString().toString()); builder.setTransportType(TransportType.AMQP); - EventHubClient ehClient = EventHubClient.createSync(builder.toString(), TestContext.EXECUTOR_SERVICE); + EventHubClient ehClient = EventHubClient.createFromConnectionStringSync(builder.toString(), TestContext.EXECUTOR_SERVICE); try { EventHubClientImpl eventHubClientImpl = (EventHubClientImpl) ehClient; final Field factoryField = EventHubClientImpl.class.getDeclaredField("underlyingFactory"); @@ -63,7 +63,7 @@ public void transportTypeAmqpWebSocketsCreatesConnectionWithPort443() throws Exc ConnectionStringBuilder builder = new ConnectionStringBuilder(TestContext.getConnectionString().toString()); builder.setTransportType(TransportType.AMQP_WEB_SOCKETS); - EventHubClient ehClient = EventHubClient.createSync(builder.toString(), TestContext.EXECUTOR_SERVICE); + EventHubClient ehClient = EventHubClient.createFromConnectionStringSync(builder.toString(), TestContext.EXECUTOR_SERVICE); try { EventHubClientImpl eventHubClientImpl = (EventHubClientImpl) ehClient; final Field factoryField = EventHubClientImpl.class.getDeclaredField("underlyingFactory"); @@ -115,7 +115,7 @@ public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { ConnectionStringBuilder builder = new ConnectionStringBuilder(TestContext.getConnectionString().toString()); builder.setTransportType(TransportType.AMQP_WEB_SOCKETS); - EventHubClient ehClient = EventHubClient.createSync(builder.toString(), TestContext.EXECUTOR_SERVICE); + EventHubClient ehClient = EventHubClient.createFromConnectionStringSync(builder.toString(), TestContext.EXECUTOR_SERVICE); try { EventHubClientImpl eventHubClientImpl = (EventHubClientImpl) ehClient; final Field factoryField = EventHubClientImpl.class.getDeclaredField("underlyingFactory"); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/BackCompatTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/BackCompatTest.java index bf47fc400b369..7f0ab332ee012 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/BackCompatTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/BackCompatTest.java @@ -63,7 +63,7 @@ public static void initialize() throws EventHubException, IOException, Interrupt final ConnectionStringBuilder connStrBuilder = TestContext.getConnectionString(); final String connectionString = connStrBuilder.toString(); - ehClient = EventHubClient.createSync(connectionString, TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString, TestContext.EXECUTOR_SERVICE); msgFactory = MessagingFactory.createFromConnectionString(connectionString, TestContext.EXECUTOR_SERVICE).get(); receiver = ehClient.createReceiverSync(TestContext.getConsumerGroupName(), PARTITION_ID, EventPosition.fromEnqueuedTime(Instant.now())); partitionMsgSender = MessageSender.create(msgFactory, "link1", connStrBuilder.getEventHubName() + "/partitions/" + PARTITION_ID).get(); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/EventDataBatchTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/EventDataBatchTest.java index 29d374d024b01..3e267fdcba6c0 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/EventDataBatchTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/EventDataBatchTest.java @@ -24,7 +24,7 @@ public class EventDataBatchTest extends ApiTestBase { @Test(expected = PayloadSizeExceededException.class) public void payloadExceededException() throws EventHubException, IOException { final ConnectionStringBuilder connStrBuilder = TestContext.getConnectionString(); - ehClient = EventHubClient.createSync(connStrBuilder.toString(), Executors.newScheduledThreadPool(1)); + ehClient = EventHubClient.createFromConnectionStringSync(connStrBuilder.toString(), Executors.newScheduledThreadPool(1)); final EventDataBatch batch = ehClient.createBatch(); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/InteropAmqpPropertiesTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/InteropAmqpPropertiesTest.java index d0a40e7b14faf..80477cf73b570 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/InteropAmqpPropertiesTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/InteropAmqpPropertiesTest.java @@ -98,7 +98,7 @@ public static void initialize() throws EventHubException, IOException, Interrupt final ConnectionStringBuilder connStrBuilder = TestContext.getConnectionString(); final String connectionString = connStrBuilder.toString(); - ehClient = EventHubClient.createSync(connectionString, TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString, TestContext.EXECUTOR_SERVICE); msgFactory = MessagingFactory.createFromConnectionString(connectionString, TestContext.EXECUTOR_SERVICE).get(); receiver = ehClient.createReceiverSync(TestContext.getConsumerGroupName(), PARTITION_ID, EventPosition.fromEnqueuedTime(Instant.now())); partitionMsgSender = MessageSender.create(msgFactory, "link1", connStrBuilder.getEventHubName() + "/partitions/" + PARTITION_ID).get(); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/InteropEventBodyTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/InteropEventBodyTest.java index 2781e685ef254..1b86eaf1bc9bb 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/InteropEventBodyTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/eventdata/InteropEventBodyTest.java @@ -48,7 +48,7 @@ public static void initialize() throws EventHubException, IOException, Interrupt final ConnectionStringBuilder connStrBuilder = TestContext.getConnectionString(); final String connectionString = connStrBuilder.toString(); - ehClient = EventHubClient.createSync(connectionString, TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString, TestContext.EXECUTOR_SERVICE); msgFactory = MessagingFactory.createFromConnectionString(connectionString, TestContext.EXECUTOR_SERVICE).get(); receiver = ehClient.createReceiverSync(TestContext.getConsumerGroupName(), PARTITION_ID, EventPosition.fromEnqueuedTime(Instant.now())); partitionSender = ehClient.createPartitionSenderSync(PARTITION_ID); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/ClientEntityCreateTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/ClientEntityCreateTest.java index adb87f217159b..f9e6bfcdc4462 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/ClientEntityCreateTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/ClientEntityCreateTest.java @@ -40,7 +40,7 @@ public void createReceiverShouldRetryAndThrowTimeoutExceptionUponRepeatedTransie final ConnectionStringBuilder localConnStr = new ConnectionStringBuilder(connStr.toString()); localConnStr.setOperationTimeout(Duration.ofSeconds(SHORT_TIMEOUT)); // to retry atleast once - final EventHubClient eventHubClient = EventHubClient.createSync(localConnStr.toString(), TestContext.EXECUTOR_SERVICE); + final EventHubClient eventHubClient = EventHubClient.createFromConnectionStringSync(localConnStr.toString(), TestContext.EXECUTOR_SERVICE); try { eventHubClient.createReceiverSync("nonexistantcg", PARTITION_ID, EventPosition.fromStartOfStream()); @@ -63,7 +63,7 @@ public void createSenderShouldRetryAndThrowTimeoutExceptionUponRepeatedTransient final ConnectionStringBuilder localConnStr = new ConnectionStringBuilder(connStr.toString()); localConnStr.setOperationTimeout(Duration.ofSeconds(SHORT_TIMEOUT)); // to retry atleast once localConnStr.setEventHubName("nonexistanteventhub"); - final EventHubClient eventHubClient = EventHubClient.createSync(localConnStr.toString(), TestContext.EXECUTOR_SERVICE); + final EventHubClient eventHubClient = EventHubClient.createFromConnectionStringSync(localConnStr.toString(), TestContext.EXECUTOR_SERVICE); try { eventHubClient.createPartitionSenderSync(PARTITION_ID); @@ -86,7 +86,7 @@ public void createInternalSenderShouldRetryAndThrowTimeoutExceptionUponRepeatedT final ConnectionStringBuilder localConnStr = new ConnectionStringBuilder(connStr.toString()); localConnStr.setOperationTimeout(Duration.ofSeconds(SHORT_TIMEOUT)); // to retry atleast once localConnStr.setEventHubName("nonexistanteventhub"); - final EventHubClient eventHubClient = EventHubClient.createSync(localConnStr.toString(), TestContext.EXECUTOR_SERVICE); + final EventHubClient eventHubClient = EventHubClient.createFromConnectionStringSync(localConnStr.toString(), TestContext.EXECUTOR_SERVICE); try { eventHubClient.sendSync(EventData.create("Testmessage".getBytes())); @@ -137,7 +137,7 @@ public void accept(MessageReceiver messageReceiver) { try { ConnectionStringBuilder localConnectionStringBuilder = new ConnectionStringBuilder(connStr.toString()); localConnectionStringBuilder.setEventHubName(nonExistentEventHubName); - final EventHubClient eventHubClient = EventHubClient.createSync(localConnectionStringBuilder.toString(), TestContext.EXECUTOR_SERVICE); + final EventHubClient eventHubClient = EventHubClient.createFromConnectionStringSync(localConnectionStringBuilder.toString(), TestContext.EXECUTOR_SERVICE); eventHubClient.createReceiverSync(EventHubClient.DEFAULT_CONSUMER_GROUP_NAME, PARTITION_ID, EventPosition.fromStartOfStream()); eventHubClient.closeSync(); } finally { @@ -185,7 +185,7 @@ public void accept(MessageSender messageSender) { try { ConnectionStringBuilder localConnectionStringBuilder = new ConnectionStringBuilder(connStr.toString()); localConnectionStringBuilder.setEventHubName(nonExistentEventHubName); - final EventHubClient eventHubClient = EventHubClient.createSync(localConnectionStringBuilder.toString(), TestContext.EXECUTOR_SERVICE); + final EventHubClient eventHubClient = EventHubClient.createFromConnectionStringSync(localConnectionStringBuilder.toString(), TestContext.EXECUTOR_SERVICE); eventHubClient.createPartitionSenderSync(PARTITION_ID); eventHubClient.closeSync(); } finally { @@ -201,7 +201,7 @@ public void createReceiverShouldThrowRespectiveExceptionUponNonTransientErrors() final ConnectionStringBuilder localConnStr = new ConnectionStringBuilder(connStr.toString()); localConnStr.setOperationTimeout(Duration.ofSeconds(SHORT_TIMEOUT)); // to retry atleast once - final EventHubClient eventHubClient = EventHubClient.createSync(localConnStr.toString(), TestContext.EXECUTOR_SERVICE); + final EventHubClient eventHubClient = EventHubClient.createFromConnectionStringSync(localConnStr.toString(), TestContext.EXECUTOR_SERVICE); try { eventHubClient.createReceiverSync("nonexistantcg", PARTITION_ID, EventPosition.fromStartOfStream()); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/MsgFactoryOpenCloseTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/MsgFactoryOpenCloseTest.java index ecb3278fafd26..a5d32ace9776c 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/MsgFactoryOpenCloseTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/MsgFactoryOpenCloseTest.java @@ -44,7 +44,7 @@ public void verifyTaskQueueEmptyOnMsgFactoryGracefulClose() throws Exception { final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); try { - final EventHubClient ehClient = EventHubClient.createSync( + final EventHubClient ehClient = EventHubClient.createFromConnectionStringSync( TestContext.getConnectionString().toString(), executor); @@ -72,7 +72,7 @@ public void verifyTaskQueueEmptyOnMsgFactoryWithPumpGracefulClose() throws Excep final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1); try { - final EventHubClient ehClient = EventHubClient.createSync( + final EventHubClient ehClient = EventHubClient.createFromConnectionStringSync( TestContext.getConnectionString().toString(), executor); @@ -150,7 +150,7 @@ public void supplyClosedExecutorServiceToEventHubClient() throws Exception { final ScheduledExecutorService testClosed = new ScheduledThreadPoolExecutor(1); testClosed.shutdown(); - EventHubClient.createSync( + EventHubClient.createFromConnectionStringSync( TestContext.getConnectionString().toString(), testClosed); } @@ -159,7 +159,7 @@ public void supplyClosedExecutorServiceToEventHubClient() throws Exception { public void supplyClosedExecutorServiceToSendOperation() throws Exception { final ScheduledExecutorService testClosed = Executors.newScheduledThreadPool(1); - final EventHubClient temp = EventHubClient.createSync( + final EventHubClient temp = EventHubClient.createFromConnectionStringSync( TestContext.getConnectionString().toString(), testClosed); temp.sendSync(EventData.create("test data - string".getBytes())); @@ -174,7 +174,7 @@ public void supplyClosedExecutorServiceToSendOperation() throws Exception { public void supplyClosedExecutorServiceToReceiveOperation() throws Exception { final ScheduledExecutorService testClosed = new ScheduledThreadPoolExecutor(1); - final PartitionReceiver temp = EventHubClient.createSync( + final PartitionReceiver temp = EventHubClient.createFromConnectionStringSync( TestContext.getConnectionString().toString(), testClosed) .createReceiverSync(TestContext.getConsumerGroupName(), PARTITION_ID, EventPosition.fromEndOfStream()); @@ -189,7 +189,7 @@ public void supplyClosedExecutorServiceToReceiveOperation() throws Exception { public void supplyClosedExecutorServiceToCreateLinkOperation() throws Exception { final ScheduledExecutorService testClosed = Executors.newScheduledThreadPool(1); - final EventHubClient temp = EventHubClient.createSync( + final EventHubClient temp = EventHubClient.createFromConnectionStringSync( TestContext.getConnectionString().toString(), testClosed); @@ -204,7 +204,7 @@ public void supplyClosedExecutorServiceToCreateLinkOperation() throws Exception public void supplyClosedExecutorServiceToCreateSenderOperation() throws Exception { final ScheduledExecutorService testClosed = new ScheduledThreadPoolExecutor(1); - final EventHubClient temp = EventHubClient.createSync( + final EventHubClient temp = EventHubClient.createFromConnectionStringSync( TestContext.getConnectionString().toString(), testClosed); @@ -218,7 +218,7 @@ public void supplyClosedExecutorServiceToCreateSenderOperation() throws Exceptio public void supplyClosedExecutorServiceToCreateReceiverOperation() throws Exception { final ScheduledExecutorService testClosed = Executors.newScheduledThreadPool(1); - final EventHubClient temp = EventHubClient.createSync( + final EventHubClient temp = EventHubClient.createFromConnectionStringSync( TestContext.getConnectionString().toString(), testClosed); @@ -232,7 +232,7 @@ public void supplyClosedExecutorServiceToCreateReceiverOperation() throws Except public void supplyClosedExecutorServiceThenMgmtOperation() throws Throwable { final ScheduledThreadPoolExecutor testClosed = new ScheduledThreadPoolExecutor(1); - final EventHubClient temp = EventHubClient.createSync( + final EventHubClient temp = EventHubClient.createFromConnectionStringSync( TestContext.getConnectionString().toString(), testClosed); @@ -250,7 +250,7 @@ public void supplyClosedExecutorServiceThenMgmtOperation() throws Throwable { public void supplyClosedExecutorServiceThenFactoryCloseOperation() throws Exception { final ScheduledExecutorService testClosed = Executors.newScheduledThreadPool(1); - final EventHubClient temp = EventHubClient.createSync( + final EventHubClient temp = EventHubClient.createFromConnectionStringSync( TestContext.getConnectionString().toString(), testClosed); @@ -264,7 +264,7 @@ public void supplyClosedExecutorServiceThenFactoryCloseOperation() throws Except public void supplyClosedExecutorServiceThenSenderCloseOperation() throws Exception { final ScheduledThreadPoolExecutor testClosed = new ScheduledThreadPoolExecutor(1); - final PartitionSender temp = EventHubClient.createSync( + final PartitionSender temp = EventHubClient.createFromConnectionStringSync( TestContext.getConnectionString().toString(), testClosed).createPartitionSenderSync(PARTITION_ID); @@ -278,7 +278,7 @@ public void supplyClosedExecutorServiceThenSenderCloseOperation() throws Excepti public void supplyClosedExecutorServiceThenReceiverCloseOperation() throws Exception { final ScheduledExecutorService testClosed = Executors.newScheduledThreadPool(1); - final PartitionReceiver temp = EventHubClient.createSync( + final PartitionReceiver temp = EventHubClient.createFromConnectionStringSync( TestContext.getConnectionString().toString(), testClosed).createReceiverSync(TestContext.getConsumerGroupName(), PARTITION_ID, EventPosition.fromEndOfStream()); @@ -291,7 +291,7 @@ public void supplyClosedExecutorServiceThenReceiverCloseOperation() throws Excep @Test(expected = RejectedExecutionException.class) public void testEventHubClientSendAfterClose() throws Exception { final ConnectionStringBuilder connectionString = TestContext.getConnectionString(); - final EventHubClient eventHubClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + final EventHubClient eventHubClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); eventHubClient.closeSync(); eventHubClient.sendSync(EventData.create("test message".getBytes())); } @@ -299,7 +299,7 @@ public void testEventHubClientSendAfterClose() throws Exception { @Test(expected = IllegalStateException.class) public void testEventHubClientSendCloseAfterSomeSends() throws Exception { final ConnectionStringBuilder connectionString = TestContext.getConnectionString(); - final EventHubClient eventHubClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + final EventHubClient eventHubClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); eventHubClient.sendSync(EventData.create("test message".getBytes())); eventHubClient.closeSync(); eventHubClient.sendSync(EventData.create("test message".getBytes())); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/ReactorFaultTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/ReactorFaultTest.java index f3cb525cb257c..2f56cfd238aaf 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/ReactorFaultTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/ReactorFaultTest.java @@ -38,7 +38,7 @@ public static void initialize() { @Ignore("TODO: Investigate testcase. This fails.") @Test() public void verifyReactorRestartsOnProtonBugs() throws Exception { - final EventHubClient eventHubClient = EventHubClient.createSync(connStr.toString(), TestContext.EXECUTOR_SERVICE); + final EventHubClient eventHubClient = EventHubClient.createFromConnectionStringSync(connStr.toString(), TestContext.EXECUTOR_SERVICE); try { final PartitionReceiver partitionReceiver = eventHubClient.createEpochReceiverSync( "$default", "0", EventPosition.fromStartOfStream(), System.currentTimeMillis()); @@ -84,7 +84,7 @@ public void handle(org.apache.qpid.proton.engine.Event e) { @Test() public void verifyTransportAbort() throws Exception { - final EventHubClient eventHubClient = EventHubClient.createSync(connStr.toString(), TestContext.EXECUTOR_SERVICE); + final EventHubClient eventHubClient = EventHubClient.createFromConnectionStringSync(connStr.toString(), TestContext.EXECUTOR_SERVICE); try { final PartitionReceiver partitionReceiver = eventHubClient.createEpochReceiverSync( "$default", "0", EventPosition.fromStartOfStream(), System.currentTimeMillis()); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/ReceiverEpochTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/ReceiverEpochTest.java index 75738ad5c27ff..e82d3d907c4cf 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/ReceiverEpochTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/ReceiverEpochTest.java @@ -35,7 +35,7 @@ public class ReceiverEpochTest extends ApiTestBase { @BeforeClass public static void initializeEventHub() throws EventHubException, IOException { final ConnectionStringBuilder connectionString = TestContext.getConnectionString(); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); } @AfterClass diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/SecurityExceptionsTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/SecurityExceptionsTest.java index b070a110e0ad5..ef0695d54e9f4 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/SecurityExceptionsTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/SecurityExceptionsTest.java @@ -36,7 +36,7 @@ public void testEventHubClientUnAuthorizedAccessKeyName() throws Throwable { .setSasKeyName("---------------wrongkey------------") .setSasKey(correctConnectionString.getSasKey()); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); ehClient.sendSync(EventData.create("Test Message".getBytes())); } @@ -49,7 +49,7 @@ public void testEventHubClientUnAuthorizedAccessKey() throws Throwable { .setSasKeyName(correctConnectionString.getSasKeyName()) .setSasKey("--------------wrongvalue-----------"); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); ehClient.sendSync(EventData.create("Test Message".getBytes())); } @@ -62,7 +62,7 @@ public void testEventHubClientInvalidAccessToken() throws Throwable { .setSharedAccessSignature("--------------invalidtoken-------------") .setOperationTimeout(Duration.ofSeconds(15)); - ehClient = EventHubClient.createSync(connectionString.toString(), RetryPolicy.getNoRetry(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), RetryPolicy.getNoRetry(), TestContext.EXECUTOR_SERVICE); try { ehClient.sendSync(EventData.create(("Test Message".getBytes()))); @@ -80,7 +80,7 @@ public void testEventHubClientNullKeyNameAndAccessToken() throws Throwable { .setSharedAccessSignature(null) .setOperationTimeout(Duration.ofSeconds(10)); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); ehClient.sendSync(EventData.create(("Test Message".getBytes()))); } @@ -97,7 +97,7 @@ public void testEventHubClientUnAuthorizedAccessToken() throws Throwable { .setEventHubName(correctConnectionString.getEventHubName()) .setSharedAccessSignature(wrongToken); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); ehClient.sendSync(EventData.create("Test Message".getBytes())); } @@ -114,7 +114,7 @@ public void testEventHubClientWrongResourceInAccessToken() throws Throwable { .setEventHubName(correctConnectionString.getEventHubName()) .setSharedAccessSignature(wrongToken); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); ehClient.sendSync(EventData.create("Test Message".getBytes())); } @@ -127,7 +127,7 @@ public void testUnAuthorizedAccessSenderCreation() throws Throwable { .setSasKeyName("------------wrongkeyname----------") .setSasKey(correctConnectionString.getSasKey()); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); ehClient.createPartitionSenderSync(PARTITION_ID); } @@ -140,7 +140,7 @@ public void testUnAuthorizedAccessReceiverCreation() throws Throwable { .setSasKeyName("---------------wrongkey------------") .setSasKey(correctConnectionString.getSasKey()); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); ehClient.createReceiverSync(TestContext.getConsumerGroupName(), PARTITION_ID, EventPosition.fromStartOfStream()); } @@ -153,7 +153,7 @@ public void testSendToNonExistentEventHub() throws Throwable { .setSasKeyName(correctConnectionString.getSasKeyName()) .setSasKey(correctConnectionString.getSasKey()); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); ehClient.sendSync(EventData.create("test string".getBytes())); } @@ -166,7 +166,7 @@ public void testReceiveFromNonExistentEventHub() throws Throwable { .setSasKeyName(correctConnectionString.getSasKeyName()) .setSasKey(correctConnectionString.getSasKey()); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); ehClient.createReceiverSync(TestContext.getConsumerGroupName(), PARTITION_ID, EventPosition.fromStartOfStream()); } diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/SendLargeMessageTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/SendLargeMessageTest.java index dee6bcbc38ed4..4ae26dcbf1fa8 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/SendLargeMessageTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/exceptioncontracts/SendLargeMessageTest.java @@ -36,10 +36,10 @@ public static void initialize() throws Exception { } public static void initializeEventHubClients(ConnectionStringBuilder connStr) throws Exception { - ehClient = EventHubClient.createSync(connStr.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connStr.toString(), TestContext.EXECUTOR_SERVICE); sender = ehClient.createPartitionSender(PARTITION_ID).get(); - receiverHub = EventHubClient.createSync(connStr.toString(), TestContext.EXECUTOR_SERVICE); + receiverHub = EventHubClient.createFromConnectionStringSync(connStr.toString(), TestContext.EXECUTOR_SERVICE); receiver = receiverHub.createReceiver(TestContext.getConsumerGroupName(), PARTITION_ID, EventPosition.fromEnqueuedTime(Instant.now())).get(); } diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/proxy/ProxySelectorTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/proxy/ProxySelectorTest.java index acadd6b1d19d6..ae755cc32b4ea 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/proxy/ProxySelectorTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/proxy/ProxySelectorTest.java @@ -52,7 +52,7 @@ public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { builder.setOperationTimeout(Duration.ofSeconds(10)); try { - EventHubClient.createSync(builder.toString(), TestContext.EXECUTOR_SERVICE); + EventHubClient.createFromConnectionStringSync(builder.toString(), TestContext.EXECUTOR_SERVICE); Assert.fail(); } catch (EventHubException ex) { // The message can vary because it is returned from proton-j, so we don't want to compare against that. diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/AadBase.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/AadBase.java new file mode 100644 index 0000000000000..241bc9469a487 --- /dev/null +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/AadBase.java @@ -0,0 +1,168 @@ +package com.microsoft.azure.eventhubs.sendrecv; + +import java.net.MalformedURLException; +import java.net.URI; +import java.text.ParseException; +import java.time.Duration; +import java.util.Base64; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Supplier; + +import org.junit.Assert; +import org.junit.BeforeClass; + +import com.microsoft.azure.eventhubs.AzureActiveDirectoryTokenProvider; +import com.microsoft.azure.eventhubs.ConnectionStringBuilder; +import com.microsoft.azure.eventhubs.EventData; +import com.microsoft.azure.eventhubs.EventHubClient; +import com.microsoft.azure.eventhubs.EventHubException; +import com.microsoft.azure.eventhubs.EventHubRuntimeInformation; +import com.microsoft.azure.eventhubs.ITokenProvider; +import com.microsoft.azure.eventhubs.JsonSecurityToken; +import com.microsoft.azure.eventhubs.PartitionReceiver; +import com.microsoft.azure.eventhubs.PartitionSender; +import com.microsoft.azure.eventhubs.SecurityToken; +import com.microsoft.azure.eventhubs.impl.ClientConstants; +import com.microsoft.azure.eventhubs.impl.EventPositionImpl; +import com.microsoft.azure.eventhubs.lib.ApiTestBase; +import com.microsoft.azure.eventhubs.lib.TestContext; + +/** + * Because of the way JUnit is structured, the individual test classes for AAD libraries have to implement + * the actual @Test methods. Each test method tests a different way of creating an EventHubClient, then calls + * innerTest() to perform a common set of data-plane operations which the token is expected to allow. + * + * Each test class implements tokenGet() to do the actual token-getting via that library's APIs. All the layers + * above that (callback, ITokenProvider, etc.) are just different plumbing to get the same token to where it + * needs to go, so those parts are implemented here in the common base class. + * + * decodeToken() provides a convenient way to get the token contents as a printable string, allowing the tester + * to verify token contents if things are not working as expected. + */ +public abstract class AadBase extends ApiTestBase { + protected static URI endpoint; + protected static String eventHubName; + + protected final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(8); + + @BeforeClass + public static void initializeEventHub() throws Exception { + ConnectionStringBuilder csb = TestContext.getConnectionString(); + AadBase.endpoint = csb.getEndpoint(); + AadBase.eventHubName = csb.getEventHubName(); + } + + protected void innerTest(final EventHubClient ehc) throws EventHubException, InterruptedException, ExecutionException { + // Try some runtime operations. We don't care about the result, just that it doesn't throw. + EventHubRuntimeInformation ehri = ehc.getRuntimeInformation().get(); + ehc.getPartitionRuntimeInformation(ehri.getPartitionIds()[ehri.getPartitionCount() - 1]).get(); + + // Open a receiver + final PartitionReceiver pReceiver = ehc.createReceiverSync("$Default", "0", EventPositionImpl.fromEndOfStream()); + + // Open a sender and do a send. + final PartitionSender pSender = ehc.createPartitionSenderSync("0"); + final String testMessage = "somedata test"; + pSender.send(EventData.create(testMessage.getBytes())); + + // Do some receives. + int scanned = 0; + boolean found = false; + while ((scanned < 10000) && !found) { + System.out.println("Scanned " + scanned); + final Iterable events = pReceiver.receiveSync(100); + for (EventData ed : events) { + scanned++; + if ((new String(ed.getBytes())).equals(testMessage)) { + found = true; + break; + } + } + } + + // Close everything. + pSender.closeSync(); + pReceiver.closeSync();; + ehc.closeSync(); + + Assert.assertTrue(found); + } + + abstract String tokenGet(final String authority, final String clientId, final String clientSecret, final String audience, final String extra) + throws MalformedURLException, InterruptedException, ExecutionException; + + protected class AuthCallback implements AzureActiveDirectoryTokenProvider.AuthenticationCallback { + final private String clientId; + final private String clientSecret; + + public AuthCallback(final String clientId, final String clientSecret) { + this.clientId = clientId; + this.clientSecret = clientSecret; + } + + @Override + public CompletableFuture acquireToken(String audience, String authority, Object state) { + //(new Throwable()).printStackTrace(); + return CompletableFuture.supplyAsync(new TestTokenSupplier(authority, this.clientId, this.clientSecret, audience)); + } + } + + protected class CustomTokenProvider implements ITokenProvider { + final private TestTokenSupplier supplier; + + public CustomTokenProvider(final String authority, final String clientId, final String clientSecret) { + this.supplier = new TestTokenSupplier(authority, clientId, clientSecret, ClientConstants.EVENTHUBS_AUDIENCE); + } + + @Override + public CompletableFuture getToken(String resource, Duration timeout) { + return CompletableFuture.supplyAsync(this.supplier).thenApply((rawToken) -> { + try { + return new JsonSecurityToken(rawToken, resource); + } catch (ParseException e) { + throw new CompletionException(e); + } + }); + } + } + + protected class TestTokenSupplier implements Supplier { + final private String authority; + final private String clientId; + final private String clientSecret; + final private String audience; + + public TestTokenSupplier(final String authority, final String clientId, final String clientSecret, final String audience) { + this.authority = authority; + this.clientId = clientId; + this.clientSecret = clientSecret; + this.audience = audience; + } + + @Override + public String get() { + String retval = ""; + + try { + retval = tokenGet(this.authority, this.clientId, this.clientSecret, this.audience, ".default"); + } + catch (Exception e) { + throw new CompletionException(e); + } + + return retval; + } + } + + protected String decodeToken(final String rawToken) { + String parts[] = rawToken.split("\\."); + String output = new String(Base64.getDecoder().decode(parts[0])); + output += '.'; + output += new String(Base64.getDecoder().decode(parts[1])); + return output; + } +} diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/AdalTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/AdalTest.java new file mode 100644 index 0000000000000..7705a16cf917c --- /dev/null +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/AdalTest.java @@ -0,0 +1,64 @@ +package com.microsoft.azure.eventhubs.sendrecv; + +import java.net.MalformedURLException; +import java.util.concurrent.ExecutionException; + +import org.junit.Test; + +import com.microsoft.aad.adal4j.AuthenticationContext; +import com.microsoft.aad.adal4j.AuthenticationResult; +import com.microsoft.aad.adal4j.ClientCredential; +import com.microsoft.azure.eventhubs.AzureActiveDirectoryTokenProvider; +import com.microsoft.azure.eventhubs.EventHubClient; + +/** + * These JUnit test cases are all commented out by default because they can only be run with special setup. + * They extract the namespace (endpoint) and event hub name from the connection string in the environment variable + * which all test cases use, but they assume that the namespace (or event hub) has been set up with special permissions. + * Within the AAD directory indicated by "authority", there is a registered application with id "clientId" and a secret + * "clientSecret". This application has been granted the "Azure Event Hubs Data Owner" role on the namespace or + * event hub. + */ +public class AdalTest extends AadBase { + final private String authority = "https://login.windows.net/replaceWithTenantIdGuid"; + final private String clientId = "replaceWithClientIdGuid"; + final private String clientSecret = "replaceWithClientSecret"; + + //@Test + public void runSendReceiveWithAuthCallbackTest() throws Exception { + final AuthCallback callback = new AuthCallback(this.clientId, this.clientSecret); + final EventHubClient ehc = EventHubClient.createWithAzureActiveDirectory(MsalTest.endpoint, MsalTest.eventHubName, + callback, this.authority, this.executorService, null).get(); + + innerTest(ehc); + } + + //@Test + public void runSendReceiveWithAADTokenProvider() throws Exception { + final AuthCallback callback = new AuthCallback(this.clientId, this.clientSecret); + final AzureActiveDirectoryTokenProvider aadTokenProvider = + new AzureActiveDirectoryTokenProvider(callback, this.authority, null); + final EventHubClient ehc = EventHubClient.createWithTokenProvider(MsalTest.endpoint, MsalTest.eventHubName, aadTokenProvider, + this.executorService, null).get(); + + innerTest(ehc); + } + + //@Test + public void runSendReceiveWithCustomTokenProvider() throws Exception { + final CustomTokenProvider tokenProvider = new CustomTokenProvider(this.authority, this.clientId, this.clientSecret); + final EventHubClient ehc = EventHubClient.createWithTokenProvider(MsalTest.endpoint, MsalTest.eventHubName, tokenProvider, + this.executorService, null).get(); + + innerTest(ehc); + } + + @Override + String tokenGet(final String authority, final String clientId, final String clientSecret, final String audience, final String extra) + throws MalformedURLException, InterruptedException, ExecutionException { + AuthenticationContext context = new AuthenticationContext(authority, true, this.executorService); + ClientCredential creds = new ClientCredential(clientId, clientSecret); + AuthenticationResult result = context.acquireToken(audience, creds, null).get(); + return result.getAccessToken(); + } +} diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/EventDataBatchAPITest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/EventDataBatchAPITest.java index 7f4750197b376..9f2707e23dccb 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/EventDataBatchAPITest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/EventDataBatchAPITest.java @@ -43,7 +43,7 @@ public class EventDataBatchAPITest extends ApiTestBase { @BeforeClass public static void initializeEventHub() throws Exception { final ConnectionStringBuilder connectionString = TestContext.getConnectionString(); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); sender = ehClient.createPartitionSenderSync(PARTITION_ID); } diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ManagedIdentityTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ManagedIdentityTest.java new file mode 100644 index 0000000000000..bc6f9204b3b91 --- /dev/null +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ManagedIdentityTest.java @@ -0,0 +1,48 @@ +package com.microsoft.azure.eventhubs.sendrecv; + +import java.net.MalformedURLException; +import java.util.concurrent.ExecutionException; + +import org.junit.Test; + +import com.microsoft.azure.eventhubs.ConnectionStringBuilder; +import com.microsoft.azure.eventhubs.EventHubClient; +import com.microsoft.azure.eventhubs.ManagedIdentityTokenProvider; +import com.microsoft.azure.eventhubs.lib.TestContext; + +/** + * These JUnit test cases are all commented out by default because they can only be run with special setup. + * They assume that they are running in a VM which has been set up with a managed identity, and that the namespace + * (or event hub) has been set up with that managed identity granted the "Azure Event Hubs Data Owner" role on the + * namespace or event hub. + */ +public class ManagedIdentityTest extends AadBase { + //@Test + public void runSendReceiveWithMITokenProvider() throws Exception { + final ManagedIdentityTokenProvider aadTokenProvider = new ManagedIdentityTokenProvider(); + final EventHubClient ehc = EventHubClient.createWithTokenProvider(ManagedIdentityTest.endpoint, + ManagedIdentityTest.eventHubName, aadTokenProvider, + this.executorService, null).get(); + + innerTest(ehc); + } + + //@Test + public void runSendReceiveWithMIConnectionString() throws Exception { + final ConnectionStringBuilder csb = TestContext.getConnectionString(); + // Remove SAS info and replace with "Authentication=Managed Identity" + csb.setSasKey(null); + csb.setSasKeyName(null); + csb.setAuthentication(ConnectionStringBuilder.MANAGED_IDENTITY_AUTHENTICATION); + final EventHubClient ehc = EventHubClient.createFromConnectionString(csb.toString(), this.executorService).get(); + + innerTest(ehc); + } + + @Override + String tokenGet(String authority, String clientId, String clientSecret, String audience, String extra) + throws MalformedURLException, InterruptedException, ExecutionException { + // Not used for these cases but required by AadBase + return null; + } +} diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/MsalTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/MsalTest.java new file mode 100644 index 0000000000000..605e400006327 --- /dev/null +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/MsalTest.java @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.azure.eventhubs.sendrecv; + +import com.microsoft.aad.msal4j.ClientCredentialParameters; +import com.microsoft.aad.msal4j.ClientSecret; +import com.microsoft.aad.msal4j.ConfidentialClientApplication; +import com.microsoft.aad.msal4j.IAuthenticationResult; +import com.microsoft.azure.eventhubs.AzureActiveDirectoryTokenProvider; +import com.microsoft.azure.eventhubs.EventHubClient; + +import java.net.MalformedURLException; +import java.util.Collections; +import java.util.concurrent.ExecutionException; + +import org.junit.Test; + +/** + * These JUnit test cases are all commented out by default because they can only be run with special setup. + * They extract the namespace (endpoint) and event hub name from the connection string in the environment variable + * which all test cases use, but they assume that the namespace (or event hub) has been set up with special permissions. + * Within the AAD directory indicated by "authority", there is a registered application with id "clientId" and a secret + * "clientSecret". This application has been granted the "Azure Event Hubs Data Owner" role on the namespace or + * event hub. + */ +public class MsalTest extends AadBase { + final private String authority = "https://login.windows.net/replaceWithTenantIdGuid"; + final private String clientId = "replaceWithClientIdGuid"; + final private String clientSecret = "replaceWithClientSecret"; + + //@Test + public void runSendReceiveWithAuthCallbackTest() throws Exception { + final AuthCallback callback = new AuthCallback(this.clientId, this.clientSecret); + final EventHubClient ehc = EventHubClient.createWithAzureActiveDirectory(MsalTest.endpoint, MsalTest.eventHubName, + callback, this.authority, this.executorService, null).get(); + + innerTest(ehc); + } + + //@Test + public void runSendReceiveWithAADTokenProvider() throws Exception { + final AuthCallback callback = new AuthCallback(this.clientId, this.clientSecret); + final AzureActiveDirectoryTokenProvider aadTokenProvider = + new AzureActiveDirectoryTokenProvider(callback, this.authority, null); + final EventHubClient ehc = EventHubClient.createWithTokenProvider(MsalTest.endpoint, MsalTest.eventHubName, aadTokenProvider, + this.executorService, null).get(); + + innerTest(ehc); + } + + //@Test + public void runSendReceiveWithCustomTokenProvider() throws Exception { + final CustomTokenProvider tokenProvider = new CustomTokenProvider(this.authority, this.clientId, this.clientSecret); + final EventHubClient ehc = EventHubClient.createWithTokenProvider(MsalTest.endpoint, MsalTest.eventHubName, tokenProvider, + this.executorService, null).get(); + + innerTest(ehc); + } + + @Override + String tokenGet(final String authority, final String clientId, final String clientSecret, final String audience, final String extra) + throws MalformedURLException, InterruptedException, ExecutionException { + ConfidentialClientApplication app = ConfidentialClientApplication.builder(clientId, new ClientSecret(clientSecret)) + .authority(authority) + .build(); + + ClientCredentialParameters parameters = ClientCredentialParameters.builder(Collections.singleton(audience + extra)).build(); + + IAuthenticationResult result = app.acquireToken(parameters).get(); + + return result.accessToken(); + } +} diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiveParallelManualTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiveParallelManualTest.java index eb73673bf219f..b6a9e82724398 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiveParallelManualTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiveParallelManualTest.java @@ -38,10 +38,10 @@ public static void initializeEventHub() throws Exception { final ConnectionStringBuilder connectionString = TestContext.getConnectionString(); ehClient = new EventHubClient[4]; - ehClient[0] = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); - ehClient[1] = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); - ehClient[2] = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); - ehClient[3] = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient[0] = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient[1] = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient[2] = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient[3] = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); } @AfterClass() diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceivePumpEventHubTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceivePumpEventHubTest.java index 30fba5766ef56..085ea3ac3fc07 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceivePumpEventHubTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceivePumpEventHubTest.java @@ -37,7 +37,7 @@ public class ReceivePumpEventHubTest extends ApiTestBase { @BeforeClass public static void initializeEventHub() throws EventHubException, IOException { final ConnectionStringBuilder connectionString = TestContext.getConnectionString(); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); } @AfterClass diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiveTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiveTest.java index ef25a601ca552..2638e7b8477ac 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiveTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiveTest.java @@ -43,7 +43,7 @@ public static void initialize() throws Exception { } public static void initializeEventHub(ConnectionStringBuilder connectionString) throws Exception { - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); TestBase.pushEventsToPartition(ehClient, PARTITION_ID, 25).get(); } diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiverIdentifierTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiverIdentifierTest.java index b1a40ba076ceb..b781f99ce4145 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiverIdentifierTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiverIdentifierTest.java @@ -35,7 +35,7 @@ public class ReceiverIdentifierTest extends ApiTestBase { public static void initializeEventHub() throws Exception { final ConnectionStringBuilder connectionString = TestContext.getConnectionString(); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); TestBase.pushEventsToPartition(ehClient, PARTITION_ID, SENT_EVENTS).get(); } diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiverRuntimeMetricsTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiverRuntimeMetricsTest.java index cb1eb8b46bc9d..a791a76924840 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiverRuntimeMetricsTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/ReceiverRuntimeMetricsTest.java @@ -37,7 +37,7 @@ public class ReceiverRuntimeMetricsTest extends ApiTestBase { public static void initializeEventHub() throws Exception { final ConnectionStringBuilder connectionString = TestContext.getConnectionString(); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); ReceiverOptions options = new ReceiverOptions(); options.setReceiverRuntimeMetricEnabled(true); diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/RequestResponseTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/RequestResponseTest.java index 13b24831731b9..f90719c4264be 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/RequestResponseTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/RequestResponseTest.java @@ -216,7 +216,7 @@ public void testGetRuntimes() throws Exception { } public void testGetRuntimeInfos(ConnectionStringBuilder connectionString) throws Exception { - EventHubClient ehc = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + EventHubClient ehc = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); EventHubRuntimeInformation ehInfo = ehc.getRuntimeInformation().get(); Assert.assertNotNull(ehInfo); @@ -258,7 +258,7 @@ public void testGetRuntimesWebSockets() throws Exception { @Test public void testGetRuntimeInfoCallTimesout() throws Exception { - final EventHubClientImpl eventHubClient = (EventHubClientImpl) EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + final EventHubClientImpl eventHubClient = (EventHubClientImpl) EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); // set operation timeout to 5ms - so that the actual operation doesn't event start final Field factoryField = EventHubClientImpl.class.getDeclaredField("underlyingFactory"); @@ -288,7 +288,7 @@ public void testGetRuntimesBadHub() throws EventHubException, IOException { .setEventHubName("NOHUBZZZZZ") .setSasKeyName(connectionString.getSasKeyName()) .setSasKey(connectionString.getSasKey()); - EventHubClient ehc = EventHubClient.createSync(bogusConnectionString.toString(), TestContext.EXECUTOR_SERVICE); + EventHubClient ehc = EventHubClient.createFromConnectionStringSync(bogusConnectionString.toString(), TestContext.EXECUTOR_SERVICE); try { ehc.getRuntimeInformation().get(); @@ -330,7 +330,7 @@ public void testGetRuntimesBadKeyname() throws EventHubException, IOException { .setEventHubName(connectionString.getEventHubName()) .setSasKeyName("xxxnokeyxxx") .setSasKey(connectionString.getSasKey()); - EventHubClient ehc = EventHubClient.createSync(bogusConnectionString.toString(), TestContext.EXECUTOR_SERVICE); + EventHubClient ehc = EventHubClient.createFromConnectionStringSync(bogusConnectionString.toString(), TestContext.EXECUTOR_SERVICE); try { ehc.getRuntimeInformation().get(); @@ -357,7 +357,7 @@ public void testGetRuntimesBadKeyname() throws EventHubException, IOException { @Test public void testGetRuntimesClosedClient() throws EventHubException, IOException, InterruptedException, ExecutionException { - EventHubClient ehc = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + EventHubClient ehc = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); ehc.closeSync(); try { diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/SendTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/SendTest.java index 3006234578c96..3ff1f229f58b9 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/SendTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/SendTest.java @@ -46,7 +46,7 @@ public static void initialize() throws Exception { } public static void initializeEventHub(final ConnectionStringBuilder connectionString) throws Exception { - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); } @AfterClass diff --git a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/SetPrefetchCountTest.java b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/SetPrefetchCountTest.java index b2dce20bf93ee..6f15ff6a1f6ca 100644 --- a/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/SetPrefetchCountTest.java +++ b/sdk/eventhubs/microsoft-azure-eventhubs/src/test/java/com/microsoft/azure/eventhubs/sendrecv/SetPrefetchCountTest.java @@ -39,7 +39,7 @@ public class SetPrefetchCountTest extends ApiTestBase { @BeforeClass public static void initializeEventHub() throws Exception { final ConnectionStringBuilder connectionString = TestContext.getConnectionString(); - ehClient = EventHubClient.createSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); + ehClient = EventHubClient.createFromConnectionStringSync(connectionString.toString(), TestContext.EXECUTOR_SERVICE); TestBase.pushEventsToPartition(ehClient, PARTITION_ID, EVENT_COUNT).get(); } diff --git a/sdk/eventhubs/pom.data.xml b/sdk/eventhubs/pom.data.xml index bd64c28df3f8b..29dd16eab9709 100644 --- a/sdk/eventhubs/pom.data.xml +++ b/sdk/eventhubs/pom.data.xml @@ -15,7 +15,7 @@ com.microsoft.azure azure-eventhubs-clients pom - 2.3.1 + 3.0.0 Microsoft Azure Event Hubs SDK Parent Java libraries for talking to Windows Azure Event Hubs