You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2018/01/11 15:41:24 UTC
[41/50] [abbrv] jclouds git commit: Completed the UserApi
Completed the UserApi
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/fa11e377
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/fa11e377
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/fa11e377
Branch: refs/heads/keystonev3
Commit: fa11e377869f471510dd22a5a916207d9f83dfdb
Parents: 98a8584
Author: Ignasi Barrera <na...@apache.org>
Authored: Thu Jan 11 15:57:07 2018 +0100
Committer: Ignasi Barrera <na...@apache.org>
Committed: Thu Jan 11 16:21:31 2018 +0100
----------------------------------------------------------------------
.../openstack/keystone/v3/KeystoneApi.java | 7 +
.../openstack/keystone/v3/domain/Group.java | 56 +++++++
.../openstack/keystone/v3/features/UserApi.java | 73 +++++++-
.../keystone/v3/features/RegionApiMockTest.java | 2 +-
.../keystone/v3/features/UserApiLiveTest.java | 88 ++++++++++
.../keystone/v3/features/UserApiMockTest.java | 168 +++++++++++++++++++
.../src/test/resources/v3/groups.json | 27 +++
.../src/test/resources/v3/user.json | 13 ++
.../src/test/resources/v3/users.json | 132 +++++++++++++++
9 files changed, 559 insertions(+), 7 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jclouds/blob/fa11e377/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/KeystoneApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/KeystoneApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/KeystoneApi.java
index d4c2176..05cd91d 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/KeystoneApi.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/KeystoneApi.java
@@ -22,6 +22,7 @@ import org.jclouds.openstack.keystone.v3.features.AuthApi;
import org.jclouds.openstack.keystone.v3.features.CatalogApi;
import org.jclouds.openstack.keystone.v3.features.ProjectApi;
import org.jclouds.openstack.keystone.v3.features.RegionApi;
+import org.jclouds.openstack.keystone.v3.features.UserApi;
import org.jclouds.rest.annotations.Delegate;
/**
@@ -52,4 +53,10 @@ public interface KeystoneApi extends Closeable {
*/
@Delegate
ProjectApi getProjectApi();
+
+ /**
+ * Provides access to user features.
+ */
+ @Delegate
+ UserApi getUserApi();
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/fa11e377/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Group.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Group.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Group.java
new file mode 100644
index 0000000..ee2738a
--- /dev/null
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/domain/Group.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.openstack.keystone.v3.domain;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+
+@AutoValue
+public abstract class Group {
+
+ public abstract String id();
+ public abstract String name();
+ @Nullable public abstract String description();
+ @Nullable public abstract String domainId();
+ @Nullable public abstract Link link();
+
+ @SerializedNames({ "id", "name", "description", "domain_id", "links" })
+ public static Group create(String id, String name, String description, String domainId, Link link) {
+ return builder().id(id).name(name).description(description).domainId(domainId).link(link).build();
+ }
+
+ Group() {
+ }
+
+ public abstract Builder toBuilder();
+
+ public static Builder builder() {
+ return new AutoValue_Group.Builder();
+ }
+
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract Builder id(String id);
+ public abstract Builder name(String String);
+ public abstract Builder description(String description);
+ public abstract Builder domainId(String domainId);
+ public abstract Builder link(Link link);
+ public abstract Group build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/fa11e377/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/features/UserApi.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/features/UserApi.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/features/UserApi.java
index 1bb884d..1617b51 100644
--- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/features/UserApi.java
+++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v3/features/UserApi.java
@@ -20,31 +20,92 @@ import java.util.List;
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.core.MediaType;
import org.jclouds.Fallbacks.EmptyListOnNotFoundOr404;
+import org.jclouds.Fallbacks.FalseOnNotFoundOr404;
+import org.jclouds.Fallbacks.NullOnNotFoundOr404;
+import org.jclouds.javax.annotation.Nullable;
import org.jclouds.openstack.keystone.auth.filters.AuthenticateRequest;
-import org.jclouds.openstack.keystone.v3.domain.Region;
+import org.jclouds.openstack.keystone.v3.domain.Group;
+import org.jclouds.openstack.keystone.v3.domain.Project;
+import org.jclouds.openstack.keystone.v3.domain.User;
import org.jclouds.openstack.v2_0.services.Identity;
import org.jclouds.rest.annotations.Endpoint;
import org.jclouds.rest.annotations.Fallback;
+import org.jclouds.rest.annotations.PATCH;
+import org.jclouds.rest.annotations.PayloadParam;
import org.jclouds.rest.annotations.RequestFilters;
import org.jclouds.rest.annotations.SelectJson;
+import org.jclouds.rest.annotations.WrapWith;
/**
- * Provides access to the Keystone Region API.
+ * Provides access to the Keystone User API.
*/
@Consumes(MediaType.APPLICATION_JSON)
@RequestFilters(AuthenticateRequest.class)
@Endpoint(Identity.class)
-@Path("/regions")
+@Path("/users")
public interface UserApi {
- @Named("regions:list")
+ @Named("users:list")
@GET
- @SelectJson("regions")
+ @SelectJson("users")
@Fallback(EmptyListOnNotFoundOr404.class)
- List<Region> list();
+ List<User> list();
+
+ @Named("users:get")
+ @GET
+ @Path("/{id}")
+ @SelectJson("user")
+ @Fallback(NullOnNotFoundOr404.class)
+ User get(@PathParam("id") String id);
+
+ @Named("users:create")
+ @POST
+ @SelectJson("user")
+ @WrapWith("user")
+ User create(@PayloadParam("name") String name, @Nullable @PayloadParam("password") String password,
+ @Nullable @PayloadParam("enabled") Boolean enabled, @Nullable @PayloadParam("domain_id") String domainId,
+ @Nullable @PayloadParam("default_project_id") String defaultProjectId);
+
+ @Named("users:update")
+ @PATCH
+ @Path("/{id}")
+ @SelectJson("user")
+ @WrapWith("user")
+ User update(@PathParam("id") String id, @PayloadParam("name") String name,
+ @Nullable @PayloadParam("password") String password, @Nullable @PayloadParam("enabled") Boolean enabled,
+ @Nullable @PayloadParam("domain_id") String domainId,
+ @Nullable @PayloadParam("default_project_id") String defaultProjectId);
+
+ @Named("users:delete")
+ @DELETE
+ @Path("/{id}")
+ @Fallback(FalseOnNotFoundOr404.class)
+ boolean delete(@PathParam("id") String id);
+
+ @Named("users:groups")
+ @GET
+ @Path("/{id}/groups")
+ @SelectJson("groups")
+ List<Group> listGroups(@PathParam("id") String id);
+
+ @Named("users:projects")
+ @GET
+ @Path("/{id}/projects")
+ @SelectJson("projects")
+ List<Project> listProjects(@PathParam("id") String id);
+
+ @Named("users:password")
+ @POST
+ @Path("/{id}/password")
+ @WrapWith("user")
+ void changePassword(@PathParam("id") String id, @PayloadParam("original_password") String originalPassword,
+ @PayloadParam("password") String newPassword);
}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/fa11e377/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiMockTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiMockTest.java
index 2e68246..bc7b1e0 100644
--- a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiMockTest.java
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/RegionApiMockTest.java
@@ -43,7 +43,7 @@ public class RegionApiMockTest extends BaseV3KeystoneApiMockTest {
assertSent(server, "GET", "/regions");
}
- public void testListEndpointsReturns404() throws InterruptedException {
+ public void testListRegionsReturns404() throws InterruptedException {
enqueueAuthentication(server);
server.enqueue(response404());
http://git-wip-us.apache.org/repos/asf/jclouds/blob/fa11e377/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/UserApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/UserApiLiveTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/UserApiLiveTest.java
new file mode 100644
index 0000000..addcf39
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/UserApiLiveTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.openstack.keystone.v3.features;
+
+import static com.google.common.collect.Iterables.any;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.openstack.keystone.v3.domain.User;
+import org.jclouds.openstack.keystone.v3.internal.BaseV3KeystoneApiLiveTest;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.base.Predicate;
+
+@Test(groups = "live", testName = "UserApiLiveTest", singleThreaded = true)
+public class UserApiLiveTest extends BaseV3KeystoneApiLiveTest {
+
+ private User user;
+
+ @BeforeClass
+ public void createTestUser() {
+ user = api().create(getClass().getSimpleName(), "p4ssw0rd", true, null, null);
+ assertNotNull(user);
+ }
+
+ @Test
+ public void testListUsers() {
+ assertTrue(any(api().list(), new Predicate<User>() {
+ @Override
+ public boolean apply(User input) {
+ return input.id().equals(user.id());
+ }
+ }));
+ }
+
+ @Test
+ public void testGetUser() {
+ assertNotNull(api().get(user.id()));
+ }
+
+ @Test
+ public void testUpdateUser() {
+ api().update(user.id(), "Updated", null, null, null, null);
+ user = api().get(user.id());
+ assertEquals(user.name(), "Updated");
+ }
+
+ @Test
+ public void testListGroups() {
+ assertNotNull(api().listGroups(user.id()));
+ }
+
+ @Test
+ public void testListProjects() {
+ assertNotNull(api().listProjects(user.id()));
+ }
+
+ @Test
+ public void testChangePassword() {
+ api().changePassword(user.id(), "p4ssw0rd", "Newp4ssw0rd");
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void deleteUser() {
+ assertTrue(api().delete(user.id()));
+ }
+
+ private UserApi api() {
+ return api.getUserApi();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/fa11e377/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/UserApiMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/UserApiMockTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/UserApiMockTest.java
new file mode 100644
index 0000000..f31ae62
--- /dev/null
+++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v3/features/UserApiMockTest.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.openstack.keystone.v3.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+
+import org.jclouds.openstack.keystone.v3.domain.Group;
+import org.jclouds.openstack.keystone.v3.domain.Project;
+import org.jclouds.openstack.keystone.v3.domain.User;
+import org.jclouds.openstack.keystone.v3.internal.BaseV3KeystoneApiMockTest;
+import org.testng.annotations.Test;
+
+@Test(groups = "unit", testName = "UserApiMockTest", singleThreaded = true)
+public class UserApiMockTest extends BaseV3KeystoneApiMockTest {
+
+ public void testListUsers() throws InterruptedException {
+ enqueueAuthentication(server);
+ server.enqueue(jsonResponse("/v3/users.json"));
+
+ List<User> users = api.getUserApi().list();
+ assertFalse(users.isEmpty());
+
+ assertEquals(server.getRequestCount(), 2);
+ assertAuthentication(server);
+ assertSent(server, "GET", "/users");
+ }
+
+ public void testListUsersReturns404() throws InterruptedException {
+ enqueueAuthentication(server);
+ server.enqueue(response404());
+
+ List<User> users = api.getUserApi().list();
+ assertTrue(users.isEmpty());
+
+ assertEquals(server.getRequestCount(), 2);
+ assertAuthentication(server);
+ assertSent(server, "GET", "/users");
+ }
+
+ public void testGetUser() throws InterruptedException {
+ enqueueAuthentication(server);
+ server.enqueue(jsonResponse("/v3/user.json"));
+
+ User user = api.getUserApi().get("0bedc61110fd4e94a251260a47f18f29");
+ assertNotNull(user);
+
+ assertEquals(server.getRequestCount(), 2);
+ assertAuthentication(server);
+ assertSent(server, "GET", "/users/0bedc61110fd4e94a251260a47f18f29");
+ }
+
+ public void testGetUserReturns404() throws InterruptedException {
+ enqueueAuthentication(server);
+ server.enqueue(response404());
+
+ User user = api.getUserApi().get("0bedc61110fd4e94a251260a47f18f29");
+ assertNull(user);
+
+ assertEquals(server.getRequestCount(), 2);
+ assertAuthentication(server);
+ assertSent(server, "GET", "/users/0bedc61110fd4e94a251260a47f18f29");
+ }
+
+ public void testCreateUser() throws InterruptedException {
+ enqueueAuthentication(server);
+ server.enqueue(jsonResponse("/v3/user.json"));
+
+ User user = api.getUserApi().create("user", "p4ssw0rd", true, "123", "789");
+ assertNotNull(user);
+
+ assertEquals(server.getRequestCount(), 2);
+ assertAuthentication(server);
+ assertSent(server, "POST", "/users", "{\"user\":{\"name\":\"user\",\"password\":\"p4ssw0rd\",\"enabled\":true,"
+ + "\"domain_id\":\"123\",\"default_project_id\":\"789\"}}");
+ }
+
+ public void testUpdateUser() throws InterruptedException {
+ enqueueAuthentication(server);
+ server.enqueue(jsonResponse("/v3/user.json"));
+
+ User user = api.getUserApi().update("0bedc61110fd4e94a251260a47f18f29", "foo", null, null, null, null);
+ assertNotNull(user);
+
+ assertEquals(server.getRequestCount(), 2);
+ assertAuthentication(server);
+ assertSent(server, "PATCH", "/users/0bedc61110fd4e94a251260a47f18f29", "{\"user\":{\"name\":\"foo\"}}");
+ }
+
+ public void testDeleteUser() throws InterruptedException {
+ enqueueAuthentication(server);
+ server.enqueue(response204());
+
+ boolean deleted = api.getUserApi().delete("0bedc61110fd4e94a251260a47f18f29");
+ assertTrue(deleted);
+
+ assertEquals(server.getRequestCount(), 2);
+ assertAuthentication(server);
+ assertSent(server, "DELETE", "/users/0bedc61110fd4e94a251260a47f18f29");
+ }
+
+ public void testDeleteUserReturns404() throws InterruptedException {
+ enqueueAuthentication(server);
+ server.enqueue(response404());
+
+ boolean deleted = api.getUserApi().delete("0bedc61110fd4e94a251260a47f18f29");
+ assertFalse(deleted);
+
+ assertEquals(server.getRequestCount(), 2);
+ assertAuthentication(server);
+ assertSent(server, "DELETE", "/users/0bedc61110fd4e94a251260a47f18f29");
+ }
+
+ public void testListGroups() throws InterruptedException {
+ enqueueAuthentication(server);
+ server.enqueue(jsonResponse("/v3/groups.json"));
+
+ List<Group> groups = api.getUserApi().listGroups("0bedc61110fd4e94a251260a47f18f29");
+ assertFalse(groups.isEmpty());
+
+ assertEquals(server.getRequestCount(), 2);
+ assertAuthentication(server);
+ assertSent(server, "GET", "/users/0bedc61110fd4e94a251260a47f18f29/groups");
+ }
+
+ public void testListProjects() throws InterruptedException {
+ enqueueAuthentication(server);
+ server.enqueue(jsonResponse("/v3/projects.json"));
+
+ List<Project> projects = api.getUserApi().listProjects("0bedc61110fd4e94a251260a47f18f29");
+ assertFalse(projects.isEmpty());
+
+ assertEquals(server.getRequestCount(), 2);
+ assertAuthentication(server);
+ assertSent(server, "GET", "/users/0bedc61110fd4e94a251260a47f18f29/projects");
+ }
+
+ public void testChangePassword() throws InterruptedException {
+ enqueueAuthentication(server);
+ server.enqueue(response204());
+
+ api.getUserApi().changePassword("0bedc61110fd4e94a251260a47f18f29", "foo", "bar");
+
+ assertEquals(server.getRequestCount(), 2);
+ assertAuthentication(server);
+ assertSent(server, "POST", "/users/0bedc61110fd4e94a251260a47f18f29/password",
+ "{\"user\":{\"original_password\":\"foo\",\"password\":\"bar\"}}");
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/fa11e377/apis/openstack-keystone/src/test/resources/v3/groups.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/v3/groups.json b/apis/openstack-keystone/src/test/resources/v3/groups.json
new file mode 100644
index 0000000..17f061c
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/v3/groups.json
@@ -0,0 +1,27 @@
+{
+ "groups": [
+ {
+ "description": "Developers cleared for work on all general projects",
+ "domain_id": "1789d1",
+ "id": "ea167b",
+ "links": {
+ "self": "https://example.com/identity/v3/groups/ea167b"
+ },
+ "name": "Developers"
+ },
+ {
+ "description": "Developers cleared for work on secret projects",
+ "domain_id": "1789d1",
+ "id": "a62db1",
+ "links": {
+ "self": "https://example.com/identity/v3/groups/a62db1"
+ },
+ "name": "Secure Developers"
+ }
+ ],
+ "links": {
+ "self": "http://example.com/identity/v3/users/9fe1d3/groups",
+ "previous": null,
+ "next": null
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jclouds/blob/fa11e377/apis/openstack-keystone/src/test/resources/v3/user.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/v3/user.json b/apis/openstack-keystone/src/test/resources/v3/user.json
new file mode 100644
index 0000000..1c64db9
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/v3/user.json
@@ -0,0 +1,13 @@
+{
+ "user": {
+ "password_expires_at": null,
+ "links": {
+ "self": "http://localhost/identity/v3/users/0bedc61110fd4e94a251260a47f18f29"
+ },
+ "enabled": true,
+ "id": "0bedc61110fd4e94a251260a47f18f29",
+ "options": {},
+ "domain_id": "default",
+ "name": "User"
+ }
+}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/fa11e377/apis/openstack-keystone/src/test/resources/v3/users.json
----------------------------------------------------------------------
diff --git a/apis/openstack-keystone/src/test/resources/v3/users.json b/apis/openstack-keystone/src/test/resources/v3/users.json
new file mode 100644
index 0000000..4830220
--- /dev/null
+++ b/apis/openstack-keystone/src/test/resources/v3/users.json
@@ -0,0 +1,132 @@
+{
+ "users": [
+ {
+ "password_expires_at": null,
+ "name": "UserApiLiveTest",
+ "links": {
+ "self": "http://localhost/identity/v3/users/0bedc61110fd4e94a251260a47f18f29"
+ },
+ "domain_id": "default",
+ "enabled": true,
+ "id": "0bedc61110fd4e94a251260a47f18f29",
+ "options": {}
+ },
+ {
+ "password_expires_at": null,
+ "name": "glance-swift",
+ "links": {
+ "self": "http://localhost/identity/v3/users/1194933d3f1147a3b0824848bb7ec5e2"
+ },
+ "domain_id": "default",
+ "enabled": true,
+ "id": "1194933d3f1147a3b0824848bb7ec5e2",
+ "options": {}
+ },
+ {
+ "password_expires_at": null,
+ "name": "nova",
+ "links": {
+ "self": "http://localhost/identity/v3/users/290999fc41fb48e397feef465d014fb6"
+ },
+ "domain_id": "default",
+ "enabled": true,
+ "id": "290999fc41fb48e397feef465d014fb6",
+ "options": {}
+ },
+ {
+ "password_expires_at": null,
+ "name": "placement",
+ "links": {
+ "self": "http://localhost/identity/v3/users/34e6ac0dd9bb4601bac4971785abd7f5"
+ },
+ "domain_id": "default",
+ "enabled": true,
+ "id": "34e6ac0dd9bb4601bac4971785abd7f5",
+ "options": {}
+ },
+ {
+ "name": "jclouds",
+ "links": {
+ "self": "http://localhost/identity/v3/users/6c3b325a28264f00865b38442429fd77"
+ },
+ "domain_id": "default",
+ "enabled": true,
+ "options": {},
+ "default_project_id": "43de288ea0ce4d2b8b811055b10f156b",
+ "id": "6c3b325a28264f00865b38442429fd77",
+ "password_expires_at": null
+ },
+ {
+ "password_expires_at": null,
+ "name": "cinder",
+ "links": {
+ "self": "http://localhost/identity/v3/users/6e705bff20794de5955acf0936f02b3f"
+ },
+ "domain_id": "default",
+ "enabled": true,
+ "id": "6e705bff20794de5955acf0936f02b3f",
+ "options": {}
+ },
+ {
+ "name": "demo",
+ "links": {
+ "self": "http://localhost/identity/v3/users/84910c7070144530a6b9627fe0e1743f"
+ },
+ "domain_id": "default",
+ "enabled": true,
+ "options": {},
+ "id": "84910c7070144530a6b9627fe0e1743f",
+ "email": "demo@example.com",
+ "password_expires_at": null
+ },
+ {
+ "password_expires_at": null,
+ "name": "swift",
+ "links": {
+ "self": "http://localhost/identity/v3/users/87306cb2f0954ca8919f0fbaf29a780f"
+ },
+ "domain_id": "default",
+ "enabled": true,
+ "id": "87306cb2f0954ca8919f0fbaf29a780f",
+ "options": {}
+ },
+ {
+ "password_expires_at": null,
+ "name": "glance",
+ "links": {
+ "self": "http://localhost/identity/v3/users/94c9f0f5e056489ebfef25870e8944fe"
+ },
+ "domain_id": "default",
+ "enabled": true,
+ "id": "94c9f0f5e056489ebfef25870e8944fe",
+ "options": {}
+ },
+ {
+ "password_expires_at": null,
+ "name": "admin",
+ "links": {
+ "self": "http://localhost/identity/v3/users/ab7bd6c2dd394fce8318e7562115d3f8"
+ },
+ "domain_id": "default",
+ "enabled": true,
+ "id": "ab7bd6c2dd394fce8318e7562115d3f8",
+ "options": {}
+ },
+ {
+ "password_expires_at": null,
+ "name": "neutron",
+ "links": {
+ "self": "http://localhost/identity/v3/users/d3607e141e334823978eec2e1ccca8de"
+ },
+ "domain_id": "default",
+ "enabled": true,
+ "id": "d3607e141e334823978eec2e1ccca8de",
+ "options": {}
+ }
+ ],
+ "links": {
+ "self": "http://localhost/identity/v3/users",
+ "previous": null,
+ "next": null
+ }
+}