You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by ga...@apache.org on 2016/02/17 01:30:24 UTC
[3/8] jclouds git commit: JCLOUDS-651: Portable support for
conditional copies
JCLOUDS-651: Portable support for conditional copies
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/8945258d
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/8945258d
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/8945258d
Branch: refs/heads/master
Commit: 8945258d79a5fc9b3a5aeb35703213ca06c74d0e
Parents: 293d3f8
Author: Andrew Gaul <ga...@apache.org>
Authored: Thu Feb 11 21:11:24 2016 -0800
Committer: Andrew Gaul <ga...@apache.org>
Committed: Tue Feb 16 16:29:54 2016 -0800
----------------------------------------------------------------------
.../blobstore/internal/BaseBlobStore.java | 39 +++
.../jclouds/blobstore/options/CopyOptions.java | 27 ++-
.../internal/BaseBlobIntegrationTest.java | 236 +++++++++++++++++++
3 files changed, 301 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/8945258d/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java b/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
index 820721e..c6d21ff 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/internal/BaseBlobStore.java
@@ -23,6 +23,7 @@ import static org.jclouds.util.Predicates2.retry;
import java.io.InputStream;
import java.io.IOException;
+import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -46,6 +47,10 @@ import org.jclouds.blobstore.strategy.internal.MultipartUploadSlicingAlgorithm;
import org.jclouds.blobstore.util.BlobUtils;
import org.jclouds.collect.Memoized;
import org.jclouds.domain.Location;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpResponseException;
import org.jclouds.io.ContentMetadata;
import org.jclouds.io.Payload;
import org.jclouds.io.PayloadSlicer;
@@ -258,6 +263,26 @@ public abstract class BaseBlobStore implements BlobStore {
throw new KeyNotFoundException(fromContainer, fromName, "while copying");
}
+ String eTag = maybeQuoteETag(blob.getMetadata().getETag());
+ if (eTag != null) {
+ if (options.ifMatch() != null && !options.ifMatch().equals(eTag)) {
+ throw returnResponseException(412);
+ }
+ if (options.ifNoneMatch() != null && options.ifNoneMatch().equals(eTag)) {
+ throw returnResponseException(412);
+ }
+ }
+
+ Date lastModified = blob.getMetadata().getLastModified();
+ if (lastModified != null) {
+ if (options.ifModifiedSince() != null && lastModified.compareTo(options.ifModifiedSince()) <= 0) {
+ throw returnResponseException(412);
+ }
+ if (options.ifUnmodifiedSince() != null && lastModified.compareTo(options.ifUnmodifiedSince()) >= 0) {
+ throw returnResponseException(412);
+ }
+ }
+
InputStream is = null;
try {
is = blob.getPayload().openStream();
@@ -311,4 +336,18 @@ public abstract class BaseBlobStore implements BlobStore {
}
return completeMultipartUpload(mpu, parts);
}
+
+ private static HttpResponseException returnResponseException(int code) {
+ HttpResponse response = HttpResponse.builder().statusCode(code).build();
+ // TODO: bogus endpoint
+ return new HttpResponseException(new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://stub")
+ .build()), response);
+ }
+
+ private static String maybeQuoteETag(String eTag) {
+ if (!eTag.startsWith("\"") && !eTag.endsWith("\"")) {
+ eTag = "\"" + eTag + "\"";
+ }
+ return eTag;
+ }
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/8945258d/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
----------------------------------------------------------------------
diff --git a/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java b/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
index bb7985c..4e11c3c 100644
--- a/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
+++ b/blobstore/src/main/java/org/jclouds/blobstore/options/CopyOptions.java
@@ -17,6 +17,7 @@
package org.jclouds.blobstore.options;
+import java.util.Date;
import java.util.Map;
import org.jclouds.io.ContentMetadata;
@@ -24,6 +25,7 @@ import org.jclouds.javax.annotation.Nullable;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableMap;
@AutoValue
@Beta
@@ -39,11 +41,34 @@ public abstract class CopyOptions {
@Nullable
public abstract Map<String, String> userMetadata();
+ @Nullable
+ public abstract Date ifModifiedSince();
+ @Nullable
+ public abstract Date ifUnmodifiedSince();
+ @Nullable
+ public abstract String ifMatch();
+ @Nullable
+ public abstract String ifNoneMatch();
+
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder contentMetadata(ContentMetadata contentMetadata);
public abstract Builder userMetadata(Map<String, String> userMetadata);
- public abstract CopyOptions build();
+ public abstract Builder ifModifiedSince(Date ifModifiedSince);
+ public abstract Builder ifUnmodifiedSince(Date ifUnmodifiedSince);
+ public abstract Builder ifMatch(String ifMatch);
+ public abstract Builder ifNoneMatch(String ifNoneMatch);
+
+ abstract Map<String, String> userMetadata();
+ abstract CopyOptions autoBuild();
+
+ public CopyOptions build() {
+ Map<String, String> userMetadata = userMetadata();
+ if (userMetadata != null) {
+ userMetadata(ImmutableMap.copyOf(userMetadata));
+ }
+ return autoBuild();
+ }
}
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/8945258d/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
----------------------------------------------------------------------
diff --git a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
index 7740dbc..369f98e 100644
--- a/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
+++ b/blobstore/src/test/java/org/jclouds/blobstore/integration/internal/BaseBlobIntegrationTest.java
@@ -42,11 +42,13 @@ import java.util.Map;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import javax.ws.rs.core.MediaType;
+import org.assertj.core.api.Fail;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.ContainerNotFoundException;
import org.jclouds.blobstore.KeyNotFoundException;
@@ -948,6 +950,240 @@ public class BaseBlobIntegrationTest extends BaseBlobStoreIntegrationTest {
}
@Test(groups = { "integration", "live" })
+ public void testCopyIfMatch() throws Exception {
+ BlobStore blobStore = view.getBlobStore();
+ String fromName = "source";
+ String toName = "to";
+ ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+ Blob blob = blobStore
+ .blobBuilder(fromName)
+ .payload(payload)
+ .contentLength(payload.size())
+ .build();
+ String fromContainer = getContainerName();
+ String toContainer = getContainerName();
+ try {
+ String eTag = blobStore.putBlob(fromContainer, blob);
+ blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifMatch(eTag).build());
+ Blob toBlob = blobStore.getBlob(toContainer, toName);
+ InputStream is = null;
+ try {
+ is = toBlob.getPayload().openStream();
+ assertEquals(ByteStreams.toByteArray(is), payload.read());
+ } finally {
+ Closeables2.closeQuietly(is);
+ }
+ } finally {
+ returnContainer(toContainer);
+ returnContainer(fromContainer);
+ }
+ }
+
+ @Test(groups = { "integration", "live" })
+ public void testCopyIfMatchNegative() throws Exception {
+ BlobStore blobStore = view.getBlobStore();
+ String fromName = "source";
+ String toName = "to";
+ ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+ Blob blob = blobStore
+ .blobBuilder(fromName)
+ .payload(payload)
+ .contentLength(payload.size())
+ .build();
+ String fromContainer = getContainerName();
+ String toContainer = getContainerName();
+ try {
+ blobStore.putBlob(fromContainer, blob);
+ try {
+ blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifMatch("fake-etag").build());
+ Fail.failBecauseExceptionWasNotThrown(HttpResponseException.class);
+ } catch (HttpResponseException hre) {
+ assertThat(hre.getResponse().getStatusCode()).isEqualTo(412);
+ }
+ } finally {
+ returnContainer(toContainer);
+ returnContainer(fromContainer);
+ }
+ }
+
+ @Test(groups = { "integration", "live" })
+ public void testCopyIfNoneMatch() throws Exception {
+ BlobStore blobStore = view.getBlobStore();
+ String fromName = "source";
+ String toName = "to";
+ ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+ Blob blob = blobStore
+ .blobBuilder(fromName)
+ .payload(payload)
+ .contentLength(payload.size())
+ .build();
+ String fromContainer = getContainerName();
+ String toContainer = getContainerName();
+ try {
+ blobStore.putBlob(fromContainer, blob);
+ blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifNoneMatch("fake-etag").build());
+ Blob toBlob = blobStore.getBlob(toContainer, toName);
+ InputStream is = null;
+ try {
+ is = toBlob.getPayload().openStream();
+ assertEquals(ByteStreams.toByteArray(is), payload.read());
+ } finally {
+ Closeables2.closeQuietly(is);
+ }
+ } finally {
+ returnContainer(toContainer);
+ returnContainer(fromContainer);
+ }
+ }
+
+ @Test(groups = { "integration", "live" })
+ public void testCopyIfNoneMatchNegative() throws Exception {
+ BlobStore blobStore = view.getBlobStore();
+ String fromName = "source";
+ String toName = "to";
+ ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+ Blob blob = blobStore
+ .blobBuilder(fromName)
+ .payload(payload)
+ .contentLength(payload.size())
+ .build();
+ String fromContainer = getContainerName();
+ String toContainer = getContainerName();
+ try {
+ String eTag = blobStore.putBlob(fromContainer, blob);
+ try {
+ blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifNoneMatch(eTag).build());
+ Fail.failBecauseExceptionWasNotThrown(HttpResponseException.class);
+ } catch (HttpResponseException hre) {
+ assertThat(hre.getResponse().getStatusCode()).isEqualTo(412);
+ }
+ } finally {
+ returnContainer(toContainer);
+ returnContainer(fromContainer);
+ }
+ }
+
+ @Test(groups = { "integration", "live" })
+ public void testCopyIfModifiedSince() throws Exception {
+ BlobStore blobStore = view.getBlobStore();
+ String fromName = "source";
+ String toName = "to";
+ ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+ Blob blob = blobStore
+ .blobBuilder(fromName)
+ .payload(payload)
+ .contentLength(payload.size())
+ .build();
+ String fromContainer = getContainerName();
+ String toContainer = getContainerName();
+ try {
+ blobStore.putBlob(fromContainer, blob);
+ Date before = new Date(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1));
+ blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifModifiedSince(before).build());
+ Blob toBlob = blobStore.getBlob(toContainer, toName);
+ InputStream is = null;
+ try {
+ is = toBlob.getPayload().openStream();
+ assertEquals(ByteStreams.toByteArray(is), payload.read());
+ } finally {
+ Closeables2.closeQuietly(is);
+ }
+ } finally {
+ returnContainer(toContainer);
+ returnContainer(fromContainer);
+ }
+ }
+
+ @Test(groups = { "integration", "live" })
+ public void testCopyIfModifiedSinceNegative() throws Exception {
+ BlobStore blobStore = view.getBlobStore();
+ String fromName = "source";
+ String toName = "to";
+ ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+ Blob blob = blobStore
+ .blobBuilder(fromName)
+ .payload(payload)
+ .contentLength(payload.size())
+ .build();
+ String fromContainer = getContainerName();
+ String toContainer = getContainerName();
+ try {
+ blobStore.putBlob(fromContainer, blob);
+ // TODO: some problem with S3 and times in the future?
+ Date after = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
+ try {
+ blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifModifiedSince(after).build());
+ Fail.failBecauseExceptionWasNotThrown(HttpResponseException.class);
+ } catch (HttpResponseException hre) {
+ // most object stores return 412 but swift returns 304
+ assertThat(hre.getResponse().getStatusCode()).isIn(304, 412);
+ }
+ } finally {
+ returnContainer(toContainer);
+ returnContainer(fromContainer);
+ }
+ }
+
+ @Test(groups = { "integration", "live" })
+ public void testCopyIfUnmodifiedSince() throws Exception {
+ BlobStore blobStore = view.getBlobStore();
+ String fromName = "source";
+ String toName = "to";
+ ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+ Blob blob = blobStore
+ .blobBuilder(fromName)
+ .payload(payload)
+ .contentLength(payload.size())
+ .build();
+ String fromContainer = getContainerName();
+ String toContainer = getContainerName();
+ try {
+ blobStore.putBlob(fromContainer, blob);
+ Date after = new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1));
+ blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifUnmodifiedSince(after).build());
+ Blob toBlob = blobStore.getBlob(toContainer, toName);
+ InputStream is = null;
+ try {
+ is = toBlob.getPayload().openStream();
+ assertEquals(ByteStreams.toByteArray(is), payload.read());
+ } finally {
+ Closeables2.closeQuietly(is);
+ }
+ } finally {
+ returnContainer(toContainer);
+ returnContainer(fromContainer);
+ }
+ }
+
+ @Test(groups = { "integration", "live" })
+ public void testCopyIfUnmodifiedSinceNegative() throws Exception {
+ BlobStore blobStore = view.getBlobStore();
+ String fromName = "source";
+ String toName = "to";
+ ByteSource payload = TestUtils.randomByteSource().slice(0, 1024);
+ Blob blob = blobStore
+ .blobBuilder(fromName)
+ .payload(payload)
+ .contentLength(payload.size())
+ .build();
+ String fromContainer = getContainerName();
+ String toContainer = getContainerName();
+ try {
+ blobStore.putBlob(fromContainer, blob);
+ Date before = new Date(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1));
+ try {
+ blobStore.copyBlob(fromContainer, fromName, toContainer, toName, CopyOptions.builder().ifUnmodifiedSince(before).build());
+ Fail.failBecauseExceptionWasNotThrown(HttpResponseException.class);
+ } catch (HttpResponseException hre) {
+ assertThat(hre.getResponse().getStatusCode()).isEqualTo(412);
+ }
+ } finally {
+ returnContainer(toContainer);
+ returnContainer(fromContainer);
+ }
+ }
+
+ @Test(groups = { "integration", "live" })
public void testMultipartUploadNoPartsAbort() throws Exception {
BlobStore blobStore = view.getBlobStore();
String container = getContainerName();