1987 support transfer acceleration (#2018)
* chore(): remove executable flag on moto/s3/response.py * chore(): ignore .eggs temp file * feat(#1987): get bucket acceleration support * feat(#1987): put bucket acceleration support * feat(#1987): suspend undefined bucket is a no-op * feat(#1987): validate accelerate_configuration status * feat(#1987): bucket containing dots do not support acceleration * doc(#1987): update implementation coverage
This commit is contained in:
parent
1b91534165
commit
d952410965
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,3 +18,4 @@ venv/
|
||||
.python-version
|
||||
.vscode/
|
||||
tests/file.tmp
|
||||
.eggs/
|
||||
|
@ -3540,7 +3540,7 @@
|
||||
- [ ] delete_object
|
||||
- [ ] delete_object_tagging
|
||||
- [ ] delete_objects
|
||||
- [ ] get_bucket_accelerate_configuration
|
||||
- [X] get_bucket_accelerate_configuration
|
||||
- [X] get_bucket_acl
|
||||
- [ ] get_bucket_analytics_configuration
|
||||
- [ ] get_bucket_cors
|
||||
@ -3574,7 +3574,7 @@
|
||||
- [ ] list_objects
|
||||
- [ ] list_objects_v2
|
||||
- [ ] list_parts
|
||||
- [ ] put_bucket_accelerate_configuration
|
||||
- [X] put_bucket_accelerate_configuration
|
||||
- [ ] put_bucket_acl
|
||||
- [ ] put_bucket_analytics_configuration
|
||||
- [X] put_bucket_cors
|
||||
|
@ -17,8 +17,11 @@ import six
|
||||
from bisect import insort
|
||||
from moto.core import BaseBackend, BaseModel
|
||||
from moto.core.utils import iso_8601_datetime_with_milliseconds, rfc_1123_datetime
|
||||
from .exceptions import BucketAlreadyExists, MissingBucket, InvalidBucketName, InvalidPart, \
|
||||
EntityTooSmall, MissingKey, InvalidNotificationDestination, MalformedXML, InvalidStorageClass, DuplicateTagKeys
|
||||
from .exceptions import (
|
||||
BucketAlreadyExists, MissingBucket, InvalidBucketName, InvalidPart, InvalidRequest,
|
||||
EntityTooSmall, MissingKey, InvalidNotificationDestination, MalformedXML, InvalidStorageClass,
|
||||
InvalidTargetBucketForLogging, DuplicateTagKeys, CrossLocationLoggingProhibitted
|
||||
)
|
||||
from .utils import clean_key_name, _VersionedKeyStore
|
||||
|
||||
MAX_BUCKET_NAME_LENGTH = 63
|
||||
@ -463,6 +466,7 @@ class FakeBucket(BaseModel):
|
||||
self.cors = []
|
||||
self.logging = {}
|
||||
self.notification_configuration = None
|
||||
self.accelerate_configuration = None
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
@ -557,7 +561,6 @@ class FakeBucket(BaseModel):
|
||||
self.rules = []
|
||||
|
||||
def set_cors(self, rules):
|
||||
from moto.s3.exceptions import InvalidRequest, MalformedXML
|
||||
self.cors = []
|
||||
|
||||
if len(rules) > 100:
|
||||
@ -607,7 +610,6 @@ class FakeBucket(BaseModel):
|
||||
self.logging = {}
|
||||
return
|
||||
|
||||
from moto.s3.exceptions import InvalidTargetBucketForLogging, CrossLocationLoggingProhibitted
|
||||
# Target bucket must exist in the same account (assuming all moto buckets are in the same account):
|
||||
if not bucket_backend.buckets.get(logging_config["TargetBucket"]):
|
||||
raise InvalidTargetBucketForLogging("The target bucket for logging does not exist.")
|
||||
@ -655,6 +657,13 @@ class FakeBucket(BaseModel):
|
||||
if region != self.region_name:
|
||||
raise InvalidNotificationDestination()
|
||||
|
||||
def set_accelerate_configuration(self, accelerate_config):
|
||||
if self.accelerate_configuration is None and accelerate_config == 'Suspended':
|
||||
# Cannot "suspend" a not active acceleration. Leaves it undefined
|
||||
return
|
||||
|
||||
self.accelerate_configuration = accelerate_config
|
||||
|
||||
def set_website_configuration(self, website_configuration):
|
||||
self.website_configuration = website_configuration
|
||||
|
||||
@ -857,6 +866,15 @@ class S3Backend(BaseBackend):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
bucket.set_notification_configuration(notification_config)
|
||||
|
||||
def put_bucket_accelerate_configuration(self, bucket_name, accelerate_configuration):
|
||||
if accelerate_configuration not in ['Enabled', 'Suspended']:
|
||||
raise MalformedXML()
|
||||
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
if bucket.name.find('.') != -1:
|
||||
raise InvalidRequest('PutBucketAccelerateConfiguration')
|
||||
bucket.set_accelerate_configuration(accelerate_configuration)
|
||||
|
||||
def initiate_multipart(self, bucket_name, key_name, metadata):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
new_multipart = FakeMultipart(key_name, metadata)
|
||||
|
31
moto/s3/responses.py
Executable file → Normal file
31
moto/s3/responses.py
Executable file → Normal file
@ -257,6 +257,13 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
return 200, {}, ""
|
||||
template = self.response_template(S3_GET_BUCKET_NOTIFICATION_CONFIG)
|
||||
return template.render(bucket=bucket)
|
||||
elif "accelerate" in querystring:
|
||||
bucket = self.backend.get_bucket(bucket_name)
|
||||
if bucket.accelerate_configuration is None:
|
||||
template = self.response_template(S3_BUCKET_ACCELERATE_NOT_SET)
|
||||
return 200, {}, template.render()
|
||||
template = self.response_template(S3_BUCKET_ACCELERATE)
|
||||
return template.render(bucket=bucket)
|
||||
|
||||
elif 'versions' in querystring:
|
||||
delimiter = querystring.get('delimiter', [None])[0]
|
||||
@ -442,6 +449,15 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
raise MalformedXML()
|
||||
except Exception as e:
|
||||
raise e
|
||||
elif "accelerate" in querystring:
|
||||
try:
|
||||
accelerate_status = self._accelerate_config_from_xml(body)
|
||||
self.backend.put_bucket_accelerate_configuration(bucket_name, accelerate_status)
|
||||
return ""
|
||||
except KeyError:
|
||||
raise MalformedXML()
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
else:
|
||||
if body:
|
||||
@ -1034,6 +1050,11 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
|
||||
return parsed_xml["NotificationConfiguration"]
|
||||
|
||||
def _accelerate_config_from_xml(self, xml):
|
||||
parsed_xml = xmltodict.parse(xml)
|
||||
config = parsed_xml['AccelerateConfiguration']
|
||||
return config['Status']
|
||||
|
||||
def _key_response_delete(self, bucket_name, query, key_name, headers):
|
||||
if query.get('uploadId'):
|
||||
upload_id = query['uploadId'][0]
|
||||
@ -1686,3 +1707,13 @@ S3_GET_BUCKET_NOTIFICATION_CONFIG = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
{% endfor %}
|
||||
</NotificationConfiguration>
|
||||
"""
|
||||
|
||||
S3_BUCKET_ACCELERATE = """
|
||||
<AccelerateConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Status>{{ bucket.accelerate_configuration }}</Status>
|
||||
</AccelerateConfiguration>
|
||||
"""
|
||||
|
||||
S3_BUCKET_ACCELERATE_NOT_SET = """
|
||||
<AccelerateConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>
|
||||
"""
|
||||
|
@ -2820,3 +2820,80 @@ def test_boto3_bucket_name_too_short():
|
||||
with assert_raises(ClientError) as exc:
|
||||
s3.create_bucket(Bucket='x'*2)
|
||||
exc.exception.response['Error']['Code'].should.equal('InvalidBucketName')
|
||||
|
||||
@mock_s3
|
||||
def test_accelerated_none_when_unspecified():
|
||||
bucket_name = 'some_bucket'
|
||||
s3 = boto3.client('s3')
|
||||
s3.create_bucket(Bucket=bucket_name)
|
||||
resp = s3.get_bucket_accelerate_configuration(Bucket=bucket_name)
|
||||
resp.shouldnt.have.key('Status')
|
||||
|
||||
@mock_s3
|
||||
def test_can_enable_bucket_acceleration():
|
||||
bucket_name = 'some_bucket'
|
||||
s3 = boto3.client('s3')
|
||||
s3.create_bucket(Bucket=bucket_name)
|
||||
resp = s3.put_bucket_accelerate_configuration(
|
||||
Bucket=bucket_name,
|
||||
AccelerateConfiguration={'Status': 'Enabled'},
|
||||
)
|
||||
resp.keys().should.have.length_of(1) # Response contains nothing (only HTTP headers)
|
||||
resp = s3.get_bucket_accelerate_configuration(Bucket=bucket_name)
|
||||
resp.should.have.key('Status')
|
||||
resp['Status'].should.equal('Enabled')
|
||||
|
||||
@mock_s3
|
||||
def test_can_suspend_bucket_acceleration():
|
||||
bucket_name = 'some_bucket'
|
||||
s3 = boto3.client('s3')
|
||||
s3.create_bucket(Bucket=bucket_name)
|
||||
resp = s3.put_bucket_accelerate_configuration(
|
||||
Bucket=bucket_name,
|
||||
AccelerateConfiguration={'Status': 'Enabled'},
|
||||
)
|
||||
resp = s3.put_bucket_accelerate_configuration(
|
||||
Bucket=bucket_name,
|
||||
AccelerateConfiguration={'Status': 'Suspended'},
|
||||
)
|
||||
resp.keys().should.have.length_of(1) # Response contains nothing (only HTTP headers)
|
||||
resp = s3.get_bucket_accelerate_configuration(Bucket=bucket_name)
|
||||
resp.should.have.key('Status')
|
||||
resp['Status'].should.equal('Suspended')
|
||||
|
||||
@mock_s3
|
||||
def test_suspending_acceleration_on_not_configured_bucket_does_nothing():
|
||||
bucket_name = 'some_bucket'
|
||||
s3 = boto3.client('s3')
|
||||
s3.create_bucket(Bucket=bucket_name)
|
||||
resp = s3.put_bucket_accelerate_configuration(
|
||||
Bucket=bucket_name,
|
||||
AccelerateConfiguration={'Status': 'Suspended'},
|
||||
)
|
||||
resp.keys().should.have.length_of(1) # Response contains nothing (only HTTP headers)
|
||||
resp = s3.get_bucket_accelerate_configuration(Bucket=bucket_name)
|
||||
resp.shouldnt.have.key('Status')
|
||||
|
||||
@mock_s3
|
||||
def test_accelerate_configuration_status_validation():
|
||||
bucket_name = 'some_bucket'
|
||||
s3 = boto3.client('s3')
|
||||
s3.create_bucket(Bucket=bucket_name)
|
||||
with assert_raises(ClientError) as exc:
|
||||
s3.put_bucket_accelerate_configuration(
|
||||
Bucket=bucket_name,
|
||||
AccelerateConfiguration={'Status': 'bad_status'},
|
||||
)
|
||||
exc.exception.response['Error']['Code'].should.equal('MalformedXML')
|
||||
|
||||
@mock_s3
|
||||
def test_accelerate_configuration_is_not_supported_when_bucket_name_has_dots():
|
||||
bucket_name = 'some.bucket.with.dots'
|
||||
s3 = boto3.client('s3')
|
||||
s3.create_bucket(Bucket=bucket_name)
|
||||
with assert_raises(ClientError) as exc:
|
||||
s3.put_bucket_accelerate_configuration(
|
||||
Bucket=bucket_name,
|
||||
AccelerateConfiguration={'Status': 'Enabled'},
|
||||
)
|
||||
exc.exception.response['Error']['Code'].should.equal('InvalidRequest')
|
||||
|
Loading…
Reference in New Issue
Block a user