JCLOUDS-457: Added completeMultipartUpload and abortMultipartUplod.

Now the Glacier client supports completeMultipartUpload and i
abortMultipartUpload operations.
This commit is contained in:
Roman C. Coedo 2014-06-01 21:19:25 +02:00 committed by Andrew Gaul
parent 76ea768cb8
commit b0dddca449
6 changed files with 180 additions and 0 deletions

View File

@ -20,6 +20,7 @@ import static org.jclouds.blobstore.attr.BlobScopes.CONTAINER;
import java.io.Closeable;
import java.net.URI;
import java.util.Map;
import javax.inject.Named;
import javax.ws.rs.DELETE;
@ -31,9 +32,11 @@ import javax.ws.rs.PathParam;
import org.jclouds.Fallbacks.NullOnNotFoundOr404;
import org.jclouds.blobstore.attr.BlobScope;
import org.jclouds.glacier.binders.BindArchiveSizeToHeaders;
import org.jclouds.glacier.binders.BindContentRangeToHeaders;
import org.jclouds.glacier.binders.BindDescriptionToHeaders;
import org.jclouds.glacier.binders.BindHashesToHeaders;
import org.jclouds.glacier.binders.BindMultipartTreeHashToHeaders;
import org.jclouds.glacier.binders.BindPartSizeToHeaders;
import org.jclouds.glacier.domain.PaginatedVaultCollection;
import org.jclouds.glacier.domain.VaultMetadata;
@ -59,6 +62,7 @@ import org.jclouds.rest.annotations.ParamValidators;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.ResponseParser;
import com.google.common.hash.HashCode;
import com.google.common.util.concurrent.ListenableFuture;
/**
@ -187,4 +191,27 @@ public interface GlacierAsyncClient extends Closeable {
@PathParam("uploadId") String uploadId,
@BinderParam(BindContentRangeToHeaders.class) ContentRange range,
@ParamValidators(PayloadValidator.class) @BinderParam(BindHashesToHeaders.class) Payload payload);
/**
* @see GlacierClient#completeMultipartUpload
*/
@Named("CompleteMultipartUpload")
@POST
@Path("/-/vaults/{vault}/multipart-uploads/{uploadId}")
@ResponseParser(ParseArchiveIdHeader.class)
ListenableFuture<String> completeMultipartUpload(
@ParamValidators(VaultNameValidator.class) @PathParam("vault") String vaultName,
@PathParam("uploadId") String uploadId,
@BinderParam(BindMultipartTreeHashToHeaders.class) Map<Integer, HashCode> hashes,
@BinderParam(BindArchiveSizeToHeaders.class) long archiveSizeInMB);
/**
* @see GlacierClient#abortMultipartUpload
*/
@Named("AbortMultipartUpload")
@DELETE
@Path("/-/vaults/{vault}/multipart-uploads/{uploadId}")
ListenableFuture<Boolean> abortMultipartUpload(
@ParamValidators(VaultNameValidator.class) @PathParam("vault") String vaultName,
@PathParam("uploadId") String uploadId);
}

View File

@ -18,6 +18,7 @@ package org.jclouds.glacier;
import java.io.Closeable;
import java.net.URI;
import java.util.Map;
import org.jclouds.glacier.domain.PaginatedVaultCollection;
import org.jclouds.glacier.domain.VaultMetadata;
@ -25,6 +26,8 @@ import org.jclouds.glacier.options.PaginationOptions;
import org.jclouds.glacier.util.ContentRange;
import org.jclouds.io.Payload;
import com.google.common.hash.HashCode;
/**
* Provides access to Amazon Glacier resources via their REST API.
* <p/>
@ -149,4 +152,32 @@ public interface GlacierClient extends Closeable {
* @see <a href="http://docs.aws.amazon.com/amazonglacier/latest/dev/api-upload-part.html" />
*/
String uploadPart(String vaultName, String uploadId, ContentRange range, Payload payload);
/**
* Completes the multipart upload.
*
* @param vaultName
* Name of the Vault where the archive is going to be stored.
* @param uploadId
* Multipart upload identifier.
* @param hashes
* Map containing the pairs partnumber-treehash of each uploaded part.
* @param archiveSizeInMB
* Size of the complete archive.
* @return A String containing the Archive identifier in Amazon Glacier.
* @see <a href="http://docs.aws.amazon.com/amazonglacier/latest/dev/api-multipart-complete-upload.html" />
*/
String completeMultipartUpload(String vaultName, String uploadId, Map<Integer, HashCode> hashes, long archiveSizeInMB);
/**
* Aborts the multipart upload.
*
* @param vaultName
* Name of the Vault where the archive was going to be stored.
* @param uploadId
* Multipart upload identifier.
* @return True if the multipart upload was aborted, false otherwise.
* @see <a href="http://docs.aws.amazon.com/amazonglacier/latest/dev/api-multipart-abort-upload.html" />
*/
boolean abortMultipartUpload(String vaultName, String uploadId);
}

