Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cross-region-support-cache level changes #93

Merged
merged 3 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@

package software.amazon.awssdk.s3accessgrants.cache;

import software.amazon.awssdk.services.s3control.S3ControlAsyncClient;
import software.amazon.awssdk.services.s3control.model.S3ControlException;

public interface S3AccessGrantsAccountIdResolver {
/**
*
* @param accountId AWS AccountId from the request context parameter
* @param s3Prefix e.g., s3://bucket-name/path/to/helloworld.txt
* @param s3ControlAsyncClient S3ControlAsynClient that will be used for making the requests
* @return AWS AccountId of the S3 Access Grants Instance that owns the location scope of the s3Prefix
* @throws S3ControlException propagate S3ControlException from service call
*/
String resolve(String accountId, String s3Prefix) throws S3ControlException;
String resolve(String accountId, String s3Prefix, S3ControlAsyncClient s3ControlAsyncClient) throws S3ControlException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package software.amazon.awssdk.s3accessgrants.cache;

import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3control.S3ControlAsyncClient;
import software.amazon.awssdk.services.s3control.model.S3ControlException;

public interface S3AccessGrantsBucketRegionResolver {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Make this interface BucketRegionResolver and the impl S3AccessGrantsBucketRegionResolver.
Make interface names very generic and not specific

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank You. Will do this in the up-coming PR for threading.


/**
*
* @param bucket name of the bucket being accessed
* @throws S3Exception propagate S3Exception from service call
*/

Region resolve(String bucket) throws S3Exception;

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,13 @@
public class S3AccessGrantsCache {

private AsyncCache<CacheKey, AwsCredentialsIdentity> cache;
private final S3ControlAsyncClient s3ControlAsyncClient;
private int maxCacheSize;
private final S3AccessGrantsCachedAccountIdResolver s3AccessGrantsCachedAccountIdResolver;
private final int cacheExpirationTimePercentage;
private static final Logger logger = Logger.loggerFor(S3AccessGrantsCache.class);

private S3AccessGrantsCache (@NotNull S3ControlAsyncClient s3ControlAsyncClient,
S3AccessGrantsCachedAccountIdResolver resolver, int maxCacheSize, int cacheExpirationTimePercentage) {
if (s3ControlAsyncClient == null) {
throw new IllegalArgumentException("S3ControlAsyncClient is required");
}
this.s3ControlAsyncClient = s3ControlAsyncClient;
private S3AccessGrantsCache (@NotNull S3AccessGrantsCachedAccountIdResolver resolver, int maxCacheSize, int cacheExpirationTimePercentage) {

this.s3AccessGrantsCachedAccountIdResolver = resolver;
this.cacheExpirationTimePercentage = cacheExpirationTimePercentage;
this.maxCacheSize = maxCacheSize;
Expand All @@ -75,14 +70,12 @@ protected static S3AccessGrantsCache.Builder builder() {
public interface Builder {
S3AccessGrantsCache build();
S3AccessGrantsCache buildWithAccountIdResolver();
S3AccessGrantsCache.Builder s3ControlAsyncClient(S3ControlAsyncClient s3ControlAsyncClient);
S3AccessGrantsCache.Builder maxCacheSize(int maxCacheSize);
S3AccessGrantsCache.Builder cacheExpirationTimePercentage(int cacheExpirationTimePercentage);
S3AccessGrantsCache.Builder s3AccessGrantsCachedAccountIdResolver(S3AccessGrantsCachedAccountIdResolver s3AccessGrantsCachedAccountIdResolver);
}

static final class BuilderImpl implements S3AccessGrantsCache.Builder {
private S3ControlAsyncClient s3ControlAsyncClient;
private int maxCacheSize = DEFAULT_ACCESS_GRANTS_MAX_CACHE_SIZE;
private S3AccessGrantsCachedAccountIdResolver s3AccessGrantsCachedAccountIdResolver;
private int cacheExpirationTimePercentage;
Expand All @@ -93,22 +86,16 @@ private BuilderImpl() {
@Override
public S3AccessGrantsCache build() {
S3AccessGrantsCachedAccountIdResolver s3AccessGrantsCachedAccountIdResolver =
S3AccessGrantsCachedAccountIdResolver.builder().S3ControlAsyncClient(s3ControlAsyncClient).build();
return new S3AccessGrantsCache(s3ControlAsyncClient, s3AccessGrantsCachedAccountIdResolver, maxCacheSize, cacheExpirationTimePercentage);
S3AccessGrantsCachedAccountIdResolver.builder().build();
return new S3AccessGrantsCache(s3AccessGrantsCachedAccountIdResolver, maxCacheSize, cacheExpirationTimePercentage);
}

@Override
public S3AccessGrantsCache buildWithAccountIdResolver() {
return new S3AccessGrantsCache(s3ControlAsyncClient, s3AccessGrantsCachedAccountIdResolver, maxCacheSize,
return new S3AccessGrantsCache(s3AccessGrantsCachedAccountIdResolver, maxCacheSize,
cacheExpirationTimePercentage);
}

@Override
public Builder s3ControlAsyncClient(S3ControlAsyncClient s3ControlAsyncClient) {
this.s3ControlAsyncClient = s3ControlAsyncClient;
return this;
}

@Override
public Builder maxCacheSize(int maxCacheSize) {
this.maxCacheSize = maxCacheSize;
Expand All @@ -134,10 +121,11 @@ public Builder s3AccessGrantsCachedAccountIdResolver(S3AccessGrantsCachedAccount
* @param cacheKey CacheKey consists of AwsCredentialsIdentity, Permission, and S3Prefix.
* @param accountId Account Id of the requester
* @param s3AccessGrantsAccessDeniedCache instance of S3AccessGrantsAccessDeniedCache
* @param s3ControlAsyncClient S3ControlAsyncClient that will be used for making the requests
* @return cached Access Grants credentials.
*/
protected CompletableFuture<AwsCredentialsIdentity> getCredentials (CacheKey cacheKey, String accountId,
S3AccessGrantsAccessDeniedCache s3AccessGrantsAccessDeniedCache) throws S3ControlException {
S3AccessGrantsAccessDeniedCache s3AccessGrantsAccessDeniedCache, S3ControlAsyncClient s3ControlAsyncClient) throws S3ControlException {

logger.debug(()->"Fetching credentials from Access Grants for s3Prefix: " + cacheKey.s3Prefix);
CompletableFuture<AwsCredentialsIdentity> credentials = searchKeyInCacheAtPrefixLevel(cacheKey);
Expand All @@ -157,7 +145,10 @@ protected CompletableFuture<AwsCredentialsIdentity> getCredentials (CacheKey cac
if (credentials == null) {
try {
logger.debug(()->"Credentials not available in the cache. Fetching credentials from Access Grants service.");
credentials = getCredentialsFromService(cacheKey,accountId).thenApply(getDataAccessResponse -> {
if (s3ControlAsyncClient == null) {
throw new IllegalArgumentException("S3ControlAsyncClient is required");
}
credentials = getCredentialsFromService(cacheKey,accountId, s3ControlAsyncClient).thenApply(getDataAccessResponse -> {
Credentials accessGrantsCredentials = getDataAccessResponse.credentials();
long duration = getTTL(accessGrantsCredentials.expiration());
AwsSessionCredentials sessionCredentials = AwsSessionCredentials.builder().accessKeyId(accessGrantsCredentials.accessKeyId())
Expand Down Expand Up @@ -198,11 +189,12 @@ long getTTL(Instant expirationTime) {
* This method calls Access Grants service to get the credentials.
* @param cacheKey CacheKey consists of AwsCredentialsIdentity, Permission, and S3Prefix.
* @param accountId Account Id of the requester.
* @param s3ControlAsyncClient regional S3 Control client used for forwarding requests.
* @return Access Grants Credentials.
* @throws S3ControlException throws Exception received from service.
*/
private CompletableFuture<GetDataAccessResponse> getCredentialsFromService(CacheKey cacheKey, String accountId) throws S3ControlException{
String resolvedAccountId = s3AccessGrantsCachedAccountIdResolver.resolve(accountId, cacheKey.s3Prefix);
private CompletableFuture<GetDataAccessResponse> getCredentialsFromService(CacheKey cacheKey, String accountId, S3ControlAsyncClient s3ControlAsyncClient) throws S3ControlException{
String resolvedAccountId = s3AccessGrantsCachedAccountIdResolver.resolve(accountId, cacheKey.s3Prefix, s3ControlAsyncClient);
logger.debug(()->"Fetching credentials from Access Grants for accountId: " + resolvedAccountId + ", s3Prefix: " + cacheKey.s3Prefix +
", permission: " + cacheKey.permission + ", privilege: " + Privilege.DEFAULT);
GetDataAccessRequest dataAccessRequest = GetDataAccessRequest.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,12 @@
*/
public class S3AccessGrantsCachedAccountIdResolver implements S3AccessGrantsAccountIdResolver {

private final S3ControlAsyncClient S3ControlAsyncClient;
private int maxCacheSize;
private int expireCacheAfterWriteSeconds;
private static final Logger logger = Logger.loggerFor(S3AccessGrantsCachedAccountIdResolver.class);

private Cache<String, String> cache;

public S3ControlAsyncClient S3ControlAsyncClient() {
return S3ControlAsyncClient;
}

public int maxCacheSize() {
return maxCacheSize;
}
Expand All @@ -63,11 +58,7 @@ public int expireCacheAfterWriteSeconds() {
protected CacheStats getCacheStats() { return cache.stats(); }

@VisibleForTesting
S3AccessGrantsCachedAccountIdResolver(@NotNull S3ControlAsyncClient S3ControlAsyncClient) {
if (S3ControlAsyncClient == null) {
throw new IllegalArgumentException("S3ControlAsyncClient is required");
}
this.S3ControlAsyncClient = S3ControlAsyncClient;
S3AccessGrantsCachedAccountIdResolver() {
this.maxCacheSize = DEFAULT_ACCOUNT_ID_MAX_CACHE_SIZE;
this.expireCacheAfterWriteSeconds = DEFAULT_ACCOUNT_ID_EXPIRE_CACHE_AFTER_WRITE_SECONDS;
}
Expand All @@ -82,12 +73,15 @@ public static Builder builder() {
}

@Override
public String resolve(String accountId, String s3Prefix) {
public String resolve(String accountId, String s3Prefix, S3ControlAsyncClient s3ControlAsyncClient) {
String bucketName = getBucketName(s3Prefix);
String s3PrefixAccountId = cache.getIfPresent(bucketName);
if (s3PrefixAccountId == null) {
logger.debug(()->"Account Id not available in the cache. Fetching account from server.");
s3PrefixAccountId = resolveFromService(accountId, s3Prefix);
if (s3ControlAsyncClient == null) {
throw new IllegalArgumentException("S3ControlAsyncClient is required for the access grants instance account resolver!");
}
s3PrefixAccountId = resolveFromService(accountId, s3Prefix, s3ControlAsyncClient);
cache.put(bucketName, s3PrefixAccountId);
}
return s3PrefixAccountId;
Expand All @@ -98,9 +92,9 @@ public String resolve(String accountId, String s3Prefix) {
* @param s3Prefix e.g., s3://bucket-name/path/to/helloworld.txt
* @return accountId from the service response
*/
private String resolveFromService(String accountId, String s3Prefix) {
private String resolveFromService(String accountId, String s3Prefix, S3ControlAsyncClient s3ControlAsyncClient) {
CompletableFuture<GetAccessGrantsInstanceForPrefixResponse> accessGrantsInstanceForPrefix =
S3ControlAsyncClient.getAccessGrantsInstanceForPrefix(GetAccessGrantsInstanceForPrefixRequest
s3ControlAsyncClient.getAccessGrantsInstanceForPrefix(GetAccessGrantsInstanceForPrefixRequest
.builder()
.accountId(accountId)
.s3Prefix(s3Prefix)
Expand All @@ -117,8 +111,6 @@ private String resolveFromService(String accountId, String s3Prefix) {
public interface Builder {
S3AccessGrantsCachedAccountIdResolver build();

Builder S3ControlAsyncClient(S3ControlAsyncClient S3ControlAsyncClient);

Builder maxCacheSize(int maxCacheSize);

Builder expireCacheAfterWriteSeconds(int expireCacheAfterWriteSeconds);
Expand All @@ -133,17 +125,10 @@ private BuilderImpl() {
}

public BuilderImpl(S3AccessGrantsCachedAccountIdResolver s3AccessGrantsCachedAccountIdResolver) {
S3ControlAsyncClient(s3AccessGrantsCachedAccountIdResolver.S3ControlAsyncClient);
maxCacheSize(s3AccessGrantsCachedAccountIdResolver.maxCacheSize);
expireCacheAfterWriteSeconds(s3AccessGrantsCachedAccountIdResolver.expireCacheAfterWriteSeconds);
}

@Override
public Builder S3ControlAsyncClient(S3ControlAsyncClient S3ControlAsyncClient) {
this.S3ControlAsyncClient = S3ControlAsyncClient;
return this;
}

public int maxCacheSize() {
return maxCacheSize;
}
Expand Down Expand Up @@ -174,7 +159,7 @@ public Builder expireCacheAfterWriteSeconds(int expireCacheAfterWriteSeconds) {

@Override
public S3AccessGrantsCachedAccountIdResolver build() {
S3AccessGrantsCachedAccountIdResolver resolver = new S3AccessGrantsCachedAccountIdResolver(S3ControlAsyncClient);
S3AccessGrantsCachedAccountIdResolver resolver = new S3AccessGrantsCachedAccountIdResolver();
resolver.maxCacheSize = maxCacheSize();
resolver.expireCacheAfterWriteSeconds = expireCAcheAfterWriteSeconds();
resolver.cache = Caffeine.newBuilder()
Expand Down
Loading
Loading