/************************************************************************* * Copyright 2013-2014 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. ************************************************************************/ package com.eucalyptus.objectstorage.providers; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.UUID; import org.apache.commons.codec.digest.DigestUtils; import org.apache.log4j.Logger; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import com.eucalyptus.objectstorage.exceptions.s3.BadDigestException; import com.eucalyptus.objectstorage.exceptions.s3.BucketNotEmptyException; import com.eucalyptus.objectstorage.exceptions.s3.InternalErrorException; import com.eucalyptus.objectstorage.exceptions.s3.InvalidPartException; import com.eucalyptus.objectstorage.exceptions.s3.InvalidPartOrderException; import com.eucalyptus.objectstorage.exceptions.s3.NoSuchBucketException; import com.eucalyptus.objectstorage.exceptions.s3.NoSuchKeyException; import com.eucalyptus.objectstorage.exceptions.s3.NoSuchUploadException; import com.eucalyptus.objectstorage.exceptions.s3.NotImplementedException; import com.eucalyptus.objectstorage.exceptions.s3.S3Exception; import com.eucalyptus.objectstorage.msgs.AbortMultipartUploadResponseType; import com.eucalyptus.objectstorage.msgs.AbortMultipartUploadType; import com.eucalyptus.objectstorage.msgs.CompleteMultipartUploadResponseType; import com.eucalyptus.objectstorage.msgs.CompleteMultipartUploadType; import com.eucalyptus.objectstorage.msgs.CopyObjectResponseType; import com.eucalyptus.objectstorage.msgs.CopyObjectType; import com.eucalyptus.objectstorage.msgs.CreateBucketResponseType; import com.eucalyptus.objectstorage.msgs.CreateBucketType; import com.eucalyptus.objectstorage.msgs.DeleteBucketResponseType; import com.eucalyptus.objectstorage.msgs.DeleteBucketType; import com.eucalyptus.objectstorage.msgs.DeleteObjectResponseType; import com.eucalyptus.objectstorage.msgs.DeleteObjectType; import com.eucalyptus.objectstorage.msgs.DeleteVersionResponseType; import com.eucalyptus.objectstorage.msgs.DeleteVersionType; import com.eucalyptus.objectstorage.msgs.GetBucketAccessControlPolicyResponseType; import com.eucalyptus.objectstorage.msgs.GetBucketAccessControlPolicyType; import com.eucalyptus.objectstorage.msgs.GetBucketLocationResponseType; import com.eucalyptus.objectstorage.msgs.GetBucketLocationType; import com.eucalyptus.objectstorage.msgs.GetBucketLoggingStatusResponseType; import com.eucalyptus.objectstorage.msgs.GetBucketLoggingStatusType; import com.eucalyptus.objectstorage.msgs.GetBucketVersioningStatusResponseType; import com.eucalyptus.objectstorage.msgs.GetBucketVersioningStatusType; import com.eucalyptus.objectstorage.msgs.GetObjectAccessControlPolicyResponseType; import com.eucalyptus.objectstorage.msgs.GetObjectAccessControlPolicyType; import com.eucalyptus.objectstorage.msgs.GetObjectExtendedResponseType; import com.eucalyptus.objectstorage.msgs.GetObjectExtendedType; import com.eucalyptus.objectstorage.msgs.GetObjectResponseType; import com.eucalyptus.objectstorage.msgs.GetObjectType; import com.eucalyptus.objectstorage.msgs.HeadBucketResponseType; import com.eucalyptus.objectstorage.msgs.HeadBucketType; import com.eucalyptus.objectstorage.msgs.HeadObjectResponseType; import com.eucalyptus.objectstorage.msgs.HeadObjectType; import com.eucalyptus.objectstorage.msgs.InitiateMultipartUploadResponseType; import com.eucalyptus.objectstorage.msgs.InitiateMultipartUploadType; import com.eucalyptus.objectstorage.msgs.ListAllMyBucketsResponseType; import com.eucalyptus.objectstorage.msgs.ListAllMyBucketsType; import com.eucalyptus.objectstorage.msgs.ListBucketResponseType; import com.eucalyptus.objectstorage.msgs.ListBucketType; import com.eucalyptus.objectstorage.msgs.ListMultipartUploadsResponseType; import com.eucalyptus.objectstorage.msgs.ListMultipartUploadsType; import com.eucalyptus.objectstorage.msgs.ListPartsResponseType; import com.eucalyptus.objectstorage.msgs.ListPartsType; import com.eucalyptus.objectstorage.msgs.ListVersionsResponseType; import com.eucalyptus.objectstorage.msgs.ListVersionsType; import com.eucalyptus.objectstorage.msgs.PostObjectResponseType; import com.eucalyptus.objectstorage.msgs.PostObjectType; import com.eucalyptus.objectstorage.msgs.PutObjectResponseType; import com.eucalyptus.objectstorage.msgs.PutObjectType; import com.eucalyptus.objectstorage.msgs.SetBucketAccessControlPolicyResponseType; import com.eucalyptus.objectstorage.msgs.SetBucketAccessControlPolicyType; import com.eucalyptus.objectstorage.msgs.SetBucketLoggingStatusResponseType; import com.eucalyptus.objectstorage.msgs.SetBucketLoggingStatusType; import com.eucalyptus.objectstorage.msgs.SetBucketVersioningStatusResponseType; import com.eucalyptus.objectstorage.msgs.SetBucketVersioningStatusType; import com.eucalyptus.objectstorage.msgs.SetObjectAccessControlPolicyResponseType; import com.eucalyptus.objectstorage.msgs.SetObjectAccessControlPolicyType; import com.eucalyptus.objectstorage.msgs.UploadPartResponseType; import com.eucalyptus.objectstorage.msgs.UploadPartType; import com.eucalyptus.objectstorage.util.ObjectStorageProperties; import com.eucalyptus.storage.common.DateFormatter; import com.eucalyptus.storage.msgs.s3.AccessControlList; import com.eucalyptus.storage.msgs.s3.AccessControlPolicy; import com.eucalyptus.storage.msgs.s3.BucketListEntry; import com.eucalyptus.storage.msgs.s3.CanonicalUser; import com.eucalyptus.storage.msgs.s3.Grant; import com.eucalyptus.storage.msgs.s3.Grantee; import com.eucalyptus.storage.msgs.s3.Initiator; import com.eucalyptus.storage.msgs.s3.ListAllMyBucketsList; import com.eucalyptus.storage.msgs.s3.ListEntry; import com.eucalyptus.storage.msgs.s3.MetaDataEntry; import com.eucalyptus.storage.msgs.s3.Part; import com.eucalyptus.storage.msgs.s3.Upload; import com.eucalyptus.util.EucalyptusCloudException; import com.google.common.base.Strings; /** * This is a fake provider for testing-purposes only. This is a stateful in-memory provider that will respond like a regular provider but with no * external calls or dependencies. Keeps track of buckets and objects in memory. Objects keep the data sent, so be careful about testing with large * objects. * <p/> * Useful for cases where specifying the provider via a mock interface, like jmock is overly complicated due to large api coverage. */ public class InMemoryProvider implements ObjectStorageProviderClient { private static final Logger LOG = Logger.getLogger(InMemoryProvider.class); static final String IN_MEMORY_USERNAME = "memoryprovider@testunit.com"; public static enum FAIL_TYPE { NOT_FOUND, INTERNAL_ERROR, NONE } // Public knobs for testing failures public static FAIL_TYPE failObjectPut = FAIL_TYPE.NONE; public static FAIL_TYPE failObjectGet = FAIL_TYPE.NONE; public static FAIL_TYPE failObjectDelete = FAIL_TYPE.NONE; public static FAIL_TYPE failBucketPut = FAIL_TYPE.NONE; public static FAIL_TYPE failBucketGet = FAIL_TYPE.NONE; public static FAIL_TYPE failBucketDelete = FAIL_TYPE.NONE; public static FAIL_TYPE failCopyObject = FAIL_TYPE.NONE; private class ObjectKey implements Comparable { String key = ""; String versionId = "null"; public ObjectKey(String k, String version) { this.key = k; this.versionId = version; } @Override public boolean equals(Object other) { if (other instanceof ObjectKey) { ObjectKey otherKey = (ObjectKey) other; return otherKey.key.equals(key) && otherKey.versionId.equals(versionId); } else { return false; } } @Override public int compareTo(Object o) { ObjectKey other = (ObjectKey) o; return (this.key + this.versionId).compareTo(other.key + other.versionId); } } private class MemoryBucket { String name; Date createdDate; AccessControlList acl; String canonicalId; ObjectStorageProperties.VersioningStatus versioningStatus; boolean loggingEnabled; String location; TreeMap<ObjectKey, MemoryObject> objects = new TreeMap<ObjectKey, MemoryObject>(); TreeMap<String, MemoryMpu> uploads = new TreeMap<>(); BucketListEntry toBucketListEntry() { return new BucketListEntry(this.name, DateFormatter.dateToListingFormattedString(this.createdDate)); } } private class MemoryObject { String key; String versionId; long size; Date modifiedDate; byte[] content; AccessControlList acl; String canonicalId; String eTag; List<MetaDataEntry> userMetadata; public ListEntry toListEntry() { return new ListEntry(key, modifiedDate.toString(), eTag, size, new CanonicalUser(canonicalId, "user"), "STANDARD"); } } private class MemoryMpu extends MemoryObject { String uploadId; TreeMap<Integer, MemoryPart> parts = new TreeMap<>(); public Upload toUploadEntry() { Upload u = new Upload(this.key, this.uploadId, new Initiator(this.canonicalId, ""), new CanonicalUser(this.canonicalId, ""), "STANDARD", this.modifiedDate); return u; } } private class MemoryPart extends MemoryObject { Integer partNumber; public Part toPartEntry() { Part p = new Part(); p.setPartNumber(this.partNumber); p.setEtag(this.eTag); p.setLastModified(this.modifiedDate); p.setSize(this.size); return p; } } private TreeMap<String, MemoryBucket> myBuckets = new TreeMap<String, MemoryBucket>(); private String getOwnerCanonicalId(String accessKeyId) { return "one-user-canonicalid"; } private MemoryBucket getBucket(String bucketName, String canonicalId) throws S3Exception { MemoryBucket b = this.myBuckets.get(bucketName); if (b == null) { throw new NoSuchBucketException(bucketName); } // Could add a canonicalId check here, but not needed yet. return b; } private MemoryObject getObject(String bucketName, String key, String canonicalId) throws S3Exception { MemoryBucket bucket = getBucket(bucketName, canonicalId); MemoryObject obj = bucket.objects.get(new ObjectKey(key, "null")); if (obj == null) { throw new NoSuchKeyException(key); } return obj; } @Override public void checkPreconditions() throws EucalyptusCloudException {} @Override public void initialize() throws EucalyptusCloudException {} @Override public void check() throws EucalyptusCloudException {} @Override public void start() throws EucalyptusCloudException {} @Override public void stop() throws EucalyptusCloudException { // Flush everything on stop myBuckets.clear(); } @Override public void enable() throws EucalyptusCloudException {} @Override public void disable() throws EucalyptusCloudException {} @Override public ListAllMyBucketsResponseType listAllMyBuckets(ListAllMyBucketsType request) throws S3Exception { ListAllMyBucketsResponseType response = request.getReply(); ListAllMyBucketsList list = new ListAllMyBucketsList(); list.setBuckets(new ArrayList<BucketListEntry>()); response.setBucketList(list); response.setOwner(new CanonicalUser(request.getEffectiveUserId(), "")); for (Map.Entry<String, MemoryBucket> bucketEntry : this.myBuckets.entrySet()) { list.getBuckets().add(bucketEntry.getValue().toBucketListEntry()); } return response; } @Override public HeadBucketResponseType headBucket(HeadBucketType request) throws S3Exception { switch (failBucketGet) { case INTERNAL_ERROR: throw new InternalErrorException(request.getBucket()); case NOT_FOUND: // Yes, this doesn't really make sense, but for consistency it's here throw new NoSuchBucketException(request.getBucket()); default: } MemoryBucket b = getBucket(request.getBucket(), request.getEffectiveUserId()); HeadBucketResponseType response = request.getReply(); response.setBucket(request.getBucket()); response.setStatus(HttpResponseStatus.OK); response.setStatusMessage("OK"); return response; } private AccessControlList genPrivateAcl(String canonicalid) { AccessControlList privateAcl = new AccessControlList(); privateAcl.setGrants(new ArrayList<Grant>()); privateAcl.getGrants().add( new Grant(new Grantee(new CanonicalUser(canonicalid, IN_MEMORY_USERNAME)), ObjectStorageProperties.Permission.FULL_CONTROL.toString())); return privateAcl; } @Override public CreateBucketResponseType createBucket(CreateBucketType request) throws S3Exception { CreateBucketResponseType response = request.getReply(); MemoryBucket bucket; switch (failBucketPut) { case INTERNAL_ERROR: throw new InternalErrorException(request.getBucket()); case NOT_FOUND: // Yes, this doesn't really make sense, but for consistency it's here throw new NoSuchBucketException(request.getBucket()); default: } try { bucket = getBucket(request.getBucket(), request.getEffectiveUserId()); } catch (NoSuchBucketException e) { // Create bucket = null; } if (bucket == null) { bucket = new MemoryBucket(); bucket.name = request.getBucket(); bucket.canonicalId = getOwnerCanonicalId(request.getEffectiveUserId()); bucket.createdDate = new Date(); bucket.acl = request.getAccessControlList(); if (request.getAccessControlList() == null) { bucket.acl = genPrivateAcl(bucket.canonicalId); } bucket.location = request.getLocationConstraint(); bucket.loggingEnabled = false; this.myBuckets.put(request.getBucket(), bucket); } response.setStatus(HttpResponseStatus.OK); response.setStatusMessage("OK"); response.setBucket(request.getBucket()); response.setTimestamp(new Date()); return response; } @Override public DeleteBucketResponseType deleteBucket(DeleteBucketType request) throws S3Exception { switch (failBucketDelete) { case INTERNAL_ERROR: throw new InternalErrorException(request.getBucket()); case NOT_FOUND: // Yes, this doesn't really make sense, but for consistency it's here throw new NoSuchBucketException(request.getBucket()); default: } DeleteBucketResponseType response = request.getReply(); response.setStatus(HttpResponseStatus.NO_CONTENT); response.setStatusMessage("NoContent"); try { MemoryBucket b = getBucket(request.getBucket(), request.getEffectiveUserId()); if (b != null) { // Do the delete if (b.objects.size() > 0) { throw new BucketNotEmptyException(request.getBucket()); } else { this.myBuckets.remove(b.name); } } } catch (NoSuchBucketException e) { // fall thru } return response; } @Override public GetBucketAccessControlPolicyResponseType getBucketAccessControlPolicy(GetBucketAccessControlPolicyType request) throws S3Exception { GetBucketAccessControlPolicyResponseType response = request.getReply(); MemoryBucket b = getBucket(request.getBucket(), request.getEffectiveUserId()); response.setAccessControlPolicy(new AccessControlPolicy()); response.getAccessControlPolicy().setAccessControlList(b.acl); response.getAccessControlPolicy().setOwner(new CanonicalUser(b.canonicalId, IN_MEMORY_USERNAME)); response.setBucket(request.getBucket()); return response; } @Override public ListBucketResponseType listBucket(ListBucketType request) throws S3Exception { switch (failBucketGet) { case INTERNAL_ERROR: throw new InternalErrorException(request.getBucket()); case NOT_FOUND: // Yes, this doesn't really make sense, but for consistency it's here throw new NoSuchBucketException(request.getBucket()); default: } /* * Does not yet support prefix, delim, pagination, etc */ MemoryBucket b = getBucket(request.getBucket(), request.getEffectiveUserId()); ListBucketResponseType response = request.getReply(); response.setContents(new ArrayList<ListEntry>()); for (MemoryObject obj : b.objects.values()) { response.getContents().add(obj.toListEntry()); } response.setDelimiter(""); response.setMarker(""); response.setIsTruncated(false); response.setMaxKeys(1000); response.setName(b.name); return response; } @Override public SetBucketAccessControlPolicyResponseType setBucketAccessControlPolicy(SetBucketAccessControlPolicyType request) throws S3Exception { // TODO Auto-generated method stub throw new NotImplementedException(); } @Override public GetBucketLocationResponseType getBucketLocation(GetBucketLocationType request) throws S3Exception { MemoryBucket b = getBucket(request.getBucket(), request.getEffectiveUserId()); GetBucketLocationResponseType response = request.getReply(); response.setBucket(request.getBucket()); response.getLocationConstraint().setLocation(b.location); return response; } @Override public SetBucketLoggingStatusResponseType setBucketLoggingStatus(SetBucketLoggingStatusType request) throws S3Exception { // TODO Auto-generated method stub return null; } @Override public GetBucketLoggingStatusResponseType getBucketLoggingStatus(GetBucketLoggingStatusType request) throws S3Exception { // TODO Auto-generated method stub throw new NotImplementedException(); } @Override public GetBucketVersioningStatusResponseType getBucketVersioningStatus(GetBucketVersioningStatusType request) throws S3Exception { // TODO Auto-generated method stub throw new NotImplementedException(); } @Override public SetBucketVersioningStatusResponseType setBucketVersioningStatus(SetBucketVersioningStatusType request) throws S3Exception { // TODO Auto-generated method stub throw new NotImplementedException(); } @Override public ListVersionsResponseType listVersions(ListVersionsType request) throws S3Exception { // TODO Auto-generated method stub throw new NotImplementedException(); } @Override public PutObjectResponseType putObject(PutObjectType request, InputStream inputData) throws S3Exception { LOG.debug("InMemory PutObject"); switch (failObjectPut) { case INTERNAL_ERROR: LOG.debug("InMemory PutObject throw internal error as specified"); throw new InternalErrorException(request.getBucket()); case NOT_FOUND: LOG.debug("InMemory PutObject throe not-found as specified"); // Yes, this doesn't really make sense, but for consistency it's here throw new NoSuchBucketException(request.getBucket()); default: } try { ObjectKey key = new ObjectKey(request.getKey(), "null"); MemoryBucket bucket = getBucket(request.getBucket(), request.getEffectiveUserId()); MemoryObject memObj = new MemoryObject(); memObj.key = request.getKey(); memObj.versionId = "null"; memObj.content = new byte[Integer.valueOf(request.getContentLength())]; memObj.modifiedDate = new Date(); memObj.canonicalId = getOwnerCanonicalId(request.getEffectiveUserId()); memObj.acl = request.getAccessControlList(); memObj.userMetadata = request.getMetaData(); if (request.getAccessControlList() == null) { memObj.acl = genPrivateAcl(memObj.canonicalId); } try { int readLength = inputData.read(memObj.content); memObj.size = readLength; } catch (IOException e) { LOG.debug("InMemory PutObject exception: ", e); throw new EucalyptusCloudException(e); } memObj.eTag = DigestUtils.md5Hex(new String(memObj.content)); if (!Strings.isNullOrEmpty(request.getContentMD5()) && !memObj.eTag.equals(request.getContentMD5())) { LOG.error("InMemory PutObject MD5 mismatch"); throw new BadDigestException(memObj.eTag); } else { // Do the put, replacing any previous object bucket.objects.put(key, memObj); } PutObjectResponseType response = request.getReply(); response.setContentType(request.getContentType()); response.setEtag(memObj.eTag); response.setVersionId("null"); response.setContentDisposition(request.getContentDisposition()); response.setLastModified(new Date()); response.setStatusMessage("OK"); response.set_return(true); LOG.debug("InMemory return response: " + response.getStatusMessage()); return response; } catch (Exception e) { LOG.debug("InMemory PutObject exception: ", e); if (e instanceof S3Exception) { throw (S3Exception) e; } else { throw new InternalErrorException(e); } } } @Override public PostObjectResponseType postObject(PostObjectType request) throws S3Exception { // TODO Auto-generated method stub throw new NotImplementedException(); } @Override public DeleteObjectResponseType deleteObject(DeleteObjectType request) throws S3Exception { switch (failObjectDelete) { case INTERNAL_ERROR: throw new InternalErrorException(request.getKey()); case NOT_FOUND: // Yes, this doesn't really make sense, but for consistency it's here throw new NoSuchKeyException(request.getKey()); default: } DeleteObjectResponseType response = request.getReply(); response.setStatusMessage("NoContent"); response.setStatus(HttpResponseStatus.NO_CONTENT); try { MemoryObject obj = getObject(request.getBucket(), request.getKey(), request.getEffectiveUserId()); if (obj != null) { MemoryBucket b = getBucket(request.getBucket(), request.getEffectiveUserId()); b.objects.remove(new ObjectKey(request.getKey(), obj.versionId)); } } catch (NoSuchKeyException e) { // Fall thru } catch (S3Exception e) { throw e; } return response; } @Override public GetObjectAccessControlPolicyResponseType getObjectAccessControlPolicy(GetObjectAccessControlPolicyType request) throws S3Exception { // TODO Auto-generated method stub throw new NotImplementedException(); } @Override public SetObjectAccessControlPolicyResponseType setObjectAccessControlPolicy(SetObjectAccessControlPolicyType request) throws S3Exception { // TODO Auto-generated method stub throw new NotImplementedException(); } @Override public GetObjectResponseType getObject(GetObjectType request) throws S3Exception { switch (failObjectGet) { case INTERNAL_ERROR: throw new InternalErrorException(request.getKey()); case NOT_FOUND: // Yes, this doesn't really make sense, but for consistency it's here throw new NoSuchKeyException(request.getKey()); default: } MemoryObject obj = getObject(request.getBucket(), request.getKey(), request.getEffectiveUserId()); GetObjectResponseType response = request.getReply(); response.setEtag(obj.eTag); response.setLastModified(obj.modifiedDate); response.setSize(obj.size); response.setVersionId(obj.versionId); response.setDataInputStream(new ByteArrayInputStream(obj.content)); response.setStatusMessage("OK"); response.setMetaData(obj.userMetadata); return response; } @Override public GetObjectExtendedResponseType getObjectExtended(GetObjectExtendedType request) throws S3Exception { // TODO Auto-generated method stub return null; } @Override public HeadObjectResponseType headObject(HeadObjectType request) throws S3Exception { switch (failObjectGet) { case INTERNAL_ERROR: throw new InternalErrorException(request.getKey()); case NOT_FOUND: // Yes, this doesn't really make sense, but for consistency it's here throw new NoSuchKeyException(request.getKey()); default: } MemoryObject obj = getObject(request.getBucket(), request.getKey(), request.getEffectiveUserId()); HeadObjectResponseType response = request.getReply(); response.setEtag(obj.eTag); response.setLastModified(obj.modifiedDate); response.setSize(obj.size); response.setVersionId(obj.versionId); response.setStatusMessage("OK"); response.setMetaData(obj.userMetadata); return response; } @Override public CopyObjectResponseType copyObject(CopyObjectType request) throws S3Exception { LOG.debug("InMemory CopyObject"); switch (failCopyObject) { case INTERNAL_ERROR: LOG.debug("InMemory CopyObject throw internal error as specified"); throw new InternalErrorException(request.getSourceBucket()); case NOT_FOUND: LOG.debug("InMemory CopyObject throw not-found as specified"); // Yes, this doesn't really make sense, but for consistency it's here throw new NoSuchBucketException(request.getSourceBucket()); default: } try { MemoryBucket sourceBucket = getBucket(request.getSourceBucket(), request.getEffectiveUserId()); MemoryBucket destBucket = getBucket(request.getDestinationBucket(), request.getEffectiveUserId()); MemoryObject sourceObject = getObject(sourceBucket.name, request.getSourceObject(), getOwnerCanonicalId(request.getEffectiveUserId())); MemoryObject destObject = new MemoryObject(); destObject.key = request.getDestinationObject(); destObject.versionId = "null"; destObject.modifiedDate = new Date(); destObject.canonicalId = getOwnerCanonicalId(request.getEffectiveUserId()); destObject.acl = request.getAccessControlList(); if (request.getAccessControlList() == null) { destObject.acl = genPrivateAcl(destObject.canonicalId); } destObject.size = sourceObject.size; destObject.content = sourceObject.content.clone(); destObject.eTag = DigestUtils.md5Hex(new String(destObject.content)); ObjectKey destKey = new ObjectKey(destObject.key, "null"); destBucket.objects.put(destKey, destObject); CopyObjectResponseType response = request.getReply(); response.setEtag(destObject.eTag); response.setVersionId("null"); response.setLastModified(new Date().toString()); response.setStatusMessage("OK"); response.set_return(true); LOG.debug("InMemory return response: " + response.getStatusMessage()); return response; } catch (Exception e) { LOG.debug("InMemory PutObject exception: ", e); if (e instanceof S3Exception) { throw (S3Exception) e; } else { throw new InternalErrorException(e); } } } @Override public DeleteVersionResponseType deleteVersion(DeleteVersionType request) throws S3Exception { // TODO Auto-generated method stub throw new NotImplementedException(); } @Override public InitiateMultipartUploadResponseType initiateMultipartUpload(InitiateMultipartUploadType request) throws S3Exception { LOG.debug("InMemory InitiateMPU"); switch (failObjectPut) { case INTERNAL_ERROR: LOG.debug("InMemory MPU throw internal error as specified"); throw new InternalErrorException(request.getBucket()); case NOT_FOUND: LOG.debug("InMemory MPU throw not-found as specified"); // Yes, this doesn't really make sense, but for consistency it's here throw new NoSuchBucketException(request.getBucket()); default: } try { MemoryBucket bucket = getBucket(request.getBucket(), request.getEffectiveUserId()); MemoryMpu memObj = new MemoryMpu(); memObj.key = request.getKey(); memObj.uploadId = UUID.randomUUID().toString(); memObj.versionId = "null"; memObj.modifiedDate = new Date(); memObj.canonicalId = getOwnerCanonicalId(request.getEffectiveUserId()); memObj.acl = request.getAccessControlList(); if (request.getAccessControlList() == null) { memObj.acl = genPrivateAcl(memObj.canonicalId); } bucket.uploads.put(memObj.uploadId, memObj); InitiateMultipartUploadResponseType response = request.getReply(); response.setUploadId(memObj.uploadId); response.setStatusMessage("OK"); response.setBucket(request.getBucket()); response.setKey(request.getKey()); response.set_return(true); LOG.debug("InMemory return response: " + response.getStatusMessage()); return response; } catch (Exception e) { LOG.debug("InMemory PutObject exception: ", e); if (e instanceof S3Exception) { throw (S3Exception) e; } else { throw new InternalErrorException(e); } } } @Override public UploadPartResponseType uploadPart(UploadPartType request, InputStream dataContent) throws S3Exception { LOG.debug("InMemory UploadPart"); switch (failObjectPut) { case INTERNAL_ERROR: LOG.debug("InMemory UploadPart throw internal error as specified"); throw new InternalErrorException(request.getBucket()); case NOT_FOUND: LOG.debug("InMemory UploadPart throw not-found as specified"); // Yes, this doesn't really make sense, but for consistency it's here throw new NoSuchBucketException(request.getBucket()); default: } try { MemoryBucket bucket = getBucket(request.getBucket(), request.getEffectiveUserId()); if (!bucket.uploads.containsKey(request.getUploadId())) { throw new NoSuchUploadException(request.getUploadId()); } MemoryMpu memMpu = bucket.uploads.get(request.getUploadId()); MemoryPart memObj = new MemoryPart(); memObj.key = request.getKey(); memObj.content = new byte[Integer.valueOf(request.getContentLength())]; memObj.modifiedDate = new Date(); memObj.canonicalId = getOwnerCanonicalId(request.getEffectiveUserId()); memObj.partNumber = Integer.parseInt(request.getPartNumber()); try { memObj.size = dataContent.read(memObj.content); } catch (IOException e) { LOG.debug("InMemory UploadPart exception: ", e); throw new EucalyptusCloudException(e); } memObj.eTag = DigestUtils.md5Hex(new String(memObj.content)); if (!Strings.isNullOrEmpty(request.getContentMD5()) && !memObj.eTag.equals(request.getContentMD5())) { LOG.error("InMemory UploadPart MD5 mismatch"); throw new BadDigestException(memObj.eTag); } else { // Do the put, replacing any previous object memMpu.parts.put(memObj.partNumber, memObj); } UploadPartResponseType response = request.getReply(); response.setContentType(request.getContentType()); response.setEtag(memObj.eTag); response.setLastModified(new Date()); response.setStatusMessage("OK"); response.set_return(true); LOG.debug("InMemory return response: " + response.getStatusMessage()); return response; } catch (Exception e) { LOG.debug("InMemory UploadPart exception: ", e); if (e instanceof S3Exception) { throw (S3Exception) e; } else { throw new InternalErrorException(e); } } } @Override public CompleteMultipartUploadResponseType completeMultipartUpload(CompleteMultipartUploadType request) throws S3Exception { try { MemoryBucket bucket = getBucket(request.getBucket(), request.getEffectiveUserId()); if (!bucket.uploads.containsKey(request.getUploadId())) { throw new NoSuchUploadException(request.getUploadId()); } // Remove the MPU MemoryMpu mpuParent = bucket.uploads.get(request.getUploadId()); int lastNumber = -1; ArrayList<MemoryPart> orderedParts = new ArrayList<>(request.getParts().size()); int sizeSum = 0; MemoryPart tmp = null; // Validate the part list for (Part p : request.getParts()) { if (p.getPartNumber() <= lastNumber) { throw new InvalidPartOrderException(p.getPartNumber().toString()); } else { tmp = mpuParent.parts.get(p.getPartNumber()); if (tmp == null || !tmp.eTag.equals(p.getEtag())) { throw new InvalidPartException(p.getPartNumber().toString()); } } orderedParts.add(tmp); sizeSum += tmp.size; lastNumber = p.getPartNumber(); } MemoryObject finishedObj = new MemoryObject(); finishedObj.key = mpuParent.key; finishedObj.canonicalId = mpuParent.canonicalId; finishedObj.modifiedDate = new Date(); finishedObj.acl = mpuParent.acl; finishedObj.size = sizeSum; finishedObj.versionId = bucket.versioningStatus == ObjectStorageProperties.VersioningStatus.Enabled ? UUID.randomUUID().toString() : "null"; // Consolidate the parts, yes this is inefficient for md5 ByteArrayOutputStream data = new ByteArrayOutputStream(sizeSum); for (MemoryPart p : orderedParts) { data.write(p.content); } finishedObj.content = data.toByteArray(); finishedObj.eTag = DigestUtils.md5Hex(finishedObj.content); // Make the object live bucket.objects.put(new ObjectKey(finishedObj.key, finishedObj.versionId), finishedObj); // Remove the upload record and all parts bucket.uploads.remove(mpuParent.uploadId); CompleteMultipartUploadResponseType response = request.getReply(); response.setEtag(finishedObj.eTag); response.setKey(request.getKey()); response.setBucket(request.getBucket()); response.setVersionId(finishedObj.versionId); response.setStatusMessage("OK"); response.set_return(true); LOG.debug("InMemory return response: " + response.getStatusMessage()); return response; } catch (Exception e) { LOG.debug("InMemory abortMultipartUpload exception: ", e); if (e instanceof S3Exception) { throw (S3Exception) e; } else { throw new InternalErrorException(e); } } } @Override public AbortMultipartUploadResponseType abortMultipartUpload(AbortMultipartUploadType request) throws S3Exception { try { MemoryBucket bucket = getBucket(request.getBucket(), request.getEffectiveUserId()); if (!bucket.uploads.containsKey(request.getUploadId())) { throw new NoSuchUploadException(request.getUploadId()); } // Remove the MPU bucket.uploads.remove(request.getUploadId()); AbortMultipartUploadResponseType response = request.getReply(); response.setStatusMessage("OK"); response.set_return(true); LOG.debug("InMemory return response: " + response.getStatusMessage()); return response; } catch (Exception e) { LOG.debug("InMemory abortMultipartUpload exception: ", e); if (e instanceof S3Exception) { throw (S3Exception) e; } else { throw new InternalErrorException(e); } } } @Override public ListPartsResponseType listParts(ListPartsType request) throws S3Exception { MemoryBucket b = getBucket(request.getBucket(), request.getEffectiveUserId()); MemoryMpu upload = b.uploads.get(request.getUploadId()); if (upload == null) { throw new NoSuchUploadException(request.getUploadId()); } ListPartsResponseType response = request.getReply(); response.setParts(new ArrayList<Part>()); for (MemoryPart p : upload.parts.values()) { response.getParts().add(p.toPartEntry()); } Upload up = upload.toUploadEntry(); response.setKey(request.getKey()); response.setUploadId(request.getUploadId()); response.setBucket(request.getBucket()); response.setInitiator(up.getInitiator()); response.setOwner(up.getOwner()); response.setStorageClass("STANDARD"); response.setIsTruncated(false); response.setMaxParts(1000); response.setBucket(b.name); return response; } @Override public ListMultipartUploadsResponseType listMultipartUploads(ListMultipartUploadsType request) throws S3Exception { MemoryBucket b = getBucket(request.getBucket(), request.getEffectiveUserId()); ListMultipartUploadsResponseType response = request.getReply(); response.setBucket(request.getBucket()); response.setDelimiter(request.getDelimiter()); response.setMaxUploads(request.getMaxUploads() != null ? Integer.parseInt(request.getMaxUploads()) : 1000); response.setPrefix(request.getPrefix()); // Does not support prefix and delims response.setUploads(new ArrayList<Upload>()); for (MemoryMpu mpu : b.uploads.values()) { response.getUploads().add(mpu.toUploadEntry()); response.setUploadIdMarker(mpu.uploadId); } response.setIsTruncated(false); return response; } }