-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation of AWS CRT S3 transfers
- Loading branch information
1 parent
8413dfe
commit a5f36d3
Showing
4 changed files
with
237 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
# Copyright 2023 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 | ||
# | ||
# https://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. | ||
""" | ||
This file contains private functionality for interacting with the AWS | ||
Common Runtime library (awscrt) in boto3. | ||
All code contained within this file is for internal usage within this | ||
project and is not intended for external consumption. All interfaces | ||
contained within are subject to abrupt breaking changes. | ||
""" | ||
|
||
import threading | ||
|
||
from botocore.session import Session | ||
from s3transfer.crt import ( | ||
BotocoreCRTCredentialsWrapper, | ||
BotocoreCRTRequestSerializer, | ||
CRTTransferManager, | ||
acquire_crt_s3_process_lock, | ||
create_s3_crt_client, | ||
) | ||
|
||
# Singletons for CRT-backed transfers | ||
_CRT_S3_CLIENT = None | ||
_BOTOCORE_CRT_SERIALIZER = None | ||
|
||
_CLIENT_CREATION_LOCK = threading.Lock() | ||
|
||
|
||
def _create_crt_client(session, config, region_name, cred_provider): | ||
"""Create a CRT S3 Client for file transfer. | ||
Instantiating many of these may lead to degraded performance or | ||
system resource exhaustion. | ||
""" | ||
create_crt_client_kwargs = { | ||
'region': region_name, | ||
'use_ssl': True, | ||
'crt_credentials_provider': cred_provider, | ||
} | ||
return create_s3_crt_client(**create_crt_client_kwargs) | ||
|
||
|
||
def _create_crt_request_serializer(session, region_name): | ||
return BotocoreCRTRequestSerializer( | ||
session, {'region_name': region_name, 'endpoint_url': None} | ||
) | ||
|
||
|
||
def _create_crt_s3_client(session, config, region_name, credentials, **kwargs): | ||
"""Create boto3 wrapper class to manage crt lock reference and S3 client.""" | ||
lock = acquire_crt_s3_process_lock('boto3') | ||
if lock is None: | ||
# If we're unable to acquire the lock, we cannot | ||
# use the CRT in this process and should default to | ||
# the classic s3transfer manager. | ||
return None | ||
|
||
cred_wrapper = BotocoreCRTCredentialsWrapper(credentials) | ||
cred_provider = cred_wrapper.to_crt_credentials_provider() | ||
return CRTS3Client( | ||
_create_crt_client(session, config, region_name, cred_provider), | ||
lock, | ||
region_name, | ||
) | ||
|
||
|
||
def _initialize_crt_transfer_primatives(client, config): | ||
session = Session() | ||
region_name = client.meta.region_name | ||
credentials = client._get_credentials() | ||
|
||
serializer = _create_crt_request_serializer(session, region_name) | ||
s3_client = _create_crt_s3_client( | ||
session, config, region_name, credentials | ||
) | ||
return serializer, s3_client | ||
|
||
|
||
def get_crt_s3_client(client, config): | ||
global _CRT_S3_CLIENT | ||
global _BOTOCORE_CRT_SERIALIZER | ||
|
||
with _CLIENT_CREATION_LOCK: | ||
if _CRT_S3_CLIENT is None: | ||
serializer, s3_client = _initialize_crt_transfer_primatives( | ||
client, config | ||
) | ||
_BOTOCORE_CRT_SERIALIZER = serializer | ||
_CRT_S3_CLIENT = s3_client | ||
|
||
return _CRT_S3_CLIENT | ||
|
||
|
||
class CRTS3Client: | ||
""" | ||
This wrapper keeps track of our underlying CRT client, the lock used to | ||
acquire it and the region we've used to instantiate the client. | ||
Due to limitations in the existing CRT interfaces, we can only make calls | ||
in a single region and does not support redirects. We track the region to | ||
ensure we don't use the CRT client when a successful request cannot be made. | ||
""" | ||
|
||
def __init__(self, crt_client, process_lock, region): | ||
self.crt_client = crt_client | ||
self.process_lock = process_lock | ||
self.region = region | ||
|
||
|
||
def create_crt_transfer_manager(client, config): | ||
"""Create a CRTTransferManager for optimized data transfer.""" | ||
crt_s3_client = get_crt_s3_client(client, config) | ||
called_region = client.meta.region_name | ||
if crt_s3_client is not None and crt_s3_client.region == called_region: | ||
return CRTTransferManager( | ||
crt_s3_client.crt_client, _BOTOCORE_CRT_SERIALIZER | ||
) | ||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# Copyright 2023 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 | ||
# | ||
# https://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. | ||
|
||
import boto3 | ||
import s3transfer | ||
from botocore.compat import HAS_CRT | ||
|
||
from tests import mock, requires_crt | ||
|
||
if HAS_CRT: | ||
import boto3.crt | ||
|
||
|
||
USW2_S3_CLIENT = boto3.client('s3', region_name='us-west-2') | ||
USE1_S3_CLIENT = boto3.client('s3', region_name='us-east-1') | ||
|
||
|
||
@requires_crt() | ||
def test_create_crt_transfer_manager_with_lock_in_use(): | ||
with mock.patch('boto3.crt.acquire_crt_s3_process_lock') as lock: | ||
lock.return_value = None | ||
|
||
# Verify we can't create a second CRT client | ||
tm = boto3.crt.create_crt_transfer_manager(USW2_S3_CLIENT, None) | ||
assert tm is None | ||
|
||
|
||
@requires_crt() | ||
def test_create_crt_transfer_manager(): | ||
tm = boto3.crt.create_crt_transfer_manager(USW2_S3_CLIENT, None) | ||
assert isinstance(tm, s3transfer.crt.CRTTransferManager) | ||
|
||
|
||
@requires_crt() | ||
def test_crt_singleton_is_returned_every_call(): | ||
first_s3_client = boto3.crt.get_crt_s3_client(USW2_S3_CLIENT, None) | ||
second_s3_client = boto3.crt.get_crt_s3_client(USW2_S3_CLIENT, None) | ||
|
||
assert isinstance(first_s3_client, boto3.crt.CRTS3Client) | ||
assert first_s3_client is second_s3_client | ||
assert first_s3_client.crt_client is second_s3_client.crt_client | ||
|
||
|
||
@requires_crt() | ||
def test_create_crt_transfer_manager_w_client_in_wrong_region(): | ||
"""Ensure we don't return the crt transfer manager if client is in | ||
different region. The CRT isn't able to handle region redirects and | ||
will consistently fail. | ||
We can remove this test once we have this fixed on the CRT side. | ||
""" | ||
usw2_s3_client = boto3.crt.create_crt_transfer_manager( | ||
USW2_S3_CLIENT, None | ||
) | ||
assert isinstance(usw2_s3_client, boto3.crt.CRTTransferManager) | ||
|
||
use1_s3_client = boto3.crt.create_crt_transfer_manager( | ||
USE1_S3_CLIENT, None | ||
) | ||
assert use1_s3_client is None |