You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by ev...@apache.org on 2014/04/29 21:56:56 UTC

git commit: ServerPredicates to make waiting easier.

Repository: jclouds
Updated Branches:
  refs/heads/1.7.x 25c37fc8c -> 65e4ad201


ServerPredicates to make waiting easier.


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

Branch: refs/heads/1.7.x
Commit: 65e4ad2012ae43d6b805e0127d8b0ab517dccbd0
Parents: 25c37fc
Author: Everett Toews <ev...@rackspace.com>
Authored: Fri Apr 25 17:01:58 2014 -0500
Committer: Everett Toews <ev...@rackspace.com>
Committed: Tue Apr 29 14:56:42 2014 -0500

----------------------------------------------------------------------
 apis/openstack-nova/pom.xml                     |  12 +
 .../nova/v2_0/predicates/ServerPredicates.java  | 118 ++++++++++
 .../nova/v2_0/features/ServerApiLiveTest.java   |  45 ++--
 .../nova/v2_0/internal/BaseNovaApiLiveTest.java |  22 +-
 .../predicates/ServerPredicatesMockTest.java    | 101 ++++++++
 .../src/test/resources/access.json              | 228 +++++++++++++++++++
 6 files changed, 498 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/65e4ad20/apis/openstack-nova/pom.xml
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/pom.xml b/apis/openstack-nova/pom.xml
index cdda298..e7731c0 100644
--- a/apis/openstack-nova/pom.xml
+++ b/apis/openstack-nova/pom.xml
@@ -101,6 +101,18 @@
       <artifactId>logback-classic</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>com.squareup.okhttp</groupId>
+      <artifactId>mockwebserver</artifactId>
+      <scope>test</scope>
+      <exclusions>
+        <!-- provided by the jclouds-bouncycastle driver -->
+        <exclusion>
+          <groupId>org.bouncycastle</groupId>
+          <artifactId>bcprov-jdk15on</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
   </dependencies>
   
   <profiles>

