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 2014/05/15 11:34:09 UTC

git commit: JCLOUDS-457: Created the skeleton of the Glacier API.

Repository: jclouds-labs-aws
Updated Branches:
  refs/heads/master ed6d635e9 -> ca269db8b


JCLOUDS-457: Created the skeleton of the Glacier API.


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

Branch: refs/heads/master
Commit: ca269db8b3d6ff93e61ac9c7410c3c2d99406de2
Parents: ed6d635
Author: Roman C. Coedo <ro...@gmail.com>
Authored: Tue Apr 22 12:15:03 2014 +0200
Committer: Andrew Gaul <ga...@apache.org>
Committed: Thu May 15 02:32:27 2014 -0700

----------------------------------------------------------------------
 glacier/pom.xml                                 | 128 +++++++++++++
 .../org/jclouds/glacier/GlacierApiMetadata.java |  98 ++++++++++
 .../org/jclouds/glacier/GlacierAsyncClient.java |  46 +++++
 .../java/org/jclouds/glacier/GlacierClient.java |  25 +++
 .../glacier/config/GlacierRestClientModule.java |  64 +++++++
 .../filters/RequestAuthorizeSignature.java      |  73 +++++++
 .../glacier/reference/GlacierHeaders.java       |  28 +++
 .../glacier/util/AWSRequestSignerV4.java        | 189 +++++++++++++++++++
 .../services/org.jclouds.apis.ApiMetadata       |   1 +
 .../jclouds/glacier/GlacierApiMetadataTest.java |  33 ++++
 .../jclouds/glacier/GlacierClientMockTest.java  |  77 ++++++++
 .../glacier/util/AWSRequestSignerV4Test.java    |  56 ++++++
 .../services/org.jclouds.apis.ApiMetadata       |   1 +
 pom.xml                                         |   1 +
 14 files changed, 820 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/pom.xml
