diff --git a/.gitignore b/.gitignore
index efb489651..0a24fe476 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,4 @@ venv/
.python-version
.vscode/
tests/file.tmp
+.eggs/
diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index 504037469..506e4e284 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -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
diff --git a/moto/s3/models.py b/moto/s3/models.py
index 9e4a6a766..59a7af580 100644
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -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)
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
old mode 100755
new mode 100644
index 856178941..42d064828
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -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 = """
{% endfor %}
"""
+
+S3_BUCKET_ACCELERATE = """
+
+ {{ bucket.accelerate_configuration }}
+
+"""
+
+S3_BUCKET_ACCELERATE_NOT_SET = """
+
+"""
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
index 6af23849c..1e9d25327 100644
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -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')