View File

@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.glacier.binders;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import org.jclouds.glacier.reference.GlacierHeaders;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.Binder;
/**
* Binds the Archive size to the request headers.
*/
public class BindArchiveSizeToHeaders implements Binder {
@SuppressWarnings("unchecked")
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object input) {
checkArgument(checkNotNull(input, "input") instanceof Long, "This binder is only valid for long");
checkNotNull(request, "request");
Long archiveSizeInMB = Long.class.cast(input);
return (R) request.toBuilder()
.replaceHeader(GlacierHeaders.ARCHIVE_SIZE, Long.toString(archiveSizeInMB << 20))
.build();
}
}

View File

@ -0,0 +1,46 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.glacier.binders;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Map;
import org.jclouds.glacier.reference.GlacierHeaders;
import org.jclouds.glacier.util.TreeHash;
import org.jclouds.http.HttpRequest;
import org.jclouds.rest.Binder;
import com.google.common.hash.HashCode;
/**
* Binds the Tree hash to the request headers.
*/
public class BindMultipartTreeHashToHeaders implements Binder {
@SuppressWarnings("unchecked")
@Override
public <R extends HttpRequest> R bindToRequest(R request, Object input) {
checkArgument(checkNotNull(input, "input") instanceof Map, "This binder is only valid for Map");
checkNotNull(request, "request");
Map<Integer, HashCode> map = Map.class.cast(input);
checkArgument(map.size() != 0, "The map cannot be empty");
return (R) request.toBuilder()
.addHeader(GlacierHeaders.TREE_HASH, TreeHash.buildTreeHashFromMap(map).toString())
.build();
}
}

View File

@ -32,6 +32,7 @@ public final class GlacierHeaders {
public static final String ARCHIVE_ID = HEADER_PREFIX + "archive-id";
public static final String MULTIPART_UPLOAD_ID = HEADER_PREFIX + "multipart-upload-id";
public static final String PART_SIZE = HEADER_PREFIX + "part-size";
public static final String ARCHIVE_SIZE = HEADER_PREFIX + "archive-size";
private GlacierHeaders() {
}

View File

@ -49,7 +49,9 @@ import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.HashCode;
import com.google.common.io.Resources;
import com.google.common.net.HttpHeaders;
import com.google.common.net.MediaType;
@ -271,4 +273,36 @@ public class GlacierClientMockTest {
assertEquals(request.getHeader(HttpHeaders.CONTENT_RANGE), range.buildHeader());
assertEquals(request.getHeader(HttpHeaders.CONTENT_LENGTH), payload.getContentMetadata().getContentLength().toString());
}
@Test
public void testCompleteMultipartUpload() throws IOException, InterruptedException {
MockResponse mr = buildBaseResponse(201);
mr.addHeader(HttpHeaders.LOCATION, ARCHIVE_LOCATION);
mr.addHeader(GlacierHeaders.ARCHIVE_ID, ARCHIVE_ID);
server.enqueue(mr);
HashCode partHashcode = HashCode.fromString("9bc1b2a288b26af7257a36277ae3816a7d4f16e89c1e7e77d0a5c48bad62b360");
ImmutableMap<Integer, HashCode> map = ImmutableMap.of(
1, partHashcode,
2, partHashcode,
3, partHashcode,
4, partHashcode);
assertEquals(client.completeMultipartUpload(VAULT_NAME, MULTIPART_UPLOAD_ID, map, 8L), ARCHIVE_ID);
RecordedRequest request = server.takeRequest();
assertEquals(request.getRequestLine(),
"POST /-/vaults/" + VAULT_NAME + "/multipart-uploads/" + MULTIPART_UPLOAD_ID + " " + HTTP);
assertEquals(request.getHeader(GlacierHeaders.TREE_HASH),
"9491cb2ed1d4e7cd53215f4017c23ec4ad21d7050a1e6bb636c4f67e8cddb844");
assertEquals(request.getHeader(GlacierHeaders.ARCHIVE_SIZE), "8388608");
}
@Test
public void testAbortMultipartUpload() throws IOException, InterruptedException {
MockResponse mr = buildBaseResponse(204);
server.enqueue(mr);
assertTrue(client.abortMultipartUpload(VAULT_NAME, MULTIPART_UPLOAD_ID));
assertEquals(server.takeRequest().getRequestLine(),
"DELETE /-/vaults/" + VAULT_NAME + "/multipart-uploads/" + MULTIPART_UPLOAD_ID + " " + HTTP);
}
}