Beginning of multipart upload support.
This commit is contained in:
parent
756955b61e
commit
f557487e06
@ -1,5 +1,7 @@
|
||||
# from boto.s3.bucket import Bucket
|
||||
# from boto.s3.key import Key
|
||||
import os
|
||||
import base64
|
||||
import md5
|
||||
|
||||
from moto.core import BaseBackend
|
||||
@ -21,10 +23,40 @@ class FakeKey(object):
|
||||
return len(self.value)
|
||||
|
||||
|
||||
class FakeMultipart(object):
|
||||
def __init__(self, key_name):
|
||||
self.key_name = key_name
|
||||
self.parts = {}
|
||||
self.id = base64.b64encode(os.urandom(43)).replace('=', '')
|
||||
|
||||
def complete(self):
|
||||
total = bytearray()
|
||||
|
||||
for part_id, index in enumerate(sorted(self.parts.keys()), start=1):
|
||||
# Make sure part ids are continuous
|
||||
if part_id != index:
|
||||
return
|
||||
|
||||
total.extend(self.parts[part_id])
|
||||
|
||||
if len(total) < 5242880:
|
||||
return
|
||||
|
||||
return total
|
||||
|
||||
def set_part(self, part_id, value):
|
||||
if part_id < 1:
|
||||
return False
|
||||
|
||||
self.parts[part_id] = value
|
||||
return True
|
||||
|
||||
|
||||
class FakeBucket(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.keys = {}
|
||||
self.multiparts = {}
|
||||
|
||||
|
||||
class S3Backend(BaseBackend):
|
||||
@ -65,6 +97,27 @@ class S3Backend(BaseBackend):
|
||||
if bucket:
|
||||
return bucket.keys.get(key_name)
|
||||
|
||||
def initiate_multipart(self, bucket_name, key_name):
|
||||
bucket = self.buckets[bucket_name]
|
||||
new_multipart = FakeMultipart(key_name)
|
||||
bucket.multiparts[new_multipart.id] = new_multipart
|
||||
|
||||
return new_multipart
|
||||
|
||||
def complete_multipart(self, bucket_name, multipart_id):
|
||||
bucket = self.buckets[bucket_name]
|
||||
multipart = bucket.multiparts[multipart_id]
|
||||
value = multipart.complete()
|
||||
if value is None:
|
||||
return False
|
||||
|
||||
self.set_key(bucket_name, multipart.key_name, value)
|
||||
|
||||
def set_part(self, bucket_name, multipart_id, part_id, value):
|
||||
bucket = self.buckets[bucket_name]
|
||||
multipart = bucket.multiparts[multipart_id]
|
||||
return multipart.set_part(part_id, value)
|
||||
|
||||
def prefix_query(self, bucket, prefix):
|
||||
key_results = set()
|
||||
folder_results = set()
|
||||
|
@ -106,6 +106,20 @@ def key_response(uri_info, method, body, headers):
|
||||
removed_key = s3_backend.delete_key(bucket_name, key_name)
|
||||
template = Template(S3_DELETE_OBJECT_SUCCESS)
|
||||
return template.render(bucket=removed_key), dict(status=204)
|
||||
elif method == 'POST':
|
||||
if body == '' and uri_info.query == 'uploads':
|
||||
multipart = s3_backend.initiate_multipart(bucket_name, key_name)
|
||||
template = Template(S3_MULTIPART_RESPONSE)
|
||||
response = template.render(
|
||||
bucket_name=bucket_name,
|
||||
key_name=key_name,
|
||||
multipart_id=multipart.id,
|
||||
)
|
||||
print response
|
||||
return response, dict()
|
||||
else:
|
||||
import pdb; pdb.set_trace()
|
||||
raise NotImplementedError("POST is only allowed for multipart uploads")
|
||||
else:
|
||||
raise NotImplementedError("Method {} has not been impelemented in the S3 backend yet".format(method))
|
||||
|
||||
@ -202,3 +216,16 @@ S3_OBJECT_COPY_RESPONSE = """<CopyObjectResponse xmlns="http://doc.s3.amazonaws.
|
||||
<LastModified>2008-02-18T13:54:10.183Z</LastModified>
|
||||
</CopyObjectResponse>
|
||||
</CopyObjectResponse>"""
|
||||
|
||||
S3_MULTIPART_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<InitiateMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Bucket>{{ bucket_name }}</Bucket>
|
||||
<Key>{{ key_name }}</Key>
|
||||
<UploadId>{{ upload_id }}</UploadId>
|
||||
</InitiateMultipartUploadResult>"""
|
||||
|
||||
S3_MULTIPART_COMPLETE_RESPONSE = """
|
||||
"""
|
||||
|
||||
S3_MULTIPART_ERROR_RESPONSE = """
|
||||
"""
|
||||
|
@ -1,4 +1,5 @@
|
||||
import urllib2
|
||||
from io import BytesIO
|
||||
|
||||
import boto
|
||||
from boto.exception import S3ResponseError
|
||||
@ -36,6 +37,26 @@ def test_my_model_save():
|
||||
conn.get_bucket('mybucket').get_key('steve').get_contents_as_string().should.equal('is awesome')
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_multipart_upload():
|
||||
conn = boto.connect_s3('the_key', 'the_secret')
|
||||
bucket = conn.create_bucket("foobar")
|
||||
|
||||
multipart = bucket.initiate_multipart_upload("the-key")
|
||||
multipart.upload_part_from_file(BytesIO('hello'), 1)
|
||||
multipart.upload_part_from_file(BytesIO('world'), 1)
|
||||
# Multipart with total size under 5MB is refused
|
||||
multipart.complete_upload().should.throw(S3ResponseError)
|
||||
|
||||
multipart = bucket.initiate_multipart_upload("the-key")
|
||||
part1 = '0' * 5242880
|
||||
multipart.upload_part_from_file(BytesIO('0' * 5242880), 1)
|
||||
part2 = '1'
|
||||
multipart.upload_part_from_file(BytesIO('1'), 1)
|
||||
multipart.complete_upload()
|
||||
bucket.get_key("the-key").get_contents_as_string().should.equal(part1 + part2)
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_missing_key():
|
||||
conn = boto.connect_s3('the_key', 'the_secret')
|
||||
|
Loading…
Reference in New Issue
Block a user