You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2018/01/11 15:41:32 UTC

[49/50] [abbrv] jclouds git commit: Completed ProjectApi and live tests

Completed ProjectApi and live tests


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

Branch: refs/heads/keystonev3
Commit: 1f84b603b4dc1ad7bf10b627a68516d1a8701a2a
Parents: 93562f8
Author: Ignasi Barrera <na...@apache.org>
Authored: Thu Jan 11 13:15:26 2018 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Thu Jan 11 16:21:31 2018 +0100

----------------------------------------------------------------------
 apis/openstack-keystone/pom.xml                 |  64 +++++-
 .../openstack/keystone/v3/domain/Catalog.java   |  35 +++-
 .../openstack/keystone/v3/domain/Endpoint.java  |  22 +-
 .../openstack/keystone/v3/domain/Project.java   |  41 +++-
 .../openstack/keystone/v3/domain/Region.java    |  28 ++-
 .../openstack/keystone/v3/domain/Token.java     |  42 ++--
 .../openstack/keystone/v3/domain/User.java      |  28 ++-
 .../keystone/v3/features/ProjectApi.java        |  70 +++++++
 .../v3/auth/V3AuthenticationApiLiveTest.java    |   6 +-
 .../v3/auth/V3AuthenticationApiMockTest.java    |  10 +-
 .../v3/features/CatalogApiLiveTest.java         |   5 +-
 .../v3/features/CatalogApiMockTest.java         |   7 +-
 .../v3/features/ProjectApiLiveTest.java         | 108 ++++++++++
 .../v3/features/ProjectApiMockTest.java         | 203 +++++++++++++++++++
 .../keystone/v3/features/RegionApiLiveTest.java |  37 ++++
 .../keystone/v3/features/RegionApiMockTest.java |  56 +++++
 .../v3/internal/BaseV3KeystoneApiLiveTest.java  |  13 ++
 .../v3/internal/BaseV3KeystoneApiMockTest.java  |   8 +-
 .../test/resources/v3/auth-password-scoped.json |   2 +-
 .../src/test/resources/v3/auth-password.json    |   2 +-
 .../src/test/resources/v3/endpoints.json        | 153 ++++++++++++++
 .../src/test/resources/v3/project.json          |  15 ++
 .../src/test/resources/v3/projects.json         |  74 +++++++
 .../src/test/resources/v3/regions.json          |  18 ++
 24 files changed, 965 insertions(+), 82 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/pom.xml
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/pom.xml b/apis/openstack-keystone/pom.xml
index b0f8cf8..36d7a11 100644
--- a/apis/openstack-keystone/pom.xml
+++ b/apis/openstack-keystone/pom.xml
@@ -32,13 +32,18 @@
   <packaging>bundle</packaging>
 
   <properties>
-    <!-- keystone endpoint -->
+    <!-- keystone version 2 -->
     <test.openstack-keystone.endpoint>http://localhost:5000/v${jclouds.api-version}/</test.openstack-keystone.endpoint>
-    <!-- keystone version -->
     <test.openstack-keystone.api-version>2.0</test.openstack-keystone.api-version>
     <test.openstack-keystone.build-version />
-    <test.openstack-keystone.identity>FIXME_IDENTITY</test.openstack-keystone.identity>
-    <test.openstack-keystone.credential>FIXME_CREDENTIALS</test.openstack-keystone.credential>
+    <test.openstack-keystone.identity>FIXME_TENANT:FIXME_USER</test.openstack-keystone.identity>
+    <test.openstack-keystone.credential>FIXME_PASSWORD</test.openstack-keystone.credential>
+    <!-- keystone version 3 -->
+    <test.openstack-keystone-3.endpoint>http://localhost/identity/v3</test.openstack-keystone-3.endpoint>
+    <test.openstack-keystone-3.api-version>3</test.openstack-keystone-3.api-version>
+    <test.openstack-keystone-3.build-version />
+    <test.openstack-keystone-3.identity>FIXME_DOMAIN:FIXME_USER</test.openstack-keystone-3.identity>
+    <test.openstack-keystone-3.credential>FIXME_PASSWORD</test.openstack-keystone-3.credential>
     <test.jclouds.keystone.credential-type>passwordCredentials</test.jclouds.keystone.credential-type>
 
     <jclouds.osgi.export>org.jclouds.openstack*;version="${project.version}"</jclouds.osgi.export>
@@ -69,6 +74,13 @@
       <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
+    <!-- A driver that supports the PATCH verb is needed for tests -->
+    <dependency>
+      <groupId>org.apache.jclouds.driver</groupId>
+      <artifactId>jclouds-okhttp</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>ch.qos.logback</groupId>
       <artifactId>logback-classic</artifactId>
@@ -123,6 +135,13 @@
                   <goal>test</goal>
                 </goals>
                 <configuration>
+                  <excludes>
+                    <exclude>none</exclude>
+                  </excludes>
+                  <includes>
+                    <include>**/keystone/v2_0/**/*IntegrationTest.java</include>
+                    <include>**/keystone/v2_0/**/*LiveTest.java</include>
+                  </includes>
                   <systemPropertyVariables>
                     <test.openstack-keystone.endpoint>${test.openstack-keystone.endpoint}</test.openstack-keystone.endpoint>
                     <test.openstack-keystone.api-version>${test.openstack-keystone.api-version}</test.openstack-keystone.api-version>
@@ -138,6 +157,43 @@
         </plugins>
       </build>
     </profile>
+    <profile>
+      <id>live-v3</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>
+                  <excludes>
+                    <exclude>none</exclude>
+                  </excludes>
+                  <includes>
+                    <include>**/keystone/v3/**/*IntegrationTest.java</include>
+                    <include>**/keystone/v3/**/*LiveTest.java</include>
+                  </includes>
+                  <systemPropertyVariables>
+                    <test.openstack-keystone-3.endpoint>${test.openstack-keystone.endpoint}</test.openstack-keystone-3.endpoint>
+                    <test.openstack-keystone-3.api-version>${test.openstack-keystone.api-version}</test.openstack-keystone-3.api-version>
+                    <test.openstack-keystone-3.build-version>${test.openstack-keystone.build-version}</test.openstack-keystone-3.build-version>
+                    <test.openstack-keystone-3.identity>${test.openstack-keystone-3.identity}</test.openstack-keystone-3.identity>
+                    <test.openstack-keystone-3.credential>${test.openstack-keystone-3.credential}</test.openstack-keystone-3.credential>
+                    <test.jclouds.keystone.credential-type>${test.jclouds.keystone.credential-type}</test.jclouds.keystone.credential-type>
+                  </systemPropertyVariables>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
   </profiles>
 
 </project>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Catalog.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Catalog.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Catalog.java
