This is an automated email from the ASF dual-hosted git repository.

abhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new b94390b  Adding Shared Access resource support for azure (#12266)
b94390b is described below

commit b94390ba33fe4e1cb5588f166edeb94d76b2edcf
Author: Karan Kumar <[email protected]>
AuthorDate: Tue Feb 22 18:27:43 2022 +0530

    Adding Shared Access resource support for azure (#12266)
    
    Azure Blob storage has multiple modes of authentication. One of them is 
Shared access resource
    . This is very useful in cases when we do not want to add the account key 
in the druid properties .
---
 docs/development/extensions-core/azure.md          |  3 +-
 .../druid/storage/azure/AzureAccountConfig.java    | 15 +++-
 .../storage/azure/AzureStorageDruidModule.java     | 46 ++++++++---
 .../storage/azure/AzureStorageDruidModuleTest.java | 94 +++++++++++++++++++---
 website/.spelling                                  |  1 +
 5 files changed, 134 insertions(+), 25 deletions(-)

diff --git a/docs/development/extensions-core/azure.md 
b/docs/development/extensions-core/azure.md
index 116b3d5..d63e74d 100644
--- a/docs/development/extensions-core/azure.md
+++ b/docs/development/extensions-core/azure.md
@@ -33,7 +33,8 @@ To use this Apache Druid extension, 
[include](../../development/extensions.md#lo
 |--------|---------------|-----------|-------|
 |`druid.storage.type`|azure||Must be set.|
 |`druid.azure.account`||Azure Storage account name.|Must be set.|
-|`druid.azure.key`||Azure Storage account key.|Must be set.|
+|`druid.azure.key`||Azure Storage account key.|Optional. Either set key or 
sharedAccessStorageToken but not both.|
+|`druid.azure.sharedAccessStorageToken`||Azure Shared Storage access 
token|Optional. Either set key or sharedAccessStorageToken but not both.| 
 |`druid.azure.container`||Azure Storage container name.|Must be set.|
 |`druid.azure.prefix`|A prefix string that will be prepended to the blob names 
for the segments published to Azure deep storage| |""|
 |`druid.azure.protocol`|the protocol to use|http or https|https|
diff --git 
a/extensions-core/azure-extensions/src/main/java/org/apache/druid/storage/azure/AzureAccountConfig.java
 
b/extensions-core/azure-extensions/src/main/java/org/apache/druid/storage/azure/AzureAccountConfig.java
index a6b0888..235ae6f 100644
--- 
a/extensions-core/azure-extensions/src/main/java/org/apache/druid/storage/azure/AzureAccountConfig.java
+++ 
b/extensions-core/azure-extensions/src/main/java/org/apache/druid/storage/azure/AzureAccountConfig.java
@@ -41,9 +41,11 @@ public class AzureAccountConfig
   private String account;
 
   @JsonProperty
-  @NotNull
   private String key;
 
+  @JsonProperty
+  private String sharedAccessStorageToken;
+
   @SuppressWarnings("unused") // Used by Jackson deserialization?
   public void setProtocol(String protocol)
   {
@@ -86,4 +88,15 @@ public class AzureAccountConfig
   {
     return key;
   }
+
+  public String getSharedAccessStorageToken()
+  {
+    return sharedAccessStorageToken;
+  }
+
+  @SuppressWarnings("unused") // Used by Jackson deserialization?
+  public void setSharedAccessStorageToken(String sharedAccessStorageToken)
+  {
+    this.sharedAccessStorageToken = sharedAccessStorageToken;
+  }
 }
diff --git 
a/extensions-core/azure-extensions/src/main/java/org/apache/druid/storage/azure/AzureStorageDruidModule.java
 
b/extensions-core/azure-extensions/src/main/java/org/apache/druid/storage/azure/AzureStorageDruidModule.java
index 2a1b373..e870aa0 100644
--- 
a/extensions-core/azure-extensions/src/main/java/org/apache/druid/storage/azure/AzureStorageDruidModule.java
+++ 
b/extensions-core/azure-extensions/src/main/java/org/apache/druid/storage/azure/AzureStorageDruidModule.java
@@ -38,6 +38,7 @@ import org.apache.druid.guice.Binders;
 import org.apache.druid.guice.JsonConfigProvider;
 import org.apache.druid.guice.LazySingleton;
 import org.apache.druid.initialization.DruidModule;
+import org.apache.druid.java.util.common.ISE;
 import org.apache.druid.java.util.common.StringUtils;
 import org.apache.druid.storage.azure.blob.ListBlobItemHolderFactory;
 
@@ -52,7 +53,10 @@ public class AzureStorageDruidModule implements DruidModule
 {
 
   static final String SCHEME = "azure";
-  public static final String STORAGE_CONNECTION_STRING = 
"DefaultEndpointsProtocol=%s;AccountName=%s;AccountKey=%s";
+  public static final String
+      STORAGE_CONNECTION_STRING_WITH_KEY = 
"DefaultEndpointsProtocol=%s;AccountName=%s;AccountKey=%s";
+  public static final String
+      STORAGE_CONNECTION_STRING_WITH_TOKEN = 
"DefaultEndpointsProtocol=%s;AccountName=%s;SharedAccessSignature=%s";
   public static final String INDEX_ZIP_FILE_NAME = "index.zip";
 
   @Override
@@ -125,17 +129,39 @@ public class AzureStorageDruidModule implements 
DruidModule
   @LazySingleton
   public Supplier<CloudBlobClient> getCloudBlobClient(final AzureAccountConfig 
config)
   {
+    if ((config.getKey() != null && config.getSharedAccessStorageToken() != 
null)
+        ||
+        (config.getKey() == null && config.getSharedAccessStorageToken() == 
null)) {
+      throw new ISE("Either set 'key' or 'sharedAccessStorageToken' in the 
azure config but not both."
+                    + " Please refer to azure documentation.");
+    }
     return Suppliers.memoize(() -> {
       try {
-        CloudStorageAccount account = CloudStorageAccount.parse(
-            StringUtils.format(
-                STORAGE_CONNECTION_STRING,
-                config.getProtocol(),
-                config.getAccount(),
-                config.getKey()
-            )
-        );
-        return account.createCloudBlobClient();
+        final CloudStorageAccount account;
+        if (config.getKey() != null) {
+          account = CloudStorageAccount.parse(
+              StringUtils.format(
+                  STORAGE_CONNECTION_STRING_WITH_KEY,
+                  config.getProtocol(),
+                  config.getAccount(),
+                  config.getKey()
+              )
+
+          );
+          return account.createCloudBlobClient();
+        } else if (config.getSharedAccessStorageToken() != null) {
+          account = CloudStorageAccount.parse(StringUtils.format(
+              STORAGE_CONNECTION_STRING_WITH_TOKEN,
+              config.getProtocol(),
+              config.getAccount(),
+              config.getSharedAccessStorageToken()
+          ));
+          return account.createCloudBlobClient();
+        } else {
+          throw new ISE(
+              "None of 'key' or 'sharedAccessStorageToken' is set in the azure 
config."
+              + " Please refer to azure extension documentation.");
+        }
       }
       catch (URISyntaxException | InvalidKeyException e) {
         throw new RuntimeException(e);
diff --git 
a/extensions-core/azure-extensions/src/test/java/org/apache/druid/storage/azure/AzureStorageDruidModuleTest.java
 
b/extensions-core/azure-extensions/src/test/java/org/apache/druid/storage/azure/AzureStorageDruidModuleTest.java
index c8ce639..d811000 100644
--- 
a/extensions-core/azure-extensions/src/test/java/org/apache/druid/storage/azure/AzureStorageDruidModuleTest.java
+++ 
b/extensions-core/azure-extensions/src/test/java/org/apache/druid/storage/azure/AzureStorageDruidModuleTest.java
@@ -26,6 +26,7 @@ import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Key;
 import com.google.inject.Module;
+import com.google.inject.ProvisionException;
 import com.google.inject.TypeLiteral;
 import com.microsoft.azure.storage.StorageCredentials;
 import com.microsoft.azure.storage.blob.CloudBlobClient;
@@ -43,7 +44,9 @@ import org.easymock.EasyMock;
 import org.easymock.EasyMockSupport;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import javax.validation.Validation;
 import javax.validation.Validator;
@@ -54,8 +57,12 @@ import java.util.Properties;
 
 public class AzureStorageDruidModuleTest extends EasyMockSupport
 {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
   private static final String AZURE_ACCOUNT_NAME;
   private static final String AZURE_ACCOUNT_KEY;
+  private static final String AZURE_SHARED_ACCESS_TOKEN;
   private static final String AZURE_CONTAINER;
   private static final String AZURE_PREFIX;
   private static final int AZURE_MAX_LISTING_LENGTH;
@@ -67,8 +74,6 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
   private CloudObjectLocation cloudObjectLocation2;
   private ListBlobItem blobItem1;
   private ListBlobItem blobItem2;
-
-
   private Injector injector;
 
   static {
@@ -76,6 +81,7 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
       AZURE_ACCOUNT_NAME = "azureAccount1";
       AZURE_ACCOUNT_KEY = Base64.getUrlEncoder()
                                 
.encodeToString("azureKey1".getBytes(StandardCharsets.UTF_8.toString()));
+      AZURE_SHARED_ACCESS_TOKEN = "dummyToken";
       AZURE_CONTAINER = "azureContainer1";
       AZURE_PREFIX = "azurePrefix1";
       AZURE_MAX_LISTING_LENGTH = 10;
@@ -96,7 +102,7 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
   }
 
   @Test
-  public void test_getAzureAccountConfig_expectedConfig()
+  public void testGetAzureAccountConfigExpectedConfig()
   {
     injector = makeInjectorWithProperties(PROPERTIES);
     AzureAccountConfig azureAccountConfig = 
injector.getInstance(AzureAccountConfig.class);
@@ -106,7 +112,21 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
   }
 
   @Test
-  public void test_getAzureDataSegmentConfig_expectedConfig()
+  public void testGetAzureAccountConfigExpectedConfigWithSAS()
+  {
+    Properties properties = initializePropertes();
+    properties.setProperty("druid.azure.sharedAccessStorageToken", 
AZURE_SHARED_ACCESS_TOKEN);
+    properties.remove("druid.azure.key");
+
+    injector = makeInjectorWithProperties(properties);
+    AzureAccountConfig azureAccountConfig = 
injector.getInstance(AzureAccountConfig.class);
+
+    Assert.assertEquals(AZURE_ACCOUNT_NAME, azureAccountConfig.getAccount());
+    Assert.assertEquals(AZURE_SHARED_ACCESS_TOKEN, 
azureAccountConfig.getSharedAccessStorageToken());
+  }
+
+  @Test
+  public void testGetAzureDataSegmentConfigExpectedConfig()
   {
     injector = makeInjectorWithProperties(PROPERTIES);
     AzureDataSegmentConfig segmentConfig = 
injector.getInstance(AzureDataSegmentConfig.class);
@@ -116,7 +136,7 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
   }
 
   @Test
-  public void test_getAzureInputDataConfig_expectedConfig()
+  public void testGetAzureInputDataConfigExpectedConfig()
   {
     injector = makeInjectorWithProperties(PROPERTIES);
     AzureInputDataConfig inputDataConfig = 
injector.getInstance(AzureInputDataConfig.class);
@@ -125,7 +145,7 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
   }
 
   @Test
-  public void test_getBlobClient_expectedClient()
+  public void testGetBlobClientExpectedClient()
   {
     injector = makeInjectorWithProperties(PROPERTIES);
 
@@ -138,7 +158,7 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
   }
 
   @Test
-  public void test_getAzureStorageContainer_expectedClient()
+  public void testGetAzureStorageContainerExpectedClient()
   {
     injector = makeInjectorWithProperties(PROPERTIES);
 
@@ -154,7 +174,27 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
   }
 
   @Test
-  public void test_getAzureCloudBlobToLocationConverter_expectedConverted()
+  public void testGetAzureStorageContainerWithSASExpectedClient()
+  {
+    Properties properties = initializePropertes();
+    properties.setProperty("druid.azure.sharedAccessStorageToken", 
AZURE_SHARED_ACCESS_TOKEN);
+    properties.remove("druid.azure.key");
+
+    injector = makeInjectorWithProperties(properties);
+
+    Supplier<CloudBlobClient> cloudBlobClient = injector.getInstance(
+        Key.get(new TypeLiteral<Supplier<CloudBlobClient>>(){})
+    );
+
+    AzureAccountConfig azureAccountConfig = 
injector.getInstance(AzureAccountConfig.class);
+    Assert.assertEquals(AZURE_SHARED_ACCESS_TOKEN, 
azureAccountConfig.getSharedAccessStorageToken());
+
+    AzureStorage azureStorage = injector.getInstance(AzureStorage.class);
+    Assert.assertSame(cloudBlobClient.get(), 
azureStorage.getCloudBlobClient());
+  }
+
+  @Test
+  public void testGetAzureCloudBlobToLocationConverterExpectedConverted()
   {
     injector = makeInjectorWithProperties(PROPERTIES);
     AzureCloudBlobHolderToCloudObjectLocationConverter 
azureCloudBlobLocationConverter1 = injector.getInstance(
@@ -165,7 +205,7 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
   }
 
   @Test
-  public void test_getAzureByteSourceFactory_canCreateAzureByteSource()
+  public void testGetAzureByteSourceFactoryCanCreateAzureByteSource()
   {
     injector = makeInjectorWithProperties(PROPERTIES);
     AzureByteSourceFactory factory = 
injector.getInstance(AzureByteSourceFactory.class);
@@ -177,7 +217,7 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
   }
 
   @Test
-  public void test_getAzureEntityFactory_canCreateAzureEntity()
+  public void testGetAzureEntityFactoryCanCreateAzureEntity()
   {
     
EasyMock.expect(cloudObjectLocation1.getBucket()).andReturn(AZURE_CONTAINER);
     
EasyMock.expect(cloudObjectLocation2.getBucket()).andReturn(AZURE_CONTAINER);
@@ -195,7 +235,7 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
   }
 
   @Test
-  public void 
test_getAzureCloudBlobIteratorFactory_canCreateAzureCloudBlobIterator()
+  public void 
testGetAzureCloudBlobIteratorFactoryCanCreateAzureCloudBlobIterator()
   {
     injector = makeInjectorWithProperties(PROPERTIES);
     AzureCloudBlobIteratorFactory factory = 
injector.getInstance(AzureCloudBlobIteratorFactory.class);
@@ -207,7 +247,7 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
   }
 
   @Test
-  public void 
test_getAzureCloudBlobIterableFactory_canCreateAzureCloudBlobIterable()
+  public void 
testGetAzureCloudBlobIterableFactoryCanCreateAzureCloudBlobIterable()
   {
     injector = makeInjectorWithProperties(PROPERTIES);
     AzureCloudBlobIterableFactory factory = 
injector.getInstance(AzureCloudBlobIterableFactory.class);
@@ -219,7 +259,7 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
   }
 
   @Test
-  public void test_getListBlobItemDruidFactory_canCreateListBlobItemDruid()
+  public void testGetListBlobItemDruidFactoryCanCreateListBlobItemDruid()
   {
     injector = makeInjectorWithProperties(PROPERTIES);
     ListBlobItemHolderFactory factory = 
injector.getInstance(ListBlobItemHolderFactory.class);
@@ -246,6 +286,34 @@ public class AzureStorageDruidModuleTest extends 
EasyMockSupport
     );
   }
 
+  @Test
+  public void testBothAccountKeyAndSAStokenSet()
+  {
+    Properties properties = initializePropertes();
+    properties.setProperty("druid.azure.sharedAccessStorageToken", 
AZURE_SHARED_ACCESS_TOKEN);
+    expectedException.expect(ProvisionException.class);
+    expectedException.expectMessage("Either set 'key' or 
'sharedAccessStorageToken' in the azure config but not both");
+    makeInjectorWithProperties(properties).getInstance(
+        Key.get(new TypeLiteral<Supplier<CloudBlobClient>>()
+        {
+        })
+    );
+  }
+
+  @Test
+  public void testBothAccountKeyAndSAStokenUnset()
+  {
+    Properties properties = initializePropertes();
+    properties.remove("druid.azure.key");
+    expectedException.expect(ProvisionException.class);
+    expectedException.expectMessage("Either set 'key' or 
'sharedAccessStorageToken' in the azure config but not both");
+    makeInjectorWithProperties(properties).getInstance(
+        Key.get(new TypeLiteral<Supplier<CloudBlobClient>>()
+        {
+        })
+    );
+  }
+
   private Injector makeInjectorWithProperties(final Properties props)
   {
     return Guice.createInjector(
diff --git a/website/.spelling b/website/.spelling
index f67272f..e3bc69e 100644
--- a/website/.spelling
+++ b/website/.spelling
@@ -629,6 +629,7 @@ maxFetchCapacityBytes
 maxFetchRetry
 prefetchTriggerBytes
 shardSpecs
+sharedAccessStorageToken
  - ../docs/development/extensions-contrib/cloudfiles.md
 StaticCloudFilesFirehose
 cloudfiles

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to