From d23b4223af0db23bfe404e7d26a1caf0cdf89406 Mon Sep 17 00:00:00 2001 From: jasdeep-hundal Date: Tue, 11 Mar 2014 17:25:36 -0700 Subject: [PATCH] JCLOUDS-494: Change Glance EndpointParam parser to negotiate endpoint version --- .../functions/ZoneToEndpointNegotiateVersion.java | 143 +++++++++++++++ .../jclouds/openstack/glance/v1_0/GlanceApi.java | 6 +- .../glance/v1_0/handlers/GlanceErrorHandler.java | 7 + .../GlanceVersionNegotiationExpectTest.java | 104 +++++++++++ .../glance/v1_0/features/ImageApiExpectTest.java | 95 ++++++---- .../v1_0/handlers/GlanceErrorHandlerTest.java | 42 ++++- .../glance/v1_0/internal/BaseGlanceExpectTest.java | 9 + .../src/test/resources/glanceVersionResponse.json | 44 +++++ .../glanceVersionResponseSchemeMismatch.json | 44 +++++ .../glanceVersionResponseVersionUnavailable.json | 14 ++ ...eystoneAuthResponseVersionedGlanceEndpoint.json | 194 +++++++++++++++++++++ 11 files changed, 653 insertions(+), 49 deletions(-) create mode 100644 openstack-glance/src/main/java/org/jclouds/openstack/glance/functions/ZoneToEndpointNegotiateVersion.java create mode 100644 openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/GlanceVersionNegotiationExpectTest.java create mode 100644 openstack-glance/src/test/resources/glanceVersionResponse.json create mode 100644 openstack-glance/src/test/resources/glanceVersionResponseSchemeMismatch.json create mode 100644 openstack-glance/src/test/resources/glanceVersionResponseVersionUnavailable.json create mode 100644 openstack-glance/src/test/resources/keystoneAuthResponseVersionedGlanceEndpoint.json diff --git a/openstack-glance/src/main/java/org/jclouds/openstack/glance/functions/ZoneToEndpointNegotiateVersion.java b/openstack-glance/src/main/java/org/jclouds/openstack/glance/functions/ZoneToEndpointNegotiateVersion.java new file mode 100644 index 0000000..3065052 --- /dev/null +++ b/openstack-glance/src/main/java/org/jclouds/openstack/glance/functions/ZoneToEndpointNegotiateVersion.java @@ -0,0 +1,143 @@ +/* + * Copyright 2014 The Apache Software Foundation. + * + * Licensed 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.glance.functions; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import java.io.InputStream; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.regex.Pattern; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.jclouds.http.HttpRequest; +import org.jclouds.json.Json; +import org.jclouds.location.Zone; +import org.jclouds.rest.annotations.ApiVersion; +import org.jclouds.rest.HttpClient; +import org.jclouds.util.Strings2; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.base.Supplier; +import com.google.common.base.Throwables; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.Iterables; + +/** + * + * @author Jasdeep Hundal + */ +@Singleton +public class ZoneToEndpointNegotiateVersion implements Function { + + public static final String VERSION_NEGOTIATION_HEADER = "Is-Version-Negotiation-Request"; + + private static final Pattern versionRegex = Pattern.compile("v[0-9]+(\\.[0-9])?[0-9]*"); + + private static class VersionsJsonResponse{ + public static class Version { + public static class Link { + public String href; + public String rel; + } + public String status; + public String id; + public List links; + } + public List versions; + } + + private final Supplier>> zoneToEndpointSupplier; + private final String apiVersion; + private final LoadingCache endpointCache; + + @Inject + public ZoneToEndpointNegotiateVersion(@Zone Supplier>> zoneToEndpointSupplier, + @ApiVersion String rawApiVersionString, final HttpClient client, final Json json) { + this.zoneToEndpointSupplier = checkNotNull(zoneToEndpointSupplier, "zoneToEndpointSupplier"); + if (!rawApiVersionString.startsWith("v")) { + this.apiVersion = "v" + rawApiVersionString; + } else { + this.apiVersion = rawApiVersionString; + } + this.endpointCache = CacheBuilder.newBuilder() + .build( + new CacheLoader() { + public URI load(URI baseEndpointUri) { + try { + List baseEndpointPathParts = Splitter.on('/').omitEmptyStrings().splitToList(baseEndpointUri.getPath()); + if (!baseEndpointPathParts.isEmpty() + && versionRegex.matcher(baseEndpointPathParts.get(baseEndpointPathParts.size() - 1)).matches()) { + // Constructs a base URI Glance endpoint by stripping the version from the received URI + baseEndpointUri = new URI(baseEndpointUri.getScheme(), baseEndpointUri.getUserInfo(), + baseEndpointUri.getHost(), baseEndpointUri.getPort(), + Joiner.on('/').join(baseEndpointPathParts.subList(0, baseEndpointPathParts.size() - 1)) + "/", + baseEndpointUri.getQuery(), baseEndpointUri.getFragment()); + } + + HttpRequest negotiationRequest = HttpRequest.builder() + .method("GET").endpoint(baseEndpointUri) + .addHeader(VERSION_NEGOTIATION_HEADER, "true").build(); + InputStream response = client.invoke(negotiationRequest).getPayload().openStream(); + VersionsJsonResponse versions = json.fromJson(Strings2.toStringAndClose(response), VersionsJsonResponse.class); + for (VersionsJsonResponse.Version version : versions.versions) { + if (apiVersion.equals(version.id)) { + // We only expect one element here, we'll get an exception here if that changes + URI versionedEndpointUri = new URI(Iterables.getOnlyElement(version.links).href); + return new URI(baseEndpointUri.getScheme(), versionedEndpointUri.getUserInfo(), + versionedEndpointUri.getHost(), versionedEndpointUri.getPort(), + versionedEndpointUri.getPath(), versionedEndpointUri.getQuery(), + versionedEndpointUri.getFragment()); + } + } + } catch (URISyntaxException ex) { + throw Throwables.propagate(ex); + } catch (IOException ex) { + throw Throwables.propagate(ex); + } + throw new UnsupportedOperationException("Glance endpoint does not support API version: " + apiVersion); + } + }); + } + + @Override + public URI apply(Object from) { + checkArgument(from instanceof String, "you must specify a zone, as a String argument"); + Map> zoneToEndpoint = zoneToEndpointSupplier.get(); + checkState(!zoneToEndpoint.isEmpty(), "no zone name to endpoint mappings configured!"); + checkArgument(zoneToEndpoint.containsKey(from), + "requested location %s, which is not in the configured locations: %s", from, zoneToEndpoint); + URI uri = zoneToEndpointSupplier.get().get(from).get(); + + try { + return endpointCache.get(uri); + } catch (ExecutionException ex) { + throw Throwables.propagate(ex); + } + } +} diff --git a/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/GlanceApi.java b/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/GlanceApi.java index 7c16f41..c962eec 100644 --- a/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/GlanceApi.java +++ b/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/GlanceApi.java @@ -21,7 +21,7 @@ import org.jclouds.javax.annotation.Nullable; import org.jclouds.location.Zone; -import org.jclouds.location.functions.ZoneToEndpoint; +import org.jclouds.openstack.glance.functions.ZoneToEndpointNegotiateVersion; import org.jclouds.openstack.glance.v1_0.features.ImageApi; import org.jclouds.openstack.v2_0.features.ExtensionApi; import org.jclouds.rest.annotations.Delegate; @@ -50,12 +50,12 @@ */ @Delegate ExtensionApi getExtensionApiForZone( - @EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + @EndpointParam(parser = ZoneToEndpointNegotiateVersion.class) @Nullable String zone); /** * Provides access to Image features. */ @Delegate - ImageApi getImageApiForZone(@EndpointParam(parser = ZoneToEndpoint.class) @Nullable String zone); + ImageApi getImageApiForZone(@EndpointParam(parser = ZoneToEndpointNegotiateVersion.class) @Nullable String zone); } diff --git a/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/handlers/GlanceErrorHandler.java b/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/handlers/GlanceErrorHandler.java index aaff9bf..3659dda 100644 --- a/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/handlers/GlanceErrorHandler.java +++ b/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/handlers/GlanceErrorHandler.java @@ -24,6 +24,7 @@ import org.jclouds.http.HttpErrorHandler; import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponseException; +import org.jclouds.openstack.glance.functions.ZoneToEndpointNegotiateVersion; import org.jclouds.rest.AuthorizationException; import org.jclouds.rest.ResourceNotFoundException; @@ -46,6 +47,12 @@ public void handleError(HttpCommand command, HttpResponse response) { message = message != null ? message : String.format("%s -> %s", command.getCurrentRequest().getRequestLine(), response.getStatusLine()); switch (response.getStatusCode()) { + // do not throw exceptions on Glance version negotiation + case 300: + if (command.getCurrentRequest().getFirstHeaderOrNull(ZoneToEndpointNegotiateVersion.VERSION_NEGOTIATION_HEADER) != null) { + return; + } + break; case 400: break; case 401: diff --git a/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/GlanceVersionNegotiationExpectTest.java b/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/GlanceVersionNegotiationExpectTest.java new file mode 100644 index 0000000..8928719 --- /dev/null +++ b/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/GlanceVersionNegotiationExpectTest.java @@ -0,0 +1,104 @@ +/* + * 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.glance.v1_0.features; + +import static org.testng.Assert.assertEquals; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.glance.v1_0.GlanceApi; +import org.jclouds.openstack.glance.v1_0.internal.BaseGlanceApiExpectTest; +import org.jclouds.openstack.glance.v1_0.parse.ParseImagesTest; +import org.testng.annotations.Test; + +/** + * @author Jasdeep Hundal + */ +@Test(groups = "unit", testName = "GlanceVersionNegotiationExpectTest") +public class GlanceVersionNegotiationExpectTest extends BaseGlanceApiExpectTest { + + /* + * Test that if Glance returns a URL for a version with a different scheme + * than we used for the base endpoint we use the scheme associated w/ the + * base endpoint. + * + * This is useful for when Glance is behind a proxy. + */ + public void testSchemeMismatch() throws Exception { + // The referenced resource contains http endpoints for versions instead of the https endpoint returned by Keystone + versionNegotiationResponse = HttpResponse.builder().statusCode(300).message("HTTP/1.1 300 Multiple Choices").payload( + payloadFromResourceWithContentType("/glanceVersionResponseSchemeMismatch.json", "application/json")).build(); + + HttpRequest list = HttpRequest.builder().method("GET") + .endpoint("https://glance.jclouds.org:9292/v1.0/images") + .addHeader("Accept", "application/json") + .addHeader("X-Auth-Token", authToken).build(); + + + HttpResponse listResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResource("/images.json")).build(); + + GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + list, listResponse); + + assertEquals(apiWhenExist.getImageApiForZone("az-1.region-a.geo-1").list().concat().toString(), + new ParseImagesTest().expected().toString()); + } + + /* + * Test that Glance version negotiation fails with an exception if there is + * no endpoint returned for the requested version. + */ + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testNonExistentVersion() throws Exception { + // The referenced resource only an endpoint for v999.999 of the GlanceApi + HttpResponse localVersionNegotiationResponse = HttpResponse.builder().statusCode(300).message("HTTP/1.1 300 Multiple Choices").payload( + payloadFromResourceWithContentType("/glanceVersionResponseVersionUnavailable.json", "application/json")).build(); + + GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + responseWithKeystoneAccess, versionNegotiationRequest, localVersionNegotiationResponse); + + apiWhenExist.getImageApiForZone("az-1.region-a.geo-1").list(); + } + + /* + * Test that Glance version negotiation happens with the base endpoint if + * the Keystone catalog returns an already versioned endpoint for Glance + */ + public void testKeystoneReturnsVersionedEndpoint() throws Exception { + // This sets the keystone response to return a Glance endpoint w/ version already present + HttpResponse localResponseWithKeystoneAccess = HttpResponse.builder().statusCode(200).message("HTTP/1.1 200").payload( + payloadFromResourceWithContentType("/keystoneAuthResponseVersionedGlanceEndpoint.json", "application/json")).build(); + + HttpRequest list = HttpRequest.builder().method("GET") + .endpoint("https://glance.jclouds.org:9292/v1.0/images") + .addHeader("Accept", "application/json") + .addHeader("X-Auth-Token", authToken).build(); + + + HttpResponse listResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResource("/images.json")).build(); + + GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, + localResponseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + list, listResponse); + + assertEquals(apiWhenExist.getImageApiForZone("az-1.region-a.geo-1").list().concat().toString(), + new ParseImagesTest().expected().toString()); + } +} diff --git a/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/ImageApiExpectTest.java b/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/ImageApiExpectTest.java index 9b4e053..d200422 100644 --- a/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/ImageApiExpectTest.java +++ b/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/features/ImageApiExpectTest.java @@ -51,7 +51,7 @@ public void testListWhenResponseIs2xx() throws Exception { HttpRequest list = HttpRequest.builder().method("GET") - .endpoint("https://glance.jclouds.org:9292/images") + .endpoint("https://glance.jclouds.org:9292/v1.0/images") .addHeader("Accept", "application/json") .addHeader("X-Auth-Token", authToken).build(); @@ -60,7 +60,8 @@ public void testListWhenResponseIs2xx() throws Exception { .payload(payloadFromResource("/images.json")).build(); GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, list, listResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + list, listResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -70,21 +71,22 @@ public void testListWhenResponseIs2xx() throws Exception { public void testListWhenReponseIs404IsEmpty() throws Exception { HttpRequest list = HttpRequest.builder().method("GET") - .endpoint("https://glance.jclouds.org:9292/images") + .endpoint("https://glance.jclouds.org:9292/v1.0/images") .addHeader("Accept", "application/json") .addHeader("X-Auth-Token", authToken).build(); HttpResponse listResponse = HttpResponse.builder().statusCode(404).build(); GlanceApi apiWhenNoExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, list, listResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + list, listResponse); assertTrue(apiWhenNoExist.getImageApiForZone("az-1.region-a.geo-1").list().concat().isEmpty()); } public void testListInDetailWhenResponseIs2xx() throws Exception { HttpRequest listInDetail = HttpRequest.builder().method("GET") - .endpoint("https://glance.jclouds.org:9292/images/detail") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/detail") .addHeader("Accept", "application/json") .addHeader("X-Auth-Token", authToken).build(); @@ -93,7 +95,8 @@ public void testListInDetailWhenResponseIs2xx() throws Exception { .payload(payloadFromResource("/images_detail.json")).build(); GlanceApi apiWhenExistInDetail = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, listInDetail, listInDetailResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + listInDetail, listInDetailResponse); assertEquals(apiWhenExistInDetail.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -103,7 +106,7 @@ public void testListInDetailWhenResponseIs2xx() throws Exception { public void testListInDetailWhenReponseIs404IsEmpty() throws Exception { HttpRequest listInDetail = HttpRequest.builder().method("GET") - .endpoint("https://glance.jclouds.org:9292/images/detail") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/detail") .addHeader("Accept", "application/json") .addHeader("X-Auth-Token", authToken).build(); @@ -111,7 +114,8 @@ public void testListInDetailWhenReponseIs404IsEmpty() throws Exception { HttpResponse listInDetailResponse = HttpResponse.builder().statusCode(404).build(); GlanceApi apiWhenNoExistInDetail = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, listInDetail, listInDetailResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + listInDetail, listInDetailResponse); assertTrue(apiWhenNoExistInDetail.getImageApiForZone("az-1.region-a.geo-1").listInDetail().concat().isEmpty()); } @@ -120,14 +124,15 @@ public void testShowWhenResponseIs2xx() throws Exception { HttpRequest show = HttpRequest .builder() .method("HEAD") - .endpoint("https://glance.jclouds.org:9292/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") .addHeader("X-Auth-Token", authToken).build(); HttpResponse showResponse = new ParseImageDetailsFromHeadersTest().response; GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, show, showResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + show, showResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -137,14 +142,15 @@ public void testShowWhenResponseIs2xx() throws Exception { public void testShowWhenReponseIs404IsNull() throws Exception { HttpRequest show = HttpRequest.builder().method("HEAD") - .endpoint("https://glance.jclouds.org:9292/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") .addHeader("X-Auth-Token", authToken).build(); HttpResponse showResponse = HttpResponse.builder().statusCode(404).build(); GlanceApi apiWhenNoExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, show, showResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + show, showResponse); assertNull(apiWhenNoExist.getImageApiForZone("az-1.region-a.geo-1").get("fcc451d0-f6e4-4824-ad8f-70ec12326d07")); } @@ -152,14 +158,15 @@ public void testShowWhenReponseIs404IsNull() throws Exception { public void testGetAsStreamWhenResponseIs2xx() throws Exception { HttpRequest get = HttpRequest.builder().method("GET") - .endpoint("https://glance.jclouds.org:9292/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") .addHeader("X-Auth-Token", authToken).build(); HttpResponse getResponse = HttpResponse.builder().statusCode(200).payload(Payloads.newStringPayload("foo")).build(); GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, getResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, getResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -169,21 +176,22 @@ public void testGetAsStreamWhenResponseIs2xx() throws Exception { public void testGetAsStreamWhenReponseIs404IsNull() throws Exception { HttpRequest get = HttpRequest.builder().method("GET") - .endpoint("https://glance.jclouds.org:9292/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") .addHeader("X-Auth-Token", authToken).build(); HttpResponse getResponse = HttpResponse.builder().statusCode(404).build(); GlanceApi apiWhenNoExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, getResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, getResponse); assertNull(apiWhenNoExist.getImageApiForZone("az-1.region-a.geo-1").getAsStream("fcc451d0-f6e4-4824-ad8f-70ec12326d07")); } public void testCreateWhenResponseIs2xx() throws Exception { HttpRequest get = HttpRequest.builder().method("POST") - .endpoint("https://glance.jclouds.org:9292/images") + .endpoint("https://glance.jclouds.org:9292/v1.0/images") .addHeader("x-image-meta-name", "test") .addHeader("Accept", MediaType.APPLICATION_JSON) .addHeader("X-Auth-Token", authToken) @@ -193,7 +201,8 @@ public void testCreateWhenResponseIs2xx() throws Exception { .payload(payloadFromResource("/image.json")).build(); GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, createResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, createResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -204,7 +213,7 @@ public void testCreateWhenResponseIs2xx() throws Exception { @Test(expectedExceptions = AuthorizationException.class) public void testCreateWhenResponseIs4xx() throws Exception { HttpRequest get = HttpRequest.builder().method("POST") - .endpoint("https://glance.jclouds.org:9292/images") + .endpoint("https://glance.jclouds.org:9292/v1.0/images") .addHeader("x-image-meta-name", "test") .addHeader("Accept", MediaType.APPLICATION_JSON) .addHeader("X-Auth-Token", authToken) @@ -214,7 +223,8 @@ public void testCreateWhenResponseIs4xx() throws Exception { .payload(payloadFromResource("/image.json")).build(); GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, createResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, createResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -223,7 +233,7 @@ public void testCreateWhenResponseIs4xx() throws Exception { public void testReserveWhenResponseIs2xx() throws Exception { HttpRequest get = HttpRequest.builder().method("POST") - .endpoint("https://glance.jclouds.org:9292/images") + .endpoint("https://glance.jclouds.org:9292/v1.0/images") .addHeader("x-image-meta-name", "test") .addHeader("Accept", MediaType.APPLICATION_JSON) .addHeader("X-Auth-Token", authToken).build(); @@ -232,7 +242,8 @@ public void testReserveWhenResponseIs2xx() throws Exception { .payload(payloadFromResource("/image.json")).build(); GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, createResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, createResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -242,7 +253,7 @@ public void testReserveWhenResponseIs2xx() throws Exception { @Test(expectedExceptions = AuthorizationException.class) public void testReserveWhenResponseIs4xx() throws Exception { HttpRequest get = HttpRequest.builder().method("POST") - .endpoint("https://glance.jclouds.org:9292/images") + .endpoint("https://glance.jclouds.org:9292/v1.0/images") .addHeader("x-image-meta-name", "test") .addHeader("Accept", MediaType.APPLICATION_JSON) .addHeader("X-Auth-Token", authToken).build(); @@ -251,7 +262,8 @@ public void testReserveWhenResponseIs4xx() throws Exception { .payload(payloadFromResource("/image.json")).build(); GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, createResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, createResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -260,7 +272,7 @@ public void testReserveWhenResponseIs4xx() throws Exception { public void testUpdateMetadataWhenResponseIs2xx() throws Exception { HttpRequest get = HttpRequest.builder().method("PUT") - .endpoint("https://glance.jclouds.org:9292/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") .headers( ImmutableMultimap.builder() .put("Accept", MediaType.APPLICATION_JSON) @@ -278,7 +290,8 @@ public void testUpdateMetadataWhenResponseIs2xx() throws Exception { .payload(payloadFromResource("/image.json")).build(); GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, updateResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, updateResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -297,7 +310,7 @@ public void testUpdateMetadataWhenResponseIs2xx() throws Exception { @Test(expectedExceptions = ResourceNotFoundException.class) public void testUpdateMetadataWhenResponseIs4xx() throws Exception { HttpRequest get = HttpRequest.builder().method("PUT") - .endpoint("https://glance.jclouds.org:9292/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") .headers( ImmutableMultimap.builder() .put("Accept", MediaType.APPLICATION_JSON) @@ -309,7 +322,8 @@ public void testUpdateMetadataWhenResponseIs4xx() throws Exception { HttpResponse updateResponse = HttpResponse.builder().statusCode(404).build(); GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, updateResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, updateResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -321,7 +335,7 @@ public void testUpdateMetadataWhenResponseIs4xx() throws Exception { public void testUpdateImageWhenResponseIs2xx() throws Exception { HttpRequest get = HttpRequest.builder().method("PUT") - .endpoint("https://glance.jclouds.org:9292/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") .addHeader("Accept", MediaType.APPLICATION_JSON) .addHeader("X-Auth-Token", authToken) .payload(payloadFromStringWithContentType("somenewdata", MediaType.APPLICATION_OCTET_STREAM)) @@ -332,7 +346,8 @@ public void testUpdateImageWhenResponseIs2xx() throws Exception { GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, updateResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, updateResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -342,7 +357,7 @@ public void testUpdateImageWhenResponseIs2xx() throws Exception { public void testUpdateNameAndImageWhenResponseIs2xx() throws Exception { HttpRequest get = HttpRequest.builder().method("PUT") - .endpoint("https://glance.jclouds.org:9292/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") .headers( ImmutableMultimap.builder() .put("Accept", MediaType.APPLICATION_JSON) @@ -356,7 +371,8 @@ public void testUpdateNameAndImageWhenResponseIs2xx() throws Exception { GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, updateResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, updateResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -367,7 +383,7 @@ public void testUpdateNameAndImageWhenResponseIs2xx() throws Exception { @Test(expectedExceptions = AuthorizationException.class) public void testUpdateNameAndImageWhenResponseIs4xx() throws Exception { HttpRequest get = HttpRequest.builder().method("PUT") - .endpoint("https://glance.jclouds.org:9292/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") .headers( ImmutableMultimap.builder() .put("Accept", MediaType.APPLICATION_JSON) @@ -379,7 +395,8 @@ public void testUpdateNameAndImageWhenResponseIs4xx() throws Exception { HttpResponse updateResponse = HttpResponse.builder().statusCode(403).build(); GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, updateResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, updateResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -389,13 +406,14 @@ public void testUpdateNameAndImageWhenResponseIs4xx() throws Exception { public void testDeleteWhenResponseIs2xx() throws Exception { HttpRequest get = HttpRequest.builder().method("DELETE") - .endpoint("https://glance.jclouds.org:9292/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") .addHeader("X-Auth-Token", authToken).build(); HttpResponse getResponse = HttpResponse.builder().statusCode(200).build(); GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, getResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, getResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); @@ -404,14 +422,15 @@ public void testDeleteWhenResponseIs2xx() throws Exception { public void testDeleteWhenResponseIs4xx() throws Exception { HttpRequest get = HttpRequest.builder().method("DELETE") - .endpoint("https://glance.jclouds.org:9292/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") + .endpoint("https://glance.jclouds.org:9292/v1.0/images/fcc451d0-f6e4-4824-ad8f-70ec12326d07") .addHeader("X-Auth-Token", authToken).build(); HttpResponse getResponse = HttpResponse.builder().statusCode(404).build(); GlanceApi apiWhenExist = requestsSendResponses(keystoneAuthWithUsernameAndPassword, - responseWithKeystoneAccess, get, getResponse); + responseWithKeystoneAccess, versionNegotiationRequest, versionNegotiationResponse, + get, getResponse); assertEquals(apiWhenExist.getConfiguredZones(), ImmutableSet.of("az-1.region-a.geo-1")); diff --git a/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/handlers/GlanceErrorHandlerTest.java b/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/handlers/GlanceErrorHandlerTest.java index 1245788..3c54ed7 100644 --- a/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/handlers/GlanceErrorHandlerTest.java +++ b/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/handlers/GlanceErrorHandlerTest.java @@ -16,45 +16,71 @@ */ package org.jclouds.openstack.glance.v1_0.handlers; -import static org.easymock.EasyMock.createMockBuilder; +import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.reportMatcher; import static org.easymock.EasyMock.verify; import java.net.URI; +import java.util.Collections; import org.easymock.IArgumentMatcher; import org.jclouds.http.HttpCommand; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; import org.testng.annotations.Test; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; + /** * * @author Adrian Cole + * @author Jasdeep Hundal */ @Test(groups = "unit", testName = "GlanceErrorHandlerTest") public class GlanceErrorHandlerTest { + @Test + public void test300UnintendedVersionNegotiation() { + assertCodeMakes("GET", URI + .create("https://glance.jclouds.org:9292/"), + 300, "Multiple Choices", "", HttpResponseException.class); + } + + @Test + public void test300VersionNegotiation() { + assertCodeMakes("GET", Multimaps.forMap(Collections.singletonMap("Is-Version-Negotiation-Request", "true")), + URI.create("https://glance.jclouds.org:9292/"), + 300, "Multiple Choices", "", null); + } + + private void assertCodeMakes(String method, URI uri, int statusCode, + String message, String content, Class expected) { + assertCodeMakes(method, Multimaps.forMap(Collections.EMPTY_MAP), uri, statusCode, message, "text/plain", content, expected); + } - private void assertCodeMakes(String method, URI uri, int statusCode, String message, String content, - Class expected) { - assertCodeMakes(method, uri, statusCode, message, "text/plain", content, expected); + private void assertCodeMakes(String method, Multimap headers, URI uri, int statusCode, + String message, String content, Class expected) { + assertCodeMakes(method, headers, uri, statusCode, message, "text/plain", content, expected); } - private void assertCodeMakes(String method, URI uri, int statusCode, String message, String contentType, + private void assertCodeMakes(String method, Multimap headers, URI uri, int statusCode, String message, String contentType, String content, Class expected) { GlanceErrorHandler function = new GlanceErrorHandler(); - HttpCommand command = createMockBuilder(HttpCommand.class).createMock(); - HttpRequest request = HttpRequest.builder().method(method).endpoint(uri).build(); + HttpCommand command = createMock(HttpCommand.class); + HttpRequest request = HttpRequest.builder().method(method).headers(headers).endpoint(uri).build(); HttpResponse response = HttpResponse.builder().statusCode(statusCode).message(message).payload(content).build(); response.getPayload().getContentMetadata().setContentType(contentType); expect(command.getCurrentRequest()).andReturn(request).atLeastOnce(); - command.setException(classEq(expected)); + if (expected != null) { + command.setException(classEq(expected)); + } replay(command); diff --git a/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/internal/BaseGlanceExpectTest.java b/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/internal/BaseGlanceExpectTest.java index 40dc393..6267b43 100644 --- a/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/internal/BaseGlanceExpectTest.java +++ b/openstack-glance/src/test/java/org/jclouds/openstack/glance/v1_0/internal/BaseGlanceExpectTest.java @@ -18,6 +18,7 @@ import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; +import org.jclouds.openstack.glance.functions.ZoneToEndpointNegotiateVersion; import org.jclouds.openstack.keystone.v2_0.internal.KeystoneFixture; import org.jclouds.rest.internal.BaseRestApiExpectTest; @@ -31,6 +32,8 @@ protected HttpRequest keystoneAuthWithAccessKeyAndSecretKey; protected String authToken; protected HttpResponse responseWithKeystoneAccess; + protected HttpRequest versionNegotiationRequest; + protected HttpResponse versionNegotiationResponse; protected HttpRequest extensionsOfGlanceRequest; protected HttpResponse extensionsOfGlanceResponse; protected HttpResponse unmatchedExtensionsOfGlanceResponse; @@ -46,5 +49,11 @@ public BaseGlanceExpectTest() { responseWithKeystoneAccess = KeystoneFixture.INSTANCE.responseWithAccess(); // now, createContext arg will need tenant prefix identity = KeystoneFixture.INSTANCE.getTenantName() + ":" + identity; + // version negotiation + versionNegotiationRequest = HttpRequest.builder().method("GET") + .endpoint("https://glance.jclouds.org:9292/") + .addHeader(ZoneToEndpointNegotiateVersion.VERSION_NEGOTIATION_HEADER, "true").build(); + versionNegotiationResponse = HttpResponse.builder().statusCode(300).message("HTTP/1.1 300 Multiple Choices").payload( + payloadFromResourceWithContentType("/glanceVersionResponse.json", "application/json")).build(); } } diff --git a/openstack-glance/src/test/resources/glanceVersionResponse.json b/openstack-glance/src/test/resources/glanceVersionResponse.json new file mode 100644 index 0000000..fa34f63 --- /dev/null +++ b/openstack-glance/src/test/resources/glanceVersionResponse.json @@ -0,0 +1,44 @@ +{ + "versions":[ + { + "status":"CURRENT", + "id":"v2.1", + "links":[ + { + "href":"https://glance.jclouds.org:9292/v2.0/", + "rel":"self" + } + ] + }, + { + "status":"SUPPORTED", + "id":"v2.0", + "links":[ + { + "href":"https://glance.jclouds.org:9292/v2.0/", + "rel":"self" + } + ] + }, + { + "status":"CURRENT", + "id":"v1.1", + "links":[ + { + "href":"https://glance.jclouds.org:9292/v1.0/", + "rel":"self" + } + ] + }, + { + "status":"SUPPORTED", + "id":"v1.0", + "links":[ + { + "href":"https://glance.jclouds.org:9292/v1.0/", + "rel":"self" + } + ] + } + ] +} diff --git a/openstack-glance/src/test/resources/glanceVersionResponseSchemeMismatch.json b/openstack-glance/src/test/resources/glanceVersionResponseSchemeMismatch.json new file mode 100644 index 0000000..bf17274 --- /dev/null +++ b/openstack-glance/src/test/resources/glanceVersionResponseSchemeMismatch.json @@ -0,0 +1,44 @@ +{ + "versions":[ + { + "status":"CURRENT", + "id":"v2.1", + "links":[ + { + "href":"http://glance.jclouds.org:9292/v2.0/", + "rel":"self" + } + ] + }, + { + "status":"SUPPORTED", + "id":"v2.0", + "links":[ + { + "href":"http://glance.jclouds.org:9292/v2.0/", + "rel":"self" + } + ] + }, + { + "status":"CURRENT", + "id":"v1.1", + "links":[ + { + "href":"http://glance.jclouds.org:9292/v1.0/", + "rel":"self" + } + ] + }, + { + "status":"SUPPORTED", + "id":"v1.0", + "links":[ + { + "href":"http://glance.jclouds.org:9292/v1.0/", + "rel":"self" + } + ] + } + ] +} diff --git a/openstack-glance/src/test/resources/glanceVersionResponseVersionUnavailable.json b/openstack-glance/src/test/resources/glanceVersionResponseVersionUnavailable.json new file mode 100644 index 0000000..bdc0b14 --- /dev/null +++ b/openstack-glance/src/test/resources/glanceVersionResponseVersionUnavailable.json @@ -0,0 +1,14 @@ +{ + "versions":[ + { + "status":"CURRENT", + "id":"v999.999", + "links":[ + { + "href":"https://glance.jclouds.org:9292/v999.999/", + "rel":"self" + } + ] + }, + ] +} diff --git a/openstack-glance/src/test/resources/keystoneAuthResponseVersionedGlanceEndpoint.json b/openstack-glance/src/test/resources/keystoneAuthResponseVersionedGlanceEndpoint.json new file mode 100644 index 0000000..77d169a --- /dev/null +++ b/openstack-glance/src/test/resources/keystoneAuthResponseVersionedGlanceEndpoint.json @@ -0,0 +1,194 @@ +{ + "access": { + "token": { + "expires": "2012-01-18T21:35:59.050Z", + "id": "Auth_4f173437e4b013bee56d1007", + "tenant": { + "id": "40806637803162", + "name": "user@jclouds.org-default-tenant" + } + }, + "user": { + "id": "36980896575174", + "name": "user@jclouds.org", + "roles": [ + { + "id": "00000000004022", + "serviceId": "110", + "name": "Admin", + "tenantName": "40806637803162" + }, + { + "id": "00000000004024", + "serviceId": "140", + "name": "user", + "tenantName": "40806637803162" + }, + { + "id": "00000000004004", + "serviceId": "100", + "name": "domainuser" + }, + { + "id": "00000000004016", + "serviceId": "120", + "name": "netadmin", + "tenantName": "40806637803162" + } + ] + }, + "serviceCatalog": [ + { + "name": "Object Storage", + "type": "object-store", + "endpoints": [ + { + "tenantName": "40806637803162", + "adminURL": "https://objects.jclouds.org/v1.0/", + "publicURL": "https://objects.jclouds.org/v1.0/40806637803162", + "region": "region-a.geo-1", + "id": "1.0" + } + ] + }, + { + "name": "Identity", + "type": "identity", + "endpoints": [ + { + "adminURL": "https://csnode.jclouds.org:35357/v2.0/", + "publicURL": "https://csnode.jclouds.org/v2.0/", + "region": "region-a.geo-1", + "id": "2.0", + "versionId": "2.0", + "list": "https://csnode.jclouds.org/extension" + } + ] + }, + { + "name": "Image Management", + "type": "image", + "endpoints": [ + { + "tenantName": "40806637803162", + "publicURL": "https://glance.jclouds.org:9292/v1.0/", + "region": "az-1.region-a.geo-1", + "id": "1.0" + } + ] + }, + { + "name": "Compute", + "type": "compute", + "endpoints": [ + { + "tenantId": "3456", + "publicURL": "https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456", + "publicURL2": "https://az-1.region-a.geo-1.ec2-compute.hpcloudsvc.com/services/Cloud", + "region": "az-1.region-a.geo-1", + "versionId": "1.1", + "versionInfo": "https://az-1.region-a.geo-1.compute.hpcloudsvc.com/v1.1/", + "versionList": "https://az-1.region-a.geo-1.compute.hpcloudsvc.com" + }, + { + "tenantId": "3456", + "publicURL": "https://az-2.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456", + "publicURL2": "https://az-2.region-a.geo-1.ec2-compute.hpcloudsvc.com/services/Cloud", + "region": "az-2.region-a.geo-1", + "versionId": "1.1", + "versionInfo": "https://az-2.region-a.geo-1.compute.hpcloudsvc.com/v1.1/", + "versionList": "https://az-2.region-a.geo-1.compute.hpcloudsvc.com" + }, + { + "tenantId": "3456", + "publicURL": "https://az-3.region-a.geo-1.compute.hpcloudsvc.com/v1.1/3456", + "publicURL2": "https://az-3.region-a.geo-1.ec2-compute.hpcloudsvc.com/services/Cloud", + "region": "az-3.region-a.geo-1", + "versionId": "1.1", + "versionInfo": "https://az-3.region-a.geo-1.compute.hpcloudsvc.com/v1.1/", + "versionList": "https://az-3.region-a.geo-1.compute.hpcloudsvc.com" + } + ] + }, + { + "type": "network", + "name": "Neutron Service", + "endpoints": [ + { + "tenantId": "3456", + "adminURL": "https://csnode.jclouds.org:9696/v1.0", + "region": "region-a.geo-1", + "versionId": "1.0", + "publicURL": "https://csnode.jclouds.org:9696/v1.0/tenants/3456", + "internalURL": "https://csnode.jclouds.org:9696/v1.0/tenants/3456" + } + ], + "endpoints_links": [] + }, + { + "type": "network", + "name": "Quantum Service", + "endpoints": [ + { + "tenantId": "3456", + "adminURL": "https://csnode.jclouds.org:9696", + "region": "region-a.geo-1", + "versionId": "2.0", + "publicURL": "https://csnode.jclouds.org:9696", + "internalURL": "https://csnode.jclouds.org:9696" + } + ], + "endpoints_links": [] + }, + { + "type": "volume", + "name": "cinder", + "endpoints": [ + { + "adminURL": "http://10.0.2.15:8776/v1/50cdb4c60374463198695d9f798fa34d", + "region": "RegionOne", + "internalURL": "http://10.0.2.15:8776/v1/50cdb4c60374463198695d9f798fa34d", + "id": "08330c2dcbfc4c6c8dc7a0949fbf5da7", + "publicURL": "http://172.16.0.1:8776/v1/50cdb4c60374463198695d9f798fa34d" + } + ], + "endpoints_links": [] + }, + { + "type": "databases", + "name": "reddwarf", + "endpoints": [ + { + "publicURL": "http://172.16.0.1:8776/v1/3456", + "tenantId": "123123", + "region": "RegionOne" + } + ] + }, + { + "type": "queuing", + "name": "marconi", + "endpoints": [ + { + "adminURL": "http://10.0.2.15:8888", + "region": "RegionOne", + "internalURL": "http://10.0.2.15:8888", + "id": "3456789", + "publicURL": "http://172.16.0.1:8888" + } + ], + "endpoints_links": [] + }, + { + "type": "dns", + "name": "dns", + "endpoints": [ + { + "publicURL": "http://172.16.0.1:8776/v1/3456", + "tenantId": "3456" + } + ] + } + ] + } +} -- 1.9.1