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/05/28 20:27:51 UTC

jclouds-labs git commit: JCLOUDS-1005: Backblaze B2 skeleton and bucket ops

Repository: jclouds-labs
Updated Branches:
  refs/heads/master 6d078e690 -> afca4993f


JCLOUDS-1005: Backblaze B2 skeleton and bucket ops


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

Branch: refs/heads/master
Commit: afca4993f80f74e13afb36fafcb5ac3837418376
Parents: 6d078e6
Author: Andrew Gaul <ga...@apache.org>
Authored: Thu May 19 10:50:45 2016 -0700
Committer: Andrew Gaul <ga...@apache.org>
Committed: Sat May 28 13:08:23 2016 -0700

----------------------------------------------------------------------
 b2/pom.xml                                      | 147 +++++++++++
 b2/src/main/java/org/jclouds/b2/B2Api.java      |  32 +++
 .../main/java/org/jclouds/b2/B2ApiMetadata.java |  85 +++++++
 .../java/org/jclouds/b2/B2ProviderMetadata.java |  66 +++++
 .../org/jclouds/b2/B2ResponseException.java     |  38 +++
 .../org/jclouds/b2/config/B2HttpApiModule.java  |  81 ++++++
 .../org/jclouds/b2/domain/Authorization.java    |  35 +++
 .../java/org/jclouds/b2/domain/B2Error.java     |  33 +++
 .../main/java/org/jclouds/b2/domain/Bucket.java |  34 +++
 .../java/org/jclouds/b2/domain/BucketList.java  |  34 +++
 .../java/org/jclouds/b2/domain/BucketType.java  |  34 +++
 .../jclouds/b2/features/AuthorizationApi.java   |  37 +++
 .../java/org/jclouds/b2/features/BucketApi.java |  75 ++++++
 .../b2/filters/RequestAuthorization.java        |  64 +++++
 .../handlers/ParseB2ErrorFromJsonContent.java   |  53 ++++
 .../org/jclouds/b2/B2ProviderMetadataTest.java  |  27 ++
 .../jclouds/b2/features/BucketApiLiveTest.java  | 106 ++++++++
 .../jclouds/b2/features/BucketApiMockTest.java  | 251 +++++++++++++++++++
 .../jclouds/b2/internal/BaseB2ApiLiveTest.java  |  33 +++
 .../resources/authorize_account_response.json   |   7 +
 b2/src/test/resources/bucket.json               |   6 +
 .../test/resources/create_bucket_request.json   |   5 +
 .../delete_bucket_already_deleted_response.json |   5 +
 .../test/resources/delete_bucket_request.json   |   4 +
 b2/src/test/resources/list_buckets_request.json |   3 +
 .../test/resources/list_buckets_response.json   |  21 ++
 b2/src/test/resources/log4j.xml                 | 106 ++++++++
 .../test/resources/update_bucket_request.json   |   5 +
 pom.xml                                         |   1 +
 29 files changed, 1428 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/pom.xml