----------------------------------------------------------------------
diff --git a/glacier/pom.xml b/glacier/pom.xml
new file mode 100644
index 0000000..5ac9979
--- /dev/null
+++ b/glacier/pom.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.jclouds.labs</groupId>
+    <artifactId>jclouds-labs-aws</artifactId>
+    <version>1.8.0-SNAPSHOT</version>
+  </parent>
+
+  <groupId>org.apache.jclouds.labs</groupId>
+  <artifactId>glacier</artifactId>
+  <name>jclouds glacier api</name>
+  <description>jclouds components to access an implementation of glacier</description>
+  <packaging>bundle</packaging>
+
+  <properties>
+    <test.glacier.endpoint>https://glacier.eu-west-1.amazonaws.com</test.glacier.endpoint>
+    <test.glacier.api-version>2012-06-01</test.glacier.api-version>
+    <test.glacier.build-version />
+    <test.glacier.identity>${test.aws.identity}</test.glacier.identity>
+    <test.glacier.credential>${test.aws.credential}</test.glacier.credential>
+
+    <jclouds.osgi.export>org.jclouds.glacier*;version="${project.version}"</jclouds.osgi.export>
+    <jclouds.osgi.import>
+        org.jclouds.labs*;version="${project.version}",
+        org.jclouds*;version="${project.version}",
+        *
+    </jclouds.osgi.import>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.jclouds.api</groupId>
+      <artifactId>sts</artifactId>
+      <version>${project.version}</version>
+      <type>jar</type>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-blobstore</artifactId>
+      <version>${project.version}</version>
+      <type>jar</type>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-core</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-blobstore</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds.driver</groupId>
+      <artifactId>jclouds-log4j</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.mockwebserver</groupId>
+      <artifactId>mockwebserver</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <profiles>
+    <profile>
+      <id>live</id>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <executions>
+              <execution>
+                <id>integration</id>
+                <phase>integration-test</phase>
+                <goals>
+                  <goal>test</goal>
+                </goals>
+                <configuration>
+                  <systemPropertyVariables>
+                    <jclouds.blobstore.httpstream.url>${jclouds.blobstore.httpstream.url}</jclouds.blobstore.httpstream.url>
+                    <jclouds.blobstore.httpstream.md5>${jclouds.blobstore.httpstream.md5}</jclouds.blobstore.httpstream.md5>
+                    <test.glacier.endpoint>${test.glacier.endpoint}</test.glacier.endpoint>
+                    <test.glacier.api-version>${test.glacier.api-version}</test.glacier.api-version>
+                    <test.glacier.build-version>${test.glacier.build-version}</test.glacier.build-version>
+                    <test.glacier.identity>${test.glacier.identity}</test.glacier.identity>
+                    <test.glacier.credential>${test.glacier.credential}</test.glacier.credential>
+                  </systemPropertyVariables>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+
+</project>

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/GlacierApiMetadata.java
----------------------------------------------------------------------
diff --git a/glacier/src/main/java/org/jclouds/glacier/GlacierApiMetadata.java b/glacier/src/main/java/org/jclouds/glacier/GlacierApiMetadata.java
new file mode 100644
index 0000000..c547e21
--- /dev/null
+++ b/glacier/src/main/java/org/jclouds/glacier/GlacierApiMetadata.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
+import static org.jclouds.reflect.Reflection2.typeToken;
+
+import java.net.URI;
+import java.util.Properties;
+
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.glacier.config.GlacierRestClientModule;
+import org.jclouds.glacier.reference.GlacierHeaders;
+import org.jclouds.rest.internal.BaseRestApiMetadata;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.reflect.TypeToken;
+import com.google.inject.Module;
+
+/**
+ * Implementation of ApiMetadata for Amazon Glacier API
+ *
+ * @author Roman Coedo
+ */
+public class GlacierApiMetadata extends BaseRestApiMetadata {
+
+   @Deprecated
+   public static final TypeToken<org.jclouds.rest.RestContext<GlacierClient, GlacierAsyncClient>> CONTEXT_TOKEN = new TypeToken<org.jclouds.rest.RestContext<GlacierClient, GlacierAsyncClient>>() {
+
+      private static final long serialVersionUID = 1L;
+   };
+
+   private static Builder builder() {
+      return new Builder();
+   }
+
+   @Override
+   public Builder toBuilder() {
+      return builder().fromApiMetadata(this);
+   }
+
+   public GlacierApiMetadata() {
+      this(builder());
+   }
+
+   protected GlacierApiMetadata(Builder builder) {
+      super(new Builder());
+   }
+
+   public static Properties defaultProperties() {
+      Properties properties = BaseRestApiMetadata.defaultProperties();
+      properties.setProperty(PROPERTY_HEADER_TAG, GlacierHeaders.DEFAULT_AMAZON_HEADERTAG);
+      return properties;
+   }
+
+   public static class Builder extends BaseRestApiMetadata.Builder<Builder> {
+
+      @SuppressWarnings("deprecation")
+      protected Builder() {
+         super(GlacierClient.class, GlacierAsyncClient.class);
+         id("glacier")
+               .name("Amazon Glacier API")
+               .identityName("Access Key ID")
+               .credentialName("Secret Access Key")
+               .defaultEndpoint("https://glacier.us-east-1.amazonaws.com")
+               .documentation(URI.create("http://docs.aws.amazon.com/amazonglacier/latest/dev/amazon-glacier-api.html"))
+               .version("2012-06-01")
+               .defaultProperties(GlacierApiMetadata.defaultProperties())
+               .context(CONTEXT_TOKEN)
+               .view(typeToken(BlobStoreContext.class))
+               .defaultModules(ImmutableSet.<Class<? extends Module>> of(GlacierRestClientModule.class));
+      }
+
+      @Override
+      public GlacierApiMetadata build() {
+         return new GlacierApiMetadata(this);
+      }
+
+      @Override
+      protected Builder self() {
+         return this;
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/GlacierAsyncClient.java
----------------------------------------------------------------------
diff --git a/glacier/src/main/java/org/jclouds/glacier/GlacierAsyncClient.java b/glacier/src/main/java/org/jclouds/glacier/GlacierAsyncClient.java
new file mode 100644
index 0000000..7f66811
--- /dev/null
+++ b/glacier/src/main/java/org/jclouds/glacier/GlacierAsyncClient.java
@@ -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;
+
+import static org.jclouds.blobstore.attr.BlobScopes.CONTAINER;
+
+import java.io.Closeable;
+import java.net.URI;
+
+import javax.inject.Named;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+
+import org.jclouds.blobstore.attr.BlobScope;
+import org.jclouds.glacier.filters.RequestAuthorizeSignature;
+import org.jclouds.glacier.reference.GlacierHeaders;
+import org.jclouds.rest.annotations.Headers;
+import org.jclouds.rest.annotations.RequestFilters;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+@Headers(keys = GlacierHeaders.VERSION, values = "2012-06-01")
+@RequestFilters(RequestAuthorizeSignature.class)
+@BlobScope(CONTAINER)
+public interface GlacierAsyncClient extends Closeable {
+
+   @Named("CreateVault")
+   @PUT
+   @Path("/-/vaults/{vault}")
+   ListenableFuture<URI> createVault(@PathParam("vault") String vaultName);
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/GlacierClient.java
----------------------------------------------------------------------
diff --git a/glacier/src/main/java/org/jclouds/glacier/GlacierClient.java b/glacier/src/main/java/org/jclouds/glacier/GlacierClient.java
new file mode 100644
index 0000000..3d392b9
--- /dev/null
+++ b/glacier/src/main/java/org/jclouds/glacier/GlacierClient.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import java.io.Closeable;
+import java.net.URI;
+
+public interface GlacierClient extends Closeable {
+
+   URI createVault(String vaultName);
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/config/GlacierRestClientModule.java
----------------------------------------------------------------------
diff --git a/glacier/src/main/java/org/jclouds/glacier/config/GlacierRestClientModule.java b/glacier/src/main/java/org/jclouds/glacier/config/GlacierRestClientModule.java
new file mode 100644
index 0000000..7631ce9
--- /dev/null
+++ b/glacier/src/main/java/org/jclouds/glacier/config/GlacierRestClientModule.java
@@ -0,0 +1,64 @@
+/*
+ * 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.config;
+
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Named;
+
+import org.jclouds.Constants;
+import org.jclouds.date.DateService;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.glacier.GlacierAsyncClient;
+import org.jclouds.glacier.GlacierClient;
+import org.jclouds.glacier.filters.RequestAuthorizeSignature;
+import org.jclouds.rest.ConfiguresRestClient;
+import org.jclouds.rest.config.RestClientModule;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import com.google.inject.Provides;
+import com.google.inject.Scopes;
+
+@ConfiguresRestClient
+public class GlacierRestClientModule extends RestClientModule<GlacierClient, GlacierAsyncClient> {
+
+   @Override
+   protected void configure() {
+      super.configure();
+      bind(RequestAuthorizeSignature.class).in(Scopes.SINGLETON);
+   }
+
+   @Provides
+   @TimeStamp
+   protected String provideTimeStamp(@TimeStamp Supplier<String> cache) {
+      return cache.get();
+   }
+
+   @Provides
+   @TimeStamp
+   Supplier<String> provideTimeStampCache(@Named(Constants.PROPERTY_SESSION_INTERVAL) long seconds,
+         final DateService dateService) {
+      return Suppliers.memoizeWithExpiration(new Supplier<String>() {
+
+         @Override
+         public String get() {
+            return dateService.iso8601SecondsDateFormat().replaceAll("[-:]", "");
+         }
+      }, seconds, TimeUnit.SECONDS);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/filters/RequestAuthorizeSignature.java
----------------------------------------------------------------------
diff --git a/glacier/src/main/java/org/jclouds/glacier/filters/RequestAuthorizeSignature.java b/glacier/src/main/java/org/jclouds/glacier/filters/RequestAuthorizeSignature.java
new file mode 100644
index 0000000..8f46a0f
--- /dev/null
+++ b/glacier/src/main/java/org/jclouds/glacier/filters/RequestAuthorizeSignature.java
@@ -0,0 +1,73 @@
+/*
+ * 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.filters;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Provider;
+import javax.inject.Singleton;
+
+import org.jclouds.Constants;
+import org.jclouds.crypto.Crypto;
+import org.jclouds.date.TimeStamp;
+import org.jclouds.domain.Credentials;
+import org.jclouds.glacier.reference.GlacierHeaders;
+import org.jclouds.glacier.util.AWSRequestSignerV4;
+import org.jclouds.http.HttpException;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpRequestFilter;
+import org.jclouds.http.HttpUtils;
+import org.jclouds.logging.Logger;
+
+import com.google.common.base.Supplier;
+import com.google.common.net.HttpHeaders;
+
+@Singleton
+public class RequestAuthorizeSignature implements HttpRequestFilter {
+
+   private final AWSRequestSignerV4 signer;
+
+   @Resource
+   @Named(Constants.LOGGER_SIGNATURE)
+   Logger signatureLog = Logger.NULL;
+
+   private final Provider<String> timeStampProvider;
+   private final HttpUtils utils;
+
+   @Inject
+   public RequestAuthorizeSignature(@TimeStamp Provider<String> timeStampProvider,
+         @org.jclouds.location.Provider Supplier<Credentials> creds, Crypto crypto, HttpUtils utils) {
+      checkNotNull(creds);
+      this.signer = new AWSRequestSignerV4(creds.get().identity, creds.get().credential, checkNotNull(crypto));
+      this.timeStampProvider = checkNotNull(timeStampProvider);
+      this.utils = checkNotNull(utils);
+   }
+
+   @Override
+   public HttpRequest filter(HttpRequest request) throws HttpException {
+      request = request.toBuilder().removeHeader(HttpHeaders.DATE)
+            .replaceHeader(GlacierHeaders.ALTERNATE_DATE, timeStampProvider.get())
+            .replaceHeader(HttpHeaders.HOST, request.getEndpoint().getHost()).build();
+      utils.logRequest(signatureLog, request, ">>");
+      request = this.signer.sign(request);
+      utils.logRequest(signatureLog, request, "<<");
+      return request;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/reference/GlacierHeaders.java
----------------------------------------------------------------------
diff --git a/glacier/src/main/java/org/jclouds/glacier/reference/GlacierHeaders.java b/glacier/src/main/java/org/jclouds/glacier/reference/GlacierHeaders.java
new file mode 100644
index 0000000..51a0afe
--- /dev/null
+++ b/glacier/src/main/java/org/jclouds/glacier/reference/GlacierHeaders.java
@@ -0,0 +1,28 @@
+/*
+ * 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.reference;
+
+public final class GlacierHeaders {
+
+   public static final String DEFAULT_AMAZON_HEADERTAG = "amz";
+   public static final String HEADER_PREFIX = "x-" + DEFAULT_AMAZON_HEADERTAG + "-";
+   public static final String VERSION = HEADER_PREFIX + "glacier-version";
+   public static final String ALTERNATE_DATE = HEADER_PREFIX + "date";
+
+   private GlacierHeaders() {
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/util/AWSRequestSignerV4.java
----------------------------------------------------------------------
diff --git a/glacier/src/main/java/org/jclouds/glacier/util/AWSRequestSignerV4.java b/glacier/src/main/java/org/jclouds/glacier/util/AWSRequestSignerV4.java
new file mode 100644
index 0000000..e893b03
--- /dev/null
+++ b/glacier/src/main/java/org/jclouds/glacier/util/AWSRequestSignerV4.java
@@ -0,0 +1,189 @@
+/*
+ * 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.util;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Map.Entry;
+
+import javax.crypto.Mac;
+
+import org.jclouds.crypto.Crypto;
+import org.jclouds.glacier.reference.GlacierHeaders;
+import org.jclouds.http.HttpException;
+import org.jclouds.http.HttpRequest;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.SortedSetMultimap;
+import com.google.common.collect.TreeMultimap;
+import com.google.common.hash.Hashing;
+import com.google.common.io.BaseEncoding;
+import com.google.common.io.ByteStreams;
+import com.google.common.net.HttpHeaders;
+
+// TODO: Query parameters, not necessary for Glacier
+// TODO: Endpoint on buildCredentialScope is being read from the static string. Uncool.
+/**
+ * Signs requests using the AWSv4 signing algorithm
+ *
+ * @see <a href="http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html" />
+ * @author Roman Coedo
+ */
+public class AWSRequestSignerV4 {
+
+   public static final String AUTH_TAG = "AWS4";
+   public static final String HEADER_TAG = "x-amz-";
+   public static final String ALGORITHM = AUTH_TAG + "-HMAC-SHA256";
+   public static final String TERMINATION_STRING = "aws4_request";
+   public static final String REGION = "us-east-1";
+   public static final String SERVICE = "glacier";
+
+   private final Crypto crypto;
+   private final String identity;
+   private final String credential;
+
+   public AWSRequestSignerV4(String identity, String credential, Crypto crypto) {
+      this.crypto = checkNotNull(crypto);
+      this.identity = checkNotNull(identity);
+      this.credential = checkNotNull(credential);
+   }
+
+   private static String buildHashedCanonicalRequest(String method, String endpoint, String hashedPayload,
+         String canonicalizedHeadersString, String signedHeaders) {
+      return sha256((method + "\n" + endpoint + "\n" + "" + "\n" + canonicalizedHeadersString + "\n" + signedHeaders
+            + "\n" + hashedPayload).getBytes());
+   }
+
+   private static String createStringToSign(String date, String credentialScope, String hashedCanonicalRequest) {
+      return ALGORITHM + "\n" + date + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
+   }
+
+   private static String formatDateWithoutTimestamp(String date) {
+      return date.substring(0, 8);
+   }
+
+   private static String buildCredentialScope(String dateWithoutTimeStamp) {
+      return dateWithoutTimeStamp + "/" + REGION + "/" + SERVICE + "/" + TERMINATION_STRING;
+   }
+
+   private static Multimap<String, String> buildCanonicalizedHeadersMap(HttpRequest request) {
+      Multimap<String, String> headers = request.getHeaders();
+      SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create();
+      for (Entry<String, String> header : headers.entries()) {
+         if (header.getKey() == null)
+            continue;
+         String key = header.getKey().toString().toLowerCase(Locale.getDefault());
+         // Ignore any headers that are not particularly interesting.
+         if (key.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE) || key.equalsIgnoreCase(HttpHeaders.CONTENT_MD5)
+               || key.equalsIgnoreCase(HttpHeaders.HOST) || key.startsWith(HEADER_TAG)) {
+            canonicalizedHeaders.put(key, header.getValue());
+         }
+      }
+      return canonicalizedHeaders;
+   }
+
+   private static String buildCanonicalizedHeadersString(Multimap<String, String> canonicalizedHeadersMap) {
+      StringBuilder canonicalizedHeadersBuffer = new StringBuilder();
+      for (Entry<String, String> header : canonicalizedHeadersMap.entries()) {
+         String key = header.getKey();
+         canonicalizedHeadersBuffer.append(key.toLowerCase()).append(':').append(header.getValue()).append('\n');
+      }
+      return canonicalizedHeadersBuffer.toString();
+   }
+
+   private static String buildSignedHeaders(Multimap<String, String> canonicalizedHeadersMap) {
+      return Joiner.on(';').join(Iterables.transform(canonicalizedHeadersMap.keySet(), new Function<String, String>() {
+
+         @Override
+         public String apply(String input) {
+            return input.toLowerCase();
+         }
+      }));
+   }
+
+   private static String sha256(byte[] unhashedBytes) {
+      return Hashing.sha256().hashBytes(unhashedBytes).toString();
+   }
+
+   private static String buildHashedPayload(HttpRequest request) {
+      try {
+         byte[] unhashedBytes = request.getPayload() == null ? "".getBytes() : ByteStreams.toByteArray(request
+               .getPayload().getInput());
+         return sha256(unhashedBytes);
+      } catch (IOException e) {
+         throw new HttpException("Error signing request", e);
+      }
+   }
+
+   private static String buildAuthHeader(String accessKey, String credentialScope, String signedHeaders,
+         String signature) {
+      return ALGORITHM + " " + "Credential=" + accessKey + "/" + credentialScope + "," + "SignedHeaders="
+            + signedHeaders + "," + "Signature=" + signature;
+   }
+
+   private byte[] hmacSha256(byte[] key, String s) {
+      try {
+         Mac hmacSHA256 = crypto.hmacSHA256(key);
+         return hmacSHA256.doFinal(s.getBytes());
+      } catch (Exception e) {
+         throw new HttpException("Error signing request", e);
+      }
+   }
+
+   private String buildSignature(String dateWithoutTimestamp, String stringToSign) {
+      byte[] kSecret = (AUTH_TAG + credential).getBytes(UTF_8);
+      byte[] kDate = hmacSha256(kSecret, dateWithoutTimestamp);
+      byte[] kRegion = hmacSha256(kDate, REGION);
+      byte[] kService = hmacSha256(kRegion, SERVICE);
+      byte[] kSigning = hmacSha256(kService, TERMINATION_STRING);
+      return BaseEncoding.base16().encode(hmacSha256(kSigning, stringToSign)).toLowerCase();
+   }
+
+   public HttpRequest sign(HttpRequest request) {
+      // Grab the needed data to build the signature
+      Multimap<String, String> canonicalizedHeadersMap = buildCanonicalizedHeadersMap(request);
+      String canonicalizedHeadersString = buildCanonicalizedHeadersString(canonicalizedHeadersMap);
+      String signedHeaders = buildSignedHeaders(canonicalizedHeadersMap);
+      String date = request.getFirstHeaderOrNull(GlacierHeaders.ALTERNATE_DATE);
+      String dateWithoutTimestamp = formatDateWithoutTimestamp(date);
+      String method = request.getMethod();
+      String endpoint = request.getEndpoint().getRawPath();
+      String credentialScope = buildCredentialScope(dateWithoutTimestamp);
+      String hashedPayload = buildHashedPayload(request);
+
+      // Task 1: Create a Canonical Request For Signature Version 4.
+      String hashedCanonicalRequest = buildHashedCanonicalRequest(method, endpoint, hashedPayload,
+            canonicalizedHeadersString, signedHeaders);
+
+      // Task 2: Create a String to Sign for Signature Version 4.
+      String stringToSign = createStringToSign(date, credentialScope, hashedCanonicalRequest);
+
+      // Task 3: Calculate the AWS Signature Version 4.
+      String signature = buildSignature(dateWithoutTimestamp, stringToSign);
+
+      // Sign the request
+      String authHeader = buildAuthHeader(identity, credentialScope, signedHeaders, signature);
+      request = request.toBuilder().replaceHeader(HttpHeaders.AUTHORIZATION, authHeader).build();
+      return request;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
----------------------------------------------------------------------
diff --git a/glacier/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/glacier/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
new file mode 100644
index 0000000..ef20fba
--- /dev/null
+++ b/glacier/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata
@@ -0,0 +1 @@
+org.jclouds.glacier.GlacierApiMetadata

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/test/java/org/jclouds/glacier/GlacierApiMetadataTest.java
----------------------------------------------------------------------
diff --git a/glacier/src/test/java/org/jclouds/glacier/GlacierApiMetadataTest.java b/glacier/src/test/java/org/jclouds/glacier/GlacierApiMetadataTest.java
new file mode 100644
index 0000000..0cb3272
--- /dev/null
+++ b/glacier/src/test/java/org/jclouds/glacier/GlacierApiMetadataTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import org.jclouds.blobstore.internal.BaseBlobStoreApiMetadataTest;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Roman Coedo
+ */
+@Test(groups = "unit", testName = "GlacierApiMetadataTest")
+public class GlacierApiMetadataTest extends BaseBlobStoreApiMetadataTest {
+
+   public GlacierApiMetadataTest() {
+      super(new GlacierApiMetadata());
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/test/java/org/jclouds/glacier/GlacierClientMockTest.java
----------------------------------------------------------------------
diff --git a/glacier/src/test/java/org/jclouds/glacier/GlacierClientMockTest.java b/glacier/src/test/java/org/jclouds/glacier/GlacierClientMockTest.java
new file mode 100644
index 0000000..b3ec6c2
--- /dev/null
+++ b/glacier/src/test/java/org/jclouds/glacier/GlacierClientMockTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
+import static org.jclouds.Constants.PROPERTY_MAX_RETRIES;
+import static org.jclouds.Constants.PROPERTY_SO_TIMEOUT;
+import static org.testng.Assert.assertEquals;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.util.Properties;
+import java.util.Set;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.concurrent.config.ExecutorServiceModule;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+import com.google.mockwebserver.MockResponse;
+import com.google.mockwebserver.MockWebServer;
+import com.google.mockwebserver.RecordedRequest;
+
+public class GlacierClientMockTest {
+
+   private static final String VAULT_NAME = "ConcreteVaultName";
+
+   private static final Set<Module> modules = ImmutableSet.<Module> of(new ExecutorServiceModule(sameThreadExecutor(),
+         sameThreadExecutor()));
+
+   static GlacierClient getGlacierClient(URL server) {
+      Properties overrides = new Properties();
+      // prevent expect-100 bug http://code.google.com/p/mockwebserver/issues/detail?id=6
+      overrides.setProperty(PROPERTY_SO_TIMEOUT, "0");
+      overrides.setProperty(PROPERTY_MAX_RETRIES, "1");
+      return ContextBuilder.newBuilder("glacier").credentials("accessKey", "secretKey").endpoint(server.toString())
+            .modules(modules).overrides(overrides).buildApi(GlacierClient.class);
+   }
+
+   public void testCreateVault() throws IOException, InterruptedException {
+      // Prepare the response
+      MockResponse mr = new MockResponse();
+      mr.setResponseCode(201);
+      mr.addHeader("x-amzn-RequestId", "AAABZpJrTyioDC_HsOmHae8EZp_uBSJr6cnGOLKp_XJCl-Q");
+      mr.addHeader("Date", "Sun, 25 Mar 2012 12:02:00 GMT");
+      mr.addHeader("Location", "/111122223333/vaults/" + VAULT_NAME);
+      MockWebServer server = new MockWebServer();
+      server.enqueue(mr);
+      server.play();
+
+      // Send the request and check the response
+      try {
+         GlacierClient client = getGlacierClient(server.getUrl("/"));
+         URI responseUri = client.createVault(VAULT_NAME);
+         assertEquals(responseUri.toString(), server.getUrl("/") + "111122223333/vaults/" + VAULT_NAME);
+         RecordedRequest request = server.takeRequest();
+         assertEquals(request.getRequestLine(), "PUT /-/vaults/" + VAULT_NAME + " HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/test/java/org/jclouds/glacier/util/AWSRequestSignerV4Test.java
----------------------------------------------------------------------
diff --git a/glacier/src/test/java/org/jclouds/glacier/util/AWSRequestSignerV4Test.java b/glacier/src/test/java/org/jclouds/glacier/util/AWSRequestSignerV4Test.java
new file mode 100644
index 0000000..24d479a
--- /dev/null
+++ b/glacier/src/test/java/org/jclouds/glacier/util/AWSRequestSignerV4Test.java
@@ -0,0 +1,56 @@
+/*
+ * 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.util;
+
+import static org.testng.Assert.assertEquals;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+
+import org.jclouds.encryption.internal.JCECrypto;
+import org.jclouds.http.HttpRequest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.Multimap;
+import com.google.common.collect.TreeMultimap;
+
+@Test(groups = "unit", testName = "AWSRequestSignerV4Test")
+public class AWSRequestSignerV4Test {
+
+   @Test
+   public void testSignatureCalculation() throws NoSuchAlgorithmException, CertificateException {
+      String auth = "AWS4-HMAC-SHA256 " + "Credential=AKIAIOSFODNN7EXAMPLE/20120525/us-east-1/glacier/aws4_request,"
+            + "SignedHeaders=host;x-amz-date;x-amz-glacier-version,"
+            + "Signature=3ce5b2f2fffac9262b4da9256f8d086b4aaf42eba5f111c21681a65a127b7c2a";
+      String identity = "AKIAIOSFODNN7EXAMPLE";
+      String credential = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
+      AWSRequestSignerV4 signer = new AWSRequestSignerV4(identity, credential, new JCECrypto());
+      HttpRequest request = signer.sign(createRequest());
+      assertEquals(request.getFirstHeaderOrNull("Authorization"), auth);
+   }
+
+   private HttpRequest createRequest() {
+      Multimap<String, String> headers = TreeMultimap.create();
+      headers.put("Host", "glacier.us-east-1.amazonaws.com");
+      headers.put("x-amz-date", "20120525T002453Z");
+      headers.put("x-amz-glacier-version", "2012-06-01");
+      HttpRequest request = HttpRequest.builder().method("PUT")
+            .endpoint("https://glacier.us-east-1.amazonaws.com/-/vaults/examplevault").headers(headers).build();
+      return request;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/test/resources/META-INF/services/org.jclouds.apis.ApiMetadata
----------------------------------------------------------------------
diff --git a/glacier/src/test/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/glacier/src/test/resources/META-INF/services/org.jclouds.apis.ApiMetadata
new file mode 100644
index 0000000..ef20fba
--- /dev/null
+++ b/glacier/src/test/resources/META-INF/services/org.jclouds.apis.ApiMetadata
@@ -0,0 +1 @@
+org.jclouds.glacier.GlacierApiMetadata

http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 1c5c45a..722bdf7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,6 +68,7 @@
     <module>elb</module>
     <module>iam</module>
     <module>rds</module>
+    <module>glacier</module>
     <module>aws-elb</module>
     <module>aws-iam</module>
     <module>aws-rds</module>