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 2014/03/14 10:03:36 UTC

[1/3] JCLOUDS-453. Add OpenStack Keystone v2.0 OS-KSADM Admin Extension support for Keystone.

Repository: jclouds
Updated Branches:
  refs/heads/master e7fccd652 -> b68f1b6e1


http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/resources/tenant_create_response.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/tenant_create_response.json b/apis/openstack-keystone/src/test/resources/tenant_create_response.json
new file mode 100644
index 0000000..f55ec03
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/tenant_create_response.json
@@ -0,0 +1 @@
+{"tenant":{"id":"t1000","name":"jclouds-tenant","description":"jclouds-description","enabled":true}}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/resources/tenant_update_response.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/tenant_update_response.json b/apis/openstack-keystone/src/test/resources/tenant_update_response.json
new file mode 100644
index 0000000..4fb042f
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/tenant_update_response.json
@@ -0,0 +1 @@
+{"tenant":{"id":"t1000","name":"jclouds-tenant-modified","description":"jclouds-description-modified","enabled":false}}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/resources/user_create_response.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/user_create_response.json b/apis/openstack-keystone/src/test/resources/user_create_response.json
new file mode 100644
index 0000000..7fb82ee
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/user_create_response.json
@@ -0,0 +1 @@
+{"user":{"id":"u1000","name":"jqsmith","email":"john.smith@example.org","enabled":true}}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/resources/user_update_response.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/user_update_response.json b/apis/openstack-keystone/src/test/resources/user_update_response.json
new file mode 100644
index 0000000..244b45d
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/user_update_response.json
@@ -0,0 +1 @@
+{"user":{"id":"u1000","name":"jqsmith-renamed","email":"john.smith.renamed@example.org","enabled":false}}
\ No newline at end of file


[3/3] git commit: JCLOUDS-453. Add OpenStack Keystone v2.0 OS-KSADM Admin Extension support for Keystone.

Posted by na...@apache.org.
JCLOUDS-453. Add OpenStack Keystone v2.0 OS-KSADM Admin Extension support for Keystone.


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

Branch: refs/heads/master
Commit: b68f1b6e1a4ff60540a85210fe811be82f6d3b77
Parents: e7fccd6
Author: Pedro Navarro PĂ©rez <pe...@gmail.com>
Authored: Thu Jan 30 11:12:05 2014 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Fri Mar 14 09:54:38 2014 +0100

----------------------------------------------------------------------
 .../openstack/keystone/v2_0/KeystoneApi.java    |  28 ++
 .../keystone/v2_0/KeystoneAsyncApi.java         |  28 ++
 .../v2_0/config/KeystoneRestClientModule.java   |  12 +
 .../openstack/keystone/v2_0/domain/Service.java |  60 +++-
 .../openstack/keystone/v2_0/domain/Tenant.java  |  65 +++--
 .../openstack/keystone/v2_0/domain/User.java    | 110 +++++--
 .../v2_0/extensions/ExtensionNamespaces.java    |  30 ++
 .../keystone/v2_0/extensions/RoleAdminApi.java  |  65 +++++
 .../v2_0/extensions/RoleAdminAsyncApi.java      | 111 +++++++
 .../v2_0/extensions/ServiceAdminApi.java        |  70 +++++
 .../v2_0/extensions/ServiceAdminAsyncApi.java   | 127 ++++++++
 .../v2_0/extensions/TenantAdminApi.java         |  80 +++++
 .../v2_0/extensions/TenantAdminAsyncApi.java    | 138 +++++++++
 .../keystone/v2_0/extensions/UserAdminApi.java  |  66 +++++
 .../v2_0/extensions/UserAdminAsyncApi.java      | 114 ++++++++
 .../v2_0/functions/internal/ParseServices.java  |  95 ++++++
 .../v2_0/options/CreateTenantOptions.java       | 144 +++++++++
 .../v2_0/options/CreateUserOptions.java         | 180 ++++++++++++
 .../v2_0/options/UpdateTenantOptions.java       | 159 ++++++++++
 .../v2_0/options/UpdateUserOptions.java         | 193 ++++++++++++
 .../v2_0/extensions/RoleAdminApiLiveTest.java   | 105 +++++++
 .../v2_0/extensions/RoleAdminApiMockTest.java   | 249 ++++++++++++++++
 .../extensions/ServiceAdminApiLiveTest.java     | 106 +++++++
 .../extensions/ServiceAdminApiMockTest.java     | 285 ++++++++++++++++++
 .../v2_0/extensions/TenantAdminApiLiveTest.java | 101 +++++++
 .../v2_0/extensions/TenantAdminApiMockTest.java | 292 +++++++++++++++++++
 .../v2_0/extensions/UserAdminApiLiveTest.java   | 102 +++++++
 .../v2_0/extensions/UserAdminApiMockTest.java   | 200 +++++++++++++
 .../v2_0/features/ServiceApiExpectTest.java     |   3 +-
 .../v2_0/features/TenantApiExpectTest.java      |   9 +-
 .../v2_0/features/TokenApiExpectTest.java       |   2 +-
 .../v2_0/features/UserApiExpectTest.java        |  19 +-
 .../v2_0/functions/internal/ParseUsersTest.java |  12 +-
 .../v2_0/parse/ParseAdminAccessTest.java        |   3 +-
 .../ParseRandomEndpointVersionAccessTest.java   |   2 +-
 .../v2_0/internal/BaseOpenStackMockTest.java    |  12 +
 .../src/test/resources/admin_extensions.json    |   1 +
 .../test/resources/role_create_response.json    |   1 +
 .../src/test/resources/role_list_response.json  |   1 +
 .../test/resources/service_create_response.json |   1 +
 .../test/resources/service_list_response.json   |   1 +
 .../test/resources/tenant_create_response.json  |   1 +
 .../test/resources/tenant_update_response.json  |   1 +
 .../test/resources/user_create_response.json    |   1 +
 .../test/resources/user_update_response.json    |   1 +
 45 files changed, 3310 insertions(+), 76 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/KeystoneApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/KeystoneApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/KeystoneApi.java
index 394fdb3..2a67c0c 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/KeystoneApi.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/KeystoneApi.java
@@ -19,6 +19,10 @@ package org.jclouds.openstack.keystone.v2_0;
 import java.io.Closeable;
 
 import org.jclouds.openstack.keystone.v2_0.domain.ApiMetadata;
+import org.jclouds.openstack.keystone.v2_0.extensions.RoleAdminApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.ServiceAdminApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.TenantAdminApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.UserAdminApi;
 import org.jclouds.openstack.keystone.v2_0.features.ServiceApi;
 import org.jclouds.openstack.keystone.v2_0.features.TenantApi;
 import org.jclouds.openstack.keystone.v2_0.features.TokenApi;
