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();