index e5977f9..cef9991 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Catalog.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Catalog.java
@@ -27,22 +27,37 @@ import com.google.common.collect.ImmutableList;
 public abstract class Catalog {
 
    public abstract String id();
-
    public abstract String name();
-
    public abstract String type();
-
    public abstract List<Endpoint> endpoints();
 
-   @SerializedNames({"id", "name", "type", "endpoints"})
-   public static Catalog create(String id,
-                                String name,
-                                String type,
-                                List<Endpoint> endpoints
-   ) {
-      return new AutoValue_Catalog(id, name, type, endpoints == null ? ImmutableList.<Endpoint>of() : ImmutableList.copyOf(endpoints));
+   @SerializedNames({ "id", "name", "type", "endpoints" })
+   public static Catalog create(String id, String name, String type, List<Endpoint> endpoints) {
+      return builder().id(id).name(name).type(type).endpoints(endpoints).build();
    }
 
    Catalog() {
    }
+
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_Catalog.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+      public abstract Builder id(String id);
+      public abstract Builder name(String name);
+      public abstract Builder type(String type);
+      public abstract Builder endpoints(List<Endpoint> endpoints);
+
+      abstract List<Endpoint> endpoints();
+      abstract Catalog autoBuild();
+
+      public Catalog build() {
+         endpoints(endpoints() == null ? ImmutableList.<Endpoint> of() : ImmutableList.copyOf(endpoints()));
+         return autoBuild();
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Endpoint.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Endpoint.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Endpoint.java
index 4e268d5..0c6b919 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Endpoint.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Endpoint.java
@@ -26,7 +26,7 @@ import com.google.auto.value.AutoValue;
 @AutoValue
 public abstract class Endpoint {
 
-   public abstract String id();
+   @Nullable public abstract String id();
    @Nullable public abstract String region();
    @Nullable public abstract String regionId();
    @Nullable public abstract String serviceId();
@@ -37,10 +37,28 @@ public abstract class Endpoint {
    @SerializedNames({ "id", "region", "region_id", "service_id", "url", "enabled", "interface" })
    public static Endpoint create(String id, String region, String regionId, String serviceId, URI url, Boolean enabled,
          String iface) {
-      return new AutoValue_Endpoint(id, region, regionId, serviceId, url, enabled, iface);
+      return builder().id(serviceId).region(region).regionId(regionId).serviceId(serviceId).url(url).enabled(enabled)
+            .iface(iface).build();
    }
 
    Endpoint() {
    }
 
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_Endpoint.Builder();
+   }
+   
+   @AutoValue.Builder
+   public abstract static class Builder {
+      public abstract Builder id(String id);
+      public abstract Builder region(String region);
+      public abstract Builder regionId(String regionId);
+      public abstract Builder serviceId(String serviceId);
+      public abstract Builder url(URI url);
+      public abstract Builder enabled(Boolean enabled);
+      public abstract Builder iface(String iface);
+      public abstract Endpoint build();
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Project.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Project.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Project.java
index c0b05c5..00ae34a 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Project.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Project.java
@@ -29,22 +29,51 @@ public abstract class Project {
 
    public abstract boolean isDomain();
    @Nullable public abstract String description();
-   public abstract String domainId();
+   @Nullable public abstract String domainId();
    @Nullable public abstract String domainName();
    public abstract boolean enabled();
-   public abstract String id();
+   @Nullable public abstract String id();
    public abstract String name();
    @Nullable public abstract String parentId();
    @Nullable public abstract List<String> tags();
+   @Nullable public abstract Link link();
 
    @SerializedNames({ "is_domain", "description", "domain_id", "domain_name", "enabled", "id", "name", "parent_id",
-         "tags" })
+         "tags", "links" })
    public static Project create(boolean isDomain, String description, String domainId, String domainName,
-         boolean enabled, String id, String name, String parentId, List<String> tags) {
-      return new AutoValue_Project(isDomain, description, domainId, domainName, enabled, id, name, parentId,
-            tags == null ? null : ImmutableList.copyOf(tags));
+         boolean enabled, String id, String name, String parentId, List<String> tags, Link link) {
+      return builder().isDomain(isDomain).description(description).domainId(domainId).domainName(domainName)
+            .enabled(enabled).id(id).name(name).parentId(parentId).tags(tags).link(link).build();
    }
 
    Project() {
    }
+   
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_Project.Builder().isDomain(false).enabled(true);
+   }
+   
+   @AutoValue.Builder
+   public abstract static class Builder {
+      public abstract Builder isDomain(boolean isDomain);
+      public abstract Builder description(String description);
+      public abstract Builder domainId(String domainId);
+      public abstract Builder domainName(String domainName);
+      public abstract Builder enabled(boolean enabled);
+      public abstract Builder id(String id);
+      public abstract Builder name(String name);
+      public abstract Builder parentId(String parentId);
+      public abstract Builder tags(List<String> tags);
+      public abstract Builder link(Link link);
+      
+      abstract List<String> tags();
+      abstract Project autoBuild();
+      
+      public Project build() {
+         tags(tags() == null ? null : ImmutableList.copyOf(tags()));
+         return autoBuild();
+      }
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Region.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Region.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Region.java
index 22e5d1c..0c1a189 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Region.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Region.java
@@ -25,22 +25,30 @@ import com.google.auto.value.AutoValue;
 public abstract class Region {
 
    public abstract String id();
-
    public abstract String name();
-
    public abstract Link link();
-
    @Nullable public abstract String parentRegionId();
 
-   @SerializedNames({"id", "description", "links", "parent_region_id"})
-   public static Region create(String id,
-                               String name,
-                               Link link,
-                               String parentRegionId
-   ) {
-      return new AutoValue_Region(id, name, link, parentRegionId);
+   @SerializedNames({ "id", "description", "links", "parent_region_id" })
+   public static Region create(String id, String name, Link link, String parentRegionId) {
+      return builder().id(id).name(name).link(link).parentRegionId(parentRegionId).build();
    }
 
    Region() {
    }
+
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_Region.Builder();
+   }
+
+   @AutoValue.Builder
+   public abstract static class Builder {
+      public abstract Builder id(String id);
+      public abstract Builder name(String name);
+      public abstract Builder link(Link link);
+      public abstract Builder parentRegionId(String parentRegionId);
+      public abstract Region build();
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Token.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Token.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Token.java
index 4c43dd2..e28aa00 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Token.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Token.java
@@ -29,15 +29,11 @@ import com.google.common.collect.ImmutableList;
 @AutoValue
 public abstract class Token implements AuthInfo {
 
-   @Nullable
-   public abstract String id();
+   @Nullable public abstract String id();
    public abstract List<String> methods();
-   @Nullable
-   public abstract Date expiresAt();
-   @Nullable
-   public abstract Object extras();
-   @Nullable
-   public abstract List<Catalog> catalog();
+   @Nullable public abstract Date expiresAt();
+   @Nullable public abstract Object extras();
+   @Nullable public abstract List<Catalog> catalog();
    public abstract List<String> auditIds();
    public abstract User user();
    public abstract Date issuedAt();
@@ -46,27 +42,15 @@ public abstract class Token implements AuthInfo {
    public String getAuthToken() {
       return id();
    }
-   @SerializedNames({"id", "methods", "expires_at", "extras", "catalog", "audit_ids", "user", "issued_at"})
-   private static Token create(
-           String id,
-           List<String> methods,
-           Date expiresAt,
-           Object extras,
-           List<Catalog> catalog,
-           List<String> auditIds,
-           User user,
-           Date issuedAt
-   ) {
-      return builder()
-              .id(id)
-              .methods(methods)
-              .expiresAt(expiresAt)
-              .extras(extras)
-              .catalog(catalog)
-              .auditIds(auditIds)
-              .user(user)
-              .issuedAt(issuedAt)
-              .build();
+   
+   @SerializedNames({ "id", "methods", "expires_at", "extras", "catalog", "audit_ids", "user", "issued_at" })
+   private static Token create(String id, List<String> methods, Date expiresAt, Object extras, List<Catalog> catalog,
+         List<String> auditIds, User user, Date issuedAt) {
+      return builder().id(id).methods(methods).expiresAt(expiresAt).extras(extras).catalog(catalog).auditIds(auditIds)
+            .user(user).issuedAt(issuedAt).build();
+   }
+
+   Token() {
    }
 
    public abstract Builder toBuilder();

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/User.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/User.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/User.java
index 171b7d1..bda6411 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/User.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/User.java
@@ -28,7 +28,6 @@ public abstract class User {
 
    @AutoValue
    public abstract static class Domain {
-
       public abstract String id();
       public abstract String name();
 
@@ -45,13 +44,34 @@ public abstract class User {
    @Nullable public abstract String domainId();
    @Nullable public abstract String defaultProjectId();
    @Nullable public abstract Boolean enabled();
+   @Nullable public abstract Link link();
 
-   @SerializedNames({ "id", "name", "password_expires_at", "domain", "domain_id", "default_project_id", "enabled" })
+   @SerializedNames({ "id", "name", "password_expires_at", "domain", "domain_id", "default_project_id", "enabled", "links" })
    public static User create(String id, String name, Date passwordExpiresAt, Domain domain, String domainId,
-         String defaultProjectId, Boolean enabled) {
-      return new AutoValue_User(id, name, passwordExpiresAt, domain, domainId, defaultProjectId, enabled);
+         String defaultProjectId, Boolean enabled, Link link) {
+      return builder().id(id).name(name).passwordExpiresAt(passwordExpiresAt).domain(domain).domainId(domainId)
+            .defaultProjectId(defaultProjectId).enabled(enabled).link(link).build();
    }
 
    User() {
    }
+   
+   public abstract Builder toBuilder();
+
+   public static Builder builder() {
+      return new AutoValue_User.Builder();
+   }
+   
+   @AutoValue.Builder
+   public abstract static class Builder {
+      public abstract Builder id(String id);
+      public abstract Builder name(String name);
+      public abstract Builder passwordExpiresAt(Date passwordExpiresAt);
+      public abstract Builder domain(Domain domain);
+      public abstract Builder domainId(String domainId);
+      public abstract Builder defaultProjectId(String defaultProjectId);
+      public abstract Builder enabled(Boolean enabled);
+      public abstract Builder link(Link link);
+      public abstract User build();
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/features/ProjectApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/features/ProjectApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/features/ProjectApi.java
index 2089949..14e8d97 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/features/ProjectApi.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/features/ProjectApi.java
@@ -17,21 +17,34 @@
 package org.jclouds.openstack.keystone.v3.features;
 
 import java.util.List;
+import java.util.Set;
 
 import javax.inject.Named;
 import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
+import javax.ws.rs.HEAD;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
 import javax.ws.rs.core.MediaType;
 
 import org.jclouds.Fallbacks.EmptyListOnNotFoundOr404;
+import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
 import org.jclouds.openstack.keystone.auth.filters.AuthenticateRequest;
 import org.jclouds.openstack.keystone.v3.domain.Project;
 import org.jclouds.openstack.v2_0.services.Identity;
 import org.jclouds.rest.annotations.Endpoint;
 import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.MapBinder;
+import org.jclouds.rest.annotations.PATCH;
+import org.jclouds.rest.annotations.PayloadParam;
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.SelectJson;
+import org.jclouds.rest.annotations.WrapWith;
+import org.jclouds.rest.binders.BindToJsonPayload;
 
 /**
  * Provides access to the Keystone Projects API.
@@ -47,4 +60,61 @@ public interface ProjectApi {
    @SelectJson("projects")
    @Fallback(EmptyListOnNotFoundOr404.class)
    List<Project> list();
+   
+   @Named("projects:get")
+   @GET
+   @Path("/{id}")
+   @SelectJson("project")
+   @Fallback(NullOnNotFoundOr404.class)
+   Project get(@PathParam("id") String id);
+   
+   @Named("projects:create")
+   @POST
+   @SelectJson("project")
+   Project create(@WrapWith("project") Project project);
+   
+   @Named("projects:update")
+   @PATCH
+   @Path("/{id}")
+   @SelectJson("project")
+   Project update(@PathParam("id") String id, @WrapWith("project") Project project);
+   
+   @Named("projects:delete")
+   @DELETE
+   @Path("/{id}")
+   @Fallback(FalseOnNotFoundOr404.class)
+   boolean delete(@PathParam("id") String id);
+   
+   @Named("projects:listTags")
+   @GET
+   @Path("/{projectId}/tags")
+   @SelectJson("tags")
+   Set<String> listTags(@PathParam("projectId") String projectId);
+   
+   @Named("projects:hasTag")
+   @HEAD
+   @Path("/{projectId}/tags/{tag}")
+   @Fallback(FalseOnNotFoundOr404.class)
+   boolean hasTag(@PathParam("projectId") String projectId, @PathParam("tag") String tag);
+   
+   @Named("projects:addTag")
+   @PUT
+   @Path("/{projectId}/tags/{tag}")
+   void addTag(@PathParam("projectId") String projectId, @PathParam("tag") String tag);
+   
+   @Named("projects:removeTag")
+   @DELETE
+   @Path("/{projectId}/tags/{tag}")
+   void removeTag(@PathParam("projectId") String projectId, @PathParam("tag") String tag);
+   
+   @Named("projects:setTags")
+   @PUT
+   @Path("/{projectId}/tags")
+   @MapBinder(BindToJsonPayload.class)
+   void setTags(@PathParam("projectId") String projectId, @PayloadParam("tags") Set<String> tags);
+   
+   @Named("projects:removeTags")
+   @DELETE
+   @Path("/{projectId}/tags")
+   void removeAllTags(@PathParam("projectId") String projectId);
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiLiveTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiLiveTest.java
index af66284..3b8a3b4 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiLiveTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiLiveTest.java
@@ -45,13 +45,13 @@ public class V3AuthenticationApiLiveTest extends BaseV3KeystoneApiLiveTest {
 
    public void testAuthenticatePassword() {
       assertNotNull(authenticationApi.authenticatePassword(TenantOrDomainAndCredentials.<PasswordCredentials> builder()
-            .tenantOrDomainName(tenant)
+            .tenantOrDomainName(tenant).scope("unscoped")
             .credentials(PasswordCredentials.builder().username(user).password(credential).build()).build()));
    }
 
    public void testAuthenticateToken() {
       assertNotNull(authenticationApi.authenticateToken(TenantOrDomainAndCredentials.<TokenCredentials> builder()
-            .tenantOrDomainName(tenant).credentials(TokenCredentials.builder().id(token.get()).build()).build()));
+            .tenantOrDomainName(tenant).scope("unscoped")
+            .credentials(TokenCredentials.builder().id(token.get()).build()).build()));
    }
-
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiMockTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiMockTest.java
index ce4be34..7cd2d29 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiMockTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/auth/V3AuthenticationApiMockTest.java
@@ -34,7 +34,8 @@ public class V3AuthenticationApiMockTest extends BaseV3KeystoneApiMockTest {
       server.enqueue(jsonResponse("/v3/token.json"));
 
       TenantOrDomainAndCredentials<PasswordCredentials> credentials = TenantOrDomainAndCredentials.<PasswordCredentials> builder()
-            .tenantOrDomainName("project")
+            .tenantOrDomainName("domain")
+            .scope("unscoped")
             .credentials(PasswordCredentials.builder().username("identity").password("credential").build()).build();
       
       AuthInfo authInfo = authenticationApi.authenticatePassword(credentials);
@@ -50,7 +51,7 @@ public class V3AuthenticationApiMockTest extends BaseV3KeystoneApiMockTest {
       server.enqueue(jsonResponse("/v3/token.json"));
 
       TenantOrDomainAndCredentials<PasswordCredentials> credentials = TenantOrDomainAndCredentials.<PasswordCredentials> builder()
-            .tenantOrDomainName("project")
+            .tenantOrDomainName("domain")
             .scope("project:1234567890")
             .credentials(PasswordCredentials.builder().username("identity").password("credential").build()).build();
       
@@ -67,7 +68,8 @@ public class V3AuthenticationApiMockTest extends BaseV3KeystoneApiMockTest {
       server.enqueue(jsonResponse("/v3/token.json"));
 
       TenantOrDomainAndCredentials<TokenCredentials> credentials = TenantOrDomainAndCredentials.<TokenCredentials> builder()
-            .tenantOrDomainName("project")
+            .tenantOrDomainName("domain")
+            .scope("unscoped")
             .credentials(TokenCredentials.builder().id("token").build()).build();
       
       AuthInfo authInfo = authenticationApi.authenticateToken(credentials);
@@ -83,7 +85,7 @@ public class V3AuthenticationApiMockTest extends BaseV3KeystoneApiMockTest {
       server.enqueue(jsonResponse("/v3/token.json"));
 
       TenantOrDomainAndCredentials<TokenCredentials> credentials = TenantOrDomainAndCredentials.<TokenCredentials> builder()
-            .tenantOrDomainName("project")
+            .tenantOrDomainName("domain")
             .scope("domain:mydomain")
             .credentials(TokenCredentials.builder().id("token").build()).build();
       

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/CatalogApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/CatalogApiLiveTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/CatalogApiLiveTest.java
index 63cd98c..3044c13 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/CatalogApiLiveTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/CatalogApiLiveTest.java
@@ -28,13 +28,10 @@ import org.testng.annotations.Test;
 @Test(groups = "live", testName = "CatalogApiLiveTest")
 public class CatalogApiLiveTest extends BaseV3KeystoneApiLiveTest {
 
+   @Test
    public void testTenants() {
       List<Endpoint> result = api.getCatalogApi().endpoints();
       assertNotNull(result);
       assertFalse(result.isEmpty());
-
-      for (Endpoint endpoint : result) {
-         assertNotNull(endpoint.id());
-      }
    }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/CatalogApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/CatalogApiMockTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/CatalogApiMockTest.java
index a8d8bf4..d7f7639 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/CatalogApiMockTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/CatalogApiMockTest.java
@@ -16,8 +16,9 @@
  */
 package org.jclouds.openstack.keystone.v3.features;
 
-import static com.google.common.collect.Iterables.size;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
 
 import java.util.List;
 
@@ -33,7 +34,7 @@ public class CatalogApiMockTest extends BaseV3KeystoneApiMockTest {
       server.enqueue(jsonResponse("/v3/endpoints.json"));
 
       List<Endpoint> endpoints = api.getCatalogApi().endpoints();
-      assertEquals(size(endpoints), 8);
+      assertFalse(endpoints.isEmpty());
       
       assertEquals(server.getRequestCount(), 2);
       assertAuthentication(server);
@@ -45,7 +46,7 @@ public class CatalogApiMockTest extends BaseV3KeystoneApiMockTest {
       server.enqueue(response404());
 
       List<Endpoint> endpoints = api.getCatalogApi().endpoints();
-      assertEquals(endpoints.size(), 0);
+      assertTrue(endpoints.isEmpty());
       
       assertEquals(server.getRequestCount(), 2);
       assertAuthentication(server);

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/ProjectApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/ProjectApiLiveTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/ProjectApiLiveTest.java
new file mode 100644
index 0000000..2c2f20a
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/ProjectApiLiveTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.openstack.keystone.v3.features;
+
+import static com.google.common.collect.Iterables.any;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.Set;
+
+import org.jclouds.openstack.keystone.v3.domain.Project;
+import org.jclouds.openstack.keystone.v3.internal.BaseV3KeystoneApiLiveTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableSet;
+
+@Test(groups = "live", testName = "ProjectApiLiveTest", singleThreaded = true)
+public class ProjectApiLiveTest extends BaseV3KeystoneApiLiveTest {
+
+   private Project project;
+   
+   @BeforeClass
+   public void createTestProject() {
+      project = api().create(Project.builder().name(getClass().getSimpleName()).build());
+      assertNotNull(project.id());
+   }
+   
+   @Test
+   public void testListProjects() {
+      assertTrue(any(api().list(), new Predicate<Project>() {
+         @Override
+         public boolean apply(Project input) {
+            return input.id().equals(project.id());
+         }
+      }));
+   }
+   
+   @Test
+   public void testGetProject() {
+      assertNotNull(api().get(project.id()));
+   }
+   
+   @Test
+   public void testUpdateProject() {
+      Project updated = api().get(project.id());
+      api().update(project.id(), updated.toBuilder().description("Updated").build());
+      project = api().get(project.id());
+      assertEquals(project.description(), "Updated");
+   }
+   
+   @Test
+   public void testSetAndListTags() {
+      api().setTags(project.id(), ImmutableSet.of("foo", "bar"));
+      Set<String> projectTags = api().listTags(project.id());
+      assertEquals(projectTags, ImmutableSet.of("foo", "bar"));
+   }
+   
+   @Test(dependsOnMethods = "testSetAndListTags")
+   public void testHasTag() {
+      assertTrue(api().hasTag(project.id(), "foo"));
+   }
+   
+   @Test(dependsOnMethods = "testSetAndListTags")
+   public void testAddTag() {
+      api().addTag(project.id(), "three");
+      assertTrue(api().hasTag(project.id(), "three"));
+   }
+   
+   @Test(dependsOnMethods = "testSetAndListTags")
+   public void testRemoveTag() {
+      api().removeTag(project.id(), "bar");
+      assertFalse(api().hasTag(project.id(), "bar"));
+   }
+   
+   @Test(dependsOnMethods = "testRemoveTag")
+   public void testRemoveAllTags() {
+      api().removeAllTags(project.id());
+      assertTrue(api().listTags(project.id()).isEmpty());
+   }
+   
+   @AfterClass(alwaysRun = true)
+   public void deleteProject() {
+      assertTrue(api().delete(project.id()));
+   }
+   
+   private ProjectApi api() {
+      return api.getProjectApi();
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/ProjectApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/ProjectApiMockTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/ProjectApiMockTest.java
new file mode 100644
index 0000000..fc71a6e
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/ProjectApiMockTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.openstack.keystone.v3.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Set;
+
+import org.jclouds.openstack.keystone.v3.domain.Project;
+import org.jclouds.openstack.keystone.v3.internal.BaseV3KeystoneApiMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableSet;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+
+@Test(groups = "unit", testName = "ProjectApiMockTest", singleThreaded = true)
+public class ProjectApiMockTest extends BaseV3KeystoneApiMockTest {
+
+   public void testListProjects() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(jsonResponse("/v3/projects.json").setResponseCode(201));
+
+      List<Project> projects = api.getProjectApi().list();
+      assertFalse(projects.isEmpty());
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "GET", "/projects");
+   }
+
+   public void testListProjectsReturns404() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(response404());
+
+      List<Project> projects = api.getProjectApi().list();
+      assertTrue(projects.isEmpty());
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "GET", "/projects");
+   }
+
+   public void testGetProject() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(jsonResponse("/v3/project.json"));
+
+      Project project = api.getProjectApi().get("2f9b30f706bc45d7923e055567be2e98");
+      assertNotNull(project);
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "GET", "/projects/2f9b30f706bc45d7923e055567be2e98");
+   }
+
+   public void testGetProjectReturns404() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(response404());
+
+      Project project = api.getProjectApi().get("2f9b30f706bc45d7923e055567be2e98");
+      assertNull(project);
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "GET", "/projects/2f9b30f706bc45d7923e055567be2e98");
+   }
+
+   public void testCreateProject() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(jsonResponse("/v3/project.json"));
+
+      Project project = api.getProjectApi().create(Project.builder().name("foo").build());
+      assertNotNull(project);
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "POST", "/projects", "{\"project\":{\"is_domain\":false,\"enabled\":true,\"name\":\"foo\"}}");
+   }
+
+   public void testUpdateProject() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(jsonResponse("/v3/project.json"));
+
+      Project project = api.getProjectApi().update("2f9b30f706bc45d7923e055567be2e98",
+            Project.builder().name("foo").build());
+      assertNotNull(project);
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "PATCH", "/projects/2f9b30f706bc45d7923e055567be2e98",
+            "{\"project\":{\"is_domain\":false,\"enabled\":true,\"name\":\"foo\"}}");
+   }
+
+   public void testDeleteProject() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(response204());
+
+      boolean deleted = api.getProjectApi().delete("2f9b30f706bc45d7923e055567be2e98");
+      assertTrue(deleted);
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "DELETE", "/projects/2f9b30f706bc45d7923e055567be2e98");
+   }
+
+   public void testDeleteProjectReturns404() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(response404());
+
+      boolean deleted = api.getProjectApi().delete("2f9b30f706bc45d7923e055567be2e98");
+      assertFalse(deleted);
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "DELETE", "/projects/2f9b30f706bc45d7923e055567be2e98");
+   }
+
+   public void testListTags() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(new MockResponse().setBody("{\"tags\":[\"foo\",\"bar\"]}"));
+
+      Set<String> tags = api.getProjectApi().listTags("2f9b30f706bc45d7923e055567be2e98");
+      assertEquals(tags, ImmutableSet.of("foo", "bar"));
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "GET", "/projects/2f9b30f706bc45d7923e055567be2e98/tags");
+   }
+
+   public void testHasTag() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(response204());
+
+      boolean hasTag = api.getProjectApi().hasTag("2f9b30f706bc45d7923e055567be2e98", "foo");
+      assertTrue(hasTag);
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "HEAD", "/projects/2f9b30f706bc45d7923e055567be2e98/tags/foo");
+   }
+
+   public void testAddTag() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(response201());
+
+      api.getProjectApi().addTag("2f9b30f706bc45d7923e055567be2e98", "foo");
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "PUT", "/projects/2f9b30f706bc45d7923e055567be2e98/tags/foo");
+   }
+
+   public void testRemoveTag() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(response204());
+
+      api.getProjectApi().removeTag("2f9b30f706bc45d7923e055567be2e98", "foo");
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "DELETE", "/projects/2f9b30f706bc45d7923e055567be2e98/tags/foo");
+   }
+
+   public void testSetTags() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(new MockResponse().setBody("{\"tags\":[\"foo\",\"bar\"]}"));
+
+      api.getProjectApi().setTags("2f9b30f706bc45d7923e055567be2e98", ImmutableSet.of("foo", "bar"));
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "PUT", "/projects/2f9b30f706bc45d7923e055567be2e98/tags", "{\"tags\":[\"foo\",\"bar\"]}");
+   }
+   
+   public void testRemoveAllTags() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(response204());
+
+      api.getProjectApi().removeAllTags("2f9b30f706bc45d7923e055567be2e98");
+
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "DELETE", "/projects/2f9b30f706bc45d7923e055567be2e98/tags");
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiLiveTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiLiveTest.java
new file mode 100644
index 0000000..3770d95
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiLiveTest.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.openstack.keystone.v3.features;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+
+import java.util.List;
+
+import org.jclouds.openstack.keystone.v3.domain.Region;
+import org.jclouds.openstack.keystone.v3.internal.BaseV3KeystoneApiLiveTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "live", testName = "RegionApiLiveTest")
+public class RegionApiLiveTest extends BaseV3KeystoneApiLiveTest {
+
+   @Test
+   public void testRegions() {
+      List<Region> result = api.getRegionApi().list();
+      assertNotNull(result);
+      assertFalse(result.isEmpty());
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiMockTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiMockTest.java
new file mode 100644
index 0000000..f640140
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiMockTest.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.openstack.keystone.v3.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+
+import org.jclouds.openstack.keystone.v3.domain.Region;
+import org.jclouds.openstack.keystone.v3.internal.BaseV3KeystoneApiMockTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "RegionApiMockTest", singleThreaded = true)
+public class RegionApiMockTest extends BaseV3KeystoneApiMockTest {
+
+   public void testListRegions() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(jsonResponse("/v3/regions.json"));
+
+      List<Region> regions = api.getRegionApi().list();
+      assertFalse(regions.isEmpty());
+      
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "GET", "/regions");
+   }
+
+   public void testListEndpointsReturns404() throws InterruptedException {
+      enqueueAuthentication(server);
+      server.enqueue(response404());
+
+      List<Region> regions = api.getRegionApi().list();
+      assertTrue(regions.isEmpty());
+      
+      assertEquals(server.getRequestCount(), 2);
+      assertAuthentication(server);
+      assertSent(server, "GET", "/regions");
+   }
+   
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/internal/BaseV3KeystoneApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/internal/BaseV3KeystoneApiLiveTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/internal/BaseV3KeystoneApiLiveTest.java
index d73c3cf..6d08b46 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/internal/BaseV3KeystoneApiLiveTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/internal/BaseV3KeystoneApiLiveTest.java
@@ -17,17 +17,21 @@
 package org.jclouds.openstack.keystone.v3.internal;
 
 import static org.jclouds.openstack.keystone.config.KeystoneProperties.CREDENTIAL_TYPE;
