Support Multi part upload for S3 using TransferManager.

This commit is contained in:
Min Chen 2013-06-05 09:40:33 -07:00
parent e92cd6d632
commit 4e404953ad
2 changed files with 262 additions and 291 deletions

View File

@ -16,20 +16,12 @@
// under the License. // under the License.
package com.cloud.storage.template; package com.cloud.storage.template;
import static com.cloud.utils.StringUtils.join; import static com.cloud.utils.StringUtils.join;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Date; import java.util.Date;
import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType; import org.apache.cloudstack.storage.command.DownloadCommand.ResourceType;
@ -51,12 +43,15 @@ import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException; import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.ProgressEvent; import com.amazonaws.services.s3.model.ProgressEvent;
import com.amazonaws.services.s3.model.ProgressListener; import com.amazonaws.services.s3.model.ProgressListener;
import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.StorageClass; import com.amazonaws.services.s3.model.StorageClass;
import com.amazonaws.services.s3.transfer.TransferManager;
import com.amazonaws.services.s3.transfer.Upload;
import com.cloud.agent.api.storage.Proxy; import com.cloud.agent.api.storage.Proxy;
import com.cloud.agent.api.to.S3TO; import com.cloud.agent.api.to.S3TO;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
@ -84,32 +79,29 @@ public class S3TemplateDownloader implements TemplateDownloader {
private GetMethod request; private GetMethod request;
private boolean resume = false; private boolean resume = false;
private DownloadCompleteCallback completionCallback; private DownloadCompleteCallback completionCallback;
S3TO s3; private S3TO s3;
boolean inited = true; private boolean inited = true;
private long MAX_TEMPLATE_SIZE_IN_BYTES; private long maxTemplateSizeInByte;
private ResourceType resourceType = ResourceType.TEMPLATE; private ResourceType resourceType = ResourceType.TEMPLATE;
private final HttpMethodRetryHandler myretryhandler; private final HttpMethodRetryHandler myretryhandler;
public S3TemplateDownloader(S3TO storageLayer, String downloadUrl, String installPath,
DownloadCompleteCallback callback, long maxTemplateSizeInBytes, String user, String password, Proxy proxy,
public S3TemplateDownloader (S3TO storageLayer, String downloadUrl, String installPath, DownloadCompleteCallback callback, long maxTemplateSizeInBytes, String user, String password, Proxy proxy, ResourceType resourceType) { ResourceType resourceType) {
this.s3 = storageLayer; this.s3 = storageLayer;
this.downloadUrl = downloadUrl; this.downloadUrl = downloadUrl;
this.installPath = installPath; this.installPath = installPath;
this.status = TemplateDownloader.Status.NOT_STARTED; this.status = TemplateDownloader.Status.NOT_STARTED;
this.resourceType = resourceType; this.resourceType = resourceType;
this.MAX_TEMPLATE_SIZE_IN_BYTES = maxTemplateSizeInBytes; this.maxTemplateSizeInByte = maxTemplateSizeInBytes;
this.totalBytes = 0; this.totalBytes = 0;
this.client = new HttpClient(s_httpClientManager); this.client = new HttpClient(s_httpClientManager);
myretryhandler = new HttpMethodRetryHandler() { myretryhandler = new HttpMethodRetryHandler() {
@Override @Override
public boolean retryMethod( public boolean retryMethod(final HttpMethod method, final IOException exception, int executionCount) {
final HttpMethod method,
final IOException exception,
int executionCount) {
if (executionCount >= 2) { if (executionCount >= 2) {
// Do not retry if over max retry count // Do not retry if over max retry count
return false; return false;
@ -146,8 +138,10 @@ public class S3TemplateDownloader implements TemplateDownloader {
if ((user != null) && (password != null)) { if ((user != null) && (password != null)) {
client.getParams().setAuthenticationPreemptive(true); client.getParams().setAuthenticationPreemptive(true);
Credentials defaultcreds = new UsernamePasswordCredentials(user, password); Credentials defaultcreds = new UsernamePasswordCredentials(user, password);
client.getState().setCredentials(new AuthScope(hostAndPort.first(), hostAndPort.second(), AuthScope.ANY_REALM), defaultcreds); client.getState().setCredentials(
s_logger.info("Added username=" + user + ", password=" + password + "for host " + hostAndPort.first() + ":" + hostAndPort.second()); new AuthScope(hostAndPort.first(), hostAndPort.second(), AuthScope.ANY_REALM), defaultcreds);
s_logger.info("Added username=" + user + ", password=" + password + "for host " + hostAndPort.first()
+ ":" + hostAndPort.second());
} else { } else {
s_logger.info("No credentials configured for host=" + hostAndPort.first() + ":" + hostAndPort.second()); s_logger.info("No credentials configured for host=" + hostAndPort.first() + ":" + hostAndPort.second());
} }
@ -164,7 +158,6 @@ public class S3TemplateDownloader implements TemplateDownloader {
} }
} }
@Override @Override
public long download(boolean resume, DownloadCompleteCallback callback) { public long download(boolean resume, DownloadCompleteCallback callback) {
switch (status) { switch (status) {
@ -176,8 +169,6 @@ public class S3TemplateDownloader implements TemplateDownloader {
} }
int bytes=0;
try { try {
// execute get method // execute get method
int responseCode = HttpStatus.SC_OK; int responseCode = HttpStatus.SC_OK;
@ -207,38 +198,39 @@ public class S3TemplateDownloader implements TemplateDownloader {
remoteSize = remoteSize2; remoteSize = remoteSize2;
} }
if (remoteSize > MAX_TEMPLATE_SIZE_IN_BYTES) { if (remoteSize > maxTemplateSizeInByte) {
s_logger.info("Remote size is too large: " + remoteSize + " , max=" + MAX_TEMPLATE_SIZE_IN_BYTES); s_logger.info("Remote size is too large: " + remoteSize + " , max=" + maxTemplateSizeInByte);
status = Status.UNRECOVERABLE_ERROR; status = Status.UNRECOVERABLE_ERROR;
errorString = "Download file size is too large"; errorString = "Download file size is too large";
return 0; return 0;
} }
if (remoteSize == 0) { if (remoteSize == 0) {
remoteSize = MAX_TEMPLATE_SIZE_IN_BYTES; remoteSize = maxTemplateSizeInByte;
} }
InputStream in = !chunked ? new BufferedInputStream(request.getResponseBodyAsStream()) InputStream in = !chunked ? new BufferedInputStream(request.getResponseBodyAsStream())
: new ChunkedInputStream(request.getResponseBodyAsStream()); : new ChunkedInputStream(request.getResponseBodyAsStream());
s_logger.info("Starting download from " + getDownloadUrl() + " to s3 bucket " + s3.getBucketName() + " remoteSize=" + remoteSize + " , max size=" + MAX_TEMPLATE_SIZE_IN_BYTES); s_logger.info("Starting download from " + getDownloadUrl() + " to s3 bucket " + s3.getBucketName()
+ " remoteSize=" + remoteSize + " , max size=" + maxTemplateSizeInByte);
Date start = new Date(); Date start = new Date();
// compute s3 key // compute s3 key
s3Key = join(asList(installPath, fileName), S3Utils.SEPARATOR); s3Key = join(asList(installPath, fileName), S3Utils.SEPARATOR);
// multi-part upload using S3 api to handle > 5G input stream
TransferManager tm = new TransferManager(S3Utils.acquireClient(s3));
// download using S3 API // download using S3 API
ObjectMetadata metadata = new ObjectMetadata(); ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(remoteSize); metadata.setContentLength(remoteSize);
PutObjectRequest putObjectRequest = new PutObjectRequest( PutObjectRequest putObjectRequest = new PutObjectRequest(s3.getBucketName(), s3Key, in, metadata)
s3.getBucketName(), s3Key, in, metadata)
.withStorageClass(StorageClass.ReducedRedundancy); .withStorageClass(StorageClass.ReducedRedundancy);
// register progress listenser // register progress listenser
putObjectRequest putObjectRequest.setProgressListener(new ProgressListener() {
.setProgressListener(new ProgressListener() {
@Override @Override
public void progressChanged( public void progressChanged(ProgressEvent progressEvent) {
ProgressEvent progressEvent) {
// s_logger.debug(progressEvent.getBytesTransfered() // s_logger.debug(progressEvent.getBytesTransfered()
// + " number of byte transferd " // + " number of byte transferd "
// + new Date()); // + new Date());
@ -256,12 +248,12 @@ public class S3TemplateDownloader implements TemplateDownloader {
} }
}); });
S3Utils.putObject(s3, putObjectRequest); // TransferManager processes all transfers asynchronously,
while (status != TemplateDownloader.Status.DOWNLOAD_FINISHED && // so this call will return immediately.
status != TemplateDownloader.Status.UNRECOVERABLE_ERROR && Upload upload = tm.upload(putObjectRequest);
status != TemplateDownloader.Status.ABORTED ){
// wait for completion upload.waitForCompletion();
}
// finished or aborted // finished or aborted
Date finish = new Date(); Date finish = new Date();
String downloaded = "(incomplete download)"; String downloaded = "(incomplete download)";
@ -277,11 +269,17 @@ public class S3TemplateDownloader implements TemplateDownloader {
status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; status = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
errorString = hte.getMessage(); errorString = hte.getMessage();
} catch (IOException ioe) { } catch (IOException ioe) {
status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; //probably a file write error? // probably a file write error
status = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
errorString = ioe.getMessage(); errorString = ioe.getMessage();
} catch (AmazonClientException ex) { } catch (AmazonClientException ex) {
status = TemplateDownloader.Status.UNRECOVERABLE_ERROR; // S3 api exception // S3 api exception
status = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
errorString = ex.getMessage(); errorString = ex.getMessage();
} catch (InterruptedException e) {
// S3 upload api exception
status = TemplateDownloader.Status.UNRECOVERABLE_ERROR;
errorString = e.getMessage();
} finally { } finally {
// close input stream // close input stream
request.releaseConnection(); request.releaseConnection();
@ -296,19 +294,16 @@ public class S3TemplateDownloader implements TemplateDownloader {
return downloadUrl; return downloadUrl;
} }
@Override @Override
public TemplateDownloader.Status getStatus() { public TemplateDownloader.Status getStatus() {
return status; return status;
} }
@Override @Override
public long getDownloadTime() { public long getDownloadTime() {
return downloadTime; return downloadTime;
} }
@Override @Override
public long getDownloadedBytes() { public long getDownloadedBytes() {
return totalBytes; return totalBytes;
@ -369,8 +364,6 @@ public class S3TemplateDownloader implements TemplateDownloader {
this.status = status; this.status = status;
} }
public boolean isResume() { public boolean isResume() {
return resume; return resume;
} }
@ -390,28 +383,9 @@ public class S3TemplateDownloader implements TemplateDownloader {
this.resume = resume; this.resume = resume;
} }
@Override @Override
public long getMaxTemplateSizeInBytes() { public long getMaxTemplateSizeInBytes() {
return this.MAX_TEMPLATE_SIZE_IN_BYTES; return this.maxTemplateSizeInByte;
}
public static void main(String[] args) {
String url ="http://dev.mysql.com/get/Downloads/MySQL-5.0/mysql-noinstall-5.0.77-win32.zip/from/http://mirror.services.wisc.edu/mysql/";
try {
URI uri = new java.net.URI(url);
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
TemplateDownloader td = new S3TemplateDownloader(null, url,"/tmp/mysql", null, TemplateDownloader.DEFAULT_MAX_TEMPLATE_SIZE_IN_BYTES, null, null, null, null);
long bytes = td.download(true, null);
if (bytes > 0) {
System.out.println("Downloaded (" + bytes + " bytes)" + " in " + td.getDownloadTime()/1000 + " secs");
} else {
System.out.println("Failed download");
}
} }
@Override @Override
@ -419,14 +393,11 @@ public class S3TemplateDownloader implements TemplateDownloader {
errorString = error; errorString = error;
} }
@Override @Override
public boolean isInited() { public boolean isInited() {
return inited; return inited;
} }
public ResourceType getResourceType() { public ResourceType getResourceType() {
return resourceType; return resourceType;
} }

View File

@ -73,7 +73,7 @@ public final class S3Utils {
super(); super();
} }
private static AmazonS3 acquireClient(final ClientOptions clientOptions) { public static AmazonS3 acquireClient(final ClientOptions clientOptions) {
final AWSCredentials credentials = new BasicAWSCredentials( final AWSCredentials credentials = new BasicAWSCredentials(
clientOptions.getAccessKey(), clientOptions.getSecretKey()); clientOptions.getAccessKey(), clientOptions.getSecretKey());