Skip to content

Commit fc0ce27

Browse files
authored
fix: use of resolved shared key, when available, in transfer requests for … (#189)
* Use of resolved shared key, when available, in transfer requests for several files. * Add Account Cache and UT. * Transfer Account cache logic to own class. * Added Unit Tests to Account Cache. * Added Override annotation. * Update DEPENDENCIES file. * Update DEPENDENCIES file. * Fix incorrect implementation. * Update DEPENDENCIES file. * Update DEPENDENCIES file.
1 parent 6eb1ed9 commit fc0ce27

File tree

9 files changed

+289
-452
lines changed

9 files changed

+289
-452
lines changed

DEPENDENCIES

Lines changed: 0 additions & 401 deletions
Large diffs are not rendered by default.

extensions/common/azure/azure-blob-core/src/main/java/org/eclipse/edc/azure/blob/api/BlobStoreApi.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ public interface BlobStoreApi {
4141
* @param accountName The name of the storage account
4242
* @param containerName The name of the container within the storage account
4343
* @param directory The name of the folder within the container of the storage account
44+
* @param accountKey The key of the storage account
4445
* @return Lazy loaded list of blobs from folder specified by the input parameters
4546
*/
46-
List<BlobItem> listContainerFolder(String accountName, String containerName, String directory);
47+
List<BlobItem> listContainerFolder(String accountName, String containerName, String directory, String accountKey);
4748

4849
void putBlob(String accountName, String containerName, String blobName, byte[] data);
4950

extensions/common/azure/azure-blob-core/src/main/java/org/eclipse/edc/azure/blob/api/BlobStoreApiImpl.java

Lines changed: 17 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import com.azure.core.credential.AzureSasCredential;
1818
import com.azure.core.util.BinaryData;
1919
import com.azure.identity.DefaultAzureCredentialBuilder;
20-
import com.azure.storage.blob.BlobServiceClient;
2120
import com.azure.storage.blob.BlobServiceClientBuilder;
2221
import com.azure.storage.blob.models.BlobItem;
2322
import com.azure.storage.blob.models.ListBlobsOptions;
@@ -30,61 +29,61 @@
3029
import com.azure.storage.common.sas.AccountSasSignatureValues;
3130
import org.eclipse.edc.azure.blob.adapter.BlobAdapter;
3231
import org.eclipse.edc.azure.blob.adapter.DefaultBlobAdapter;
32+
import org.eclipse.edc.azure.blob.cache.AccountCache;
33+
import org.eclipse.edc.azure.blob.cache.AccountCacheImpl;
3334
import org.eclipse.edc.spi.security.Vault;
3435

3536
import java.time.OffsetDateTime;
36-
import java.util.HashMap;
3737
import java.util.List;
38-
import java.util.Map;
39-
import java.util.Objects;
38+
39+
import static org.eclipse.edc.azure.blob.utils.BlobStoreUtils.createEndpoint;
4040

4141
public class BlobStoreApiImpl implements BlobStoreApi {
4242

43-
private final Vault vault;
4443
private final String blobstoreEndpointTemplate;
45-
private final Map<String, BlobServiceClient> cache = new HashMap<>();
44+
private final AccountCache accountCache;
4645

4746
public BlobStoreApiImpl(Vault vault, String blobstoreEndpointTemplate) {
48-
this.vault = vault;
4947
this.blobstoreEndpointTemplate = blobstoreEndpointTemplate;
48+
this.accountCache = new AccountCacheImpl(vault, blobstoreEndpointTemplate);
5049
}
5150

5251
@Override
5352
public void createContainer(String accountName, String containerName) {
54-
getBlobServiceClient(accountName).createBlobContainer(containerName);
53+
accountCache.getBlobServiceClient(accountName).createBlobContainer(containerName);
5554
}
5655

5756
@Override
5857
public void deleteContainer(String accountName, String containerName) {
59-
getBlobServiceClient(accountName).deleteBlobContainer(containerName);
58+
accountCache.getBlobServiceClient(accountName).deleteBlobContainer(containerName);
6059
}
6160

6261
@Override
6362
public boolean exists(String accountName, String containerName) {
64-
return getBlobServiceClient(accountName).getBlobContainerClient(containerName).exists();
63+
return accountCache.getBlobServiceClient(accountName).getBlobContainerClient(containerName).exists();
6564
}
6665

6766
@Override
6867
public String createContainerSasToken(String accountName, String containerName, String permissionSpec, OffsetDateTime expiry) {
6968
var permissions = BlobContainerSasPermission.parse(permissionSpec);
7069
var values = new BlobServiceSasSignatureValues(expiry, permissions);
71-
return getBlobServiceClient(accountName).getBlobContainerClient(containerName).generateSas(values);
70+
return accountCache.getBlobServiceClient(accountName).getBlobContainerClient(containerName).generateSas(values);
7271
}
7372

7473
@Override
7574
public List<BlobItem> listContainer(String accountName, String containerName) {
76-
return getBlobServiceClient(accountName).getBlobContainerClient(containerName).listBlobs().stream().toList();
75+
return accountCache.getBlobServiceClient(accountName).getBlobContainerClient(containerName).listBlobs().stream().toList();
7776
}
7877

7978
@Override
80-
public List<BlobItem> listContainerFolder(String accountName, String containerName, String directory) {
79+
public List<BlobItem> listContainerFolder(String accountName, String containerName, String directory, String accountKey) {
8180
var options = new ListBlobsOptions().setPrefix(directory);
82-
return getBlobServiceClient(accountName).getBlobContainerClient(containerName).listBlobs(options, null).stream().toList();
81+
return accountCache.getBlobServiceClient(accountName, accountKey).getBlobContainerClient(containerName).listBlobs(options, null).stream().toList();
8382
}
8483

8584
@Override
8685
public void putBlob(String accountName, String containerName, String blobName, byte[] data) {
87-
var blobServiceClient = getBlobServiceClient(accountName);
86+
var blobServiceClient = accountCache.getBlobServiceClient(accountName);
8887
blobServiceClient.getBlobContainerClient(containerName).getBlobClient(blobName).upload(BinaryData.fromBytes(data), true);
8988
}
9089

@@ -95,41 +94,15 @@ public String createAccountSas(String accountName, String containerName, String
9594
var services = AccountSasService.parse("b");
9695
var resourceTypes = AccountSasResourceType.parse("co");
9796
var values = new AccountSasSignatureValues(expiry, permissions, services, resourceTypes);
98-
return getBlobServiceClient(accountName).generateAccountSas(values);
97+
return accountCache.getBlobServiceClient(accountName).generateAccountSas(values);
9998
}
10099

101100
@Override
102101
public byte[] getBlob(String account, String container, String blobName) {
103-
var client = getBlobServiceClient(account);
102+
var client = accountCache.getBlobServiceClient(account);
104103
return client.getBlobContainerClient(container).getBlobClient(blobName).downloadContent().toBytes();
105104
}
106105

107-
private BlobServiceClient getBlobServiceClient(String accountName) {
108-
Objects.requireNonNull(accountName, "accountName");
109-
110-
if (cache.containsKey(accountName)) {
111-
return cache.get(accountName);
112-
}
113-
114-
var accountKey = vault.resolveSecret(accountName + "-key1");
115-
var endpoint = createEndpoint(accountName);
116-
117-
var blobServiceClient = accountKey == null ?
118-
new BlobServiceClientBuilder().credential(new DefaultAzureCredentialBuilder().build())
119-
.endpoint(endpoint)
120-
.buildClient() :
121-
new BlobServiceClientBuilder().credential(createCredential(accountKey, accountName))
122-
.endpoint(endpoint)
123-
.buildClient();
124-
125-
cache.put(accountName, blobServiceClient);
126-
return blobServiceClient;
127-
}
128-
129-
private StorageSharedKeyCredential createCredential(String accountKey, String accountName) {
130-
return new StorageSharedKeyCredential(accountName, accountKey);
131-
}
132-
133106
@Override
134107
public BlobAdapter getBlobAdapter(String accountName, String containerName, String blobName, String sharedKey) {
135108
var builder = new BlobServiceClientBuilder().credential(new StorageSharedKeyCredential(accountName, sharedKey));
@@ -150,7 +123,7 @@ public BlobAdapter getBlobAdapter(String accountName, String containerName, Stri
150123

151124
private BlobAdapter getBlobAdapter(String accountName, String containerName, String blobName, BlobServiceClientBuilder builder) {
152125
var blobServiceClient = builder
153-
.endpoint(createEndpoint(accountName))
126+
.endpoint(createEndpoint(blobstoreEndpointTemplate, accountName))
154127
.buildClient();
155128

156129
var blockBlobClient = blobServiceClient
@@ -160,8 +133,4 @@ private BlobAdapter getBlobAdapter(String accountName, String containerName, Str
160133

161134
return new DefaultBlobAdapter(blockBlobClient);
162135
}
163-
164-
private String createEndpoint(String accountName) {
165-
return String.format(blobstoreEndpointTemplate, accountName);
166-
}
167136
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.azure.blob.cache;
16+
17+
import com.azure.storage.blob.BlobServiceClient;
18+
19+
public interface AccountCache {
20+
21+
22+
/**
23+
* Initially, confirms if account is stored in cache and, if so, returns it. If not, saves the account in cache and retrieves it.
24+
*
25+
* @param accountName The name of the storage account.
26+
* @param accountKey The key of the storage account
27+
* @return The blob service client corresponding to the client to a storage account.
28+
*/
29+
BlobServiceClient getBlobServiceClient(String accountName, String accountKey);
30+
31+
/**
32+
* Initially, confirms if account is stored in cache and, if so, returns it. If not, resolved the account key and saves the account in cache and retrieves it.
33+
*
34+
* @param accountName The name of the storage account.
35+
* @return The blob service client corresponding to the client to a storage account.
36+
*/
37+
BlobServiceClient getBlobServiceClient(String accountName);
38+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.azure.blob.cache;
16+
17+
import com.azure.identity.DefaultAzureCredentialBuilder;
18+
import com.azure.storage.blob.BlobServiceClient;
19+
import com.azure.storage.blob.BlobServiceClientBuilder;
20+
import com.azure.storage.common.StorageSharedKeyCredential;
21+
import org.eclipse.edc.spi.security.Vault;
22+
import org.eclipse.edc.util.string.StringUtils;
23+
24+
import java.util.HashMap;
25+
import java.util.Map;
26+
import java.util.Objects;
27+
28+
import static org.eclipse.edc.azure.blob.utils.BlobStoreUtils.createEndpoint;
29+
30+
public class AccountCacheImpl implements AccountCache {
31+
32+
private final Vault vault;
33+
private final Map<String, BlobServiceClient> accountCache = new HashMap<>();
34+
private final String endpointTemplate;
35+
36+
public AccountCacheImpl(Vault vault, String endpointTemplate) {
37+
this.vault = vault;
38+
this.endpointTemplate = endpointTemplate;
39+
}
40+
41+
@Override
42+
public BlobServiceClient getBlobServiceClient(String accountName) {
43+
if (isAccountInCache(accountName)) {
44+
return getAccount(accountName);
45+
}
46+
47+
var accountKey = vault.resolveSecret(accountName + "-key1");
48+
49+
return saveAccount(accountName, accountKey);
50+
}
51+
52+
@Override
53+
public BlobServiceClient getBlobServiceClient(String accountName, String accountKey) {
54+
if (StringUtils.isNullOrBlank(accountKey)) {
55+
return getBlobServiceClient(accountName);
56+
}
57+
58+
if (isAccountInCache(accountName)) {
59+
return getAccount(accountName);
60+
}
61+
62+
return saveAccount(accountName, accountKey);
63+
}
64+
65+
private BlobServiceClient getAccount(String accountName) {
66+
return accountCache.get(accountName);
67+
}
68+
69+
private boolean isAccountInCache(String accountName) {
70+
Objects.requireNonNull(accountName, "accountName");
71+
return accountCache.containsKey(accountName);
72+
}
73+
74+
private BlobServiceClient saveAccount(String accountName, String accountKey) {
75+
var endpoint = createEndpoint(endpointTemplate, accountName);
76+
77+
var blobServiceClient = accountKey == null ?
78+
new BlobServiceClientBuilder().credential(new DefaultAzureCredentialBuilder().build())
79+
.endpoint(endpoint)
80+
.buildClient() :
81+
new BlobServiceClientBuilder().credential(new StorageSharedKeyCredential(accountName, accountKey))
82+
.endpoint(endpoint)
83+
.buildClient();
84+
85+
accountCache.put(accountName, blobServiceClient);
86+
return blobServiceClient;
87+
}
88+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.azure.blob.utils;
16+
17+
public class BlobStoreUtils {
18+
19+
public static String createEndpoint(String endpointTemplate, String accountName) {
20+
return String.format(endpointTemplate, accountName);
21+
}
22+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Apache License, Version 2.0 which is available at
6+
* https://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* SPDX-License-Identifier: Apache-2.0
9+
*
10+
* Contributors:
11+
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation
12+
*
13+
*/
14+
15+
package org.eclipse.edc.azure.blob.cache;
16+
17+
import org.eclipse.edc.spi.security.Vault;
18+
import org.junit.jupiter.api.Test;
19+
20+
import static org.assertj.core.api.Assertions.assertThat;
21+
import static org.eclipse.edc.azure.blob.utils.BlobStoreUtils.createEndpoint;
22+
import static org.junit.jupiter.api.Assertions.assertEquals;
23+
import static org.mockito.Mockito.mock;
24+
25+
class AccountCacheImplTest {
26+
27+
private static final String ENDPOINT_TEMPLATE = "https://%s.blob.core.windows.net";
28+
private static final String ACCOUNT_NAME = "test_account";
29+
private static final String OTHER_ACCOUNT_NAME = "other_test_account";
30+
private static final String ACCOUNT_KEY = "test_key";
31+
32+
private final AccountCache accountCache = new AccountCacheImpl(mock(Vault.class), ENDPOINT_TEMPLATE);
33+
34+
@Test
35+
void getAccount_withKey_succeeds() {
36+
var result = accountCache.getBlobServiceClient(ACCOUNT_NAME, ACCOUNT_KEY);
37+
assertThat(result.getAccountName()).isEqualTo(ACCOUNT_NAME);
38+
assertEquals(result.getAccountUrl(), createEndpoint(ENDPOINT_TEMPLATE, ACCOUNT_NAME));
39+
}
40+
41+
@Test
42+
void getAccount_succeeds() {
43+
var result = accountCache.getBlobServiceClient(ACCOUNT_NAME);
44+
assertThat(result.getAccountName()).isEqualTo(ACCOUNT_NAME);
45+
assertEquals(result.getAccountUrl(), createEndpoint(ENDPOINT_TEMPLATE, ACCOUNT_NAME));
46+
}
47+
48+
@Test
49+
void getAccount_sameAccount_succeeds() {
50+
var firstAccount = accountCache.getBlobServiceClient(ACCOUNT_NAME);
51+
var secondAccount = accountCache.getBlobServiceClient(ACCOUNT_NAME);
52+
assertThat(firstAccount).isEqualTo(secondAccount);
53+
}
54+
55+
@Test
56+
void getAccount_differentAccounts_succeeds() {
57+
var firstAccount = accountCache.getBlobServiceClient(ACCOUNT_NAME);
58+
var secondAccount = accountCache.getBlobServiceClient(OTHER_ACCOUNT_NAME);
59+
assertThat(firstAccount).isNotEqualTo(secondAccount);
60+
}
61+
62+
@Test
63+
void getAccount_sameAccountWithKey_succeeds() {
64+
var firstAccount = accountCache.getBlobServiceClient(ACCOUNT_NAME, ACCOUNT_KEY);
65+
var secondAccount = accountCache.getBlobServiceClient(ACCOUNT_NAME, ACCOUNT_KEY);
66+
assertThat(firstAccount).isEqualTo(secondAccount);
67+
}
68+
69+
@Test
70+
void getAccount_WithNullKey_succeeds() {
71+
var result = accountCache.getBlobServiceClient(ACCOUNT_NAME, null);
72+
assertThat(result.getAccountName()).isEqualTo(ACCOUNT_NAME);
73+
}
74+
}

extensions/data-plane/data-plane-azure-storage/src/main/java/org/eclipse/edc/connector/dataplane/azure/storage/pipeline/AzureStorageDataSource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ private AzureStorageDataSource() {
5656
public StreamResult<Stream<Part>> openPartStream() {
5757

5858
if (!StringUtils.isNullOrBlank(blobPrefix)) {
59-
var folderBlobs = blobStoreApi.listContainerFolder(accountName, containerName, blobPrefix);
59+
var folderBlobs = blobStoreApi.listContainerFolder(accountName, containerName, blobPrefix, sharedKey);
6060
if (folderBlobs.isEmpty()) {
6161
monitor.severe(format("Error listing blobs in the container %s with prefix %s", containerName, blobPrefix));
6262
return failure(new StreamFailure(List.of(format("Error listing blobs in the container %s with prefix %s", containerName, blobPrefix)), GENERAL_ERROR));

0 commit comments

Comments
 (0)