----------------------------------------------------------------------
diff --git a/b2/pom.xml b/b2/pom.xml
new file mode 100644
index 0000000..6283223
--- /dev/null
+++ b/b2/pom.xml
@@ -0,0 +1,147 @@
+<?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:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         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</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+
+  <!-- TODO: when out of labs, switch to org.jclouds.api -->
+  <groupId>org.apache.jclouds.labs</groupId>
+  <artifactId>b2</artifactId>
+  <name>Apache jclouds B2 API</name>
+  <description>BlobStore binding to the Backblaze B2 API</description>
+  <packaging>bundle</packaging>
+
+  <properties>
+    <test.b2.identity>FIXME_IDENTITY</test.b2.identity>
+    <test.b2.credential>FIXME_CREDENTIAL</test.b2.credential>
+    <test.b2.build-version />
+    <jclouds.osgi.export>org.jclouds.b2*;version="${project.version}"</jclouds.osgi.export>
+    <jclouds.osgi.import>org.jclouds*;version="${project.version}",*</jclouds.osgi.import>
+  </properties>
+
+  <!-- For modernizer, which depends on jclouds-resources snapshot. -->
+  <pluginRepositories>
+    <pluginRepository>
+      <id>apache-snapshots</id>
+      <url>https://repository.apache.org/content/repositories/snapshots</url>
+      <releases>
+        <enabled>false</enabled>
+      </releases>
+      <snapshots>
+        <enabled>true</enabled>
+      </snapshots>
+    </pluginRepository>
+  </pluginRepositories>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-blobstore</artifactId>
+      <version>${project.parent.version}</version>
+      <type>jar</type>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-core</artifactId>
+      <version>${project.parent.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds</groupId>
+      <artifactId>jclouds-blobstore</artifactId>
+      <version>${project.parent.version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jclouds.driver</groupId>
+      <artifactId>jclouds-log4j</artifactId>
+      <version>${project.parent.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.auto.value</groupId>
+      <artifactId>auto-value</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.auto.service</groupId>
+      <artifactId>auto-service</artifactId>
+      <optional>true</optional>
+    </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>
+                  <groups>live</groups>
+                  <excludedGroups>livelong</excludedGroups>
+                  <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.b2.endpoint>${test.b2.endpoint}</test.b2.endpoint>
+                    <test.b2.api-version>${test.b2.api-version}</test.b2.api-version>
+                    <test.b2.build-version>${test.b2.build-version}</test.b2.build-version>
+                    <test.b2.identity>${test.b2.identity}</test.b2.identity>
+                    <test.b2.credential>${test.b2.credential}</test.b2.credential>
+                  </systemPropertyVariables>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+</project>

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/B2Api.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/B2Api.java b/b2/src/main/java/org/jclouds/b2/B2Api.java
new file mode 100644
index 0000000..caa8f98
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/B2Api.java
@@ -0,0 +1,32 @@
+/*
+ * 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.labs.b2;
+
+import java.io.Closeable;
+
+import org.jclouds.labs.b2.features.AuthorizationApi;
+import org.jclouds.labs.b2.features.BucketApi;
+import org.jclouds.rest.annotations.Delegate;
+
+/** Provides access to Backblaze B2 resources via their REST API. */
+public interface B2Api extends Closeable {
+   @Delegate
+   AuthorizationApi getAuthorizationApi();
+
+   @Delegate
+   BucketApi getBucketApi();
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/B2ApiMetadata.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/B2ApiMetadata.java b/b2/src/main/java/org/jclouds/b2/B2ApiMetadata.java
new file mode 100644
index 0000000..16c4176
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/B2ApiMetadata.java
@@ -0,0 +1,85 @@
+/*
+ * 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.labs.b2;
+
+import static org.jclouds.reflect.Reflection2.typeToken;
+
+import java.net.URI;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import org.jclouds.Constants;
+import org.jclouds.apis.ApiMetadata;
+import org.jclouds.blobstore.BlobStoreContext;
+import org.jclouds.labs.b2.config.B2HttpApiModule;
+import org.jclouds.rest.internal.BaseHttpApiMetadata;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
+public final class B2ApiMetadata extends BaseHttpApiMetadata {
+   @Override
+   public Builder toBuilder() {
+      return new Builder().fromApiMetadata(this);
+   }
+
+   public B2ApiMetadata() {
+      this(new Builder());
+   }
+
+   protected B2ApiMetadata(Builder builder) {
+      super(builder);
+   }
+
+   public static Properties defaultProperties() {
+      Properties properties = BaseHttpApiMetadata.defaultProperties();
+      properties.setProperty(Constants.PROPERTY_SESSION_INTERVAL, String.valueOf(TimeUnit.HOURS.toSeconds(1)));
+      return properties;
+   }
+
+   public static class Builder extends BaseHttpApiMetadata.Builder<B2Api, Builder> {
+
+      protected Builder() {
+         super(B2Api.class);
+         id("b2")
+                 .name("Backblaze B2 API")
+                 .identityName("Account Id")
+                 .credentialName("Application Key")
+                 .documentation(URI.create("https://www.backblaze.com/b2/docs/"))
+                 .defaultEndpoint("https://api.backblaze.com/")
+                 .defaultProperties(B2ApiMetadata.defaultProperties())
+                 .view(typeToken(BlobStoreContext.class))
+                 .defaultModules(ImmutableSet.<Class<? extends Module>>of(
+                         B2HttpApiModule.class));
+      }
+
+      @Override
+      public B2ApiMetadata build() {
+         return new B2ApiMetadata(this);
+      }
+
+      @Override
+      protected Builder self() {
+         return this;
+      }
+
+      @Override
+      public Builder fromApiMetadata(ApiMetadata in) {
+         return this;
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/B2ProviderMetadata.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/B2ProviderMetadata.java b/b2/src/main/java/org/jclouds/b2/B2ProviderMetadata.java
new file mode 100644
index 0000000..037fd9f
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/B2ProviderMetadata.java
@@ -0,0 +1,66 @@
+/*
+ * 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.labs.b2;
+
+import java.util.Properties;
+
+import org.jclouds.providers.ProviderMetadata;
+import org.jclouds.providers.internal.BaseProviderMetadata;
+
+import com.google.auto.service.AutoService;
+
+@AutoService(ProviderMetadata.class)
+public final class B2ProviderMetadata extends BaseProviderMetadata {
+   @Override
+   public Builder toBuilder() {
+      return new Builder().fromProviderMetadata(this);
+   }
+
+   public B2ProviderMetadata() {
+      this(new Builder());
+   }
+
+   protected B2ProviderMetadata(Builder builder) {
+      super(builder);
+   }
+
+   public static Properties defaultProperties() {
+      Properties properties = B2ApiMetadata.defaultProperties();
+      return properties;
+   }
+
+   public static class Builder extends BaseProviderMetadata.Builder {
+
+      protected Builder() {
+         id("b2")
+                 .name("Backblaze B2")
+                 .apiMetadata(new B2ApiMetadata())
+                 .endpoint("https://api.backblaze.com/")
+                 .defaultProperties(B2ProviderMetadata.defaultProperties());
+      }
+
+      @Override
+      public B2ProviderMetadata build() {
+         return new B2ProviderMetadata(this);
+      }
+
+      @Override
+      public Builder fromProviderMetadata(ProviderMetadata in) {
+         return this;
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/B2ResponseException.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/B2ResponseException.java b/b2/src/main/java/org/jclouds/b2/B2ResponseException.java
new file mode 100644
index 0000000..9ab60ed
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/B2ResponseException.java
@@ -0,0 +1,38 @@
+/*
+ * 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.labs.b2;
+
+import org.jclouds.labs.b2.domain.B2Error;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpResponseException;
+
+import com.google.common.base.Preconditions;
+
+public final class B2ResponseException extends HttpResponseException {
+   private final B2Error error;
+
+   public B2ResponseException(HttpCommand command, HttpResponse response, B2Error error) {
+      super("request " + command.getCurrentRequest().getRequestLine() + " failed with code " + response.getStatusCode()
+            + ", error: " + Preconditions.checkNotNull(error, "error").toString(), command, response);
+      this.error = error;
+   }
+
+   public B2Error getError() {
+      return error;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/config/B2HttpApiModule.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/config/B2HttpApiModule.java b/b2/src/main/java/org/jclouds/b2/config/B2HttpApiModule.java
new file mode 100644
index 0000000..a266d31
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/config/B2HttpApiModule.java
@@ -0,0 +1,81 @@
+/*
+ * 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.labs.b2.config;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.jclouds.Constants;
+import org.jclouds.collect.Memoized;
+import org.jclouds.labs.b2.B2Api;
+import org.jclouds.labs.b2.domain.Authorization;
+import org.jclouds.labs.b2.filters.RequestAuthorization;
+import org.jclouds.labs.b2.handlers.ParseB2ErrorFromJsonContent;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.annotation.ClientError;
+import org.jclouds.http.annotation.Redirection;
+import org.jclouds.http.annotation.ServerError;
+import org.jclouds.rest.AuthorizationException;
+import org.jclouds.rest.ConfiguresHttpApi;
+import org.jclouds.rest.config.HttpApiModule;
+import org.jclouds.rest.suppliers.MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier;
+
+import com.google.common.base.Supplier;
+import com.google.inject.Provides;
+import com.google.inject.Scopes;
+
+/** Configures the mappings. Installs the Object and Parser modules. */
+@ConfiguresHttpApi
+public final class B2HttpApiModule extends HttpApiModule<B2Api> {
+   @Override
+   protected void configure() {
+      super.configure();
+      bind(RequestAuthorization.class).in(Scopes.SINGLETON);
+   }
+
+   @Override
+   protected void bindErrorHandlers() {
+      bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(ParseB2ErrorFromJsonContent.class);
+      bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(ParseB2ErrorFromJsonContent.class);
+      bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(ParseB2ErrorFromJsonContent.class);
+   }
+
+   @Provides
+   @Singleton
+   static Supplier<Authorization> provideAuthorizationSupplier(final B2Api b2Api) {
+      return new Supplier<Authorization>() {
+            @Override
+            public Authorization get() {
+               return b2Api.getAuthorizationApi().authorizeAccount();
+            }
+         };
+   }
+
+   @Provides
+   @Singleton
+   @Memoized
+   static Supplier<Authorization> provideAuthorizationCache(
+         AtomicReference<AuthorizationException> authException,
+         @Named(Constants.PROPERTY_SESSION_INTERVAL) long seconds,
+         Supplier<Authorization>  uncached) {
+      return MemoizedRetryOnTimeOutButNotOnAuthorizationExceptionSupplier.create(
+            authException, uncached, seconds, TimeUnit.SECONDS);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/domain/Authorization.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/domain/Authorization.java b/b2/src/main/java/org/jclouds/b2/domain/Authorization.java
new file mode 100644
index 0000000..d95d028
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/domain/Authorization.java
@@ -0,0 +1,35 @@
+/*
+ * 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.labs.b2.domain;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Authorization {
+   public abstract String accountId();
+   public abstract String apiUrl();
+   public abstract String authorizationToken();
+   public abstract String downloadUrl();
+   public abstract long minimumPartSize();
+
+   @SerializedNames({"accountId", "apiUrl", "authorizationToken", "downloadUrl", "minimumPartSize"})
+   public static Authorization create(String accountId, String apiUrl, String authorizationToken, String downloadUrl, long minimumPartSize) {
+      return new AutoValue_Authorization(accountId, apiUrl, authorizationToken, downloadUrl, minimumPartSize);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/domain/B2Error.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/domain/B2Error.java b/b2/src/main/java/org/jclouds/b2/domain/B2Error.java
new file mode 100644
index 0000000..a17ecb9
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/domain/B2Error.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.labs.b2.domain;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class B2Error {
+   public abstract String code();
+   public abstract String message();
+   public abstract int status();
+
+   @SerializedNames({ "code", "message", "status" })
+   public static B2Error create(String code, String message, int status) {
+      return new AutoValue_B2Error(code, message, status);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/domain/Bucket.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/domain/Bucket.java b/b2/src/main/java/org/jclouds/b2/domain/Bucket.java
new file mode 100644
index 0000000..6aff182
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/domain/Bucket.java
@@ -0,0 +1,34 @@
+/*
+ * 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.labs.b2.domain;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Bucket {
+   public abstract String bucketId();
+   public abstract String accountId();
+   public abstract String bucketName();
+   public abstract BucketType bucketType();
+
+   @SerializedNames({"bucketId", "accountId", "bucketName", "bucketType"})
+   public static Bucket create(String bucketId, String accountId, String bucketName, BucketType bucketType) {
+      return new AutoValue_Bucket(bucketId, accountId, bucketName, bucketType);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/domain/BucketList.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/domain/BucketList.java b/b2/src/main/java/org/jclouds/b2/domain/BucketList.java
new file mode 100644
index 0000000..4be4596
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/domain/BucketList.java
@@ -0,0 +1,34 @@
+/*
+ * 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.labs.b2.domain;
+
+import java.util.List;
+
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+@AutoValue
+public abstract class BucketList {
+   public abstract List<Bucket> buckets();
+
+   @SerializedNames({"buckets"})
+   public static BucketList create(List<Bucket> buckets) {
+      return new AutoValue_BucketList(ImmutableList.copyOf(buckets));
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/domain/BucketType.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/domain/BucketType.java b/b2/src/main/java/org/jclouds/b2/domain/BucketType.java
new file mode 100644
index 0000000..61b0481
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/domain/BucketType.java
@@ -0,0 +1,34 @@
+/*
+ * 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.labs.b2.domain;
+
+import com.google.common.base.CaseFormat;
+
+public enum BucketType {
+   ALL_PUBLIC,
+   ALL_PRIVATE,
+   SNAPSHOT;
+
+   public static BucketType fromValue(String symbol) {
+      return BucketType.valueOf(CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, symbol));
+   }
+
+   @Override
+   public String toString() {
+      return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/features/AuthorizationApi.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/features/AuthorizationApi.java b/b2/src/main/java/org/jclouds/b2/features/AuthorizationApi.java
new file mode 100644
index 0000000..9789541
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/features/AuthorizationApi.java
@@ -0,0 +1,37 @@
+/*
+ * 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.labs.b2.features;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+
+import org.jclouds.http.filters.BasicAuthentication;
+import org.jclouds.labs.b2.domain.Authorization;
+import org.jclouds.rest.annotations.RequestFilters;
+
+public interface AuthorizationApi {
+   @Named("b2_authorize_account")
+   @GET
+   @Path("/b2api/v1/b2_authorize_account")
+   @RequestFilters(BasicAuthentication.class)
+   @Consumes(APPLICATION_JSON)
+   Authorization authorizeAccount();
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/features/BucketApi.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/features/BucketApi.java b/b2/src/main/java/org/jclouds/b2/features/BucketApi.java
new file mode 100644
index 0000000..6b341bd
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/features/BucketApi.java
@@ -0,0 +1,75 @@
+/*
+ * 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.labs.b2.features;
+
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+
+import static org.jclouds.blobstore.attr.BlobScopes.CONTAINER;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.blobstore.attr.BlobScope;
+import org.jclouds.labs.b2.domain.Bucket;
+import org.jclouds.labs.b2.domain.BucketList;
+import org.jclouds.labs.b2.domain.BucketType;
+import org.jclouds.labs.b2.filters.RequestAuthorization;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.MapBinder;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.PayloadParams;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+@RequestFilters(RequestAuthorization.class)
+@BlobScope(CONTAINER)
+@Consumes(APPLICATION_JSON)
+@Produces(APPLICATION_JSON)
+public interface BucketApi {
+   @Named("b2_create_bucket")
+   @POST
+   @Path("/b2api/v1/b2_create_bucket")
+   @MapBinder(BindToJsonPayload.class)
+   @PayloadParams(keys = {"accountId"}, values = {"{jclouds.identity}"})
+   Bucket createBucket(@PayloadParam("bucketName") String bucketName, @PayloadParam("bucketType") BucketType bucketType);
+
+   @Named("b2_delete_bucket")
+   @POST
+   @Path("/b2api/v1/b2_delete_bucket")
+   @MapBinder(BindToJsonPayload.class)
+   @PayloadParams(keys = {"accountId"}, values = {"{jclouds.identity}"})
+   @Fallback(NullOnNotFoundOr404.class)
+   Bucket deleteBucket(@PayloadParam("bucketId") String bucketId);
+
+   @Named("b2_update_bucket")
+   @POST
+   @Path("/b2api/v1/b2_update_bucket")
+   @MapBinder(BindToJsonPayload.class)
+   @PayloadParams(keys = {"accountId"}, values = {"{jclouds.identity}"})
+   Bucket updateBucket(@PayloadParam("bucketId") String bucketId, @PayloadParam("bucketType") BucketType bucketType);
+
+   @Named("b2_list_buckets")
+   @POST
+   @Path("/b2api/v1/b2_list_buckets")
+   @MapBinder(BindToJsonPayload.class)
+   @PayloadParams(keys = {"accountId"}, values = {"{jclouds.identity}"})
+   BucketList listBuckets();
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/filters/RequestAuthorization.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/filters/RequestAuthorization.java b/b2/src/main/java/org/jclouds/b2/filters/RequestAuthorization.java
new file mode 100644
index 0000000..ce54199
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/filters/RequestAuthorization.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.labs.b2.filters;
+
+import java.net.URI;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.collect.Memoized;
+import org.jclouds.domain.Credentials;
+import org.jclouds.http.HttpException;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpRequestFilter;
+import org.jclouds.labs.b2.domain.Authorization;
+import org.jclouds.location.Provider;
+
+import com.google.common.base.Supplier;
+import com.google.common.net.HttpHeaders;
+
+@Singleton
+public final class RequestAuthorization implements HttpRequestFilter {
+   private final Supplier<Credentials> creds;
+   private final Supplier<Authorization> auth;
+
+   @Inject
+   RequestAuthorization(@Provider Supplier<Credentials> creds, @Memoized Supplier<Authorization> auth) {
+      this.creds = creds;
+      this.auth = auth;
+   }
+
+   @Override
+   public HttpRequest filter(HttpRequest request) throws HttpException {
+      Credentials creds = this.creds.get();
+      Authorization auth = this.auth.get();
+
+      // Replace with API URL
+      URI endpoint = request.getEndpoint();
+      endpoint = URI.create(auth.apiUrl() +
+            (endpoint.getPort() == -1 ? "" : ":" + endpoint.getPort()) +
+            endpoint.getPath() +
+            (endpoint.getQuery() == null ? "" : "?" + endpoint.getQuery()));
+
+      request = request.toBuilder()
+            .endpoint(endpoint)
+            .replaceHeader(HttpHeaders.AUTHORIZATION, auth.authorizationToken())
+            .build();
+      return request;
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/main/java/org/jclouds/b2/handlers/ParseB2ErrorFromJsonContent.java
----------------------------------------------------------------------
diff --git a/b2/src/main/java/org/jclouds/b2/handlers/ParseB2ErrorFromJsonContent.java b/b2/src/main/java/org/jclouds/b2/handlers/ParseB2ErrorFromJsonContent.java
new file mode 100644
index 0000000..f042478
--- /dev/null
+++ b/b2/src/main/java/org/jclouds/b2/handlers/ParseB2ErrorFromJsonContent.java
@@ -0,0 +1,53 @@
+/*
+ * 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.labs.b2.handlers;
+
+import org.jclouds.blobstore.ContainerNotFoundException;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpErrorHandler;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.labs.b2.B2ResponseException;
+import org.jclouds.labs.b2.domain.B2Error;
+
+import com.google.inject.Inject;
+import com.google.inject.TypeLiteral;
+
+public final class ParseB2ErrorFromJsonContent extends ParseJson<B2Error> implements HttpErrorHandler {
+   @Inject
+   ParseB2ErrorFromJsonContent(Json json) {
+      super(json, TypeLiteral.get(B2Error.class));
+   }
+
+   private static Exception refineException(B2Error error, Exception exception) {
+      if ("bad_bucket_id".equals(error.code())) {
+         return new ContainerNotFoundException(exception);
+      } else if ("bad_json".equals(error.code())) {
+         return new IllegalArgumentException(error.message(), exception);
+      } else {
+         return exception;
+      }
+   }
+
+   @Override
+   public void handleError(HttpCommand command, HttpResponse response) {
+      B2Error error = this.apply(response);
+      Exception exception = refineException(error, new B2ResponseException(command, response, error));
+      command.setException(exception);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/java/org/jclouds/b2/B2ProviderMetadataTest.java
----------------------------------------------------------------------
diff --git a/b2/src/test/java/org/jclouds/b2/B2ProviderMetadataTest.java b/b2/src/test/java/org/jclouds/b2/B2ProviderMetadataTest.java
new file mode 100644
index 0000000..91c2f1a
--- /dev/null
+++ b/b2/src/test/java/org/jclouds/b2/B2ProviderMetadataTest.java
@@ -0,0 +1,27 @@
+/*
+ * 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.labs.b2;
+
+import org.jclouds.providers.internal.BaseProviderMetadataTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "B2ProviderMetadataTest")
+public final class B2ProviderMetadataTest extends BaseProviderMetadataTest {
+   public B2ProviderMetadataTest() {
+      super(new B2ProviderMetadata(), new B2ApiMetadata());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/java/org/jclouds/b2/features/BucketApiLiveTest.java
----------------------------------------------------------------------
diff --git a/b2/src/test/java/org/jclouds/b2/features/BucketApiLiveTest.java b/b2/src/test/java/org/jclouds/b2/features/BucketApiLiveTest.java
new file mode 100644
index 0000000..46db59f
--- /dev/null
+++ b/b2/src/test/java/org/jclouds/b2/features/BucketApiLiveTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.labs.b2.features;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
+
+import java.util.Random;
+
+import org.jclouds.labs.b2.domain.Bucket;
+import org.jclouds.labs.b2.domain.BucketList;
+import org.jclouds.labs.b2.domain.BucketType;
+import org.jclouds.labs.b2.internal.BaseB2ApiLiveTest;
+import org.testng.annotations.Test;
+
+public final class BucketApiLiveTest extends BaseB2ApiLiveTest {
+   private static final String BUCKET_NAME = "jcloudstestbucket" + new Random().nextInt(Integer.MAX_VALUE);
+
+   @Test(groups = "live")
+   public void testCreateBucket() {
+      BucketApi bucketApi = api.getBucketApi();
+
+      Bucket response = bucketApi.createBucket(BUCKET_NAME, BucketType.ALL_PRIVATE);
+      try {
+         assertThat(response.bucketName()).isEqualTo(BUCKET_NAME);
+         assertThat(response.bucketType()).isEqualTo(BucketType.ALL_PRIVATE);
+      } finally {
+         response = bucketApi.deleteBucket(response.bucketId());
+         assertThat(response.bucketName()).isEqualTo(BUCKET_NAME);
+         assertThat(response.bucketType()).isEqualTo(BucketType.ALL_PRIVATE);
+      }
+   }
+
+   @Test(groups = "live")
+   public void testDeleteAlreadyDeletedBucket() {
+      BucketApi bucketApi = api.getBucketApi();
+
+      Bucket response = bucketApi.createBucket(BUCKET_NAME, BucketType.ALL_PRIVATE);
+      response = bucketApi.deleteBucket(response.bucketId());
+
+      response = bucketApi.deleteBucket(response.bucketId());
+      assertThat(response).isNull();
+   }
+
+   @Test(groups = "live")
+   public void testDeleteInvalidBucketId() {
+      BucketApi bucketApi = api.getBucketApi();
+
+      try {
+         bucketApi.deleteBucket("4a48fe8875c6214145260818");
+         failBecauseExceptionWasNotThrown(IllegalArgumentException.class);
+      } catch (IllegalArgumentException iae) {
+         assertThat(iae.getMessage()).isEqualTo("bucketId not valid for account");
+      }
+   }
+
+   @Test(groups = "live")
+   public void testUpdateBucket() {
+      BucketApi bucketApi = api.getBucketApi();
+
+      Bucket response = bucketApi.createBucket(BUCKET_NAME, BucketType.ALL_PRIVATE);
+      try {
+         response = bucketApi.updateBucket(response.bucketId(), BucketType.ALL_PUBLIC);
+         assertThat(response.bucketName()).isEqualTo(BUCKET_NAME);
+         assertThat(response.bucketType()).isEqualTo(BucketType.ALL_PUBLIC);
+      } finally {
+         response = bucketApi.deleteBucket(response.bucketId());
+         assertThat(response.bucketName()).isEqualTo(BUCKET_NAME);
+      }
+   }
+
+   @Test(groups = "live")
+   public void testListBuckets() {
+      BucketApi bucketApi = api.getBucketApi();
+
+      Bucket response = bucketApi.createBucket(BUCKET_NAME, BucketType.ALL_PRIVATE);
+      try {
+         boolean found = false;
+         BucketList buckets = bucketApi.listBuckets();
+         for (Bucket bucket : buckets.buckets()) {
+            if (bucket.bucketName().equals(BUCKET_NAME)) {
+               assertThat(response.bucketType()).isEqualTo(BucketType.ALL_PRIVATE);
+               found = true;
+            }
+         }
+         assertThat(found).isTrue();
+      } finally {
+         response = bucketApi.deleteBucket(response.bucketId());
+         assertThat(response.bucketName()).isEqualTo(BUCKET_NAME);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/java/org/jclouds/b2/features/BucketApiMockTest.java
----------------------------------------------------------------------
diff --git a/b2/src/test/java/org/jclouds/b2/features/BucketApiMockTest.java b/b2/src/test/java/org/jclouds/b2/features/BucketApiMockTest.java
new file mode 100644
index 0000000..35c4188
--- /dev/null
+++ b/b2/src/test/java/org/jclouds/b2/features/BucketApiMockTest.java
@@ -0,0 +1,251 @@
+/*
+ * 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.labs.b2.features;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Set;
+import java.util.Properties;
+
+import org.jclouds.ContextBuilder;
+import org.jclouds.concurrent.config.ExecutorServiceModule;
+import org.jclouds.labs.b2.B2Api;
+import org.jclouds.labs.b2.domain.Bucket;
+import org.jclouds.labs.b2.domain.BucketList;
+import org.jclouds.labs.b2.domain.BucketType;
+import org.jclouds.util.Strings2;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.google.inject.Module;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+@Test(groups = "unit", testName = "BucketApiMockTest")
+public final class BucketApiMockTest {
+   private final Set<Module> modules = ImmutableSet.<Module> of(
+         new ExecutorServiceModule(MoreExecutors.sameThreadExecutor()));
+
+   public void testCreateBucket() throws Exception {
+      MockWebServer server = createMockWebServer();
+      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
+      server.enqueue(new MockResponse().setBody(stringFromResource("/bucket.json")));
+
+      try {
+         BucketApi api = api(server.getUrl("/").toString(), "b2").getBucketApi();
+         Bucket response = api.createBucket("any_name_you_pick", BucketType.ALL_PRIVATE);
+         assertThat(response.bucketId()).isEqualTo("4a48fe8875c6214145260818");
+         assertThat(response.bucketName()).isEqualTo("any_name_you_pick");
+         assertThat(response.bucketType()).isEqualTo(BucketType.ALL_PRIVATE);
+
+         assertThat(server.getRequestCount()).isEqualTo(2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_create_bucket", "/create_bucket_request.json");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testDeleteBucket() throws Exception {
+      MockWebServer server = createMockWebServer();
+      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
+      server.enqueue(new MockResponse().setBody(stringFromResource("/bucket.json")));
+
+      try {
+         BucketApi api = api(server.getUrl("/").toString(), "b2").getBucketApi();
+         Bucket response = api.deleteBucket("4a48fe8875c6214145260818");
+         assertThat(response.bucketId()).isEqualTo("4a48fe8875c6214145260818");
+         assertThat(response.bucketName()).isEqualTo("any_name_you_pick");
+         assertThat(response.bucketType()).isEqualTo(BucketType.ALL_PRIVATE);
+
+         assertThat(server.getRequestCount()).isEqualTo(2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_delete_bucket", "/delete_bucket_request.json");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testDeleteAlreadyDeletedBucket() throws Exception {
+      MockWebServer server = createMockWebServer();
+      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
+      server.enqueue(new MockResponse().setResponseCode(400).setBody(stringFromResource("/delete_bucket_already_deleted_response.json")));
+
+      try {
+         BucketApi api = api(server.getUrl("/").toString(), "b2").getBucketApi();
+         Bucket response = api.deleteBucket("4a48fe8875c6214145260818");
+         assertThat(response).isNull();
+
+         assertThat(server.getRequestCount()).isEqualTo(2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_delete_bucket", "/delete_bucket_request.json");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testUpdateBucket() throws Exception {
+      MockWebServer server = createMockWebServer();
+      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
+      server.enqueue(new MockResponse().setBody(stringFromResource("/bucket.json")));
+
+      try {
+         BucketApi api = api(server.getUrl("/").toString(), "b2").getBucketApi();
+         Bucket response = api.updateBucket("4a48fe8875c6214145260818", BucketType.ALL_PRIVATE);
+         assertThat(response.bucketId()).isEqualTo("4a48fe8875c6214145260818");
+         assertThat(response.bucketName()).isEqualTo("any_name_you_pick");
+         assertThat(response.bucketType()).isEqualTo(BucketType.ALL_PRIVATE);
+
+         assertThat(server.getRequestCount()).isEqualTo(2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_update_bucket", "/update_bucket_request.json");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testListBuckets() throws Exception {
+      MockWebServer server = createMockWebServer();
+      server.enqueue(new MockResponse().setBody(stringFromResource("/authorize_account_response.json")));
+      server.enqueue(new MockResponse().setBody(stringFromResource("/list_buckets_response.json")));
+
+      try {
+         BucketApi api = api(server.getUrl("/").toString(), "b2").getBucketApi();
+         BucketList response = api.listBuckets();
+
+         assertThat(response.buckets()).hasSize(3);
+
+         assertThat(response.buckets().get(0).bucketName()).isEqualTo("Kitten Videos");
+         assertThat(response.buckets().get(0).bucketType()).isEqualTo(BucketType.ALL_PRIVATE);
+
+         assertThat(response.buckets().get(1).bucketName()).isEqualTo("Puppy Videos");
+         assertThat(response.buckets().get(1).bucketType()).isEqualTo(BucketType.ALL_PUBLIC);
+
+         assertThat(response.buckets().get(2).bucketName()).isEqualTo("Vacation Pictures");
+         assertThat(response.buckets().get(2).bucketType()).isEqualTo(BucketType.ALL_PRIVATE);
+
+         assertThat(server.getRequestCount()).isEqualTo(2);
+         assertAuthentication(server);
+         assertRequest(server.takeRequest(), "POST", "/b2api/v1/b2_list_buckets", "/list_buckets_request.json");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public B2Api api(String uri, String provider, Properties overrides) {
+      return ContextBuilder.newBuilder(provider)
+            .credentials("ACCOUNT_ID", "APPLICATION_KEY")
+            .endpoint(uri)
+            .overrides(overrides)
+            .modules(modules)
+            .buildApi(B2Api.class);
+   }
+
+   public B2Api api(String uri, String provider) {
+      return api(uri, provider, new Properties());
+   }
+
+   public static MockWebServer createMockWebServer() throws IOException {
+      MockWebServer server = new MockWebServer();
+      server.play();
+      URL url = server.getUrl("");
+      return server;
+   }
+
+   public void assertAuthentication(MockWebServer server) {
+      assertThat(server.getRequestCount()).isGreaterThanOrEqualTo(1);
+      try {
+         assertThat(server.takeRequest().getRequestLine()).isEqualTo("GET /b2api/v1/b2_authorize_account HTTP/1.1");
+      } catch (InterruptedException e) {
+         throw Throwables.propagate(e);
+      }
+   }
+
+   /**
+    * Ensures the request has a json header for the proper REST methods.
+    *
+    * @param request
+    * @param method
+    *           The request method (such as GET).
+    * @param path
+    *           The path requested for this REST call.
+    * @see RecordedRequest
+    */
+   public void assertRequest(RecordedRequest request, String method, String path) {
+      assertThat(request.getMethod()).isEqualTo(method);
+      assertThat(request.getPath()).isEqualTo(path);
+   }
+
+   /**
+    * Ensures the request is json and has the same contents as the resource
+    * file provided.
+    *
+    * @param request
+    * @param method
+    *           The request method (such as GET).
+    * @param resourceLocation
+    *           The location of the resource file. Contents will be compared to
+    *           the request body as JSON.
+    * @see RecordedRequest
+    */
+   public void assertRequest(RecordedRequest request, String method, String path, String resourceLocation) {
+      assertRequest(request, method, path);
+      assertContentTypeIsJson(request);
+      JsonParser parser = new JsonParser();
+      JsonElement requestJson;
+      try {
+         requestJson = parser.parse(new String(request.getBody(), Charsets.UTF_8));
+      } catch (Exception e) {
+         throw Throwables.propagate(e);
+      }
+      JsonElement resourceJson = parser.parse(stringFromResource(resourceLocation));
+      assertThat(requestJson).isEqualTo(resourceJson);
+   }
+
+   /**
+    * Ensures the request has a json header.
+    *
+    * @param request
+    * @see RecordedRequest
+    */
+   private void assertContentTypeIsJson(RecordedRequest request) {
+      assertThat(request.getHeaders()).contains("Content-Type: application/json");
+   }
+
+   /**
+    * Get a string from a resource
+    *
+    * @param resourceName
+    *           The name of the resource.
+    * @return The content of the resource
+    */
+   public String stringFromResource(String resourceName) {
+      try {
+         return Strings2.toStringAndClose(getClass().getResourceAsStream(resourceName));
+      } catch (IOException e) {
+         throw Throwables.propagate(e);
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/java/org/jclouds/b2/internal/BaseB2ApiLiveTest.java
----------------------------------------------------------------------
diff --git a/b2/src/test/java/org/jclouds/b2/internal/BaseB2ApiLiveTest.java b/b2/src/test/java/org/jclouds/b2/internal/BaseB2ApiLiveTest.java
new file mode 100644
index 0000000..59ac34a
--- /dev/null
+++ b/b2/src/test/java/org/jclouds/b2/internal/BaseB2ApiLiveTest.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.labs.b2.internal;
+
+import org.jclouds.apis.ApiMetadata;
+import org.jclouds.apis.BaseApiLiveTest;
+import org.jclouds.labs.b2.B2Api;
+import org.jclouds.labs.b2.B2ApiMetadata;
+
+public class BaseB2ApiLiveTest extends BaseApiLiveTest<B2Api> {
+   protected BaseB2ApiLiveTest() {
+      provider = "b2";
+   }
+
+   @Override
+   protected ApiMetadata createApiMetadata() {
+      return new B2ApiMetadata();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/resources/authorize_account_response.json
----------------------------------------------------------------------
diff --git a/b2/src/test/resources/authorize_account_response.json b/b2/src/test/resources/authorize_account_response.json
new file mode 100644
index 0000000..ba02ea7
--- /dev/null
+++ b/b2/src/test/resources/authorize_account_response.json
@@ -0,0 +1,7 @@
+{
+    "accountId": "YOUR_ACCOUNT_ID",
+    "apiUrl": "http://localhost",
+    "authorizationToken": "2_20150807002553_443e98bf57f978fa58c284f8_24d25d99772e3ba927778b39c9b0198f412d2163_acct",
+    "downloadUrl": "http://localhost",
+    "minimumPartSize": 100000000
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/resources/bucket.json
----------------------------------------------------------------------
diff --git a/b2/src/test/resources/bucket.json b/b2/src/test/resources/bucket.json
new file mode 100644
index 0000000..50b2bde
--- /dev/null
+++ b/b2/src/test/resources/bucket.json
@@ -0,0 +1,6 @@
+{
+    "bucketId" : "4a48fe8875c6214145260818",
+    "accountId" : "010203040506",
+    "bucketName" : "any_name_you_pick",
+    "bucketType" : "allPrivate"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/resources/create_bucket_request.json
----------------------------------------------------------------------
diff --git a/b2/src/test/resources/create_bucket_request.json b/b2/src/test/resources/create_bucket_request.json
new file mode 100644
index 0000000..8b72ad5
--- /dev/null
+++ b/b2/src/test/resources/create_bucket_request.json
@@ -0,0 +1,5 @@
+{
+    "accountId": "ACCOUNT_ID",
+    "bucketName": "any_name_you_pick",
+    "bucketType": "allPrivate"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/resources/delete_bucket_already_deleted_response.json
----------------------------------------------------------------------
diff --git a/b2/src/test/resources/delete_bucket_already_deleted_response.json b/b2/src/test/resources/delete_bucket_already_deleted_response.json
new file mode 100644
index 0000000..6ac1748
--- /dev/null
+++ b/b2/src/test/resources/delete_bucket_already_deleted_response.json
@@ -0,0 +1,5 @@
+{
+   "status" : 400,
+   "code" : "bad_bucket_id",
+   "message" : "Bucket id 4a48fe8875c6214145260818 does not exist"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/resources/delete_bucket_request.json
----------------------------------------------------------------------
diff --git a/b2/src/test/resources/delete_bucket_request.json b/b2/src/test/resources/delete_bucket_request.json
new file mode 100644
index 0000000..8719353
--- /dev/null
+++ b/b2/src/test/resources/delete_bucket_request.json
@@ -0,0 +1,4 @@
+{
+    "accountId": "ACCOUNT_ID",
+    "bucketId": "4a48fe8875c6214145260818"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/resources/list_buckets_request.json
----------------------------------------------------------------------
diff --git a/b2/src/test/resources/list_buckets_request.json b/b2/src/test/resources/list_buckets_request.json
new file mode 100644
index 0000000..e2854f5
--- /dev/null
+++ b/b2/src/test/resources/list_buckets_request.json
@@ -0,0 +1,3 @@
+{
+    "accountId": "ACCOUNT_ID"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/resources/list_buckets_response.json
----------------------------------------------------------------------
diff --git a/b2/src/test/resources/list_buckets_response.json b/b2/src/test/resources/list_buckets_response.json
new file mode 100644
index 0000000..dbf4ad8
--- /dev/null
+++ b/b2/src/test/resources/list_buckets_response.json
@@ -0,0 +1,21 @@
+{
+    "buckets": [
+    {
+        "bucketId": "4a48fe8875c6214145260818",
+        "accountId": "30f20426f0b1",
+        "bucketName" : "Kitten Videos",
+        "bucketType": "allPrivate"
+    },
+    {
+        "bucketId" : "5b232e8875c6214145260818",
+        "accountId": "30f20426f0b1",
+        "bucketName": "Puppy Videos",
+        "bucketType": "allPublic"
+    },
+    {
+        "bucketId": "87ba238875c6214145260818",
+        "accountId": "30f20426f0b1",
+        "bucketName": "Vacation Pictures",
+        "bucketType" : "allPrivate"
+    } ]
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/resources/log4j.xml
----------------------------------------------------------------------
diff --git a/b2/src/test/resources/log4j.xml b/b2/src/test/resources/log4j.xml
new file mode 100644
index 0000000..9b64842
--- /dev/null
+++ b/b2/src/test/resources/log4j.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+    <!--
+        For more configuration infromation and examples see the Apache
+        Log4j website: http://logging.apache.org/log4j/
+    -->
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"
+    debug="false">
+
+    <!-- A time/date based rolling appender -->
+    <appender name="WIREFILE" class="org.apache.log4j.DailyRollingFileAppender">
+        <param name="File" value="target/test-data/jclouds-wire.log" />
+        <param name="Append" value="true" />
+
+        <!-- Rollover at midnight each day -->
+        <param name="DatePattern" value="'.'yyyy-MM-dd" />
+
+        <param name="Threshold" value="TRACE" />
+
+        <layout class="org.apache.log4j.PatternLayout">
+            <!-- The default pattern: Date Priority [Category] Message\n -->
+            <param name="ConversionPattern" value="%d %-5p [%c] (%t) %m%n" />
+
+            <!--
+                The full pattern: Date MS Priority [Category]
+                (Thread:NDC) Message\n <param name="ConversionPattern"
+                value="%d %-5r %-5p [%c] (%t:%x) %m%n"/>
+            -->
+        </layout>
+    </appender>
+
+    <!-- A time/date based rolling appender -->
+    <appender name="FILE" class="org.apache.log4j.DailyRollingFileAppender">
+        <param name="File" value="target/test-data/jclouds.log" />
+        <param name="Append" value="true" />
+
+        <!-- Rollover at midnight each day -->
+        <param name="DatePattern" value="'.'yyyy-MM-dd" />
+
+        <param name="Threshold" value="TRACE" />
+
+        <layout class="org.apache.log4j.PatternLayout">
+            <!-- The default pattern: Date Priority [Category] Message\n -->
+            <param name="ConversionPattern" value="%d %-5p [%c] (%t) %m%n" />
+
+            <!--
+                The full pattern: Date MS Priority [Category]
+                (Thread:NDC) Message\n <param name="ConversionPattern"
+                value="%d %-5r %-5p [%c] (%t:%x) %m%n"/>
+            -->
+        </layout>
+    </appender>
+    <!-- A time/date based rolling appender -->
+    <appender name="BLOBSTOREFILE" class="org.apache.log4j.DailyRollingFileAppender">
+        <param name="File" value="target/test-data/jclouds-blobstore.log" />
+        <param name="Append" value="true" />
+        <param name="DatePattern" value="'.'yyyy-MM-dd" />
+        <param name="Threshold" value="TRACE" />
+        <layout class="org.apache.log4j.PatternLayout">
+            <param name="ConversionPattern" value="%d %-5p [%c] (%t) %m%n" />
+        </layout>
+    </appender>
+
+    <appender name="ASYNC" class="org.apache.log4j.AsyncAppender">
+        <appender-ref ref="FILE" />
+    </appender>
+
+    <appender name="ASYNCWIRE" class="org.apache.log4j.AsyncAppender">
+        <appender-ref ref="WIREFILE" />
+    </appender>
+
+    <appender name="ASYNCBLOBSTORE" class="org.apache.log4j.AsyncAppender">
+        <appender-ref ref="BLOBSTOREFILE" />
+    </appender>
+    <!-- ================ -->
+    <!-- Limit categories -->
+    <!-- ================ -->
+
+    <category name="org.jclouds">
+        <priority value="DEBUG" />
+        <appender-ref ref="ASYNC" />
+    </category>
+
+    <category name="jclouds.headers">
+        <priority value="DEBUG" />
+        <appender-ref ref="ASYNCWIRE" />
+    </category>
+    <!--
+        NOTE enabling this will break stream tests <category
+        name="jclouds.wire"> <priority value="DEBUG" /> <appender-ref
+        ref="ASYNCWIRE" /> </category>
+    -->
+    <category name="jclouds.blobstore">
+        <priority value="DEBUG" />
+        <appender-ref ref="ASYNCBLOBSTORE" />
+    </category>
+    <!-- ======================= -->
+    <!-- Setup the Root category -->
+    <!-- ======================= -->
+
+    <root>
+        <priority value="WARN" />
+    </root>
+
+</log4j:configuration>

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/b2/src/test/resources/update_bucket_request.json
----------------------------------------------------------------------
diff --git a/b2/src/test/resources/update_bucket_request.json b/b2/src/test/resources/update_bucket_request.json
new file mode 100644
index 0000000..64cc475
--- /dev/null
+++ b/b2/src/test/resources/update_bucket_request.json
@@ -0,0 +1,5 @@
+{
+    "accountId": "ACCOUNT_ID",
+    "bucketId": "4a48fe8875c6214145260818",
+    "bucketType": "allPrivate"
+}

http://git-wip-us.apache.org/repos/asf/jclouds-labs/blob/afca4993/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index cb03655..716eac6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,6 +67,7 @@
   <modules>
     <module>azurecompute-arm</module>
     <module>azurecompute</module>
+    <module>b2</module>
     <module>docker</module>
     <module>cdmi</module>
     <module>cloudsigma2</module>