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 2015/04/09 08:21:33 UTC

[03/16] jclouds git commit: JCLOUDS-871: Azure Copy Blob support

JCLOUDS-871: Azure Copy Blob support

API reference:

http://msdn.microsoft.com/en-us/library/dd894037.aspx


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/54874000
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/54874000
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/54874000

Branch: refs/heads/1.9.x
Commit: 5487400037c901b4ea0bb0244ada9fcf109a1d3d
Parents: 75f6d76
Author: Andrew Gaul <ga...@apache.org>
Authored: Sat Sep 6 18:03:23 2014 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Wed Apr 8 23:13:51 2015 -0700

----------------------------------------------------------------------
 .../storage/reference/AzureStorageHeaders.java  |   7 +
 .../org/jclouds/azureblob/AzureBlobClient.java  |  13 ++
 .../binders/BindAzureCopyOptionsToRequest.java  |  70 ++++++++
 .../azureblob/options/CopyBlobOptions.java      | 109 +++++++++++++
 .../azureblob/AzureBlobClientLiveTest.java      | 160 +++++++++++++++++++
 .../jclouds/azureblob/AzureBlobClientTest.java  |  99 ++++++++++++
 6 files changed, 458 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/54874000/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java
index 0c60e51..b57caa9 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java
@@ -24,6 +24,13 @@ package org.jclouds.azure.storage.reference;
 public final class AzureStorageHeaders {
 
    public static final String USER_METADATA_PREFIX = "x-ms-meta-";
+
+   public static final String COPY_SOURCE = "x-ms-copy-source";
+   public static final String COPY_SOURCE_IF_MODIFIED_SINCE = "x-ms-source-if-modified-since";
+   public static final String COPY_SOURCE_IF_UNMODIFIED_SINCE = "x-ms-source-if-unmodified-since";
+   public static final String COPY_SOURCE_IF_MATCH = "x-ms-source-if-match";
+   public static final String COPY_SOURCE_IF_NONE_MATCH = "x-ms-source-if-none-match";
+
    public static final String REQUEST_ID = "x-ms-request-id";
    public static final String VERSION = "x-ms-version";
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/54874000/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
index 368dda0..ed7c5f8 100644
--- a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java
@@ -26,6 +26,7 @@ import static org.jclouds.blobstore.BlobStoreFallbacks.NullOnContainerNotFound;
 import static org.jclouds.blobstore.BlobStoreFallbacks.NullOnKeyNotFound;
 
 import java.io.Closeable;
+import java.net.URI;
 import java.util.List;
 import java.util.Map;
 
@@ -57,6 +58,7 @@ import org.jclouds.azureblob.functions.ParseBlobFromHeadersAndHttpContent;
 import org.jclouds.azureblob.functions.ParseBlobPropertiesFromHeaders;
 import org.jclouds.azureblob.functions.ParseContainerPropertiesFromHeaders;
 import org.jclouds.azureblob.functions.ParsePublicAccessHeader;
+import org.jclouds.azureblob.options.CopyBlobOptions;
 import org.jclouds.azureblob.options.CreateContainerOptions;
 import org.jclouds.azureblob.options.ListBlobsOptions;
 import org.jclouds.azureblob.predicates.validators.BlockIdValidator;
@@ -442,4 +444,15 @@ public interface AzureBlobClient extends Closeable {
          @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container,
          @PathParam("name") String name);
 
+   /**
+    * @throws ContainerNotFoundException if the container is not present.
+    */
+   @Named("CopyBlob")
+   @PUT
+   @Path("{toContainer}/{toName}")
+   @Headers(keys = AzureStorageHeaders.COPY_SOURCE, values = "{copySource}")
+   void copyBlob(
+         @PathParam("copySource") URI copySource,
+         @PathParam("toContainer") @ParamValidators(ContainerNameValidator.class) String toContainer, @PathParam("toName") String toName,
+         @BinderParam(BindAzureCopyOptionsToRequest.class) CopyBlobOptions options);
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/54874000/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureCopyOptionsToRequest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureCopyOptionsToRequest.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureCopyOptionsToRequest.java
new file mode 100644
index 0000000..218fe16
--- /dev/null
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/binders/BindAzureCopyOptionsToRequest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.azureblob.binders;
+
+import java.util.Date;
+import java.util.Map;
+
+import org.jclouds.azure.storage.reference.AzureStorageHeaders;
+import org.jclouds.azureblob.options.CopyBlobOptions;
+import org.jclouds.date.DateService;
+import org.jclouds.date.internal.SimpleDateFormatDateService;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.Binder;
+
+import com.google.common.base.Optional;
+
+/** Binds options to a copyBlob request. */
+public class BindAzureCopyOptionsToRequest implements Binder {
+   private static final DateService dateService = new SimpleDateFormatDateService();
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+      HttpRequest.Builder builder = request.toBuilder();
+      CopyBlobOptions options = (CopyBlobOptions) input;
+
+      Optional<Map<String, String>> userMetadata = options.getUserMetadata();
+      if (userMetadata.isPresent()) {
+         for (Map.Entry<String, String> entry : userMetadata.get().entrySet()) {
+            builder.addHeader(AzureStorageHeaders.USER_METADATA_PREFIX + entry.getKey(), entry.getValue());
+         }
+      }
+
+      Optional<Date> ifModifiedSince = options.getIfModifiedSince();
+      if (ifModifiedSince.isPresent()) {
+         builder.addHeader(AzureStorageHeaders.COPY_SOURCE_IF_MODIFIED_SINCE, dateService.rfc822DateFormat(ifModifiedSince.get()));
+      }
+
+      Optional<Date> ifUnmodifiedSince = options.getIfUnmodifiedSince();
+      if (ifUnmodifiedSince.isPresent()) {
+         builder.addHeader(AzureStorageHeaders.COPY_SOURCE_IF_UNMODIFIED_SINCE, dateService.rfc822DateFormat(ifUnmodifiedSince.get()));
+      }
+
+      Optional<String> ifMatch = options.getIfMatch();
+      if (ifMatch.isPresent()) {
+         builder.addHeader(AzureStorageHeaders.COPY_SOURCE_IF_MATCH, ifMatch.get());
+      }
+
+      Optional<String> ifNoneMatch = options.getIfNoneMatch();
+      if (ifNoneMatch.isPresent()) {
+         builder.addHeader(AzureStorageHeaders.COPY_SOURCE_IF_NONE_MATCH, ifNoneMatch.get());
+      }
+
+      return (R) builder.build();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/54874000/providers/azureblob/src/main/java/org/jclouds/azureblob/options/CopyBlobOptions.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/options/CopyBlobOptions.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/options/CopyBlobOptions.java
new file mode 100644
index 0000000..9baf624
--- /dev/null
+++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/options/CopyBlobOptions.java
@@ -0,0 +1,109 @@
+/*
+ * 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.azureblob.options;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Date;
+import java.util.Map;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+
+public final class CopyBlobOptions {
+   public static final CopyBlobOptions NONE = CopyBlobOptions.builder().build();
+
+   private final Optional<Map<String, String>> userMetadata;
+   private final Optional<Date> ifModifiedSince;
+   private final Optional<Date> ifUnmodifiedSince;
+   private final Optional<String> ifMatch;
+   private final Optional<String> ifNoneMatch;
+
+   private CopyBlobOptions(Map<String, String> userMetadata, Date ifModifiedSince, Date ifUnmodifiedSince,
+         String ifMatch, String ifNoneMatch) {
+      this.userMetadata = Optional.fromNullable(userMetadata);
+      this.ifModifiedSince = Optional.fromNullable(ifModifiedSince);
+      this.ifUnmodifiedSince = Optional.fromNullable(ifUnmodifiedSince);
+      this.ifMatch = Optional.fromNullable(ifMatch);
+      this.ifNoneMatch = Optional.fromNullable(ifNoneMatch);
+   }
+
+   public static Builder builder() {
+      return new Builder();
+   }
+
+   public Optional<Map<String, String>> getUserMetadata() {
+      return userMetadata;
+   }
+
+   public Optional<Date> getIfModifiedSince() {
+      return Optional.fromNullable(ifModifiedSince.isPresent() ? (Date) ifModifiedSince.get().clone() : null);
+   }
+
+   public Optional<Date> getIfUnmodifiedSince() {
+      return Optional.fromNullable(ifUnmodifiedSince.isPresent() ? (Date) ifUnmodifiedSince.get().clone() : null);
+   }
+
+   public Optional<String> getIfMatch() {
+      return ifMatch;
+   }
+
+   public Optional<String> getIfNoneMatch() {
+      return ifNoneMatch;
+   }
+
+   public static class Builder {
+      private Map<String, String> userMetadata;
+      private Date ifModifiedSince;
+      private Date ifUnmodifiedSince;
+      private String ifMatch;
+      private String ifNoneMatch;
+
+      Builder() {
+      }
+
+      public Builder overrideUserMetadata(Map<String, String> userMetadata) {
+         this.userMetadata = ImmutableMap.copyOf(checkNotNull(userMetadata, "userMetadata"));
+         return this;
+      }
+
+      public Builder ifModifiedSince(Date ifModifiedSince) {
+         this.ifModifiedSince = (Date) checkNotNull(ifModifiedSince, "ifModifiedSince").clone();
+         return this;
+      }
+
+      public Builder ifUnmodifiedSince(Date ifUnmodifiedSince) {
+         this.ifUnmodifiedSince = (Date) checkNotNull(ifUnmodifiedSince, "ifUnmodifiedSince").clone();
+         return this;
+      }
+
+      public Builder ifMatch(String ifMatch) {
+         this.ifMatch = checkNotNull(ifMatch, "ifMatch");
+         return this;
+      }
+
+      public Builder ifNoneMatch(String ifNoneMatch) {
+         this.ifNoneMatch = checkNotNull(ifNoneMatch, "ifNoneMatch");
+         return this;
+      }
+
+      public CopyBlobOptions build() {
+         return new CopyBlobOptions(userMetadata, ifModifiedSince, ifUnmodifiedSince, ifMatch, ifNoneMatch);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/54874000/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
index 2dccf19..db3ef9d 100644
--- a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
+++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientLiveTest.java
@@ -18,6 +18,7 @@ package org.jclouds.azureblob;
 
 import static com.google.common.io.BaseEncoding.base16;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown;
 import static org.jclouds.azure.storage.options.ListOptions.Builder.includeMetadata;
 import static org.jclouds.azureblob.options.CreateContainerOptions.Builder.withMetadata;
 import static org.jclouds.azureblob.options.CreateContainerOptions.Builder.withPublicAccess;
@@ -29,6 +30,8 @@ import java.lang.reflect.UndeclaredThrowableException;
 import java.net.URI;
 import java.security.SecureRandom;
 import java.util.Arrays;
+import java.util.Date;
+import java.util.Map;
 import java.util.Set;
 
 import org.jclouds.azure.storage.AzureStorageResponseException;
@@ -40,14 +43,17 @@ import org.jclouds.azureblob.domain.ContainerProperties;
 import org.jclouds.azureblob.domain.ListBlobBlocksResponse;
 import org.jclouds.azureblob.domain.ListBlobsResponse;
 import org.jclouds.azureblob.domain.PublicAccess;
+import org.jclouds.azureblob.options.CopyBlobOptions;
 import org.jclouds.azureblob.options.ListBlobsOptions;
 import org.jclouds.blobstore.ContainerNotFoundException;
 import org.jclouds.blobstore.integration.internal.BaseBlobStoreIntegrationTest;
 import org.jclouds.http.HttpResponseException;
 import org.jclouds.http.options.GetOptions;
+import org.jclouds.io.ByteStreams2;
 import org.jclouds.io.Payloads;
 import org.jclouds.util.Strings2;
 import org.jclouds.util.Throwables2;
+import org.jclouds.utils.TestUtils;
 import org.testng.annotations.Test;
 
 import com.google.common.base.Charsets;
@@ -56,6 +62,7 @@ import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.Iterables;
 import com.google.common.hash.Hashing;
 import com.google.common.io.BaseEncoding;
+import com.google.common.io.ByteSource;
 
 @Test(groups = "live", singleThreaded = true)
 public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
@@ -384,4 +391,157 @@ public class AzureBlobClientLiveTest extends BaseBlobStoreIntegrationTest {
       client.setPublicAccessForContainer(blockContainer, access);
       assertThat(client.getPublicAccessForContainer(blockContainer)).isEqualTo(access);
    }
+
+   @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateContainer" })
+   public void testCopyBlob() throws Exception {
+      ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024);
+
+      // create blob
+      AzureBlob object = getApi().newBlob();
+      object.getProperties().setName("from");
+      object.setPayload(byteSource.read());
+      getApi().putBlob(privateContainer, object);
+
+      // copy blob
+      URI copySource = view.getSigner().signGetBlob(privateContainer, "from").getEndpoint();
+      getApi().copyBlob(copySource, privateContainer, "to", CopyBlobOptions.NONE);
+
+      // ensure copied blob matches original
+      AzureBlob getBlob = getApi().getBlob(privateContainer, "to");
+      assertEquals(ByteStreams2.toByteArrayAndClose(getBlob.getPayload().openStream()), byteSource.read());
+      assertThat(getBlob.getProperties().getMetadata().isEmpty());
+   }
+
+   @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateContainer" })
+   public void testCopyBlobReplaceMetadata() throws Exception {
+      ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024);
+
+      // create blob
+      AzureBlob object = getApi().newBlob();
+      object.getProperties().setName("from");
+      object.setPayload(byteSource.read());
+      getApi().putBlob(privateContainer, object);
+
+      // copy blob
+      URI copySource = view.getSigner().signGetBlob(privateContainer, "from").getEndpoint();
+      Map<String, String> newMetadata = ImmutableMap.of("foo", "bar");
+      getApi().copyBlob(copySource, privateContainer, "to", CopyBlobOptions.builder().overrideUserMetadata(newMetadata).build());
+
+      // ensure copied blob matches original
+      AzureBlob getBlob = getApi().getBlob(privateContainer, "to");
+      assertEquals(ByteStreams2.toByteArrayAndClose(getBlob.getPayload().openStream()), byteSource.read());
+      assertThat(getBlob.getProperties().getMetadata()).isEqualTo(newMetadata);
+   }
+
+   @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateContainer" })
+   public void testCopyBlobIfModifiedSince() throws Exception {
+      ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024);
+
+      // create blob
+      AzureBlob object = getApi().newBlob();
+      object.getProperties().setName("from");
+      object.setPayload(byteSource.read());
+      String eTag = getApi().putBlob(privateContainer, object);
+
+      long now = System.currentTimeMillis();
+      Date before = new Date(now - 1000 * 1000);
+      Date after = new Date(now + 1000 * 1000);
+      URI copySource = view.getSigner().signGetBlob(privateContainer, "from").getEndpoint();
+
+      // failure case
+      try {
+         getApi().copyBlob(copySource, privateContainer, "to-if-modified-since", CopyBlobOptions.builder().ifModifiedSince(after).build());
+         failBecauseExceptionWasNotThrown(AzureStorageResponseException.class);
+      } catch (AzureStorageResponseException asre) {
+         assertThat(asre.getResponse().getStatusCode()).as("status code").isEqualTo(412);
+      }
+
+      // success case
+      getApi().copyBlob(copySource, privateContainer, "to-if-modified-since", CopyBlobOptions.builder().ifModifiedSince(before).build());
+      AzureBlob getBlob = getApi().getBlob(privateContainer, "to-if-modified-since");
+      assertEquals(ByteStreams2.toByteArrayAndClose(getBlob.getPayload().openStream()), byteSource.read());
+   }
+
+   @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateContainer" })
+   public void testCopyBlobIfUnmodifiedSince() throws Exception {
+      ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024);
+
+      // create blob
+      AzureBlob object = getApi().newBlob();
+      object.getProperties().setName("from");
+      object.setPayload(byteSource.read());
+      String eTag = getApi().putBlob(privateContainer, object);
+
+      long now = System.currentTimeMillis();
+      Date before = new Date(now - 1000 * 1000);
+      Date after = new Date(now + 1000 * 1000);
+      URI copySource = view.getSigner().signGetBlob(privateContainer, "from").getEndpoint();
+
+      // failure case
+      try {
+         getApi().copyBlob(copySource, privateContainer, "to-if-unmodifed-since", CopyBlobOptions.builder().ifUnmodifiedSince(before).build());
+         failBecauseExceptionWasNotThrown(AzureStorageResponseException.class);
+      } catch (AzureStorageResponseException asre) {
+         assertThat(asre.getResponse().getStatusCode()).as("status code").isEqualTo(412);
+      }
+
+      // success case
+      getApi().copyBlob(copySource, privateContainer, "to-if-unmodifed-since", CopyBlobOptions.builder().ifUnmodifiedSince(after).build());
+      AzureBlob getBlob = getApi().getBlob(privateContainer, "to-if-unmodifed-since");
+      assertEquals(ByteStreams2.toByteArrayAndClose(getBlob.getPayload().openStream()), byteSource.read());
+   }
+
+   @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateContainer" })
+   public void testCopyBlobIfMatch() throws Exception {
+      ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024);
+
+      // create blob
+      AzureBlob object = getApi().newBlob();
+      object.getProperties().setName("from");
+      object.setPayload(byteSource.read());
+      String eTag = getApi().putBlob(privateContainer, object);
+      String fakeETag = "0x8CEB669D794AFE2";
+
+      URI copySource = view.getSigner().signGetBlob(privateContainer, "from").getEndpoint();
+
+      // failure case
+      try {
+         getApi().copyBlob(copySource, privateContainer, "to-if-match", CopyBlobOptions.builder().ifMatch(fakeETag).build());
+         failBecauseExceptionWasNotThrown(AzureStorageResponseException.class);
+      } catch (AzureStorageResponseException asre) {
+         assertThat(asre.getResponse().getStatusCode()).as("status code").isEqualTo(412);
+      }
+
+      // success case
+      getApi().copyBlob(copySource, privateContainer, "to-if-match", CopyBlobOptions.builder().ifMatch(eTag).build());
+      AzureBlob getBlob = getApi().getBlob(privateContainer, "to-if-match");
+      assertEquals(ByteStreams2.toByteArrayAndClose(getBlob.getPayload().openStream()), byteSource.read());
+   }
+
+   @Test(timeOut = 5 * 60 * 1000, dependsOnMethods = { "testCreateContainer" })
+   public void testCopyBlobIfNoneMatch() throws Exception {
+      ByteSource byteSource = TestUtils.randomByteSource().slice(0, 1024);
+
+      // create blob
+      AzureBlob object = getApi().newBlob();
+      object.getProperties().setName("from");
+      object.setPayload(byteSource.read());
+      String eTag = getApi().putBlob(privateContainer, object);
+      String fakeETag = "0x8CEB669D794AFE2";
+
+      URI copySource = view.getSigner().signGetBlob(privateContainer, "from").getEndpoint();
+
+      // failure case
+      try {
+         getApi().copyBlob(copySource, privateContainer, "to-if-none-match", CopyBlobOptions.builder().ifNoneMatch(eTag).build());
+         failBecauseExceptionWasNotThrown(AzureStorageResponseException.class);
+      } catch (AzureStorageResponseException asre) {
+         assertThat(asre.getResponse().getStatusCode()).as("status code").isEqualTo(412);
+      }
+
+      // success case
+      getApi().copyBlob(copySource, privateContainer, "to-if-none-match", CopyBlobOptions.builder().ifNoneMatch(fakeETag).build());
+      AzureBlob getBlob = getApi().getBlob(privateContainer, "to-if-none-match");
+      assertEquals(ByteStreams2.toByteArrayAndClose(getBlob.getPayload().openStream()), byteSource.read());
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/54874000/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
----------------------------------------------------------------------
diff --git a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
index 5e82b35..af6ea34 100644
--- a/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
+++ b/providers/azureblob/src/test/java/org/jclouds/azureblob/AzureBlobClientTest.java
@@ -22,6 +22,8 @@ import static org.jclouds.reflect.Reflection2.method;
 import static org.testng.Assert.assertEquals;
 
 import java.io.IOException;
+import java.net.URI;
+import java.util.Date;
 import java.util.Map;
 
 import org.jclouds.Fallbacks.TrueOnNotFoundOr404;
@@ -33,6 +35,7 @@ import org.jclouds.azureblob.domain.PublicAccess;
 import org.jclouds.azureblob.functions.ParseBlobFromHeadersAndHttpContent;
 import org.jclouds.azureblob.functions.ParseContainerPropertiesFromHeaders;
 import org.jclouds.azureblob.functions.ParsePublicAccessHeader;
+import org.jclouds.azureblob.options.CopyBlobOptions;
 import org.jclouds.azureblob.options.CreateContainerOptions;
 import org.jclouds.azureblob.options.ListBlobsOptions;
 import org.jclouds.azureblob.xml.AccountNameEnumerationResultsHandler;
@@ -304,6 +307,102 @@ public class AzureBlobClientTest extends BaseRestAnnotationProcessingTest<AzureB
       assertFallbackClassEquals(method, null);
    }
 
+   public void testCopyBlob() throws Exception {
+      Invokable<?, ?> method = method(AzureBlobClient.class, "copyBlob", URI.class, String.class, String.class, CopyBlobOptions.class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of(
+               URI.create("https://identity.blob.core.windows.net/fromcontainer/fromblob"), "tocontainer", "toblob", CopyBlobOptions.NONE));
+
+      assertRequestLineEquals(request,
+               "PUT https://identity.blob.core.windows.net/tocontainer/toblob HTTP/1.1");
+      checkFilters(request);
+      assertNonPayloadHeadersEqual(request,
+               "x-ms-copy-source: https://identity.blob.core.windows.net/fromcontainer/fromblob\n" +
+               "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+   }
+
+   public void testCopyBlobOverwriteUserMetadata() throws Exception {
+      CopyBlobOptions options = CopyBlobOptions.builder().overrideUserMetadata(ImmutableMap.of("foo", "bar")).build();
+      Invokable<?, ?> method = method(AzureBlobClient.class, "copyBlob", URI.class, String.class, String.class, CopyBlobOptions.class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of(
+               URI.create("https://identity.blob.core.windows.net/fromcontainer/fromblob"), "tocontainer", "toblob", options));
+
+      assertRequestLineEquals(request,
+               "PUT https://identity.blob.core.windows.net/tocontainer/toblob HTTP/1.1");
+      checkFilters(request);
+      assertNonPayloadHeadersEqual(request,
+               "x-ms-copy-source: https://identity.blob.core.windows.net/fromcontainer/fromblob\n" +
+               "x-ms-meta-foo: bar\n" +
+               "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+   }
+
+   public void testCopyBlobIfModifiedSince() throws Exception {
+      CopyBlobOptions options = CopyBlobOptions.builder().ifModifiedSince(new Date(1000)).build();
+      Invokable<?, ?> method = method(AzureBlobClient.class, "copyBlob", URI.class, String.class, String.class, CopyBlobOptions.class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of(
+               URI.create("https://identity.blob.core.windows.net/fromcontainer/fromblob"), "tocontainer", "toblob", options));
+
+      assertRequestLineEquals(request,
+               "PUT https://identity.blob.core.windows.net/tocontainer/toblob HTTP/1.1");
+      checkFilters(request);
+      assertNonPayloadHeadersEqual(request,
+               "x-ms-copy-source: https://identity.blob.core.windows.net/fromcontainer/fromblob\n" +
+               "x-ms-source-if-modified-since: Thu, 01 Jan 1970 00:00:01 GMT\n" +
+               "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+   }
+
+   public void testCopyBlobIfUnmodifiedSince() throws Exception {
+      CopyBlobOptions options = CopyBlobOptions.builder().ifUnmodifiedSince(new Date(1000)).build();
+      Invokable<?, ?> method = method(AzureBlobClient.class, "copyBlob", URI.class, String.class, String.class, CopyBlobOptions.class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of(
+               URI.create("https://identity.blob.core.windows.net/fromcontainer/fromblob"), "tocontainer", "toblob", options));
+
+      assertRequestLineEquals(request,
+               "PUT https://identity.blob.core.windows.net/tocontainer/toblob HTTP/1.1");
+      checkFilters(request);
+      assertNonPayloadHeadersEqual(request,
+               "x-ms-copy-source: https://identity.blob.core.windows.net/fromcontainer/fromblob\n" +
+               "x-ms-source-if-unmodified-since: Thu, 01 Jan 1970 00:00:01 GMT\n" +
+               "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+   }
+
+   public void testCopyBlobIfMatch() throws Exception {
+      String eTag = "0x8CEB669D794AFE2";
+      CopyBlobOptions options = CopyBlobOptions.builder().ifMatch(eTag).build();
+      Invokable<?, ?> method = method(AzureBlobClient.class, "copyBlob", URI.class, String.class, String.class, CopyBlobOptions.class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of(
+               URI.create("https://identity.blob.core.windows.net/fromcontainer/fromblob"), "tocontainer", "toblob", options));
+
+      assertRequestLineEquals(request,
+               "PUT https://identity.blob.core.windows.net/tocontainer/toblob HTTP/1.1");
+      checkFilters(request);
+      assertNonPayloadHeadersEqual(request,
+               "x-ms-copy-source: https://identity.blob.core.windows.net/fromcontainer/fromblob\n" +
+               "x-ms-source-if-match: " + eTag + "\n" +
+               "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+   }
+
+   public void testCopyBlobIfNoneMatch() throws Exception {
+      String eTag = "0x8CEB669D794AFE2";
+      CopyBlobOptions options = CopyBlobOptions.builder().ifNoneMatch(eTag).build();
+      Invokable<?, ?> method = method(AzureBlobClient.class, "copyBlob", URI.class, String.class, String.class, CopyBlobOptions.class);
+      GeneratedHttpRequest request = processor.createRequest(method, ImmutableList.<Object> of(
+               URI.create("https://identity.blob.core.windows.net/fromcontainer/fromblob"), "tocontainer", "toblob", options));
+
+      assertRequestLineEquals(request,
+               "PUT https://identity.blob.core.windows.net/tocontainer/toblob HTTP/1.1");
+      checkFilters(request);
+      assertNonPayloadHeadersEqual(request,
+               "x-ms-copy-source: https://identity.blob.core.windows.net/fromcontainer/fromblob\n" +
+               "x-ms-source-if-none-match: " + eTag + "\n" +
+               "x-ms-version: 2013-08-15\n");
+      assertPayloadEquals(request, null, null, false);
+   }
+
    @Override
    protected void checkFilters(HttpRequest request) {
       assertEquals(request.getFilters().size(), 1);