| /* |
| * 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.v2_0.internal; |
| |
| import static com.google.common.util.concurrent.MoreExecutors.newDirectExecutorService; |
| import static org.jclouds.Constants.PROPERTY_MAX_RETRIES; |
| import static org.testng.Assert.assertEquals; |
| import static org.testng.Assert.assertTrue; |
| |
| import java.io.Closeable; |
| import java.io.IOException; |
| import java.net.URL; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.concurrent.BlockingQueue; |
| import java.util.concurrent.LinkedBlockingQueue; |
| import java.util.regex.Pattern; |
| |
| import okhttp3.mockwebserver.MockResponse; |
| import okhttp3.mockwebserver.MockWebServer; |
| import okhttp3.mockwebserver.QueueDispatcher; |
| import okhttp3.mockwebserver.RecordedRequest; |
| import okhttp3.mockwebserver.SocketPolicy; |
| |
| import org.jclouds.ContextBuilder; |
| import org.jclouds.concurrent.config.ExecutorServiceModule; |
| import org.jclouds.util.Strings2; |
| |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.net.HttpHeaders; |
| import com.google.common.net.MediaType; |
| import com.google.common.reflect.TypeToken; |
| import com.google.gson.JsonElement; |
| import com.google.gson.JsonParser; |
| import com.google.inject.Module; |
| |
| |
| public class BaseOpenStackMockTest<A extends Closeable> { |
| |
| /** |
| * Needed for backwards-compatibility. This variable might not be up-to-date |
| * with current services available. |
| * */ |
| @Deprecated |
| public static final String accessRackspace = "{\"access\":{\"token\":{\"id\":\"b84f4a37-5126-4603-9521-ccd0665fbde1\",\"expires\":\"2013-04-13T16:49:57.000-05:00\",\"tenant\":{\"id\":\"123123\",\"name\":\"123123\"}},\"serviceCatalog\":[{\"endpoints\":[{\"tenantId\":\"123123\",\"publicURL\":\"URL/v1.0/123123\"}],\"name\":\"cloudMonitoring\",\"type\":\"rax:monitor\"},{\"endpoints\":[{\"region\":\"DFW\",\"tenantId\":\"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9\",\"publicURL\":\"URL/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9\"},{\"region\":\"ORD\",\"tenantId\":\"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9\",\"publicURL\":\"URL/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9\"}],\"name\":\"cloudFilesCDN\",\"type\":\"rax:object-cdn\"},{\"endpoints\":[{\"region\":\"ORD\",\"tenantId\":\"123123\",\"publicURL\":\"URL/v1.0/123123\"},{\"region\":\"DFW\",\"tenantId\":\"123123\",\"publicURL\":\"URL/v1.0/123123\"}],\"name\":\"cloudLoadBalancers\",\"type\":\"rax:load-balancer\"},{\"endpoints\":[{\"region\":\"DFW\",\"tenantId\":\"123123\",\"publicURL\":\"URL/v1.0/123123\"},{\"region\":\"ORD\",\"tenantId\":\"123123\",\"publicURL\":\"URL/v1.0/123123\"}],\"name\":\"cloudDatabases\",\"type\":\"rax:database\"},{\"endpoints\":[{\"region\":\"DFW\",\"tenantId\":\"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9\",\"publicURL\":\"URL/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9\",\"internalURL\":\"URL/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9\"},{\"region\":\"ORD\",\"tenantId\":\"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9\",\"publicURL\":\"URL/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9\",\"internalURL\":\"URL/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9\"}],\"name\":\"cloudFiles\",\"type\":\"object-store\"},{\"endpoints\":[{\"tenantId\":\"123123\",\"publicURL\":\"URL/v1.0/123123\",\"versionInfo\":\"URL/v1.0\",\"versionList\":\"URL/\",\"versionId\":\"1.0\"}],\"name\":\"cloudServers\",\"type\":\"compute\"},{\"endpoints\":[{\"region\":\"DFW\",\"tenantId\":\"123123\",\"publicURL\":\"URL/v2/123123\",\"versionInfo\":\"URL/v2\",\"versionList\":\"URL/\",\"versionId\":\"2\"},{\"region\":\"ORD\",\"tenantId\":\"123123\",\"publicURL\":\"URL/v2/123123\",\"versionInfo\":\"URL/v2\",\"versionList\":\"URL/\",\"versionId\":\"2\"}],\"name\":\"cloudServersOpenStack\",\"type\":\"compute\"},{\"endpoints\":[{\"tenantId\":\"123123\",\"publicURL\":\"URL/v1.0/123123\"}],\"name\":\"cloudDNS\",\"type\":\"rax:dns\"},{\"endpoints\":[{\"tenantId\":\"123123\",\"publicURL\":\"URL/v1.0/123123\"}],\"name\":\"cloudBackup\",\"type\":\"rax:backup\"},{\"endpoints\":[{\"region\":\"DFW\",\"tenantId\":\"123123\",\"publicURL\":\"URL/v1/123123\"},{\"region\":\"ORD\",\"tenantId\":\"123123\",\"publicURL\":\"URL/v1/123123\"}],\"name\":\"cloudBlockStorage\",\"type\":\"volume\"},{\"endpoints\":[{\"region\":\"DFW\",\"tenantId\":\"123123\",\"publicURL\":\"URL/v1/123123\",\"internalURL\":\"URL/v1/123123\"},{\"region\":\"ORD\",\"tenantId\":\"123123\",\"publicURL\":\"URL/v1/123123\",\"internalURL\":\"URL/v1/123123\"}],\"name\":\"zaqar\",\"type\":\"messaging\"},{\"endpoints\":[{\"region\":\"DFW\",\"tenantId\":\"123123\",\"publicURL\":\"URL/v1/123123\",\"internalURL\":\"URL/v1/123123\"},{\"region\":\"ORD\",\"tenantId\":\"123123\",\"publicURL\":\"URL/v1/123123\",\"internalURL\":\"URL/v1/123123\"}],\"name\":\"autoscale\",\"type\":\"rax:autoscale\"}],\"user\":{\"id\":\"1234\",\"roles\":[{\"id\":\"3\",\"description\":\"User Admin Role.\",\"name\":\"identity:user-admin\"}],\"name\":\"jclouds-joe\",\"RAX-AUTH:defaultRegion\":\"DFW\"}}}"; |
| |
| private final Set<Module> modules = ImmutableSet.<Module> of( |
| new ExecutorServiceModule(newDirectExecutorService())); |
| |
| /** |
| * Pattern for replacing the URL token with the correct local address. |
| */ |
| private static final Pattern urlTokenPattern = Pattern.compile(":\\s*\"\\s*URL"); |
| |
| @SuppressWarnings("serial") |
| public A api(String uri, String provider, Properties overrides) { |
| if (!overrides.containsKey(PROPERTY_MAX_RETRIES)) { |
| overrides.setProperty(PROPERTY_MAX_RETRIES, "1"); |
| } |
| |
| return ContextBuilder.newBuilder(provider) |
| .credentials("jclouds:joe", "letmein") |
| .endpoint(uri) |
| .overrides(overrides) |
| .modules(modules) |
| .buildApi(new TypeToken<A>(getClass()) {}); |
| } |
| |
| public A api(String uri, String provider) { |
| return api(uri, provider, new Properties()); |
| } |
| |
| public static MockWebServer mockOpenStackServer() throws IOException { |
| MockWebServer server = new MockWebServer(); |
| server.start(); |
| URL url = server.url("").url(); |
| server.setDispatcher(getURLReplacingQueueDispatcher(url.toString())); |
| return server; |
| } |
| |
| /** |
| * there's no built-in way to defer evaluation of a response header, hence |
| * this method, which allows us to send back links to the mock server. |
| */ |
| public static QueueDispatcher getURLReplacingQueueDispatcher(final String url) { |
| final String strippedUrl = url.endsWith("/") ? url.substring(0, url.length() - 1) : url; |
| |
| final QueueDispatcher dispatcher = new QueueDispatcher() { |
| protected final BlockingQueue<MockResponse> responseQueue = new LinkedBlockingQueue<MockResponse>(); |
| |
| @Override |
| public MockResponse dispatch(RecordedRequest request) throws InterruptedException { |
| MockResponse response = responseQueue.take(); |
| if (response.getBody() != null) { |
| /* |
| * "URL" must be used in the service catalog sample (such as |
| * access.json or accessRackspace.json) for the declared service |
| * endpoints. |
| */ |
| String newBody = urlTokenPattern.matcher(new String(response.getBody().readByteArray())).replaceAll(": \"" + strippedUrl); |
| |
| response = response.setBody(newBody); |
| } |
| return response; |
| } |
| |
| @Override |
| public void enqueueResponse(MockResponse response) { |
| responseQueue.add(response); |
| } |
| |
| @Override |
| public MockResponse peek() { |
| return super.peek().setSocketPolicy(SocketPolicy.EXPECT_CONTINUE); |
| } |
| }; |
| |
| return dispatcher; |
| } |
| |
| /** |
| * Ensure json headers are included |
| * */ |
| public MockResponse addCommonHeaders(MockResponse mockResponse) { |
| mockResponse.addHeader(HttpHeaders.ACCEPT, MediaType.JSON_UTF_8.withoutParameters()); |
| return mockResponse; |
| } |
| |
| /** |
| * Get a string from a resource |
| * |
| * @param resourceName |
| * The name of the resource. |
| * @return The content of the resource |
| */ |
| public String stringFromResource(String resourceName) { |
| try { |
| return Strings2.toStringAndClose(getClass().getResourceAsStream(resourceName)); |
| } catch (IOException e) { |
| throw Throwables.propagate(e); |
| } |
| } |
| |
| /** |
| * Ensures server received authentication request. |
| */ |
| public void assertAuthentication(MockWebServer server) { |
| assertTrue(server.getRequestCount() >= 1); |
| try { |
| assertEquals(server.takeRequest().getRequestLine(), "POST /tokens HTTP/1.1"); |
| } catch (InterruptedException e) { |
| Throwables.propagate(e); |
| } |
| } |
| |
| /** |
| * Ensures server received an extensions request. Most OpenStack APIs that expose an {@link ExtensionApi} |
| * should use this method. |
| */ |
| public void assertExtensions(MockWebServer server) { |
| assertExtensions(server, ""); |
| } |
| |
| /** |
| * Ensures server received a <i>versioned</i> extensions request. This is necessary for testing APIs whose version |
| * is not included in the Endpoint's {@link Endpoint#getPublicURL() publicURL}. |
| */ |
| public void assertExtensions(MockWebServer server, String path) { |
| assertTrue(server.getRequestCount() >= 1); |
| try { |
| assertEquals(server.takeRequest().getRequestLine(), "GET " + path + "/extensions HTTP/1.1"); |
| } catch (InterruptedException e) { |
| Throwables.propagate(e); |
| } |
| } |
| |
| /** |
| * Ensures the request has a json header. |
| * |
| * @param request |
| * @see RecordedRequest |
| */ |
| private void assertContentTypeIsJSON(RecordedRequest request) { |
| assertTrue(request.getHeaders().values("Content-Type").contains("application/json")); |
| } |
| |
| /** |
| * Ensures the request has a json header for the proper REST methods. |
| * |
| * @param request |
| * @param method |
| * The request method (such as GET). |
| * @param path |
| * The path requested for this REST call. |
| * @see RecordedRequest |
| */ |
| public void assertRequest(RecordedRequest request, String method, String path) { |
| assertEquals(request.getMethod(), method); |
| assertEquals(request.getPath(), path); |
| } |
| |
| /** |
| * Ensures the request is json and has the same contents as the resource |
| * file provided. |
| * |
| * @param request |
| * @param method |
| * The request method (such as GET). |
| * @param resourceLocation |
| * The location of the resource file. Contents will be compared to |
| * the request body as JSON. |
| * @see RecordedRequest |
| */ |
| public void assertRequest(RecordedRequest request, String method, String path, String resourceLocation) { |
| assertRequest(request, method, path); |
| assertContentTypeIsJSON(request); |
| JsonParser parser = new JsonParser(); |
| JsonElement requestJson = null; // to be compared |
| JsonElement resourceJson; // to be compared |
| try { |
| requestJson = parser.parse(request.getBody().readUtf8()); |
| } catch (Exception e) { |
| Throwables.propagate(e); |
| } |
| resourceJson = parser.parse(stringFromResource(resourceLocation)); |
| assertEquals(requestJson, resourceJson); // Compare as JSON |
| } |
| } |