+import static org.jclouds.openstack.keystone.config.KeystoneProperties.SCOPE;
 import static org.jclouds.openstack.keystone.config.KeystoneProperties.SERVICE_TYPE;
 
 import java.util.Properties;
 
 import org.jclouds.apis.BaseApiLiveTest;
+import org.jclouds.http.okhttp.config.OkHttpCommandExecutorServiceModule;
+import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
 import org.jclouds.openstack.keystone.auth.AuthenticationApi;
 import org.jclouds.openstack.keystone.auth.config.Authentication;
 import org.jclouds.openstack.keystone.v3.KeystoneApi;
 import org.jclouds.rest.ApiContext;
 
 import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableSet;
 import com.google.inject.Key;
 import com.google.inject.Module;
 import com.google.inject.TypeLiteral;
@@ -54,11 +58,20 @@ public class BaseV3KeystoneApiLiveTest extends BaseApiLiveTest<KeystoneApi> {
    protected Properties setupProperties() {
       Properties props = super.setupProperties();
       setIfTestSystemPropertyPresent(props, CREDENTIAL_TYPE);
+      setIfTestSystemPropertyPresent(props, SCOPE);
       String customServiceType = setIfTestSystemPropertyPresent(props, SERVICE_TYPE);
       if (customServiceType == null) {
          props.setProperty(SERVICE_TYPE, "identityv3");
       }
       return props;
    }
+   
+   @Override
+   protected Iterable<Module> setupModules() {
+      ImmutableSet.Builder<Module> modules = ImmutableSet.builder();
+      modules.add(new OkHttpCommandExecutorServiceModule());
+      modules.add(new SLF4JLoggingModule());
+      return modules.build();
+   }
 
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/internal/BaseV3KeystoneApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/internal/BaseV3KeystoneApiMockTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/internal/BaseV3KeystoneApiMockTest.java
index 914f760..bdb8aa9 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/internal/BaseV3KeystoneApiMockTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/internal/BaseV3KeystoneApiMockTest.java
@@ -29,6 +29,7 @@ import java.util.UUID;
 
 import org.jclouds.ContextBuilder;
 import org.jclouds.concurrent.config.ExecutorServiceModule;
+import org.jclouds.http.okhttp.config.OkHttpCommandExecutorServiceModule;
 import org.jclouds.json.Json;
 import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
 import org.jclouds.openstack.keystone.auth.AuthenticationApi;
@@ -70,7 +71,7 @@ public class BaseV3KeystoneApiMockTest {
       server.play();
       
       ApiContext<KeystoneApi> ctx = ContextBuilder.newBuilder("openstack-keystone-3")
-              .credentials("project:identity", "credential")
+              .credentials("domain:identity", "credential")
               .endpoint(url(""))
               .modules(modules())
               .overrides(overrides())
@@ -96,6 +97,7 @@ public class BaseV3KeystoneApiMockTest {
    protected Set<Module> modules() {
       ImmutableSet.Builder<Module> modules = ImmutableSet.builder();
       modules.add(new ExecutorServiceModule(newDirectExecutorService()));
+      modules.add(new OkHttpCommandExecutorServiceModule());
       modules.add(new SLF4JLoggingModule());
       return modules.build();
    }
@@ -117,6 +119,10 @@ public class BaseV3KeystoneApiMockTest {
       return new MockResponse().setStatus("HTTP/1.1 404 Not Found");
    }
    
+   protected MockResponse response201() {
+      return new MockResponse().setStatus("HTTP/1.1 201 Created");
+   }
+   
    protected MockResponse response204() {
       return new MockResponse().setStatus("HTTP/1.1 204 No Content");
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/resources/v3/auth-password-scoped.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/v3/auth-password-scoped.json b/apis/openstack-keystone/src/test/resources/v3/auth-password-scoped.json
index 48bfc9d..993e036 100644
--- a/apis/openstack-keystone/src/test/resources/v3/auth-password-scoped.json
+++ b/apis/openstack-keystone/src/test/resources/v3/auth-password-scoped.json
@@ -8,7 +8,7 @@
                 "user": {
                     "name": "identity",
                     "domain": {
-                        "name": "project"
+                        "name": "domain"
                     },
                     "password": "credential"
                 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/resources/v3/auth-password.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/v3/auth-password.json b/apis/openstack-keystone/src/test/resources/v3/auth-password.json
index 3335211..2c2e8bd 100644
--- a/apis/openstack-keystone/src/test/resources/v3/auth-password.json
+++ b/apis/openstack-keystone/src/test/resources/v3/auth-password.json
@@ -8,7 +8,7 @@
                 "user": {
                     "name": "identity",
                     "domain": {
-                        "name": "project"
+                        "name": "domain"
                     },
                     "password": "credential"
                 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/resources/v3/endpoints.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/v3/endpoints.json b/apis/openstack-keystone/src/test/resources/v3/endpoints.json
new file mode 100644
index 0000000..0a097e0
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/v3/endpoints.json
@@ -0,0 +1,153 @@
+{
+  "endpoints": [
+    {
+      "region_id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/endpoints/151d1dd2c86b4af783143ab1aa9d9a39"
+      },
+      "url": "http://localhost/compute/v2/$(project_id)s",
+      "region": "RegionOne",
+      "enabled": true,
+      "interface": "public",
+      "service_id": "a14c47dc13194bf2a2195e861db9f906",
+      "id": "151d1dd2c86b4af783143ab1aa9d9a39"
+    },
+    {
+      "region_id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/endpoints/1a9f6c6bea0e4ff5bb8b17e3647a706e"
+      },
+      "url": "http://localhost/volume/v2/$(project_id)s",
+      "region": "RegionOne",
+      "enabled": true,
+      "interface": "public",
+      "service_id": "238841bcff5f4f2b9ee2f7973c19e22a",
+      "id": "1a9f6c6bea0e4ff5bb8b17e3647a706e"
+    },
+    {
+      "region_id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/endpoints/206d6d831dbf44ccad854ffb419b4f02"
+      },
+      "url": "http://localhost/volume/v1/$(project_id)s",
+      "region": "RegionOne",
+      "enabled": true,
+      "interface": "public",
+      "service_id": "a222e99e3d24476baa7762858dc34006",
+      "id": "206d6d831dbf44ccad854ffb419b4f02"
+    },
+    {
+      "region_id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/endpoints/23f60684390f4376aca5d828f2381e6e"
+      },
+      "url": "http://localhost:8080",
+      "region": "RegionOne",
+      "enabled": true,
+      "interface": "admin",
+      "service_id": "2774503aa5354d70a801df09a813db46",
+      "id": "23f60684390f4376aca5d828f2381e6e"
+    },
+    {
+      "region_id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/endpoints/572671cba1d34bd29fdd160eac891971"
+      },
+      "url": "http://localhost/volume/v3/$(project_id)s",
+      "region": "RegionOne",
+      "enabled": true,
+      "interface": "public",
+      "service_id": "be984bfb8b5f447d8a0ea3fa075054fc",
+      "id": "572671cba1d34bd29fdd160eac891971"
+    },
+    {
+      "region_id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/endpoints/882c0c04727744ee8d20839f5f9eec9a"
+      },
+      "url": "http://localhost/compute/v2.1",
+      "region": "RegionOne",
+      "enabled": true,
+      "interface": "public",
+      "service_id": "fcc6a934957545bbb5fee29a217530ef",
+      "id": "882c0c04727744ee8d20839f5f9eec9a"
+    },
+    {
+      "region_id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/endpoints/9f1b314e3bc5403a8dcdef14c9eb044c"
+      },
+      "url": "http://localhost/identity/v3",
+      "region": "RegionOne",
+      "enabled": true,
+      "interface": "public",
+      "service_id": "6b73ec12f7754a8696d758561a1cf5f1",
+      "id": "9f1b314e3bc5403a8dcdef14c9eb044c"
+    },
+    {
+      "region_id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/endpoints/bb2aa713d1154809aeb414e6fd71ba95"
+      },
+      "url": "http://localhost/image",
+      "region": "RegionOne",
+      "enabled": true,
+      "interface": "public",
+      "service_id": "5dbcb1098b2e41e083c05c9006bd9830",
+      "id": "bb2aa713d1154809aeb414e6fd71ba95"
+    },
+    {
+      "region_id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/endpoints/dcf25870430f468d8f8dfd9d3acb95cc"
+      },
+      "url": "http://localhost:9696/",
+      "region": "RegionOne",
+      "enabled": true,
+      "interface": "public",
+      "service_id": "d12688144a6e4d9db17ead14f4670d5b",
+      "id": "dcf25870430f468d8f8dfd9d3acb95cc"
+    },
+    {
+      "region_id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/endpoints/eb1517fb194a4df09109aa4606c245b9"
+      },
+      "url": "http://localhost/identity/v3",
+      "region": "RegionOne",
+      "enabled": true,
+      "interface": "admin",
+      "service_id": "6b73ec12f7754a8696d758561a1cf5f1",
+      "id": "eb1517fb194a4df09109aa4606c245b9"
+    },
+    {
+      "region_id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/endpoints/edd066392b3549508739e18d5e69dbb7"
+      },
+      "url": "http://localhost/placement",
+      "region": "RegionOne",
+      "enabled": true,
+      "interface": "public",
+      "service_id": "74efa863cb264dcbba922ff254a19876",
+      "id": "edd066392b3549508739e18d5e69dbb7"
+    },
+    {
+      "region_id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/endpoints/ef84daa849894b8ebda274615a083139"
+      },
+      "url": "http://localhost:8080/v1/AUTH_$(project_id)s",
+      "region": "RegionOne",
+      "enabled": true,
+      "interface": "public",
+      "service_id": "2774503aa5354d70a801df09a813db46",
+      "id": "ef84daa849894b8ebda274615a083139"
+    }
+  ],
+  "links": {
+    "self": "http://localhost/identity/v3/endpoints",
+    "previous": null,
+    "next": null
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/resources/v3/project.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/v3/project.json b/apis/openstack-keystone/src/test/resources/v3/project.json
new file mode 100644
index 0000000..0a47ffa
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/v3/project.json
@@ -0,0 +1,15 @@
+{
+  "project": {
+    "is_domain": false,
+    "description": "Updated",
+    "links": {
+      "self": "http://localhost/identity/v3/projects/2f9b30f706bc45d7923e055567be2e98"
+    },
+    "tags": [],
+    "enabled": true,
+    "id": "2f9b30f706bc45d7923e055567be2e98",
+    "parent_id": "default",
+    "domain_id": "default",
+    "name": "ProjectApiLiveTest"
+  }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/resources/v3/projects.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/v3/projects.json b/apis/openstack-keystone/src/test/resources/v3/projects.json
new file mode 100644
index 0000000..2f9faa0
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/v3/projects.json
@@ -0,0 +1,74 @@
+{
+  "links": {
+    "self": "http://localhost/identity/v3/projects",
+    "previous": null,
+    "next": null
+  },
+  "projects": [
+    {
+      "is_domain": false,
+      "description": "Updated",
+      "links": {
+        "self": "http://localhost/identity/v3/projects/2f9b30f706bc45d7923e055567be2e98"
+      },
+      "tags": [],
+      "enabled": true,
+      "id": "2f9b30f706bc45d7923e055567be2e98",
+      "parent_id": "default",
+      "domain_id": "default",
+      "name": "ProjectApiLiveTest"
+    },
+    {
+      "is_domain": false,
+      "description": "",
+      "links": {
+        "self": "http://localhost/identity/v3/projects/2fa489f9b84541b8a614c8c9df0d7596"
+      },
+      "tags": [],
+      "enabled": true,
+      "id": "2fa489f9b84541b8a614c8c9df0d7596",
+      "parent_id": "default",
+      "domain_id": "default",
+      "name": "service"
+    },
+    {
+      "is_domain": false,
+      "description": "",
+      "links": {
+        "self": "http://localhost/identity/v3/projects/43de288ea0ce4d2b8b811055b10f156b"
+      },
+      "tags": [],
+      "enabled": true,
+      "id": "43de288ea0ce4d2b8b811055b10f156b",
+      "parent_id": "default",
+      "domain_id": "default",
+      "name": "jclouds"
+    },
+    {
+      "is_domain": false,
+      "description": "Bootstrap project for initializing the cloud.",
+      "links": {
+        "self": "http://localhost/identity/v3/projects/90131053e7384f8a9e970544e0845913"
+      },
+      "tags": [],
+      "enabled": true,
+      "id": "90131053e7384f8a9e970544e0845913",
+      "parent_id": "default",
+      "domain_id": "default",
+      "name": "admin"
+    },
+    {
+      "is_domain": false,
+      "description": "",
+      "links": {
+        "self": "http://localhost/identity/v3/projects/d91b807dc87d477381500e8c920b10c7"
+      },
+      "tags": [],
+      "enabled": true,
+      "id": "d91b807dc87d477381500e8c920b10c7",
+      "parent_id": "default",
+      "domain_id": "default",
+      "name": "demo"
+    }
+  ]
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/1f84b603/apis/openstack-keystone/src/test/resources/v3/regions.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/v3/regions.json b/apis/openstack-keystone/src/test/resources/v3/regions.json
new file mode 100644
index 0000000..326a6fe
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/v3/regions.json
@@ -0,0 +1,18 @@
+{
+  "regions": [
+    {
+      "parent_region_id": null,
+      "id": "RegionOne",
+      "links": {
+        "self": "http://localhost/identity/v3/regions/RegionOne"
+      },
+      "description": ""
+    }
+  ],
+  "links": {
+    "self": "http://localhost/identity/v3/regions",
+    "previous": null,
+    "next": null
+  }
+}
+