@@ -74,4 +78,28 @@ public interface KeystoneApi extends Closeable {
     */
    @Delegate
    Optional<? extends TenantApi> getTenantApi();
+   
+   /** 
+    * Provides synchronous access to Admin user features 
+    */
+   @Delegate
+   Optional<? extends UserAdminApi> getUserAdminApi();
+   
+   /** 
+    * Provides synchronous access to Admin tenant features 
+    */
+   @Delegate
+   Optional<? extends TenantAdminApi> getTenantAdminApi();
+   
+   /** 
+    * Provides synchronous access to Admin role features 
+    */
+   @Delegate
+   Optional<? extends RoleAdminApi> getRoleAdminApi();
+   
+   /** 
+    * Provides synchronous access to Admin service features 
+    */
+   @Delegate
+   Optional<? extends ServiceAdminApi> getServiceAdminApi();
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/KeystoneAsyncApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/KeystoneAsyncApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/KeystoneAsyncApi.java
index b5f044c..e0a0b1d 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/KeystoneAsyncApi.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/KeystoneAsyncApi.java
@@ -24,6 +24,10 @@ import javax.ws.rs.core.MediaType;
 
 import org.jclouds.Fallbacks.NullOnNotFoundOr404;
 import org.jclouds.openstack.keystone.v2_0.domain.ApiMetadata;
+import org.jclouds.openstack.keystone.v2_0.extensions.RoleAdminAsyncApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.ServiceAdminAsyncApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.TenantAdminAsyncApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.UserAdminAsyncApi;
 import org.jclouds.openstack.keystone.v2_0.features.ServiceAsyncApi;
 import org.jclouds.openstack.keystone.v2_0.features.TenantAsyncApi;
 import org.jclouds.openstack.keystone.v2_0.features.TokenAsyncApi;
@@ -87,4 +91,28 @@ public interface KeystoneAsyncApi extends Closeable {
     */
    @Delegate
    Optional<? extends TenantAsyncApi> getTenantApi();
+
+   /**
+    * @see KeystoneApi#getUserAdminApi()
+    */
+   @Delegate
+   Optional<? extends UserAdminAsyncApi> getUserAdminApi();
+   
+   /**
+    * @see KeystoneApi#getTenantAdminApi()
+    */
+   @Delegate
+   Optional<? extends TenantAdminAsyncApi> getTenantAdminApi();
+   
+   /**
+    * @see KeystoneApi#getRoleAdminApi()
+    */
+   @Delegate
+   Optional<? extends RoleAdminAsyncApi> getRoleAdminApi();
+   
+   /**
+    * @see KeystoneApi#getServiceAdminApi()
+    */
+   @Delegate
+   Optional<? extends ServiceAdminAsyncApi> getServiceAdminApi();
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/config/KeystoneRestClientModule.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/config/KeystoneRestClientModule.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/config/KeystoneRestClientModule.java
index ff17044..9006d23 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/config/KeystoneRestClientModule.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/config/KeystoneRestClientModule.java
@@ -34,6 +34,14 @@ import org.jclouds.http.annotation.ServerError;
 import org.jclouds.location.Provider;
 import org.jclouds.openstack.keystone.v2_0.KeystoneApi;
 import org.jclouds.openstack.keystone.v2_0.KeystoneAsyncApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.RoleAdminApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.RoleAdminAsyncApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.ServiceAdminApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.ServiceAdminAsyncApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.TenantAdminApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.TenantAdminAsyncApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.UserAdminApi;
+import org.jclouds.openstack.keystone.v2_0.extensions.UserAdminAsyncApi;
 import org.jclouds.openstack.keystone.v2_0.features.ServiceApi;
 import org.jclouds.openstack.keystone.v2_0.features.ServiceAsyncApi;
 import org.jclouds.openstack.keystone.v2_0.features.TenantApi;
@@ -85,6 +93,10 @@ public class KeystoneRestClientModule<S extends KeystoneApi, A extends KeystoneA
             .put(TokenApi.class, TokenAsyncApi.class)
             .put(UserApi.class, UserAsyncApi.class)
             .put(TenantApi.class, TenantAsyncApi.class)
+            .put(UserAdminApi.class, UserAdminAsyncApi.class)
+            .put(TenantAdminApi.class, TenantAdminAsyncApi.class)
+            .put(RoleAdminApi.class, RoleAdminAsyncApi.class)
+            .put(ServiceAdminApi.class, ServiceAdminAsyncApi.class)
             .build();
 
    @SuppressWarnings("unchecked")

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/Service.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/Service.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/Service.java
index 0480c45..b4836ff 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/Service.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/Service.java
@@ -21,6 +21,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import java.beans.ConstructorProperties;
 import java.util.Set;
 
+import org.jclouds.javax.annotation.Nullable;
+
 import com.google.common.base.Objects;
 import com.google.common.base.Objects.ToStringHelper;
 import com.google.common.collect.ForwardingSet;
@@ -48,9 +50,19 @@ public class Service extends ForwardingSet<Endpoint> {
    public abstract static class Builder<T extends Builder<T>>  {
       protected abstract T self();
 
+      protected String id;
       protected String type;
       protected String name;
+      protected String description;
       protected ImmutableSet.Builder<Endpoint> endpoints = ImmutableSet.<Endpoint>builder();
+      
+      /**
+       * @see Service#getId()
+       */
+      public T id(String id) {
+         this.id = id;
+         return self();
+      }
 
       /**
        * @see Service#getType()
@@ -67,6 +79,14 @@ public class Service extends ForwardingSet<Endpoint> {
          this.name = name;
          return self();
       }
+      
+      /**
+       * @see Service#getDescription()
+       */
+      public T description(String description) {
+         this.description = description;
+         return self();
+      }
 
       /**
        * @see Service#delegate()
@@ -85,13 +105,15 @@ public class Service extends ForwardingSet<Endpoint> {
       }
 
       public Service build() {
-         return new Service(type, name, endpoints.build());
+         return new Service(id, type, name, description, endpoints.build());
       }
 
       public T fromService(Service in) {
          return this
+               .id(in.getId())
                .type(in.getType())
                .name(in.getName())
+               .description(in.getDescription())
                .endpoints(in);
       }
    }
@@ -102,17 +124,31 @@ public class Service extends ForwardingSet<Endpoint> {
       }
    }
 
+   private final String id;
    private final String type;
    private final String name;
+   private final String description;
    private final Set<Endpoint> endpoints;
 
    @ConstructorProperties({
-         "type", "name", "endpoints"
+         "id", "type", "name", "description", "endpoints"
    })
-   protected Service(String type, String name, Set<Endpoint> endpoints) {
+   protected Service(@Nullable String id, String type, String name, @Nullable String description, @Nullable Set<Endpoint> endpoints) {
+      this.id = id;
       this.type = checkNotNull(type, "type");
       this.name = checkNotNull(name, "name");
-      this.endpoints = ImmutableSet.copyOf(checkNotNull(endpoints, "endpoints"));
+      this.description = description;
+      this.endpoints = endpoints == null ? ImmutableSet.<Endpoint>of() : ImmutableSet.copyOf(endpoints);
+   }
+   
+   /**
+    * When providing an ID, it is assumed that the service exists in the current OpenStack deployment
+    *
+    * @return the id of the service in the current OpenStack deployment
+    */
+   @Nullable
+   public String getId() {
+      return this.id;
    }
 
    /**
@@ -130,10 +166,17 @@ public class Service extends ForwardingSet<Endpoint> {
    public String getName() {
       return this.name;
    }
+   
+   /**
+    * @return the description of the service
+    */
+   public String getDescription() {
+      return this.description;
+   }
 
    @Override
    public int hashCode() {
-      return Objects.hashCode(type, name, endpoints);
+      return Objects.hashCode(id, type, name, description, endpoints);
    }
 
    @Override
@@ -141,14 +184,17 @@ public class Service extends ForwardingSet<Endpoint> {
       if (this == obj) return true;
       if (obj == null || getClass() != obj.getClass()) return false;
       Service that = Service.class.cast(obj);
-      return Objects.equal(this.type, that.type)
+      return Objects.equal(this.id, that.id) 
+            && Objects.equal(this.type, that.type)
             && Objects.equal(this.name, that.name)
+            && Objects.equal(this.description, that.description)
             && Objects.equal(this.endpoints, that.endpoints);
    }
 
    protected ToStringHelper string() {
       return Objects.toStringHelper(this).omitNullValues()
-            .add("type", type).add("name", name).add("endpoints", endpoints);
+            .add("id", id).add("type", type).add("name", name)
+            .add("description", description).add("endpoints", endpoints);
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/Tenant.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/Tenant.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/Tenant.java
index 418a464..6293184 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/Tenant.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/Tenant.java
@@ -26,12 +26,14 @@ import com.google.common.base.Objects;
 import com.google.common.base.Objects.ToStringHelper;
 
 /**
- * A container used to group or isolate resources and/or identity objects. Depending on the service
- * operator, a tenant may map to a customer, account, organization, or project.
- *
+ * A container used to group or isolate resources and/or identity objects.
+ * Depending on the service operator, a tenant may map to a customer, account,
+ * organization, or project.
+ * 
  * @author Adrian Cole
- * @see <a href="http://docs.openstack.org/api/openstack-identity-service/2.0/content/Identity-Service-Concepts-e1362.html"
-/>
+ * @see <a href=
+ *      "http://docs.openstack.org/api/openstack-identity-service/2.0/content/Identity-Service-Concepts-e1362.html"
+ *      />
  */
 public class Tenant {
 
@@ -43,12 +45,13 @@ public class Tenant {
       return new ConcreteBuilder().fromTenant(this);
    }
 
-   public abstract static class Builder<T extends Builder<T>>  {
+   public abstract static class Builder<T extends Builder<T>> {
       protected abstract T self();
 
       protected String id;
       protected String name;
       protected String description;
+      protected Boolean enabled;
 
       /**
        * @see Tenant#getId()
@@ -59,6 +62,14 @@ public class Tenant {
       }
 
       /**
+       * @see Tenant#isEnabled()
+       */
+      public T enabled(Boolean enabled) {
+         this.enabled = enabled;
+         return self();
+      }
+
+      /**
        * @see Tenant#getName()
        */
       public T name(String name) {
@@ -75,14 +86,11 @@ public class Tenant {
       }
 
       public Tenant build() {
-         return new Tenant(id, name, description);
+         return new Tenant(id, name, description, enabled);
       }
 
       public T fromTenant(Tenant in) {
-         return this
-               .id(in.getId())
-               .name(in.getName())
-               .description(in.getDescription());
+         return this.id(in.getId()).name(in.getName()).description(in.getDescription());
       }
    }
 
@@ -96,19 +104,20 @@ public class Tenant {
    private final String id;
    private final String name;
    private final String description;
+   private final Boolean enabled;
 
-   @ConstructorProperties({
-         "id", "name", "description"
-   })
-   protected Tenant(String id, String name, @Nullable String description) {
+   @ConstructorProperties({ "id", "name", "description", "enabled" })
+   protected Tenant(String id, String name, @Nullable String description, @Nullable Boolean enabled) {
       this.id = checkNotNull(id, "id");
       this.name = checkNotNull(name, "name");
       this.description = description;
+      this.enabled = enabled;
    }
 
    /**
-    * When providing an ID, it is assumed that the tenant exists in the current OpenStack deployment
-    *
+    * When providing an ID, it is assumed that the tenant exists in the current
+    * OpenStack deployment
+    * 
     * @return the id of the tenant in the current OpenStack deployment
     */
    public String getId() {
@@ -130,6 +139,13 @@ public class Tenant {
       return this.description;
    }
 
+   /**
+    * @return if the tenant is enabled
+    */
+   public boolean isEnabled() {
+      return this.enabled;
+   }
+
    @Override
    public int hashCode() {
       return Objects.hashCode(id, name, description);
@@ -137,17 +153,18 @@ public class Tenant {
 
    @Override
    public boolean equals(Object obj) {
-      if (this == obj) return true;
-      if (obj == null || getClass() != obj.getClass()) return false;
+      if (this == obj)
+         return true;
+      if (obj == null || getClass() != obj.getClass())
+         return false;
       Tenant that = Tenant.class.cast(obj);
-      return Objects.equal(this.id, that.id)
-            && Objects.equal(this.name, that.name)
-            && Objects.equal(this.description, that.description);
+      return Objects.equal(this.id, that.id) && Objects.equal(this.name, that.name)
+            && Objects.equal(this.description, that.description) && Objects.equal(this.enabled, that.enabled);
    }
 
    protected ToStringHelper string() {
-      return Objects.toStringHelper(this).omitNullValues()
-            .add("id", id).add("name", name).add("description", description);
+      return Objects.toStringHelper(this).omitNullValues().add("id", id).add("name", name)
+            .add("description", description).add("enabled", enabled);
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/User.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/User.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/User.java
index 47eaf06..fd3127d 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/User.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/User.java
@@ -29,14 +29,16 @@ import com.google.common.collect.ForwardingSet;
 import com.google.common.collect.ImmutableSet;
 
 /**
- * A digital representation of a person, system, or service who uses OpenStack cloud services.
- * Keystone authentication services will validate that incoming request are being made by the user
- * who claims to be making the call. Users have a login and may be assigned tokens to access users.
- * Users may be directly assigned to a particular tenant and behave as if they are contained in that
- * tenant.
- *
+ * A digital representation of a person, system, or service who uses OpenStack
+ * cloud services. Keystone authentication services will validate that incoming
+ * request are being made by the user who claims to be making the call. Users
+ * have a login and may be assigned tokens to access users. Users may be
+ * directly assigned to a particular tenant and behave as if they are contained
+ * in that tenant.
+ * 
  * @author Adrian Cole
- * @see <a href="http://docs.openstack.org/api/openstack-identity-service/2.0/content/Identity-User-Concepts-e1362.html"
+ * @see <a href=
+ *      "http://docs.openstack.org/api/openstack-identity-service/2.0/content/Identity-User-Concepts-e1362.html"
  *      />
  */
 public class User extends ForwardingSet<Role> {
@@ -54,6 +56,9 @@ public class User extends ForwardingSet<Role> {
 
       protected String id;
       protected String name;
+      protected String email;
+      protected Boolean enabled;
+      protected String tenantId;
       protected ImmutableSet.Builder<Role> roles = ImmutableSet.<Role> builder();
 
       /**
@@ -73,6 +78,30 @@ public class User extends ForwardingSet<Role> {
       }
 
       /**
+       * @see User#getEmail()
+       */
+      public T email(String email) {
+         this.email = email;
+         return self();
+      }
+
+      /**
+       * @see User#isEnabled()
+       */
+      public T enabled(Boolean enabled) {
+         this.enabled = enabled;
+         return self();
+      }
+
+      /**
+       * @see User#getTenantId
+       */
+      public T tenantId(String tenantId) {
+         this.tenantId = tenantId;
+         return self();
+      }
+
+      /**
        * @see User#delegate()
        */
       public T role(Role role) {
@@ -89,14 +118,12 @@ public class User extends ForwardingSet<Role> {
       }
 
       public User build() {
-         return new User(id, name, roles.build());
+         return new User(id, name, email, enabled, tenantId, roles.build());
       }
 
       public T fromUser(User in) {
-         return this
-               .id(in.getId())
-               .name(in.getName())
-               .roles(in);
+         return this.id(in.getId()).name(in.getName()).email(in.getEmail()).enabled(in.isEnabled())
+               .tenantId(in.getTenantId()).roles(in);
       }
    }
 
@@ -109,20 +136,26 @@ public class User extends ForwardingSet<Role> {
 
    private final String id;
    private final String name;
+   private final String email;
+   private final Boolean enabled;
+   private final String tenantId;
    private final Set<Role> roles;
 
-   @ConstructorProperties({
-         "id", "name", "roles"
-   })
-   protected User(String id, String name, @Nullable Set<Role> roles) {
+   @ConstructorProperties({ "id", "name", "email", "enabled", "tenantId", "roles" })
+   protected User(String id, String name, @Nullable String email, @Nullable Boolean enabled, @Nullable String tenantId,
+         @Nullable Set<Role> roles) {
       this.id = checkNotNull(id, "id");
       this.name = checkNotNull(name, "name");
-      this.roles = roles == null ? ImmutableSet.<Role>of() : ImmutableSet.copyOf(roles);
+      this.email = email;
+      this.enabled = enabled;
+      this.tenantId = tenantId;
+      this.roles = roles == null ? ImmutableSet.<Role> of() : ImmutableSet.copyOf(roles);
    }
 
    /**
-    * When providing an ID, it is assumed that the user exists in the current OpenStack deployment
-    *
+    * When providing an ID, it is assumed that the user exists in the current
+    * OpenStack deployment
+    * 
     * @return the id of the user in the current OpenStack deployment
     */
    public String getId() {
@@ -136,24 +169,47 @@ public class User extends ForwardingSet<Role> {
       return this.name;
    }
 
+   /**
+    * @return the e-mail
+    */
+   public String getEmail() {
+      return this.email;
+   }
+
+   /**
+    * @return if the user is enabled
+    */
+   public boolean isEnabled() {
+      return this.enabled;
+   }
+
+   /**
+    * @return the user tenant
+    */
+   public String getTenantId() {
+      return this.tenantId;
+   }
+
    @Override
    public int hashCode() {
-      return Objects.hashCode(id, name, roles);
+      return Objects.hashCode(id, name, email, enabled, tenantId, roles);
    }
 
    @Override
    public boolean equals(Object obj) {
-      if (this == obj) return true;
-      if (obj == null || getClass() != obj.getClass()) return false;
+      if (this == obj)
+         return true;
+      if (obj == null || getClass() != obj.getClass())
+         return false;
       User that = User.class.cast(obj);
-      return Objects.equal(this.id, that.id)
-            && Objects.equal(this.name, that.name)
-            && Objects.equal(this.roles, that.roles);
+      return Objects.equal(this.id, that.id) && Objects.equal(this.name, that.name)
+            && Objects.equal(this.roles, that.roles) && Objects.equal(this.enabled, that.enabled)
+            && Objects.equal(this.tenantId, that.tenantId) && Objects.equal(this.email, that.email);
    }
 
    protected ToStringHelper string() {
-      return Objects.toStringHelper(this).omitNullValues()
-            .add("id", id).add("name", name).add("roles", roles);
+      return Objects.toStringHelper(this).omitNullValues().add("id", id).add("name", name).add("email", email)
+            .add("enabled", enabled).add("roles", roles).add("tenanId", tenantId);
    }
 
    @Override

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/ExtensionNamespaces.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/ExtensionNamespaces.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/ExtensionNamespaces.java
new file mode 100644
index 0000000..7ec238d
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/ExtensionNamespaces.java
@@ -0,0 +1,30 @@
+/*
+ * 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.v2_0.extensions;
+
+/**
+ * Extension namespaces
+ *
+ * @author Pedro Navarro
+ * @see <a href= "http://docs.openstack.org/developer/keystone/extension_development.html" />
+ */
+public interface ExtensionNamespaces {
+   /**
+    * OpenStack Keystone Admin Support
+    */
+   public static final String OS_KSADM = "http://docs.openstack.org/identity/api/ext/OS-KSADM/v1.0";
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminApi.java
new file mode 100644
index 0000000..f0d7536
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminApi.java
@@ -0,0 +1,65 @@
+/*
+ * 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.v2_0.extensions;
+
+import org.jclouds.openstack.keystone.v2_0.domain.Role;
+import org.jclouds.openstack.v2_0.ServiceType;
+import org.jclouds.openstack.v2_0.services.Extension;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.FluentIterable;
+
+/**
+ * Provides synchronous access to Role Administration actions.
+ * <p/>
+ * 
+ * @see org.jclouds.openstack.keystone.v2_0.extensions.RoleAdminAsyncApi
+ * @author Pedro Navarro
+ */
+@Beta
+@Extension(of = ServiceType.IDENTITY, namespace = ExtensionNamespaces.OS_KSADM)
+public interface RoleAdminApi {
+
+   /**
+    * Returns a summary list of roles.
+    * 
+    * @return The list of roles
+    */
+   FluentIterable<? extends Role> list();
+
+   /**
+    * Creates a new Role
+    * 
+    * @return the new Role
+    */
+   Role create(String name);
+
+   /**
+    * Gets the role
+    * 
+    * @return the role
+    */
+   Role get(String roleId);
+
+   /**
+    * Deletes a role
+    * 
+    * @return true if successful
+    */
+   boolean delete(String roleId);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminAsyncApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminAsyncApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminAsyncApi.java
new file mode 100644
index 0000000..71ad4f0
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminAsyncApi.java
@@ -0,0 +1,111 @@
+/*
+ * 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.v2_0.extensions;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.EmptyFluentIterableOnNotFoundOr404;
+import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.openstack.keystone.v2_0.domain.Role;
+import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest;
+import org.jclouds.openstack.v2_0.ServiceType;
+import org.jclouds.openstack.v2_0.services.Extension;
+import org.jclouds.rest.annotations.Fallback;
+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 com.google.common.annotations.Beta;
+import com.google.common.collect.FluentIterable;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Provides asynchronous access to roles Administration actions.
+ * <p/>
+ * 
+ * @see org.jclouds.openstack.keystone.v2_0.extensions.RoleAdminApi
+ * @author Pedro Navarro
+ */
+@Beta
+@Extension(of = ServiceType.IDENTITY, namespace = ExtensionNamespaces.OS_KSADM)
+@RequestFilters(AuthenticateRequest.class)
+public interface RoleAdminAsyncApi {
+
+   /**
+    * Returns a summary list of roles.
+    * 
+    * @return The list of roles
+    */
+   @Named("role:list")
+   @GET
+   @Path("OS-KSADM/roles")
+   @SelectJson("roles")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Fallback(EmptyFluentIterableOnNotFoundOr404.class)
+   ListenableFuture<? extends FluentIterable<? extends Role>> list();
+
+   /**
+    * Creates a new role
+    * 
+    * @return the new role
+    */
+   @Named("role:create")
+   @POST
+   @Path("OS-KSADM/roles")
+   @SelectJson("role")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Produces(MediaType.APPLICATION_JSON)
+   @Fallback(NullOnNotFoundOr404.class)
+   @WrapWith("role")
+   ListenableFuture<? extends Role> create(@PayloadParam("name") String name);
+
+   /**
+    * Gets the role
+    * 
+    * @return the role
+    */
+   @Named("role:get")
+   @GET
+   @SelectJson("role")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Path("OS-KSADM/roles/{roleId}")
+   @Fallback(NullOnNotFoundOr404.class)
+   ListenableFuture<? extends Role> get(@PathParam("roleId") String roleId);
+
+   /**
+    * Deletes an role.
+    * 
+    * @return true if successful
+    */
+   @Named("role:delete")
+   @DELETE
+   @Path("OS-KSADM/roles/{id}")
+   @Consumes
+   @Fallback(FalseOnNotFoundOr404.class)
+   ListenableFuture<Boolean> delete(@PathParam("id") String id);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminApi.java
new file mode 100644
index 0000000..c476abb
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminApi.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.openstack.keystone.v2_0.extensions;
+
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.openstack.keystone.v2_0.domain.Service;
+import org.jclouds.openstack.v2_0.ServiceType;
+import org.jclouds.openstack.v2_0.domain.PaginatedCollection;
+import org.jclouds.openstack.v2_0.options.PaginationOptions;
+import org.jclouds.openstack.v2_0.services.Extension;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Provides synchronous access to Service Administration actions.
+ * <p/>
+ * 
+ * @see org.jclouds.openstack.keystone.v2_0.extensions.ServiceAdminAsyncApi
+ * @author Pedro Navarro
+ */
+@Beta
+@Extension(of = ServiceType.IDENTITY, namespace = ExtensionNamespaces.OS_KSADM)
+public interface ServiceAdminApi {
+
+   /**
+    * Retrieve the list of services
+    * <p/>
+    * 
+    * @return the list of services
+    */
+   PagedIterable<? extends Service> list();
+
+   PaginatedCollection<? extends Service> list(PaginationOptions options);
+
+   /**
+    * Creates a new Service
+    * 
+    * @return the new Service
+    */
+   Service create(String name, String type, String description);
+
+   /**
+    * Gets the service
+    * 
+    * @return the service
+    */
+   Service get(String serviceId);
+
+   /**
+    * Deletes a service
+    * 
+    * @return true if successful
+    */
+   boolean delete(String serviceId);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminAsyncApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminAsyncApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminAsyncApi.java
new file mode 100644
index 0000000..142bbae
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminAsyncApi.java
@@ -0,0 +1,127 @@
+/*
+ * 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.v2_0.extensions;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.EmptyPagedIterableOnNotFoundOr404;
+import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.openstack.keystone.v2_0.KeystoneFallbacks.EmptyPaginatedCollectionOnNotFoundOr404;
+import org.jclouds.openstack.keystone.v2_0.domain.Service;
+import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest;
+import org.jclouds.openstack.keystone.v2_0.functions.internal.ParseServices;
+import org.jclouds.openstack.keystone.v2_0.functions.internal.ParseServices.ToPagedIterable;
+import org.jclouds.openstack.v2_0.ServiceType;
+import org.jclouds.openstack.v2_0.domain.PaginatedCollection;
+import org.jclouds.openstack.v2_0.options.PaginationOptions;
+import org.jclouds.openstack.v2_0.services.Extension;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.ResponseParser;
+import org.jclouds.rest.annotations.SelectJson;
+import org.jclouds.rest.annotations.Transform;
+import org.jclouds.rest.annotations.WrapWith;
+
+import com.google.common.annotations.Beta;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Provides asynchronous access to services Administration actions.
+ * <p/>
+ * 
+ * @see org.jclouds.openstack.keystone.v2_0.extensions.ServiceAdminApi
+ * @author Pedro Navarro
+ */
+@Beta
+@Extension(of = ServiceType.IDENTITY, namespace = ExtensionNamespaces.OS_KSADM)
+@RequestFilters(AuthenticateRequest.class)
+public interface ServiceAdminAsyncApi {
+
+   /**
+    * @see ServiceApi#list()
+    */
+   @Named("service:list")
+   @GET
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Path("OS-KSADM/services")
+   @ResponseParser(ParseServices.class)
+   @Transform(ToPagedIterable.class)
+   @Fallback(EmptyPagedIterableOnNotFoundOr404.class)
+   ListenableFuture<? extends PagedIterable<? extends Service>> list();
+
+   /** @see ServiceApi#list(PaginationOptions) */
+   @Named("service:list")
+   @GET
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Path("OS-KSADM/services")
+   @ResponseParser(ParseServices.class)
+   @Fallback(EmptyPaginatedCollectionOnNotFoundOr404.class)
+   ListenableFuture<? extends PaginatedCollection<? extends Service>> list(PaginationOptions options);
+
+   /**
+    * Creates a new service
+    * 
+    * @return the new service
+    */
+   @Named("service:create")
+   @POST
+   @Path("OS-KSADM/services")
+   @SelectJson("OS-KSADM:service")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Produces(MediaType.APPLICATION_JSON)
+   @WrapWith("OS-KSADM:service")
+   @Fallback(NullOnNotFoundOr404.class)
+   ListenableFuture<? extends Service> create(@PayloadParam("name") String name, @PayloadParam("type") String type,
+         @PayloadParam("description") String description);
+
+   /**
+    * Gets the service
+    * 
+    * @return the service
+    */
+   @Named("service:get")
+   @GET
+   @SelectJson("OS-KSADM:service")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Path("OS-KSADM/services/{serviceId}")
+   @Fallback(NullOnNotFoundOr404.class)
+   ListenableFuture<? extends Service> get(@PathParam("serviceId") String serviceId);
+
+   /**
+    * Deletes a service.
+    * 
+    * @return true if successful
+    */
+   @Named("service:delete")
+   @DELETE
+   @Path("OS-KSADM/services/{id}")
+   @Consumes
+   @Fallback(FalseOnNotFoundOr404.class)
+   ListenableFuture<Boolean> delete(@PathParam("id") String id);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminApi.java
new file mode 100644
index 0000000..ce455d3
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminApi.java
@@ -0,0 +1,80 @@
+/*
+ * 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.v2_0.extensions;
+
+import org.jclouds.openstack.keystone.v2_0.domain.Tenant;
+import org.jclouds.openstack.keystone.v2_0.options.CreateTenantOptions;
+import org.jclouds.openstack.keystone.v2_0.options.UpdateTenantOptions;
+import org.jclouds.openstack.v2_0.ServiceType;
+import org.jclouds.openstack.v2_0.services.Extension;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Provides synchronous access to Tenant Administration actions.
+ * <p/>
+ * 
+ * @see org.jclouds.openstack.keystone.v2_0.extensions.TenantAdminAsyncApi
+ * @author Pedro Navarro
+ */
+@Beta
+@Extension(of = ServiceType.IDENTITY, namespace = ExtensionNamespaces.OS_KSADM)
+public interface TenantAdminApi {
+
+   /**
+    * Creates a new tenant
+    * 
+    * @return the new tenant
+    */
+   Tenant create(String name);
+   
+   /**
+    * Creates a new tenant
+    * 
+    * @return the new tenant
+    */
+   Tenant create(String name, CreateTenantOptions options);
+
+   /**
+    * Deletes a tenant
+    * 
+    * @return true if successful
+    */
+   boolean delete(String userId);
+
+   /**
+    * Updates a tenant
+    * 
+    * @return the updated tenant
+    */
+   Tenant update(String id, UpdateTenantOptions options);
+
+   /**
+    * Adds role to a user on a tenant
+    * 
+    * @return true if successful
+    */
+   boolean addRoleOnTenant(String tenantId, String userId, String roleId);
+
+   /**
+    * Deletes role to a user on tenant
+    * 
+    * @return true if successful
+    */
+   boolean deleteRoleOnTenant(String tenantId, String userId, String roleId);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminAsyncApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminAsyncApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminAsyncApi.java
new file mode 100644
index 0000000..63f5eab
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminAsyncApi.java
@@ -0,0 +1,138 @@
+/*
+ * 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.v2_0.extensions;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.openstack.keystone.v2_0.domain.Tenant;
+import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest;
+import org.jclouds.openstack.keystone.v2_0.options.CreateTenantOptions;
+import org.jclouds.openstack.keystone.v2_0.options.UpdateTenantOptions;
+import org.jclouds.openstack.v2_0.ServiceType;
+import org.jclouds.openstack.v2_0.services.Extension;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.MapBinder;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SelectJson;
+
+import com.google.common.annotations.Beta;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Provides asynchronous access to tenants Administration actions.
+ * <p/>
+ * 
+ * @see org.jclouds.openstack.keystone.v2_0.extensions.TenantAdminApi
+ * @author Pedro Navarro
+ */
+@Beta
+@Extension(of = ServiceType.IDENTITY, namespace = ExtensionNamespaces.OS_KSADM)
+@RequestFilters(AuthenticateRequest.class)
+public interface TenantAdminAsyncApi {
+   
+   /**
+    * Creates a new tenant
+    * 
+    * @return the new tenant
+    */
+   @Named("tenant:create")
+   @POST
+   @Path("/tenants")
+   @SelectJson("tenant")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Produces(MediaType.APPLICATION_JSON)
+   @Fallback(NullOnNotFoundOr404.class)
+   ListenableFuture<? extends Tenant> create(@PayloadParam("name") String name);
+
+   /**
+    * Creates a new tenant
+    * 
+    * @return the new tenant
+    */
+   @Named("tenant:create")
+   @POST
+   @Path("/tenants")
+   @SelectJson("tenant")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @MapBinder(CreateTenantOptions.class)
+   @Fallback(NullOnNotFoundOr404.class)
+   ListenableFuture<? extends Tenant> create(@PayloadParam("name") String name, CreateTenantOptions options);
+
+   /**
+    * Deletes a tenant.
+    * 
+    * @return true if successful
+    */
+   @Named("tenant:delete")
+   @DELETE
+   @Path("/tenants/{id}")
+   @Consumes
+   @Fallback(FalseOnNotFoundOr404.class)
+   ListenableFuture<Boolean> delete(@PathParam("id") String id);
+
+   /**
+    * Updates a tenant
+    * 
+    * @return the updated tenant
+    */
+   @Named("tenant:updatetenant")
+   @PUT
+   @Path("/tenants/{id}")
+   @SelectJson("tenant")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @MapBinder(UpdateTenantOptions.class)
+   @Fallback(NullOnNotFoundOr404.class)
+   ListenableFuture<? extends Tenant> update(@PathParam("id") String id, UpdateTenantOptions options);
+
+   /**
+    * Adds role to a user on a tenant
+    * 
+    * @return true if successful
+    */
+   @Named("tenant:addroleontenant")
+   @PUT
+   @Path("/tenants/{id}/users/{userId}/roles/OS-KSADM/{roleId}")
+   @Consumes
+   @Fallback(FalseOnNotFoundOr404.class)
+   ListenableFuture<Boolean> addRoleOnTenant(@PathParam("id") String tenantId, @PathParam("userId") String userdId,
+         @PathParam("roleId") String roleId);
+
+   /**
+    * Deletes role to a user on tenant
+    * 
+    * @return
+    */
+   @Named("tenant:deleteroleontenant")
+   @DELETE
+   @Path("/tenants/{id}/users/{userId}/roles/OS-KSADM/{roleId}")
+   @Consumes
+   @Fallback(FalseOnNotFoundOr404.class)
+   ListenableFuture<Boolean> deleteRoleOnTenant(@PathParam("id") String tenantId, @PathParam("userId") String userdId,
+         @PathParam("roleId") String roleId);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminApi.java
new file mode 100644
index 0000000..9fe8dcf
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminApi.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.openstack.keystone.v2_0.extensions;
+
+import org.jclouds.openstack.keystone.v2_0.domain.User;
+import org.jclouds.openstack.keystone.v2_0.options.CreateUserOptions;
+import org.jclouds.openstack.keystone.v2_0.options.UpdateUserOptions;
+import org.jclouds.openstack.v2_0.ServiceType;
+import org.jclouds.openstack.v2_0.services.Extension;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Provides synchronous access to User Administration actions.
+ * <p/>
+ * 
+ * @see org.jclouds.openstack.keystone.v2_0.extensions.UserAdminAsyncApi
+ * @author Pedro Navarro
+ */
+@Beta
+@Extension(of = ServiceType.IDENTITY, namespace = ExtensionNamespaces.OS_KSADM)
+public interface UserAdminApi {
+   
+   /**
+    * Creates a new user
+    * 
+    * @return the new user
+    */
+   User create(String name, String password);
+
+   /**
+    * Creates a new user
+    * 
+    * @return the new user
+    */
+   User create(String name, String password, CreateUserOptions options);
+
+   /**
+    * Deletes an user
+    * 
+    * @return true if successful
+    */
+   boolean delete(String userId);
+
+   /**
+    * Updates an user
+    * 
+    * @return the updated user
+    */
+   User update(String id, UpdateUserOptions options);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminAsyncApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminAsyncApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminAsyncApi.java
new file mode 100644
index 0000000..254e8af
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminAsyncApi.java
@@ -0,0 +1,114 @@
+/*
+ * 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.v2_0.extensions;
+
+import javax.inject.Named;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.openstack.keystone.v2_0.domain.User;
+import org.jclouds.openstack.keystone.v2_0.filters.AuthenticateRequest;
+import org.jclouds.openstack.keystone.v2_0.options.CreateUserOptions;
+import org.jclouds.openstack.keystone.v2_0.options.UpdateUserOptions;
+import org.jclouds.openstack.v2_0.ServiceType;
+import org.jclouds.openstack.v2_0.services.Extension;
+import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.MapBinder;
+import org.jclouds.rest.annotations.PayloadParam;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.annotations.SelectJson;
+
+import com.google.common.annotations.Beta;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Provides asynchronous access to Users Administration actions.
+ * <p/>
+ * 
+ * @see org.jclouds.openstack.keystone.v2_0.extensions.UserAdminApi
+ * @author Pedro Navarro
+ */
+@Beta
+@Extension(of = ServiceType.IDENTITY, namespace = ExtensionNamespaces.OS_KSADM)
+@RequestFilters(AuthenticateRequest.class)
+public interface UserAdminAsyncApi {
+   
+   /**
+    * Creates a new user
+    * 
+    * @return the new user
+    */
+   @Named("user:create")
+   @POST
+   @Path("/users")
+   @SelectJson("user")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @Produces(MediaType.APPLICATION_JSON)
+   @Fallback(NullOnNotFoundOr404.class)
+   ListenableFuture<? extends User> create(@PayloadParam("name") String name,
+         @PayloadParam("password") String password);
+
+   /**
+    * Creates a new user
+    * 
+    * @return the new user
+    */
+   @Named("user:create")
+   @POST
+   @Path("/users")
+   @SelectJson("user")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @MapBinder(CreateUserOptions.class)
+   @Fallback(NullOnNotFoundOr404.class)
+   ListenableFuture<? extends User> create(@PayloadParam("name") String name,
+         @PayloadParam("password") String password, CreateUserOptions options);
+
+   /**
+    * Deletes an user.
+    * 
+    * @return true if successful
+    */
+   @Named("user:delete")
+   @DELETE
+   @Path("/users/{id}")
+   @Consumes
+   @Fallback(FalseOnNotFoundOr404.class)
+   ListenableFuture<Boolean> delete(@PathParam("id") String id);
+
+   /**
+    * Updates an user
+    * 
+    * @return the updated user
+    */
+   @Named("user:updateuser")
+   @PUT
+   @Path("/users/{id}")
+   @SelectJson("user")
+   @Consumes(MediaType.APPLICATION_JSON)
+   @MapBinder(UpdateUserOptions.class)
+   @Fallback(NullOnNotFoundOr404.class)
+   ListenableFuture<? extends User> update(@PathParam("id") String id, UpdateUserOptions options);
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseServices.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseServices.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseServices.java
new file mode 100644
index 0000000..a40ffc8
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseServices.java
@@ -0,0 +1,95 @@
+/*
+ * 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.v2_0.functions.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.beans.ConstructorProperties;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.collect.IterableWithMarker;
+import org.jclouds.collect.internal.Arg0ToPagedIterable;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.json.Json;
+import org.jclouds.openstack.keystone.v2_0.KeystoneApi;
+import org.jclouds.openstack.keystone.v2_0.domain.Service;
+import org.jclouds.openstack.keystone.v2_0.extensions.ServiceAdminApi;
+import org.jclouds.openstack.keystone.v2_0.functions.internal.ParseServices.Services;
+import org.jclouds.openstack.v2_0.domain.Link;
+import org.jclouds.openstack.v2_0.domain.PaginatedCollection;
+import org.jclouds.openstack.v2_0.options.PaginationOptions;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.inject.TypeLiteral;
+
+/**
+ * boiler plate until we determine a better way
+ * 
+ * @author Pedro Navarro
+ */
+@Beta
+@Singleton
+public class ParseServices extends ParseJson<Services> {
+   static class Services extends PaginatedCollection<Service> {
+
+      @ConstructorProperties({ "OS-KSADM:services", "services_links" })
+      protected Services(Iterable<Service> services, Iterable<Link> services_links) {
+         super(services, services_links);
+      }
+
+   }
+
+   @Inject
+   public ParseServices(Json json) {
+      super(json, TypeLiteral.get(Services.class));
+   }
+
+   public static class ToPagedIterable extends Arg0ToPagedIterable.FromCaller<Service, ToPagedIterable> {
+
+      private final KeystoneApi api;
+
+      @Inject
+      protected ToPagedIterable(KeystoneApi api) {
+         this.api = checkNotNull(api, "api");
+      }
+
+      @Override
+      protected Function<Object, IterableWithMarker<Service>> markerToNextForArg0(Optional<Object> ignored) {
+         final ServiceAdminApi serviceApi = api.getServiceAdminApi().get();
+         return new Function<Object, IterableWithMarker<Service>>() {
+
+            @SuppressWarnings("unchecked")
+            @Override
+            public IterableWithMarker<Service> apply(Object input) {
+               PaginationOptions paginationOptions = PaginationOptions.class.cast(input);
+               return IterableWithMarker.class.cast(serviceApi.list(paginationOptions));
+            }
+
+            @Override
+            public String toString() {
+               return "listServices()";
+            }
+         };
+      }
+
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/CreateTenantOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/CreateTenantOptions.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/CreateTenantOptions.java
new file mode 100644
index 0000000..6ad805b
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/CreateTenantOptions.java
@@ -0,0 +1,144 @@
+/*
+ * 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.v2_0.options;
+
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.MapBinder;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * @author Pedro Navarro
+ */
+public class CreateTenantOptions implements MapBinder {
+   @Inject
+   private BindToJsonPayload jsonBinder;
+
+   private String description;
+   private boolean enabled;
+
+   @Override
+   public boolean equals(Object object) {
+      if (this == object) {
+         return true;
+      }
+      if (object instanceof CreateTenantOptions) {
+         final CreateTenantOptions other = CreateTenantOptions.class.cast(object);
+         return equal(description, other.description) && equal(enabled, other.enabled);
+      } else {
+         return false;
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(description, enabled);
+   }
+
+   protected ToStringHelper string() {
+      ToStringHelper toString = Objects.toStringHelper("").omitNullValues();
+      toString.add("description", description);
+      toString.add("enabled", Boolean.valueOf(enabled));
+      return toString;
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+
+   static class ServerRequest {
+      final String name;
+      String description;
+      boolean enabled;
+
+      private ServerRequest(String name) {
+         this.name = name;
+      }
+
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams) {
+      ServerRequest tenant = new ServerRequest(checkNotNull(postParams.get("name"), "name parameter not present")
+            .toString());
+      if (description != null)
+         tenant.description = description;
+      tenant.enabled = enabled;
+
+      return bindToRequest(request, ImmutableMap.of("tenant", tenant));
+   }
+
+   /**
+    * Gets the default tenant description
+    */
+   public String getDescription() {
+      return this.description;
+   }
+
+   /**
+    * A description can be defined when creating a tenant.
+    */
+   public CreateTenantOptions description(String description) {
+      this.description = description;
+      return this;
+   }
+
+   public boolean isEnabled() {
+      return this.enabled;
+   }
+
+   public CreateTenantOptions enabled(boolean enabled) {
+      this.enabled = enabled;
+      return this;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see CreateTenantOptions#description(String)
+       */
+      public static CreateTenantOptions description(String description) {
+         CreateTenantOptions options = new CreateTenantOptions();
+         return options.description(description);
+      }
+
+      /**
+       * @see CreateTenantOptions#enabled(boolean)
+       */
+      public static CreateTenantOptions enabled(boolean enabled) {
+         CreateTenantOptions options = new CreateTenantOptions();
+         return options.enabled(enabled);
+      }
+
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+      return jsonBinder.bindToRequest(request, input);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/CreateUserOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/CreateUserOptions.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/CreateUserOptions.java
new file mode 100644
index 0000000..a63291b
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/CreateUserOptions.java
@@ -0,0 +1,180 @@
+/*
+ * 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.v2_0.options;
+
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.MapBinder;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * @author Pedro Navarro
+ */
+public class CreateUserOptions implements MapBinder{
+   @Inject
+   private BindToJsonPayload jsonBinder;
+   
+   private String tenant;
+   private String password;
+   private String email;
+   private boolean enabled;
+
+   @Override
+   public boolean equals(Object object) {
+      if (this == object) {
+         return true;
+      }
+      if (object instanceof CreateUserOptions) {
+         final CreateUserOptions other = CreateUserOptions.class.cast(object);
+         return equal(tenant, other.tenant) && equal(password, other.password) && equal(email, other.email)
+               && equal(enabled, other.enabled);
+      } else {
+         return false;
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(tenant, password, email, enabled);
+   }
+
+   protected ToStringHelper string() {
+      ToStringHelper toString = Objects.toStringHelper("").omitNullValues();
+      toString.add("tenant", tenant);
+      toString.add("password", password);
+      toString.add("email", email);
+      toString.add("enabled", Boolean.valueOf(enabled));
+      return toString;
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+
+   static class ServerRequest {
+      final String name;
+      String tenant;
+      String password;
+      String email;
+      boolean enabled;
+
+      private ServerRequest(String name, String password) {
+         this.name = name;
+         this.password = password;
+      }
+
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams) {
+      ServerRequest user = new ServerRequest(checkNotNull(postParams.get("name"), "name parameter not present")
+            .toString(), checkNotNull(postParams.get("password"), "password parameter not present")
+            .toString());
+      if (email != null)
+         user.email = email;
+      if (password != null)
+         user.password = password;
+      if (tenant != null)
+         user.tenant = tenant;
+      user.enabled = enabled;
+
+      return bindToRequest(request, ImmutableMap.of("user", user));
+   }
+
+   /**
+    * Gets the default user tenant
+    */
+   public String getTenant() {
+      return this.tenant;
+   }
+
+   /**
+    * A default tenant can be defined when creating an user.
+    */
+   public CreateUserOptions tenant(String tenant) {
+      this.tenant = tenant;
+      return this;
+   }
+
+   /**
+    * Gets the user e-mail
+    */
+   public String getEmail() {
+      return this.email;
+   }
+
+   /**
+    * @see #getEmail()
+    */
+   public CreateUserOptions email(String email) {
+      this.email = email;
+      return this;
+   }
+
+   public boolean isEnabled() {
+      return this.enabled;
+   }
+
+   public CreateUserOptions enabled(boolean enabled) {
+      this.enabled = enabled;
+      return this;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see CreateUserOptions#tenant(String)
+       */
+      public static CreateUserOptions tenant(String tenant) {
+         CreateUserOptions options = new CreateUserOptions();
+         return options.tenant(tenant);
+      }
+
+      /**
+       * @see CreateUserOptions#email(String)
+       */
+      public static CreateUserOptions email(String email) {
+         CreateUserOptions options = new CreateUserOptions();
+         return options.email(email);
+      }
+
+      /**
+       * @see CreateUserOptions#enabled(boolean)
+       */
+      public static CreateUserOptions enabled(boolean enabled) {
+         CreateUserOptions options = new CreateUserOptions();
+         return options.enabled(enabled);
+      }
+
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+      return jsonBinder.bindToRequest(request, input);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/UpdateTenantOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/UpdateTenantOptions.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/UpdateTenantOptions.java
new file mode 100644
index 0000000..0f7c12a
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/UpdateTenantOptions.java
@@ -0,0 +1,159 @@
+/*
+ * 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.v2_0.options;
+
+import static com.google.common.base.Objects.equal;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.MapBinder;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * @author Pedro Navarro
+ */
+public class UpdateTenantOptions implements MapBinder {
+   @Inject
+   private BindToJsonPayload jsonBinder;
+
+   private String name;
+   private String description;
+   private boolean enabled;
+
+   @Override
+   public boolean equals(Object object) {
+      if (this == object) {
+         return true;
+      }
+      if (object instanceof UpdateTenantOptions) {
+         final UpdateTenantOptions other = UpdateTenantOptions.class.cast(object);
+         return equal(description, other.description) && equal(enabled, other.enabled) && equal(name, other.name);
+      } else {
+         return false;
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(name, description, enabled);
+   }
+
+   protected ToStringHelper string() {
+      ToStringHelper toString = Objects.toStringHelper("").omitNullValues();
+      toString.add("name", name);
+      toString.add("description", description);
+      toString.add("enabled", Boolean.valueOf(enabled));
+      return toString;
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+
+   static class ServerRequest {
+      String name;
+      String description;
+      boolean enabled;
+
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams) {
+      ServerRequest tenant = new ServerRequest();
+      if (description != null)
+         tenant.description = description;
+      if (name != null)
+         tenant.name = name;
+      tenant.enabled = enabled;
+
+      return bindToRequest(request, ImmutableMap.of("tenant", tenant));
+   }
+
+   /**
+    * Gets the default tenant description
+    */
+   public String getDescription() {
+      return this.description;
+   }
+
+   /**
+    * A description can be defined when creating a tenant.
+    */
+   public UpdateTenantOptions description(String description) {
+      this.description = description;
+      return this;
+   }
+
+   public boolean isEnabled() {
+      return this.enabled;
+   }
+
+   public UpdateTenantOptions enabled(boolean enabled) {
+      this.enabled = enabled;
+      return this;
+   }
+
+   public UpdateTenantOptions name(String name) {
+      this.name = name;
+      return this;
+   }
+
+   public String getName() {
+      return this.name;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see UpdateTenantOptions#name(String)
+       */
+      public static UpdateTenantOptions name(String name) {
+         UpdateTenantOptions options = new UpdateTenantOptions();
+         return options.name(name);
+      }
+
+      /**
+       * @see UpdateTenantOptions#description(String)
+       */
+      public static UpdateTenantOptions description(String description) {
+         UpdateTenantOptions options = new UpdateTenantOptions();
+         return options.description(description);
+      }
+
+      /**
+       * @see UpdateTenantOptions#enabled(boolean)
+       */
+      public static UpdateTenantOptions enabled(boolean enabled) {
+         UpdateTenantOptions options = new UpdateTenantOptions();
+         return options.enabled(enabled);
+      }
+
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+      return jsonBinder.bindToRequest(request, input);
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/UpdateUserOptions.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/UpdateUserOptions.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/UpdateUserOptions.java
new file mode 100644
index 0000000..8e64f36
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/options/UpdateUserOptions.java
@@ -0,0 +1,193 @@
+/*
+ * 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.v2_0.options;
+
+import static com.google.common.base.Objects.equal;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.rest.MapBinder;
+import org.jclouds.rest.binders.BindToJsonPayload;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.ImmutableMap;
+
+/**
+ * @author Pedro Navarro
+ */
+public class UpdateUserOptions implements MapBinder{
+   @Inject
+   private BindToJsonPayload jsonBinder;
+
+   private String name;
+   private String email;
+   private String password;
+   private boolean enabled;
+
+   @Override
+   public boolean equals(Object object) {
+      if (this == object) {
+         return true;
+      }
+      if (object instanceof UpdateUserOptions) {
+         final UpdateUserOptions other = UpdateUserOptions.class.cast(object);
+         return equal(name, other.name) && equal(email, other.email)
+               && equal(enabled, other.enabled) && equal(password, other.password);
+      } else {
+         return false;
+      }
+   }
+
+   @Override
+   public int hashCode() {
+      return Objects.hashCode(name, email, enabled, password);
+   }
+
+   protected ToStringHelper string() {
+      ToStringHelper toString = Objects.toStringHelper("").omitNullValues();
+      toString.add("name", name);
+      toString.add("email", email);
+      toString.add("password", password);
+      toString.add("enabled", Boolean.valueOf(enabled));
+      return toString;
+   }
+
+   @Override
+   public String toString() {
+      return string().toString();
+   }
+
+   static class ServerRequest {
+      
+      String name;
+      String email;
+      String password;
+      boolean enabled;
+
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Map<String, Object> postParams) {
+      ServerRequest user = new ServerRequest();
+      if (email != null)
+         user.email = email;
+      if (name != null)
+         user.name = name;
+      if (password != null)
+         user.password = password;
+      user.enabled = enabled;
+
+      return bindToRequest(request, ImmutableMap.of("user", user));
+   }
+
+   /**
+    * Gets the default user name
+    */
+   public String getName() {
+      return this.name;
+   }
+
+   /**
+    * A name can be defined when updating an user.
+    */
+   public UpdateUserOptions name(String name) {
+      this.name = name;
+      return this;
+   }
+
+   /**
+    * Gets the default password
+    */
+   public String getPassword() {
+      return password;
+   }
+
+   public UpdateUserOptions password(String password) {
+      this.password = password;
+      return this;
+   }
+   
+   /**
+    * Gets the user e-mail
+    */
+   public String getEmail() {
+      return this.email;
+   }
+
+   /**
+    * @see #getEmail()
+    */
+   public UpdateUserOptions email(String email) {
+      this.email = email;
+      return this;
+   }
+
+   public boolean isEnabled() {
+      return this.enabled;
+   }
+
+   public UpdateUserOptions enabled(boolean enabled) {
+      this.enabled = enabled;
+      return this;
+   }
+
+   public static class Builder {
+
+      /**
+       * @see UpdateUserOptions#name(String)
+       */
+      public static UpdateUserOptions name(String name) {
+         UpdateUserOptions options = new UpdateUserOptions();
+         return options.name(name);
+      }
+
+      /**
+       * @see UpdateUserOptions#email(String)
+       */
+      public static UpdateUserOptions email(String email) {
+         UpdateUserOptions options = new UpdateUserOptions();
+         return options.email(email);
+      }
+
+      /**
+       * @see UpdateUserOptions#enabled(boolean)
+       */
+      public static UpdateUserOptions enabled(boolean enabled) {
+         UpdateUserOptions options = new UpdateUserOptions();
+         return options.enabled(enabled);
+      }
+      
+      /**
+       * @see UpdateUserOptions#password(String)
+       */
+      public static UpdateUserOptions password(String password) {
+         UpdateUserOptions options = new UpdateUserOptions();
+         return options.password(password);
+      }
+
+   }
+
+   @Override
+   public <R extends HttpRequest> R bindToRequest(R request, Object input) {
+      return jsonBinder.bindToRequest(request, input);
+   }
+
+}


[2/3] JCLOUDS-453. Add OpenStack Keystone v2.0 OS-KSADM Admin Extension support for Keystone.

Posted by na...@apache.org.
http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminApiLiveTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminApiLiveTest.java
new file mode 100644
index 0000000..d196295
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminApiLiveTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.v2_0.extensions;
+
+import static org.jclouds.util.Predicates2.retry;
+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.v2_0.domain.Role;
+import org.jclouds.openstack.keystone.v2_0.internal.BaseKeystoneApiLiveTest;
+import org.testng.SkipException;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+
+/**
+ * Tests behavior of RoleAdminApi
+ * 
+ * @author Pedro Navarro
+ */
+@Test(groups = "live", testName = "RoleAdminApiLiveTest", singleThreaded = true)
+public class RoleAdminApiLiveTest extends BaseKeystoneApiLiveTest {
+
+   private Optional<? extends RoleAdminApi> roleAdminOption;
+
+   private Role testRole;
+
+   @BeforeClass(groups = { "integration", "live" })
+   @Override
+   public void setup() {
+      super.setup();
+      roleAdminOption = api.getRoleAdminApi();
+      if (!roleAdminOption.isPresent()) {
+         throw new SkipException("The tests are skipped since OS-KSADM extension is not exposed through the Keystone API");
+      }
+   }
+
+   @AfterClass(groups = { "integration", "live" })
+   @Override
+   protected void tearDown() {
+      if (testRole != null) {
+         final String roleId = testRole.getId();
+         boolean success = roleAdminOption.get().delete(roleId);
+         assertTrue(retry(new Predicate<RoleAdminApi>() {
+            public boolean apply(RoleAdminApi roleApi) {
+               return roleApi.get(roleId) == null;
+            }
+         }, 5 * 1000L).apply(roleAdminOption.get()));
+      }
+      super.tearDown();
+   }
+
+   public void testCreateRole() {
+      testRole = roleAdminOption.get().create("jclouds-test-role");
+      assertTrue(retry(new Predicate<RoleAdminApi>() {
+         public boolean apply(RoleAdminApi roleApi) {
+            return roleApi.get(testRole.getId()) != null;
+         }
+      }, 180 * 1000L).apply(roleAdminOption.get()));
+
+      assertEquals(roleAdminOption.get().get(testRole.getId()).getName(), "jclouds-test-role");
+   }
+
+   public void testListRoles() {
+      RoleAdminApi roleApi = roleAdminOption.get();
+      Set<? extends Role> roles = roleApi.list().toSet();
+      assertNotNull(roles);
+      assertFalse(roles.isEmpty());
+      for (Role role : roles) {
+         Role aRole = roleApi.get(role.getId());
+         assertEquals(aRole, role);
+      }
+
+   }
+
+   @Test(dependsOnMethods = { "testCreateRole" })
+   public void testGetRole() {
+
+      Role testGetRole = roleAdminOption.get().get(testRole.getId());
+      assertNotNull(testGetRole);
+      assertEquals(testGetRole.getName(), "jclouds-test-role");
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminApiMockTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminApiMockTest.java
new file mode 100644
index 0000000..1b67614
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/RoleAdminApiMockTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.v2_0.extensions;
+
+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.Set;
+
+import org.jclouds.openstack.keystone.v2_0.KeystoneApi;
+import org.jclouds.openstack.keystone.v2_0.domain.Role;
+import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+/**
+ * Tests RoleApi Guice wiring and parsing
+ * 
+ * @author Pedro Navarro
+ */
+@Test(groups = "unit", testName = "RoleAdminApiMockTest")
+public class RoleAdminApiMockTest extends BaseOpenStackMockTest<KeystoneApi> {
+
+   Set<Role> expectedRoles = ImmutableSet.of(
+         Role.builder().id("22529316b2384072b2e8946af5e8cfb6").name("admin").build(),
+         Role.builder().id("9fe2ff9ee4384b1894a90878d3e92bab").name("_member_")
+               .description("Default role for project membership").build());
+
+   public void listRoles() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(200).setBody(
+            stringFromResource("/role_list_response.json"))));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         RoleAdminApi roleAdminApi = keystoneApi.getRoleAdminApi().get();
+         FluentIterable<? extends Role> roles = roleAdminApi.list();
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateRoleRequest = server.takeRequest();
+         assertEquals(updateRoleRequest.getRequestLine(), "GET /OS-KSADM/roles HTTP/1.1");
+
+         assertEquals(roles.size(), 2);
+         assertEquals(roles.toSet(), expectedRoles);
+
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void listZeroRoles() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         RoleAdminApi roleAdminApi = keystoneApi.getRoleAdminApi().get();
+         FluentIterable<? extends Role> roles = roleAdminApi.list();
+
+         ImmutableList<? extends Role> roleList = roles.toList();
+
+         assertTrue(roleList.isEmpty());
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateRoleRequest = server.takeRequest();
+         assertEquals(updateRoleRequest.getRequestLine(), "GET /OS-KSADM/roles HTTP/1.1");
+
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void createRole() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201).setBody(
+            stringFromResource("/role_create_response.json"))));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         RoleAdminApi roleAdminApi = keystoneApi.getRoleAdminApi().get();
+         Role testRole = roleAdminApi.create("jclouds-role");
+
+         assertNotNull(testRole);
+         assertEquals(testRole.getId(), "r1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest createRoleRequest = server.takeRequest();
+         assertEquals(createRoleRequest.getRequestLine(), "POST /OS-KSADM/roles HTTP/1.1");
+         assertEquals(new String(createRoleRequest.getBody()), "{\"role\":{\"name\":\"jclouds-role\"}}");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void createRoleFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404).setBody(
+            stringFromResource("/role_create_response.json"))));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         RoleAdminApi roleAdminApi = keystoneApi.getRoleAdminApi().get();
+         Role testRole = roleAdminApi.create("jclouds-role");
+
+         assertNull(testRole);
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest createRoleRequest = server.takeRequest();
+         assertEquals(createRoleRequest.getRequestLine(), "POST /OS-KSADM/roles HTTP/1.1");
+         assertEquals(new String(createRoleRequest.getBody()), "{\"role\":{\"name\":\"jclouds-role\"}}");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void getRole() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(200).setBody(
+            stringFromResource("/role_create_response.json"))));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         RoleAdminApi roleAdminApi = keystoneApi.getRoleAdminApi().get();
+         Role role = roleAdminApi.get("r1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateRoleRequest = server.takeRequest();
+         assertEquals(updateRoleRequest.getRequestLine(), "GET /OS-KSADM/roles/r1000 HTTP/1.1");
+
+         /*
+          * Check response
+          */
+         assertEquals(role.getId(), "r1000");
+         assertEquals(role.getName(), "jclouds-role");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void getRoleFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         RoleAdminApi roleAdminApi = keystoneApi.getRoleAdminApi().get();
+         Role role = roleAdminApi.get("r1000");
+
+         assertNull(role);
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateRoleRequest = server.takeRequest();
+         assertEquals(updateRoleRequest.getRequestLine(), "GET /OS-KSADM/roles/r1000 HTTP/1.1");
+
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteRole() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(204)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         RoleAdminApi roleAdminApi = keystoneApi.getRoleAdminApi().get();
+         boolean success = roleAdminApi.delete("r1000");
+
+         assertTrue(success);
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateRoleRequest = server.takeRequest();
+         assertEquals(updateRoleRequest.getRequestLine(), "DELETE /OS-KSADM/roles/r1000 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteRoleFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         RoleAdminApi roleAdminApi = keystoneApi.getRoleAdminApi().get();
+         boolean success = roleAdminApi.delete("r1000");
+
+         assertFalse(success);
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateRoleRequest = server.takeRequest();
+         assertEquals(updateRoleRequest.getRequestLine(), "DELETE /OS-KSADM/roles/r1000 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminApiLiveTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminApiLiveTest.java
new file mode 100644
index 0000000..97d9e05
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminApiLiveTest.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.openstack.keystone.v2_0.extensions;
+
+import static org.jclouds.util.Predicates2.retry;
+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.v2_0.domain.Service;
+import org.jclouds.openstack.keystone.v2_0.internal.BaseKeystoneApiLiveTest;
+import org.testng.SkipException;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+
+/**
+ * Tests behavior of ServiceAdminApi
+ * 
+ * @author Pedro Navarro
+ */
+@Test(groups = "live", testName = "ServiceAdminApiLiveTest", singleThreaded = true)
+public class ServiceAdminApiLiveTest extends BaseKeystoneApiLiveTest {
+
+   private Optional<? extends ServiceAdminApi> serviceAdminOption;
+
+   private Service testService;
+
+   @BeforeClass(groups = { "integration", "live" })
+   @Override
+   public void setup() {
+      super.setup();
+      serviceAdminOption = api.getServiceAdminApi();
+      if (!serviceAdminOption.isPresent()) {
+         throw new SkipException("The tests are skipped since OS-KSADM extension is not exposed through the Keystone API");
+      }
+   }
+
+   @AfterClass(groups = { "integration", "live" })
+   @Override
+   protected void tearDown() {
+      if (testService != null) {
+         final String serviceId = testService.getId();
+         boolean success = serviceAdminOption.get().delete(serviceId);
+         assertTrue(retry(new Predicate<ServiceAdminApi>() {
+            public boolean apply(ServiceAdminApi serviceApi) {
+               return serviceApi.get(serviceId) == null;
+            }
+         }, 5 * 1000L).apply(serviceAdminOption.get()));
+      }
+      super.tearDown();
+   }
+
+   public void testListServices() {
+      ServiceAdminApi serviceApi = serviceAdminOption.get();
+      Set<? extends Service> services = serviceApi.list().concat().toSet();
+      assertNotNull(services);
+      assertFalse(services.isEmpty());
+      for (Service service : services) {
+         Service aService = serviceApi.get(service.getId());
+         assertEquals(aService, service);
+      }
+
+   }
+
+   @Test
+   public void testCreateService() {
+      testService = serviceAdminOption.get().create("jclouds-test-service", "jclouds-service-type",
+            "jclouds-service-description");
+      assertTrue(retry(new Predicate<ServiceAdminApi>() {
+         public boolean apply(ServiceAdminApi serviceApi) {
+            return serviceApi.get(testService.getId()) != null;
+         }
+      }, 180 * 1000L).apply(serviceAdminOption.get()));
+
+      assertEquals(serviceAdminOption.get().get(testService.getId()).getName(), "jclouds-test-service");
+   }
+
+   @Test(dependsOnMethods = { "testCreateService" })
+   public void testGetService() {
+      Service testGetService = serviceAdminOption.get().get(testService.getId());
+      assertNotNull(testGetService);
+      assertEquals(testGetService.getName(), "jclouds-test-service");
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminApiMockTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminApiMockTest.java
new file mode 100644
index 0000000..e58329d
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/ServiceAdminApiMockTest.java
@@ -0,0 +1,285 @@
+/*
+ * 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.v2_0.extensions;
+
+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.Set;
+
+import org.jclouds.collect.PagedIterable;
+import org.jclouds.openstack.keystone.v2_0.KeystoneApi;
+import org.jclouds.openstack.keystone.v2_0.domain.Service;
+import org.jclouds.openstack.v2_0.domain.PaginatedCollection;
+import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
+import org.jclouds.openstack.v2_0.options.PaginationOptions;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+/**
+ * Tests ServiceApi Guice wiring and parsing
+ * 
+ * @author Pedro Navarro
+ */
+@Test(groups = "unit", testName = "ServiceAdminApiMockTest")
+public class ServiceAdminApiMockTest extends BaseOpenStackMockTest<KeystoneApi> {
+
+   Set<Service> expectedServices = ImmutableSet.of(
+         Service.builder().name("neutron").type("network").id("150a35a1e24547fdb4122b7fc90929b0")
+               .description("Network Service").build(),
+         Service.builder().name("cinder").type("volume").id("313b229fcede4a148f5bd11199264f8e")
+               .description("OpenStack Volume Service").build());
+
+   public void listServices() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(200).setBody(
+            stringFromResource("/service_list_response.json"))));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         ServiceAdminApi serviceAdminApi = keystoneApi.getServiceAdminApi().get();
+         PagedIterable<? extends Service> services = serviceAdminApi.list();
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateServiceRequest = server.takeRequest();
+         assertEquals(updateServiceRequest.getRequestLine(), "GET /OS-KSADM/services HTTP/1.1");
+
+         assertEquals(services.concat().size(), 2);
+         assertEquals(services.concat().toSet(), expectedServices);
+
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void listZeroServices() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         ServiceAdminApi serviceAdminApi = keystoneApi.getServiceAdminApi().get();
+         PagedIterable<? extends Service> services = serviceAdminApi.list();
+
+         ImmutableList<? extends Service> servicesList = services.concat().toList();
+         assertTrue(servicesList.isEmpty());
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateServiceRequest = server.takeRequest();
+         assertEquals(updateServiceRequest.getRequestLine(), "GET /OS-KSADM/services HTTP/1.1");
+
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void listServicesPage() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(200).setBody(
+            stringFromResource("/service_list_response.json"))));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         ServiceAdminApi serviceAdminApi = keystoneApi.getServiceAdminApi().get();
+         PaginatedCollection<? extends Service> services = serviceAdminApi.list(new PaginationOptions());
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateServiceRequest = server.takeRequest();
+         assertEquals(updateServiceRequest.getRequestLine(), "GET /OS-KSADM/services HTTP/1.1");
+
+         assertEquals(services.size(), 2);
+         assertEquals(services.toSet(), expectedServices);
+
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void createService() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201).setBody(
+            stringFromResource("/service_create_response.json"))));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         ServiceAdminApi serviceAdminApi = keystoneApi.getServiceAdminApi().get();
+         Service testService = serviceAdminApi.create("jclouds-service-test", "jclouds-service-type",
+               "jclouds-service-description");
+
+         assertNotNull(testService);
+         assertEquals(testService.getId(), "s1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest createServiceRequest = server.takeRequest();
+         assertEquals(createServiceRequest.getRequestLine(), "POST /OS-KSADM/services HTTP/1.1");
+         String bodyRequest = new String(createServiceRequest.getBody());
+         assertEquals(
+               bodyRequest,
+               "{\"OS-KSADM:service\":{\"name\":\"jclouds-service-test\",\"type\":\"jclouds-service-type\",\"description\":\"jclouds-service-description\"}}");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void createServiceFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         ServiceAdminApi serviceAdminApi = keystoneApi.getServiceAdminApi().get();
+         Service testService = serviceAdminApi.create("jclouds-service-test", "jclouds-service-type",
+               "jclouds-service-description");
+
+         assertNull(testService);
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest createServiceRequest = server.takeRequest();
+         assertEquals(createServiceRequest.getRequestLine(), "POST /OS-KSADM/services HTTP/1.1");
+         String bodyRequest = new String(createServiceRequest.getBody());
+         assertEquals(
+               bodyRequest,
+               "{\"OS-KSADM:service\":{\"name\":\"jclouds-service-test\",\"type\":\"jclouds-service-type\",\"description\":\"jclouds-service-description\"}}");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void getService() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(200).setBody(
+            stringFromResource("/service_create_response.json"))));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         ServiceAdminApi serviceAdminApi = keystoneApi.getServiceAdminApi().get();
+         Service service = serviceAdminApi.get("s1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateServiceRequest = server.takeRequest();
+         assertEquals(updateServiceRequest.getRequestLine(), "GET /OS-KSADM/services/s1000 HTTP/1.1");
+
+         /*
+          * Check response
+          */
+         assertEquals(service.getId(), "s1000");
+         assertEquals(service.getName(), "jclouds-service-test");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void getServiceFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         ServiceAdminApi serviceAdminApi = keystoneApi.getServiceAdminApi().get();
+         Service service = serviceAdminApi.get("s1000");
+
+         assertNull(service);
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateServiceRequest = server.takeRequest();
+         assertEquals(updateServiceRequest.getRequestLine(), "GET /OS-KSADM/services/s1000 HTTP/1.1");
+
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteService() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(204)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         ServiceAdminApi serviceAdminApi = keystoneApi.getServiceAdminApi().get();
+         serviceAdminApi.delete("s1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateServiceRequest = server.takeRequest();
+         assertEquals(updateServiceRequest.getRequestLine(), "DELETE /OS-KSADM/services/s1000 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteServiceFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         ServiceAdminApi serviceAdminApi = keystoneApi.getServiceAdminApi().get();
+         boolean success = serviceAdminApi.delete("s1000");
+
+         assertFalse(success);
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateServiceRequest = server.takeRequest();
+         assertEquals(updateServiceRequest.getRequestLine(), "DELETE /OS-KSADM/services/s1000 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminApiLiveTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminApiLiveTest.java
new file mode 100644
index 0000000..266ca6b
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminApiLiveTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.v2_0.extensions;
+
+import static org.jclouds.util.Predicates2.retry;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.openstack.keystone.v2_0.domain.Tenant;
+import org.jclouds.openstack.keystone.v2_0.features.TenantApi;
+import org.jclouds.openstack.keystone.v2_0.internal.BaseKeystoneApiLiveTest;
+import org.jclouds.openstack.keystone.v2_0.options.CreateTenantOptions;
+import org.jclouds.openstack.keystone.v2_0.options.UpdateTenantOptions;
+import org.testng.SkipException;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+
+/**
+ * Tests behavior of TenantAdminApi
+ * 
+ * @author Pedro Navarro
+ */
+@Test(groups = "live", testName = "TenantAdminApiLiveTest", singleThreaded = true)
+public class TenantAdminApiLiveTest extends BaseKeystoneApiLiveTest {
+
+   private Optional<? extends TenantAdminApi> tenantAdminOption;
+   private Optional<? extends TenantApi> tenantApi;
+
+   private Tenant testTenant;
+
+   @BeforeClass(groups = { "integration", "live" })
+   @Override
+   public void setup() {
+      super.setup();
+      tenantAdminOption = api.getTenantAdminApi();
+      if (!tenantAdminOption.isPresent()) {
+         throw new SkipException(
+               "The tests are skipped since OS-KSADM extension is not exposed through the Keystone API");
+      }
+      tenantApi = api.getTenantApi();
+   }
+
+   @AfterClass(groups = { "integration", "live" })
+   @Override
+   protected void tearDown() {
+      if (testTenant != null) {
+         final String tenantId = testTenant.getId();
+         boolean success = tenantAdminOption.get().delete(tenantId);
+         assertTrue(retry(new Predicate<TenantApi>() {
+            public boolean apply(TenantApi tenantApi) {
+               return tenantApi.get(tenantId) == null;
+            }
+         }, 5 * 1000L).apply(tenantApi.get()));
+      }
+      super.tearDown();
+   }
+
+   public void testCreateTenant() {
+      testTenant = tenantAdminOption.get().create("jclouds-test-tenant",
+            CreateTenantOptions.Builder.enabled(true).description("jclouds-test-description"));
+      assertTrue(retry(new Predicate<TenantApi>() {
+         public boolean apply(TenantApi tenantApi) {
+            return tenantApi.get(testTenant.getId()) != null;
+         }
+      }, 180 * 1000L).apply(tenantApi.get()));
+
+      assertEquals(tenantApi.get().get(testTenant.getId()).getName(), "jclouds-test-tenant");
+      assertEquals(tenantApi.get().get(testTenant.getId()).getDescription(), "jclouds-test-description");
+      assertEquals(tenantApi.get().get(testTenant.getId()).isEnabled(), true);
+   }
+
+   public void testUpdateTenant() {
+      testTenant = tenantAdminOption.get().update(
+            testTenant.getId(),
+            UpdateTenantOptions.Builder.description("jclouds-test-description-modified").enabled(false)
+                  .name("jclouds-test-tenant-modified"));
+
+      assertEquals(tenantApi.get().get(testTenant.getId()).getName(), "jclouds-test-tenant-modified");
+      assertEquals(tenantApi.get().get(testTenant.getId()).getDescription(), "jclouds-test-description-modified");
+      assertEquals(tenantApi.get().get(testTenant.getId()).isEnabled(), false);
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminApiMockTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminApiMockTest.java
new file mode 100644
index 0000000..b6342f9
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/TenantAdminApiMockTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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.v2_0.extensions;
+
+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 org.jclouds.openstack.keystone.v2_0.KeystoneApi;
+import org.jclouds.openstack.keystone.v2_0.domain.Tenant;
+import org.jclouds.openstack.keystone.v2_0.options.CreateTenantOptions;
+import org.jclouds.openstack.keystone.v2_0.options.UpdateTenantOptions;
+import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
+import org.testng.annotations.Test;
+
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+/**
+ * Tests TenantApi Guice wiring and parsing
+ * 
+ * @author Pedro Navarro
+ */
+@Test(groups = "unit", testName = "TenantAdminApiMockTest")
+public class TenantAdminApiMockTest extends BaseOpenStackMockTest<KeystoneApi> {
+
+   public void createTenant() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201).setBody(
+            stringFromResource("/tenant_create_response.json"))));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         TenantAdminApi tenantAdminApi = keystoneApi.getTenantAdminApi().get();
+         CreateTenantOptions createTenantOptions = CreateTenantOptions.Builder.description("jclouds-description")
+               .enabled(true);
+         Tenant testTenant = tenantAdminApi.create("jclouds-tenant", createTenantOptions);
+
+         assertNotNull(testTenant);
+         assertEquals(testTenant.getId(), "t1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest createTenantRequest = server.takeRequest();
+         assertEquals(createTenantRequest.getRequestLine(), "POST /tenants HTTP/1.1");
+         assertEquals(new String(createTenantRequest.getBody()),
+               "{\"tenant\":{\"name\":\"jclouds-tenant\",\"description\":\"jclouds-description\",\"enabled\":true}}");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void createTenantFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         TenantAdminApi tenantAdminApi = keystoneApi.getTenantAdminApi().get();
+         CreateTenantOptions createTenantOptions = CreateTenantOptions.Builder.description("jclouds-description")
+               .enabled(true);
+         Tenant testTenant = tenantAdminApi.create("jclouds-tenant", createTenantOptions);
+
+         assertNull(testTenant);
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest createTenantRequest = server.takeRequest();
+         assertEquals(createTenantRequest.getRequestLine(), "POST /tenants HTTP/1.1");
+         assertEquals(new String(createTenantRequest.getBody()),
+               "{\"tenant\":{\"name\":\"jclouds-tenant\",\"description\":\"jclouds-description\",\"enabled\":true}}");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void updateTenant() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(200).setBody(
+            stringFromResource("/tenant_update_response.json"))));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         TenantAdminApi tenantAdminApi = keystoneApi.getTenantAdminApi().get();
+         UpdateTenantOptions updateTenantOptions = UpdateTenantOptions.Builder
+               .description("jclouds-description-modified").enabled(false).name("jclouds-tenant-modified");
+         Tenant updatedTenant = tenantAdminApi.update("t1000", updateTenantOptions);
+
+         assertNotNull(updatedTenant);
+         assertEquals(updatedTenant.getId(), "t1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateTenantRequest = server.takeRequest();
+         assertEquals(updateTenantRequest.getRequestLine(), "PUT /tenants/t1000 HTTP/1.1");
+         assertEquals(
+               new String(updateTenantRequest.getBody()),
+               "{\"tenant\":{\"name\":\"jclouds-tenant-modified\",\"description\":\"jclouds-description-modified\",\"enabled\":false}}");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void updateTenantFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         TenantAdminApi tenantAdminApi = keystoneApi.getTenantAdminApi().get();
+         UpdateTenantOptions updateTenantOptions = UpdateTenantOptions.Builder
+               .description("jclouds-description-modified").enabled(false).name("jclouds-tenant-modified");
+         Tenant updatedTenant = tenantAdminApi.update("t1000", updateTenantOptions);
+
+         assertNull(updatedTenant);
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateTenantRequest = server.takeRequest();
+         assertEquals(updateTenantRequest.getRequestLine(), "PUT /tenants/t1000 HTTP/1.1");
+         assertEquals(
+               new String(updateTenantRequest.getBody()),
+               "{\"tenant\":{\"name\":\"jclouds-tenant-modified\",\"description\":\"jclouds-description-modified\",\"enabled\":false}}");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteTenant() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(204)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         TenantAdminApi tenantAdminApi = keystoneApi.getTenantAdminApi().get();
+         tenantAdminApi.delete("t1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateTenantRequest = server.takeRequest();
+         assertEquals(updateTenantRequest.getRequestLine(), "DELETE /tenants/t1000 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteTenantFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         TenantAdminApi tenantAdminApi = keystoneApi.getTenantAdminApi().get();
+         boolean success = tenantAdminApi.delete("t1000");
+
+         assertFalse(success);
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateTenantRequest = server.takeRequest();
+         assertEquals(updateTenantRequest.getRequestLine(), "DELETE /tenants/t1000 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void addRoleOnTenant() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         TenantAdminApi tenantAdminApi = keystoneApi.getTenantAdminApi().get();
+         tenantAdminApi.addRoleOnTenant("u1000", "t1000", "r1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateTenantRequest = server.takeRequest();
+         assertEquals(updateTenantRequest.getRequestLine(),
+               "PUT /tenants/u1000/users/t1000/roles/OS-KSADM/r1000 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void addRoleOnTenantFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         TenantAdminApi tenantAdminApi = keystoneApi.getTenantAdminApi().get();
+         boolean success = tenantAdminApi.addRoleOnTenant("u1000", "t1000", "r1000");
+
+         assertFalse(success);
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateTenantRequest = server.takeRequest();
+         assertEquals(updateTenantRequest.getRequestLine(),
+               "PUT /tenants/u1000/users/t1000/roles/OS-KSADM/r1000 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteRoleOnTenant() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(204)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         TenantAdminApi tenantAdminApi = keystoneApi.getTenantAdminApi().get();
+         tenantAdminApi.deleteRoleOnTenant("t1000", "u1000", "r1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateTenantRequest = server.takeRequest();
+         assertEquals(updateTenantRequest.getRequestLine(),
+               "DELETE /tenants/t1000/users/u1000/roles/OS-KSADM/r1000 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteRoleOnTenantFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         TenantAdminApi tenantAdminApi = keystoneApi.getTenantAdminApi().get();
+         boolean success = tenantAdminApi.deleteRoleOnTenant("t1000", "u1000", "r1000");
+
+         assertFalse(success);
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateTenantRequest = server.takeRequest();
+         assertEquals(updateTenantRequest.getRequestLine(),
+               "DELETE /tenants/t1000/users/u1000/roles/OS-KSADM/r1000 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminApiLiveTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminApiLiveTest.java
new file mode 100644
index 0000000..63cf258
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminApiLiveTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.v2_0.extensions;
+
+import static org.jclouds.util.Predicates2.retry;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.openstack.keystone.v2_0.domain.User;
+import org.jclouds.openstack.keystone.v2_0.features.UserApi;
+import org.jclouds.openstack.keystone.v2_0.internal.BaseKeystoneApiLiveTest;
+import org.jclouds.openstack.keystone.v2_0.options.CreateUserOptions;
+import org.jclouds.openstack.keystone.v2_0.options.UpdateUserOptions;
+import org.testng.SkipException;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+
+/**
+ * Tests behavior of UserAdminApi
+ * 
+ * @author Pedro Navarro
+ */
+@Test(groups = "live", testName = "UserAdminApiLiveTest", singleThreaded = true)
+public class UserAdminApiLiveTest extends BaseKeystoneApiLiveTest {
+
+   private Optional<? extends UserAdminApi> userAdminOption;
+   private Optional<? extends UserApi> userApi;
+
+   private User testUser;
+
+   @BeforeClass(groups = { "integration", "live" })
+   @Override
+   public void setup() {
+      super.setup();
+      userAdminOption = api.getUserAdminApi();
+      if (!userAdminOption.isPresent()) {
+         throw new SkipException("The tests are skipped since OS-KSADM extension is not exposed through the Keystone API");
+      }
+      userApi = api.getUserApi();
+   }
+
+   @AfterClass(groups = { "integration", "live" })
+   @Override
+   protected void tearDown() {
+      if (testUser != null) {
+         final String userId = testUser.getId();
+         boolean success = userAdminOption.get().delete(userId);
+         assertTrue(retry(new Predicate<UserApi>() {
+            public boolean apply(UserApi userApi) {
+               return userApi.get(userId) == null;
+            }
+         }, 5 * 1000L).apply(userApi.get()));
+      }
+      super.tearDown();
+   }
+
+   @Test
+   public void testCreateUser() {
+      testUser = userAdminOption.get().create("jclouds-test-user", "jclouds-test-password",
+            CreateUserOptions.Builder.email("jclouds-test@jclouds.org").enabled(true));
+      assertTrue(retry(new Predicate<UserApi>() {
+         public boolean apply(UserApi userApi) {
+            return userApi.get(testUser.getId()) != null;
+         }
+      }, 180 * 1000L).apply(userApi.get()));
+
+      assertEquals(userApi.get().get(testUser.getId()).getName(), "jclouds-test-user");
+      assertEquals(userApi.get().get(testUser.getId()).getEmail(), "jclouds-test@jclouds.org");
+      assertEquals(userApi.get().get(testUser.getId()).isEnabled(), true);
+   }
+
+   @Test(dependsOnMethods = { "testCreateUser" })
+   public void testUpdateUser() {
+      testUser = userAdminOption.get().update(
+            testUser.getId(),
+            UpdateUserOptions.Builder.email("jclouds-test.modified@jclouds.org").enabled(false)
+                  .name("jclouds-test-user-modified").password("jclouds-test-password-modified"));
+
+      assertEquals(userApi.get().get(testUser.getId()).getName(), "jclouds-test-user-modified");
+      assertEquals(userApi.get().get(testUser.getId()).getEmail(), "jclouds-test.modified@jclouds.org");
+      assertEquals(userApi.get().get(testUser.getId()).isEnabled(), false);
+
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminApiMockTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminApiMockTest.java
new file mode 100644
index 0000000..5b6daf3
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/extensions/UserAdminApiMockTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.v2_0.extensions;
+
+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 org.jclouds.openstack.keystone.v2_0.KeystoneApi;
+import org.jclouds.openstack.keystone.v2_0.domain.User;
+import org.jclouds.openstack.keystone.v2_0.options.CreateUserOptions;
+import org.jclouds.openstack.keystone.v2_0.options.UpdateUserOptions;
+import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
+import org.testng.annotations.Test;
+
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import com.squareup.okhttp.mockwebserver.RecordedRequest;
+
+/**
+ * Tests UserApi Guice wiring and parsing
+ * 
+ * @author Pedro Navarro
+ */
+@Test(groups = "unit", testName = "UserAdminApiMockTest")
+public class UserAdminApiMockTest extends BaseOpenStackMockTest<KeystoneApi> {
+
+   public void createUser() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(201).setBody(
+            stringFromResource("/user_create_response.json"))));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         UserAdminApi userAdminApi = keystoneApi.getUserAdminApi().get();
+         CreateUserOptions createUserOptions = CreateUserOptions.Builder.email("john.smith@example.org").enabled(true);
+         User testUser = userAdminApi.create("jqsmith", "jclouds-password", createUserOptions);
+
+         assertNotNull(testUser);
+         assertEquals(testUser.getId(), "u1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest createUserRequest = server.takeRequest();
+         assertEquals(createUserRequest.getRequestLine(), "POST /users HTTP/1.1");
+         assertEquals(
+               new String(createUserRequest.getBody()),
+               "{\"user\":{\"name\":\"jqsmith\",\"password\":\"jclouds-password\",\"email\":\"john.smith@example.org\",\"enabled\":true}}");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void createUserFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         UserAdminApi userAdminApi = keystoneApi.getUserAdminApi().get();
+         CreateUserOptions createUserOptions = CreateUserOptions.Builder.email("john.smith@example.org").enabled(true);
+         User testUser = userAdminApi.create("jqsmith", "jclouds-password", createUserOptions);
+
+         assertNull(testUser);
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest createUserRequest = server.takeRequest();
+         assertEquals(createUserRequest.getRequestLine(), "POST /users HTTP/1.1");
+         assertEquals(
+               new String(createUserRequest.getBody()),
+               "{\"user\":{\"name\":\"jqsmith\",\"password\":\"jclouds-password\",\"email\":\"john.smith@example.org\",\"enabled\":true}}");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void updateUser() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(200).setBody(
+            stringFromResource("/user_update_response.json"))));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         UserAdminApi userAdminApi = keystoneApi.getUserAdminApi().get();
+         UpdateUserOptions updateUserOptions = UpdateUserOptions.Builder.email("john.smith.renamed@example.org")
+               .enabled(false).name("jqsmith-renamed").password("jclouds-password");
+         User updatedUser = userAdminApi.update("u1000", updateUserOptions);
+
+         assertNotNull(updatedUser);
+         assertEquals(updatedUser.getId(), "u1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateUserRequest = server.takeRequest();
+         assertEquals(updateUserRequest.getRequestLine(), "PUT /users/u1000 HTTP/1.1");
+         assertEquals(
+               new String(updateUserRequest.getBody()),
+               "{\"user\":{\"name\":\"jqsmith-renamed\",\"email\":\"john.smith.renamed@example.org\",\"password\":\"jclouds-password\",\"enabled\":false}}");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void updateUserFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         UserAdminApi userAdminApi = keystoneApi.getUserAdminApi().get();
+         UpdateUserOptions updateUserOptions = UpdateUserOptions.Builder.email("john.smith.renamed@example.org")
+               .enabled(false).name("jqsmith-renamed").password("jclouds-password");
+         User updatedUser = userAdminApi.update("u1000", updateUserOptions);
+
+         assertNull(updatedUser);
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateUserRequest = server.takeRequest();
+         assertEquals(updateUserRequest.getRequestLine(), "PUT /users/u1000 HTTP/1.1");
+         assertEquals(
+               new String(updateUserRequest.getBody()),
+               "{\"user\":{\"name\":\"jqsmith-renamed\",\"email\":\"john.smith.renamed@example.org\",\"password\":\"jclouds-password\",\"enabled\":false}}");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteUser() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(204)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         UserAdminApi userAdminApi = keystoneApi.getUserAdminApi().get();
+         userAdminApi.delete("u1000");
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateUserRequest = server.takeRequest();
+         assertEquals(updateUserRequest.getRequestLine(), "DELETE /users/u1000 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void deleteUserFail() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access_version_uids.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/admin_extensions.json"))));
+      server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(404)));
+
+      try {
+         KeystoneApi keystoneApi = api(server.getUrl("/").toString(), "openstack-keystone");
+         UserAdminApi userAdminApi = keystoneApi.getUserAdminApi().get();
+         boolean success = userAdminApi.delete("u1000");
+
+         assertFalse(success);
+
+         assertEquals(server.getRequestCount(), 3);
+         assertAuthentication(server);
+         assertExtensions(server);
+         RecordedRequest updateUserRequest = server.takeRequest();
+         assertEquals(updateUserRequest.getRequestLine(), "DELETE /users/u1000 HTTP/1.1");
+      } finally {
+         server.shutdown();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/ServiceApiExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/ServiceApiExpectTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/ServiceApiExpectTest.java
index 4a47e84..fcd9964 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/ServiceApiExpectTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/ServiceApiExpectTest.java
@@ -53,7 +53,8 @@ public class ServiceApiExpectTest extends BaseKeystoneRestApiExpectTest<Keystone
       assertFalse(tenants.isEmpty());
 
       Set<Tenant> expected = ImmutableSet.of(Tenant.builder().name("demo").id("05d1dc7af71646deba64cfc17b81bec0")
-               .build(), Tenant.builder().name("admin").id("7aa2e17ec29f44d193c48feaba0852cc").build());
+               .enabled(true).build(), Tenant.builder().name("admin").id("7aa2e17ec29f44d193c48feaba0852cc")
+               .enabled(true).build());
 
       assertEquals(tenants, expected);
    }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/TenantApiExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/TenantApiExpectTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/TenantApiExpectTest.java
index 51e61e4..3546222 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/TenantApiExpectTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/TenantApiExpectTest.java
@@ -48,7 +48,8 @@ public class TenantApiExpectTest extends BaseKeystoneRestApiExpectTest<KeystoneA
    }
 
    Set<Tenant> expectedTenants = ImmutableSet.of(Tenant.builder().name("demo").id("05d1dc7af71646deba64cfc17b81bec0")
-            .build(), Tenant.builder().name("admin").id("7aa2e17ec29f44d193c48feaba0852cc").build());
+            .enabled(true).build(), Tenant.builder().name("admin").id("7aa2e17ec29f44d193c48feaba0852cc").enabled(true)
+            .build());
 
    public void testListTenants() {
       TenantApi api = requestsSendResponses(
@@ -114,7 +115,8 @@ public class TenantApiExpectTest extends BaseKeystoneRestApiExpectTest<KeystoneA
                .getTenantApi().get();
       Tenant tenant = api.get("013ba41150a14830bec85ffe93353bcc");
       assertNotNull(tenant);
-      assertEquals(tenant, Tenant.builder().id("013ba41150a14830bec85ffe93353bcc").name("admin").build());
+      assertEquals(tenant, Tenant.builder().id("013ba41150a14830bec85ffe93353bcc").name("admin").enabled(true).
+            build());
    }
 
    @Test(expectedExceptions = AuthorizationException.class)
@@ -135,7 +137,8 @@ public class TenantApiExpectTest extends BaseKeystoneRestApiExpectTest<KeystoneA
                .getTenantApi().get();
       Tenant tenant = api.getByName("admin");
       assertNotNull(tenant);
-      assertEquals(tenant, Tenant.builder().id("013ba41150a14830bec85ffe93353bcc").name("admin").build());
+      assertEquals(tenant, Tenant.builder().id("013ba41150a14830bec85ffe93353bcc").name("admin").enabled(true).
+            build());
    }
 
    public void testGetTenantByNameFailNotFound() {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/TokenApiExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/TokenApiExpectTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/TokenApiExpectTest.java
index 7924677..dd6e603 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/TokenApiExpectTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/TokenApiExpectTest.java
@@ -66,7 +66,7 @@ public class TokenApiExpectTest extends BaseKeystoneRestApiExpectTest<KeystoneAp
       assertNotNull(token);
       assertEquals(token,
             Token.builder().id("167eccdc790946969ced473732e8109b").expires(dateService.iso8601SecondsDateParse("2012-04-28T12:42:50Z"))
-                  .tenant(Tenant.builder().id("4cea93f5464b4f1c921fb3e0461d72b5").name("demo").build()).build());
+                  .tenant(Tenant.builder().id("4cea93f5464b4f1c921fb3e0461d72b5").name("demo").enabled(true).build()).build());
    }
 
    public void testGetTokenFailNotFound() {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/UserApiExpectTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/UserApiExpectTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/UserApiExpectTest.java
index 59e65db..9228b9b 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/UserApiExpectTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/features/UserApiExpectTest.java
@@ -51,11 +51,14 @@ public class UserApiExpectTest extends BaseKeystoneRestApiExpectTest<KeystoneApi
    }
    
    Set<User> expectedUsers = ImmutableSet.of(
-            User.builder().name("nova").id("e021dfd758eb44a89f1c57c8ef3be8e2").build(),
-            User.builder().name("glance").id("3f6c1c9ba993495ead7d2eb2192e284f").build(),
-            User.builder().name("demo").id("667b2e1420604df8b67cd8ea57d4ee64").build(),
-            User.builder().name("admin").id("2b9b606181634ae9ac86fd95a8bc2cde").build()
-      );
+         User.builder().name("nova").id("e021dfd758eb44a89f1c57c8ef3be8e2").email("nova@example.com").enabled(true).
+         tenantId("ab1da202f5774cceb5da2aeff1f0aa87").build(),
+         User.builder().name("glance").id("3f6c1c9ba993495ead7d2eb2192e284f").email("glance@example.com").enabled(true).
+         tenantId("ab1da202f5774cceb5da2aeff1f0aa87").build(),
+         User.builder().name("demo").id("667b2e1420604df8b67cd8ea57d4ee64").email("demo@example.com").enabled(true).
+         tenantId(null).build(),
+         User.builder().name("admin").id("2b9b606181634ae9ac86fd95a8bc2cde").email("admin@example.com").enabled(true).
+         tenantId(null).build());
    
    public void testListUsers() {
       UserApi api = requestsSendResponses(
@@ -106,7 +109,8 @@ public class UserApiExpectTest extends BaseKeystoneRestApiExpectTest<KeystoneApi
             .getUserApi().get();
       User user = api.get("e021dfd758eb44a89f1c57c8ef3be8e2");
       assertNotNull(user);
-      assertEquals(user, User.builder().name("nova").id("e021dfd758eb44a89f1c57c8ef3be8e2").build());
+      assertEquals(user, User.builder().name("nova").id("e021dfd758eb44a89f1c57c8ef3be8e2").email("nova@example.com").enabled(true).
+            tenantId("ab1da202f5774cceb5da2aeff1f0aa87").build());
    }
 
    public void testGetUserFailNotFound() {
@@ -125,7 +129,8 @@ public class UserApiExpectTest extends BaseKeystoneRestApiExpectTest<KeystoneApi
             .getUserApi().get();
       User user = api.getByName("nova");
       assertNotNull(user);
-      assertEquals(user, User.builder().name("nova").id("e021dfd758eb44a89f1c57c8ef3be8e2").build());
+      assertEquals(user, User.builder().name("nova").id("e021dfd758eb44a89f1c57c8ef3be8e2").email("nova@example.com").enabled(true).
+            tenantId("ab1da202f5774cceb5da2aeff1f0aa87").build());
    }
 
    public void testGetUserByNameFailNotFound() {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseUsersTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseUsersTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseUsersTest.java
index 9e01690..d01d63b 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseUsersTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseUsersTest.java
@@ -49,10 +49,14 @@ public class ParseUsersTest {
    }.getType();
 
    Set<User> expectedUsers = ImmutableSet.of(
-         User.builder().name("nova").id("e021dfd758eb44a89f1c57c8ef3be8e2").build(),
-         User.builder().name("glance").id("3f6c1c9ba993495ead7d2eb2192e284f").build(),
-         User.builder().name("demo").id("667b2e1420604df8b67cd8ea57d4ee64").build(),
-         User.builder().name("admin").id("2b9b606181634ae9ac86fd95a8bc2cde").build());
+         User.builder().name("nova").id("e021dfd758eb44a89f1c57c8ef3be8e2").email("nova@example.com").enabled(true).
+         tenantId("ab1da202f5774cceb5da2aeff1f0aa87").build(),
+         User.builder().name("glance").id("3f6c1c9ba993495ead7d2eb2192e284f").email("glance@example.com").enabled(true).
+         tenantId("ab1da202f5774cceb5da2aeff1f0aa87").build(),
+         User.builder().name("demo").id("667b2e1420604df8b67cd8ea57d4ee64").email("demo@example.com").enabled(true).
+         tenantId(null).build(),
+         User.builder().name("admin").id("2b9b606181634ae9ac86fd95a8bc2cde").email("admin@example.com").enabled(true).
+         tenantId(null).build());
 
    public void testParseUsersInMap() throws JsonSyntaxException, IOException {
       String json = Strings2.toStringAndClose(getClass().getResourceAsStream("/user_list.json"));

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/parse/ParseAdminAccessTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/parse/ParseAdminAccessTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/parse/ParseAdminAccessTest.java
index 01468da..b05627d 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/parse/ParseAdminAccessTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/parse/ParseAdminAccessTest.java
@@ -50,7 +50,8 @@ public class ParseAdminAccessTest extends BaseItemParserTest<Access> {
                    .token(Token.builder()
                                .expires(new SimpleDateFormatDateService().iso8601SecondsDateParse("2012-08-01T13:08:52Z"))
                                .id("946b8ad1ede4422f87ab21dcba27896d")
-                               .tenant(Tenant.builder().id("2fdc88ae152948c690b97ba307acae9b").name("admin").build()).build())
+                               .tenant(Tenant.builder().id("2fdc88ae152948c690b97ba307acae9b").name("admin")
+                               .enabled(true).build()).build())
                    .user(User.builder()
                          .id("b4d134cfe3cf43ad8ba0c2fc5b5d8f91")
                          .name("admin")

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/parse/ParseRandomEndpointVersionAccessTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/parse/ParseRandomEndpointVersionAccessTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/parse/ParseRandomEndpointVersionAccessTest.java
index 44da840..96512d4 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/parse/ParseRandomEndpointVersionAccessTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/parse/ParseRandomEndpointVersionAccessTest.java
@@ -53,7 +53,7 @@ public class ParseRandomEndpointVersionAccessTest extends BaseItemParserTest<Acc
                                .tenant(Tenant.builder()
                                               //  "enabled": true,
                                              .id("82d8d2f865484776a1daf1e2245d3317")
-                                             .name("demo").build()).build())
+                                             .name("demo").enabled(true).build()).build())
                     .service(Service.builder().type("compute").name("nova")
                                     .endpoint(Endpoint.builder()
                                                       .adminURL("http://10.10.10.10:8774/v2/82d8d2f865484776a1daf1e2245d3317")

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/java/org/jclouds/openstack/v2_0/internal/BaseOpenStackMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/v2_0/internal/BaseOpenStackMockTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/v2_0/internal/BaseOpenStackMockTest.java
index ecc8b2b..dc2d660 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/v2_0/internal/BaseOpenStackMockTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/v2_0/internal/BaseOpenStackMockTest.java
@@ -150,6 +150,18 @@ public class BaseOpenStackMockTest<A extends Closeable> {
          Throwables.propagate(e);
       }
    }
+   
+   /**
+    * Ensures server received authentication request.
+    */
+   public void assertExtensions(MockWebServer server) {
+      assertTrue(server.getRequestCount() >= 1);
+      try {
+         assertEquals(server.takeRequest().getRequestLine(), "GET /extensions HTTP/1.1");
+      } catch (InterruptedException e) {
+         Throwables.propagate(e);
+      }
+   }
 
    /**
     * Ensures the request has a json header.

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/resources/admin_extensions.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/admin_extensions.json b/apis/openstack-keystone/src/test/resources/admin_extensions.json
new file mode 100644
index 0000000..65eae4a
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/admin_extensions.json
@@ -0,0 +1 @@
+{"extensions":[{"name":"OS-KSADM","alias":"OS-KSADM","namespace":"http://docs.openstack.org/identity/api/ext/OS-KSADM/v1.0","updated":"2011-08-08T00:00:00+00:00","description":"Keystone Administration Extension"}]}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/resources/role_create_response.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/role_create_response.json b/apis/openstack-keystone/src/test/resources/role_create_response.json
new file mode 100644
index 0000000..0826512
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/role_create_response.json
@@ -0,0 +1 @@
+{"role":{"id":"r1000","name":"jclouds-role"}}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/resources/role_list_response.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/role_list_response.json b/apis/openstack-keystone/src/test/resources/role_list_response.json
new file mode 100644
index 0000000..3cf9f8f
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/role_list_response.json
@@ -0,0 +1 @@
+{"roles": [{"id": "22529316b2384072b2e8946af5e8cfb6", "name": "admin"}, {"enabled": "True", "description": "Default role for project membership", "name": "_member_", "id": "9fe2ff9ee4384b1894a90878d3e92bab"}]}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/resources/service_create_response.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/service_create_response.json b/apis/openstack-keystone/src/test/resources/service_create_response.json
new file mode 100644
index 0000000..d80d5bd
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/service_create_response.json
@@ -0,0 +1 @@
+{"OS-KSADM:service": {"id": "s1000", "type": "jclouds-service-type", "name": "jclouds-service-test", "description": "jclouds-service-description"}}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b68f1b6e/apis/openstack-keystone/src/test/resources/service_list_response.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/service_list_response.json b/apis/openstack-keystone/src/test/resources/service_list_response.json
new file mode 100644
index 0000000..3d7d52e
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/service_list_response.json
@@ -0,0 +1 @@
+{"OS-KSADM:services": [{"id": "150a35a1e24547fdb4122b7fc90929b0", "type": "network", "name": "neutron", "description": "Network Service"}, {"id": "313b229fcede4a148f5bd11199264f8e", "type": "volume", "name": "cinder", "description": "OpenStack Volume Service"}]}
\ No newline at end of file