|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
""" |
|
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 |
|
|
|
import botocore.exceptions |
|
from botocore.session import Session |
|
from s3transfer.crt import ( |
|
BotocoreCRTCredentialsWrapper, |
|
BotocoreCRTRequestSerializer, |
|
CRTTransferManager, |
|
acquire_crt_s3_process_lock, |
|
create_s3_crt_client, |
|
) |
|
|
|
|
|
CRT_S3_CLIENT = None |
|
BOTOCORE_CRT_SERIALIZER = None |
|
|
|
CLIENT_CREATION_LOCK = threading.Lock() |
|
PROCESS_LOCK_NAME = 'boto3' |
|
|
|
|
|
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, lock, **kwargs |
|
): |
|
"""Create boto3 wrapper class to manage crt lock reference and S3 client.""" |
|
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, |
|
cred_wrapper, |
|
) |
|
|
|
|
|
def _initialize_crt_transfer_primatives(client, config): |
|
lock = acquire_crt_s3_process_lock(PROCESS_LOCK_NAME) |
|
if lock is None: |
|
|
|
|
|
|
|
return None, None |
|
|
|
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, lock |
|
) |
|
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, cred_provider): |
|
self.crt_client = crt_client |
|
self.process_lock = process_lock |
|
self.region = region |
|
self.cred_provider = cred_provider |
|
|
|
|
|
def is_crt_compatible_request(client, crt_s3_client): |
|
""" |
|
Boto3 client must use same signing region and credentials |
|
as the CRT_S3_CLIENT singleton. Otherwise fallback to classic. |
|
""" |
|
if crt_s3_client is None: |
|
return False |
|
|
|
boto3_creds = client._get_credentials() |
|
if boto3_creds is None: |
|
return False |
|
|
|
is_same_identity = compare_identity( |
|
boto3_creds.get_frozen_credentials(), crt_s3_client.cred_provider |
|
) |
|
is_same_region = client.meta.region_name == crt_s3_client.region |
|
return is_same_region and is_same_identity |
|
|
|
|
|
def compare_identity(boto3_creds, crt_s3_creds): |
|
try: |
|
crt_creds = crt_s3_creds() |
|
except botocore.exceptions.NoCredentialsError: |
|
return False |
|
|
|
is_matching_identity = ( |
|
boto3_creds.access_key == crt_creds.access_key_id |
|
and boto3_creds.secret_key == crt_creds.secret_access_key |
|
and boto3_creds.token == crt_creds.session_token |
|
) |
|
return is_matching_identity |
|
|
|
|
|
def create_crt_transfer_manager(client, config): |
|
"""Create a CRTTransferManager for optimized data transfer.""" |
|
crt_s3_client = get_crt_s3_client(client, config) |
|
if is_crt_compatible_request(client, crt_s3_client): |
|
return CRTTransferManager( |
|
crt_s3_client.crt_client, BOTOCORE_CRT_SERIALIZER |
|
) |
|
return None |
|
|