diff --git a/blobstore/src/main/java/org/jclouds/blobstore/options/PutOptions.java b/blobstore/src/main/java/org/jclouds/blobstore/options/PutOptions.java index 70adcb777a..b8af2be7c0 100644 --- a/blobstore/src/main/java/org/jclouds/blobstore/options/PutOptions.java +++ b/blobstore/src/main/java/org/jclouds/blobstore/options/PutOptions.java @@ -77,23 +77,39 @@ public class PutOptions implements Cloneable { } /** - * split large blobs into pieces, if supported by the provider + * split large blobs into pieces, if supported by the provider. + * + * Equivalent to multipart(true) */ public PutOptions multipart() { - this.multipart = true; + return multipart(true); + } + + /** + * whether to split large blobs into pieces, if supported by the provider + */ + public PutOptions multipart(boolean val) { + this.multipart = val; return this; } public static class Builder { + public static PutOptions fromPutOptions(PutOptions putOptions) { + return multipart(putOptions.multipart); + } + /** * @see PutOptions#multipart() */ public static PutOptions multipart() { - PutOptions options = new PutOptions(); - return options.multipart(); + return multipart(true); } + public static PutOptions multipart(boolean val) { + PutOptions options = new PutOptions(); + return options.multipart(val); + } } @Override diff --git a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/internal/ParallelMultipartUploadStrategy.java b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/internal/ParallelMultipartUploadStrategy.java index d16cca3c5d..bee6d98174 100644 --- a/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/internal/ParallelMultipartUploadStrategy.java +++ b/providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/strategy/internal/ParallelMultipartUploadStrategy.java @@ -241,7 +241,11 @@ public class ParallelMultipartUploadStrategy implements AsyncMultipartUploadStra throw rtex; } } else { - ListenableFuture futureETag = ablobstore.putBlob(container, blob, options); + // Issue 936: don't just call putBlob, as that will see options=multiPart and + // recursively call this execute method again; instead mark as not multipart + // because it can all fit in one go. + PutOptions nonMultipartOptions = PutOptions.Builder.multipart(false); + ListenableFuture futureETag = ablobstore.putBlob(container, blob, nonMultipartOptions); return maxTime != null ? futureETag.get(maxTime,TimeUnit.SECONDS) : futureETag.get(); } diff --git a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java index cde3e84eab..61339bcd6a 100644 --- a/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java +++ b/providers/aws-s3/src/test/java/org/jclouds/aws/s3/AWSS3ClientLiveTest.java @@ -34,6 +34,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.zip.GZIPInputStream; +import org.jclouds.blobstore.AsyncBlobStore; import org.jclouds.blobstore.BlobStore; import org.jclouds.blobstore.KeyNotFoundException; import org.jclouds.blobstore.domain.Blob; @@ -44,9 +45,9 @@ import org.jclouds.s3.S3Client; import org.jclouds.s3.S3ClientLiveTest; import org.jclouds.s3.domain.ListBucketResponse; import org.jclouds.s3.domain.ObjectMetadata; +import org.jclouds.s3.domain.ObjectMetadata.StorageClass; import org.jclouds.s3.domain.ObjectMetadataBuilder; import org.jclouds.s3.domain.S3Object; -import org.jclouds.s3.domain.ObjectMetadata.StorageClass; import org.testng.ITestContext; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -161,6 +162,20 @@ public class AWSS3ClientLiveTest extends S3ClientLiveTest { } } + public void testMultipartAsynchronouslySmallBlob() throws IOException, InterruptedException, Exception { + String containerName = getContainerName(); + + try { + AsyncBlobStore asyncBlobStore = view.getAsyncBlobStore(); + asyncBlobStore.createContainerInLocation(null, containerName).get(); + Blob blob = asyncBlobStore.blobBuilder("small").payload("small").build(); + asyncBlobStore.putBlob(containerName, blob, PutOptions.Builder.multipart()).get(); + + } finally { + returnContainer(containerName); + } + } + public void testPutWithReducedRedundancyStorage() throws InterruptedException { String containerName = getContainerName(); try {