http://git-wip-us.apache.org/repos/asf/jclouds/blob/65e4ad20/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/ServerPredicates.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/ServerPredicates.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/ServerPredicates.java
new file mode 100644
index 0000000..47812eb
--- /dev/null
+++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/predicates/ServerPredicates.java
@@ -0,0 +1,118 @@
+/*
+ * 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.nova.v2_0.predicates;
+
+import com.google.common.base.Predicate;
+import org.jclouds.openstack.nova.v2_0.domain.Server;
+import org.jclouds.openstack.nova.v2_0.domain.ServerCreated;
+import org.jclouds.openstack.nova.v2_0.features.ServerApi;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.jclouds.openstack.nova.v2_0.domain.Server.Status;
+import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.ACTIVE;
+import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.SHUTOFF;
+import static org.jclouds.util.Predicates2.retry;
+
+/**
+ * This class tests to see if a Server or ServerCreated has reached a desired status. This class is most useful when
+ * paired with a RetryablePredicate as in the code below. Together these classes can be used to block execution until
+ * the Server or ServerCreated has reached that desired status. This is useful when your Server needs to be 100% ready
+ * before you can continue with execution.
+ * <p/>
+ * For example, you can use the factory methods like so.
+ * <p/>
+ * <pre>
+ * {@code
+ * ServerCreated serverCreated = serverApi.create("my-server", image.getId(), flavor.getId());
+ *
+ * if (!ServerPredicates.awaitActive(serverApi).apply(serverCreated.getId())) {
+ *     throw new TimeoutException("Timeout on server: " + serverCreated);
+ * }
+ * </pre>
+ *
+ * <pre>
+ * {@code
+ * if (!ServerPredicates.awaitStatus(serverApi, ACTIVE, 300, 2).apply(server.getId())) {
+ *   throw new TimeoutException("Timeout on server: " + serverCreated);
+ * }
+ * </pre>
+ */
+public class ServerPredicates {
+   private static final int TEN_MINUTES = 600;
+   private static final int FIVE_SECONDS = 5;
+
+   /**
+    * Waits until a Server is ACTIVE.
+    * 
+    * @param serverApi The ServerApi in the zone where your Server resides.
+    * @return Predicate that will check the status every 5 seconds for a maximum of 10 minutes.
+    */
+   public static Predicate<String> awaitActive(ServerApi serverApi) {
+      return awaitStatus(serverApi, ACTIVE, TEN_MINUTES, FIVE_SECONDS);
+   }
+   
+   /**
+    * Waits until a Server is SHUTOFF.
+    * 
+    * @param serverApi The ServerApi in the zone where your Server resides.
+    * @return Predicate that will check the status every 5 seconds for a maximum of 10 minutes.
+    */
+   public static Predicate<String> awaitShutoff(ServerApi serverApi) {
+      return awaitStatus(serverApi, SHUTOFF, TEN_MINUTES, FIVE_SECONDS);
+   }
+
+   /**
+    * Waits until a Server reaches Status.
+    *
+    * @param serverApi The ServerApi in the zone where your Server resides.
+    * @return Predicate that will check the status every periodInSec seconds for a maximum of maxWaitInSec minutes.
+    */
+   public static Predicate<String> awaitStatus(
+           ServerApi serverApi, Status status, long maxWaitInSec, long periodInSec) {
+      ServerStatusPredicate statusPredicate = new ServerStatusPredicate(serverApi, status);
+
+      return retry(statusPredicate, maxWaitInSec, periodInSec, periodInSec, SECONDS);
+   }
+   
+   public static class ServerStatusPredicate implements Predicate<String> {
+      private final ServerApi serverApi;
+      private final Status status;
+
+      public ServerStatusPredicate(ServerApi serverApi, Status status) {
+         this.serverApi = checkNotNull(serverApi, "serverApi must be defined");
+         this.status = checkNotNull(status, "status must be defined");
+      }
+
+      /**
+       * @return boolean Return true when the Server reaches the Status, false otherwise
+       * @throws IllegalStateException if the Server associated with serverId does not exist
+       */
+      @Override
+      public boolean apply(String serverId) {
+         checkNotNull(serverId, "server must be defined");
+
+         Server server = serverApi.get(serverId);
+
+         if (server == null) {
+            throw new IllegalStateException(String.format("Server %s not found.", serverId));
+         }
+
+         return status.equals(server.getStatus());
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/65e4ad20/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiLiveTest.java
index 3553d18..7e406d1 100644
--- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiLiveTest.java
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/features/ServerApiLiveTest.java
@@ -16,11 +16,14 @@
  */
 package org.jclouds.openstack.nova.v2_0.features;
 
+import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.ACTIVE;
+import static org.jclouds.openstack.nova.v2_0.predicates.ServerPredicates.awaitActive;
 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 org.jclouds.http.HttpResponseException;
 import org.jclouds.openstack.nova.v2_0.domain.Network;
 import org.jclouds.openstack.nova.v2_0.domain.Server;
 import org.jclouds.openstack.nova.v2_0.domain.ServerCreated;
@@ -84,9 +87,9 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
       for (String zoneId : zones) {
          ServerApi serverApi = api.getServerApiForZone(zoneId);
          try {
-            serverId = createServer(zoneId, "nova", Server.Status.ACTIVE).getId();
+            serverId = createServer(zoneId, "nova").getId();
             Server server = serverApi.get(serverId);
-            assertEquals(server.getStatus(), Server.Status.ACTIVE);
+            assertEquals(server.getStatus(), ACTIVE);
          } finally {
             serverApi.delete(serverId);
          }
@@ -112,9 +115,10 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
             ServerCreated server = serverApi.create(hostName, imageIdForZone(zoneId), "1", options);
             serverId = server.getId();
 
-            blockUntilServerInState(server.getId(), serverApi, Server.Status.ACTIVE);
+            awaitActive(serverApi).apply(server.getId());
+
             Server serverCheck = serverApi.get(serverId);
-            assertEquals(serverCheck.getStatus(), Server.Status.ACTIVE);
+            assertEquals(serverCheck.getStatus(), ACTIVE);
          } finally {
             if (serverId != null) {
                serverApi.delete(serverId);
@@ -129,9 +133,12 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
       for (String zoneId : zones) {
          ServerApi serverApi = api.getServerApiForZone(zoneId);
          try {
-            serverId = createServer(zoneId, "err", Server.Status.ERROR).getId();
-            Server server = serverApi.get(serverId);
-            assertEquals(server.getStatus(), Server.Status.ERROR);
+             serverId = createServer(zoneId, "err").getId();
+         } catch (HttpResponseException e) {
+            // Here is an implementation detail difference between OpenStack and some providers.
+            // Some providers accept a bad availability zone and create the server in the zoneId.
+            // Vanilla OpenStack will error out with a 400 Bad Request
+            assertEquals(e.getResponse().getStatusCode(), 400);
          } finally {
             serverApi.delete(serverId);
          }
@@ -146,11 +153,11 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
       for (String zoneId : zones) {
          ServerApi serverApi = api.getServerApiForZone(zoneId);
          try {
-            serverId = createServer(zoneId, Server.Status.ACTIVE).getId();
+            serverId = createServer(zoneId, null).getId();
 
             Server server = serverApi.get(serverId);
 
-            assertEquals(server.getStatus(), Server.Status.ACTIVE);
+            assertEquals(server.getStatus(), ACTIVE);
 
             RebuildServerOptions options = new RebuildServerOptions().
                   withImage(server.getImage().getId()).
@@ -173,22 +180,18 @@ public class ServerApiLiveTest extends BaseNovaApiLiveTest {
       }
    }
 
-   private Server createServer(String regionId, Server.Status serverStatus) {
-      ServerApi serverApi = api.getServerApiForZone(regionId);
+   private Server createServer(String zoneId, String availabilityZoneId) {
+      ServerApi serverApi = api.getServerApiForZone(zoneId);
+
       CreateServerOptions options = new CreateServerOptions();
-      ServerCreated server = serverApi.create(hostName, imageIdForZone(regionId), flavorRefForZone(regionId), options);
+      if (availabilityZoneId != null) {
+          options = options.availabilityZone(availabilityZoneId);
+      }
 
-      blockUntilServerInState(server.getId(), serverApi, serverStatus);
+      ServerCreated server = serverApi.create(hostName, imageIdForZone(zoneId), flavorRefForZone(zoneId), options);
 
-      return serverApi.get(server.getId());
-   }
+      awaitActive(serverApi).apply(server.getId());
 
-   private Server createServer(String regionId, String availabilityZoneId, Server.Status serverStatus) {
-      ServerApi serverApi = api.getServerApiForZone(regionId);
-      CreateServerOptions options = new CreateServerOptions();
-      options = options.availabilityZone(availabilityZoneId);
-      ServerCreated server = serverApi.create(hostName, imageIdForZone(regionId), flavorRefForZone(regionId), options);
-      blockUntilServerInState(server.getId(), serverApi, serverStatus);
       return serverApi.get(server.getId());
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/65e4ad20/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/internal/BaseNovaApiLiveTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/internal/BaseNovaApiLiveTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/internal/BaseNovaApiLiveTest.java
index da1847b..fd8ad6f 100644
--- a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/internal/BaseNovaApiLiveTest.java
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/internal/BaseNovaApiLiveTest.java
@@ -19,6 +19,7 @@ package org.jclouds.openstack.nova.v2_0.internal;
 import java.util.Properties;
 import java.util.Set;
 
+import com.google.common.collect.*;
 import org.jclouds.apis.BaseApiLiveTest;
 import org.jclouds.openstack.keystone.v2_0.config.KeystoneProperties;
 import org.jclouds.openstack.nova.v2_0.NovaApi;
@@ -35,9 +36,6 @@ import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 import com.google.common.base.Throwables;
-import com.google.common.collect.ComparisonChain;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Ordering;
 
 /**
  * Tests behavior of {@code NovaApi}
@@ -58,7 +56,15 @@ public class BaseNovaApiLiveTest extends BaseApiLiveTest<NovaApi> {
    @Override
    public void setup() {
       super.setup();
-      zones = api.getConfiguredZones();
+
+      String testZone = System.getProperty("test." + provider + ".zone");
+
+      if (testZone != null) {
+         zones = ImmutableSet.of(testZone);
+      } else {
+         zones = api.getConfiguredZones();
+      }
+
       for (String zone : zones) {
          ServerApi serverApi = api.getServerApiForZone(zone);
          for (Resource server : serverApi.list().concat()) {
@@ -83,7 +89,7 @@ public class BaseNovaApiLiveTest extends BaseApiLiveTest<NovaApi> {
       return serverApi.get(server.getId());
    }
 
-   /** 
+   /**
     * Will block until the requested server is in the correct state, if Extended Server Status extension is loaded
     * this will continue to block while any task is in progress.
     */
@@ -100,10 +106,12 @@ public class BaseNovaApiLiveTest extends BaseApiLiveTest<NovaApi> {
          }
       }
    }
-   
+
    protected String imageIdForZone(String zoneId) {
       ImageApi imageApi = api.getImageApiForZone(zoneId);
-      return Iterables.getLast(imageApi.list().concat()).getId();
+
+      // Get the first image from the list as it tends to be "lighter" and faster to start
+      return Iterables.get(imageApi.list().concat(), 0).getId();
    }
 
    protected String flavorRefForZone(String zoneId) {

http://git-wip-us.apache.org/repos/asf/jclouds/blob/65e4ad20/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/predicates/ServerPredicatesMockTest.java
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/predicates/ServerPredicatesMockTest.java b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/predicates/ServerPredicatesMockTest.java
new file mode 100644
index 0000000..4498c7c
--- /dev/null
+++ b/apis/openstack-nova/src/test/java/org/jclouds/openstack/nova/v2_0/predicates/ServerPredicatesMockTest.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.nova.v2_0.predicates;
+
+import com.squareup.okhttp.mockwebserver.MockResponse;
+import com.squareup.okhttp.mockwebserver.MockWebServer;
+import org.jclouds.openstack.nova.v2_0.NovaApi;
+import org.jclouds.openstack.nova.v2_0.features.ServerApi;
+import org.jclouds.openstack.v2_0.internal.BaseOpenStackMockTest;
+import org.testng.annotations.Test;
+
+import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.ACTIVE;
+import static org.jclouds.openstack.nova.v2_0.domain.Server.Status.SHUTOFF;
+import static org.jclouds.openstack.nova.v2_0.predicates.ServerPredicates.*;
+import static org.testng.Assert.*;
+
+@Test(groups = "unit", testName = "ServerPredicatesMockTest")
+public class ServerPredicatesMockTest extends BaseOpenStackMockTest<NovaApi> {
+   public void testAwaitActive() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(new MockResponse().setBody(stringFromResource("/access.json")));
+      server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
+      server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
+      server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
+      String serverDetailsActive = stringFromResource("/server_details.json").replace("BUILD(scheduling)", ACTIVE.value());
+      server.enqueue(new MockResponse().setBody(serverDetailsActive));
+
+      try {
+         NovaApi novaApi = api(server.getUrl("/").toString(), "openstack-nova");
+         ServerApi serverApi = novaApi.getServerApiForZone(("RegionOne"));
+
+         boolean result = awaitActive(serverApi).apply("52415800-8b69-11e0-9b19-734f000004d2");
+
+         assertTrue(result);
+         assertEquals(server.getRequestCount(), 5);
+         assertAuthentication(server);
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testAwaitShutoff() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(new MockResponse().setBody(stringFromResource("/access.json")));
+      server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
+      server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
+      server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
+      server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
+      server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
+      String serverDetailsShutoff = stringFromResource("/server_details.json").replace("BUILD(scheduling)", SHUTOFF.value());
+      server.enqueue(new MockResponse().setBody(serverDetailsShutoff));
+
+      try {
+         NovaApi novaApi = api(server.getUrl("/").toString(), "openstack-nova");
+         ServerApi serverApi = novaApi.getServerApiForZone(("RegionOne"));
+
+         boolean result = awaitShutoff(serverApi).apply("52415800-8b69-11e0-9b19-734f000004d2");
+
+         assertTrue(result);
+         assertEquals(server.getRequestCount(), 7);
+         assertAuthentication(server);
+      } finally {
+         server.shutdown();
+      }
+   }
+
+   public void testAwaitTimeout() throws Exception {
+      MockWebServer server = mockOpenStackServer();
+      server.enqueue(new MockResponse().setBody(stringFromResource("/access.json")));
+
+      for (int i=0; i < 20; i++) {
+         server.enqueue(new MockResponse().setBody(stringFromResource("/server_details.json")));
+      }
+
+      try {
+         NovaApi novaApi = api(server.getUrl("/").toString(), "openstack-nova");
+         ServerApi serverApi = novaApi.getServerApiForZone(("RegionOne"));
+
+         boolean result = awaitStatus(serverApi, ACTIVE, 3, 1).apply("52415800-8b69-11e0-9b19-734f000004d2");
+
+         assertFalse(result);
+         assertAuthentication(server);
+      } finally {
+         server.shutdown();
+      }
+   }
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/65e4ad20/apis/openstack-nova/src/test/resources/access.json
----------------------------------------------------------------------
diff --git a/apis/openstack-nova/src/test/resources/access.json b/apis/openstack-nova/src/test/resources/access.json
new file mode 100644
index 0000000..84a9625
--- /dev/null
+++ b/apis/openstack-nova/src/test/resources/access.json
@@ -0,0 +1,228 @@
+{
+    "access": {
+        "metadata": {
+            "roles": [
+                "9fe2ff9ee4384b1894a90878d3e92bab",
+                "b926cb0f4e2642678735f86c2b06205e",
+                "33484487e73d4da0918a19b9c7e1f8ae",
+                "f2e54c2105fb49e29479af047115cebc"
+            ],
+            "is_admin": 0
+        },
+        "user": {
+            "name":"joe",
+            "roles": [
+                {
+                    "name":"_member_"
+                },
+                {
+                    "name":"anotherrole"
+                },
+                {
+                    "name":"heat_stack_owner"
+                },
+                {
+                    "name":"Member"
+                }
+            ],
+            "id":"8fbf8e68d36e4ac7bcf912a26213bd49",
+            "roles_links": [],
+            "username":"joe"
+        },
+        "serviceCatalog": [
+            {
+                "name":"nova",
+                "type":"compute",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL/v2/da0d12be20394afb851716e10a49e4a7",
+                        "id":"2122bcaa704343c19ad2578410d4961d",
+                        "internalURL":"URL/v2/da0d12be20394afb851716e10a49e4a7",
+                        "region":"RegionOne",
+                        "adminURL":"URL/v2/da0d12be20394afb851716e10a49e4a7"
+                    }
+                ]
+            },
+            {
+                "name":"neutron",
+                "type":"network",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL/",
+                        "id":"65a4d3f13cfb49a6a57a04e205cc2158",
+                        "internalURL":"URL/",
+                        "region":"RegionOne",
+                        "adminURL":"URL/"
+                    }
+                ]
+            },
+            {
+                "name":"cinderv2",
+                "type":"volumev2",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL/v2/da0d12be20394afb851716e10a49e4a7",
+                        "id":"31fe4d92eac44044b05be21c6f44cebc",
+                        "internalURL":"URL/v2/da0d12be20394afb851716e10a49e4a7",
+                        "region":"RegionOne",
+                        "adminURL":"URL/v2/da0d12be20394afb851716e10a49e4a7"
+                    }
+                ]
+            },
+            {
+                "name":"trove",
+                "type":"database",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL/v1.0/da0d12be20394afb851716e10a49e4a7",
+                        "id":"06b7a7dbd25c4a01819c879700a9712a",
+                        "internalURL":"URL/v1.0/da0d12be20394afb851716e10a49e4a7",
+                        "region":"RegionOne",
+                        "adminURL":"URL/v1.0/da0d12be20394afb851716e10a49e4a7"
+                    }
+                ]
+            },
+            {
+                "name":"s3",
+                "type":"s3",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL",
+                        "id":"93b0b67091324e8ba01b62ee0584994c",
+                        "internalURL":"URL",
+                        "region":"RegionOne",
+                        "adminURL":"URL"
+                    }
+                ]
+            },
+            {
+                "name":"glance",
+                "type":"image",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL",
+                        "id":"a542e91bcfa046bfa1bf2397356d1414",
+                        "internalURL":"URL",
+                        "region":"RegionOne",
+                        "adminURL":"URL"
+                    }
+                ]
+            },
+            {
+                "name":"novav3",
+                "type":"computev3",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL/v3",
+                        "id":"9c3e8abb576d483db93bcef70c67bc1d",
+                        "internalURL":"URL/v3",
+                        "region":"RegionOne",
+                        "adminURL":"URL/v3"
+                    }
+                ]
+            },
+            {
+                "name":"heat",
+                "type":"cloudformation",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL/v1",
+                        "id":"6f4ca5ca9698425b85c300b3fc176c39",
+                        "internalURL":"URL/v1",
+                        "region":"RegionOne",
+                        "adminURL":"URL/v1"
+                    }
+                ]
+            },
+            {
+                "name":"cinder",
+                "type":"volume",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL/v1/da0d12be20394afb851716e10a49e4a7",
+                        "id":"037039c676694a35aa28d34fce09e51d",
+                        "internalURL":"URL/v1/da0d12be20394afb851716e10a49e4a7",
+                        "region":"RegionOne",
+                        "adminURL":"URL/v1/da0d12be20394afb851716e10a49e4a7"
+                    }
+                ]
+            },
+            {
+                "name":"ec2",
+                "type":"ec2",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL/services/Cloud",
+                        "id":"1d242631bccb4ff4ba7a395dbcb51648",
+                        "internalURL":"URL/services/Cloud",
+                        "region":"RegionOne",
+                        "adminURL":"URL/services/Admin"
+                    }
+                ]
+            },
+            {
+                "name":"heat",
+                "type":"orchestration",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL/v1/da0d12be20394afb851716e10a49e4a7",
+                        "id":"199d00075e4a40308a6ad2aa8980d0cd",
+                        "internalURL":"URL/v1/da0d12be20394afb851716e10a49e4a7",
+                        "region":"RegionOne",
+                        "adminURL":"URL/v1/da0d12be20394afb851716e10a49e4a7"
+                    }
+                ]
+            },
+            {
+                "name":"swift",
+                "type":"object-store",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL/v1/AUTH_da0d12be20394afb851716e10a49e4a7",
+                        "id":"26b2cb1efb044193b847fc3f2fb12e82",
+                        "internalURL":"URL/v1/AUTH_da0d12be20394afb851716e10a49e4a7",
+                        "region":"RegionOne",
+                        "adminURL":"URL"
+                    }
+                ]
+            },
+            {
+                "name":"keystone",
+                "type":"identity",
+                "endpoints_links": [],
+                "endpoints": [
+                    {
+                        "publicURL":"URL/v2.0",
+                        "id":"1bbfe80b50df4c4a84040aa782e42140",
+                        "internalURL":"URL/v2.0",
+                        "region":"RegionOne",
+                        "adminURL":"URL/v2.0"
+                    }
+                ]
+            }
+        ],
+        "token": {
+            "tenant": {
+                "name":"jclouds",
+                "id":"da0d12be20394afb851716e10a49e4a7",
+                "enabled": true,
+                "description": null
+            },
+            "id":"TOKEN",
+            "expires":"2014-04-28T22:48:24Z",
+            "issued_at":"2014-04-28T21:48:24.972896"
+        }
+    }
+}
\ No newline at end of file