mirror of
https://github.com/apache/jclouds.git
synced 2025-02-17 23:46:13 +00:00
work around DynECT api and session limitations
This commit is contained in:
parent
e2f18eb64a
commit
0d6fbce68a
@ -61,6 +61,11 @@
|
|||||||
<type>test-jar</type>
|
<type>test-jar</type>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.mockwebserver</groupId>
|
||||||
|
<artifactId>mockwebserver</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jclouds.driver</groupId>
|
<groupId>org.jclouds.driver</groupId>
|
||||||
<artifactId>jclouds-slf4j</artifactId>
|
<artifactId>jclouds-slf4j</artifactId>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
package org.jclouds.dynect.v3;
|
package org.jclouds.dynect.v3;
|
||||||
|
|
||||||
import static org.jclouds.Constants.PROPERTY_MAX_REDIRECTS;
|
import static org.jclouds.Constants.PROPERTY_MAX_REDIRECTS;
|
||||||
|
import static org.jclouds.Constants.PROPERTY_RETRY_DELAY_START;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
@ -51,8 +52,9 @@ public class DynECTProviderMetadata extends BaseProviderMetadata {
|
|||||||
|
|
||||||
public static Properties defaultProperties() {
|
public static Properties defaultProperties() {
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
// job polling occurs via redirect loop
|
// job polling occurs via redirect loop and can take a while
|
||||||
properties.setProperty(PROPERTY_MAX_REDIRECTS, "20");
|
properties.setProperty(PROPERTY_MAX_REDIRECTS, "100");
|
||||||
|
properties.setProperty(PROPERTY_RETRY_DELAY_START, "200");
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,10 +18,22 @@
|
|||||||
*/
|
*/
|
||||||
package org.jclouds.dynect.v3.config;
|
package org.jclouds.dynect.v3.config;
|
||||||
|
|
||||||
|
import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
|
||||||
import static org.jclouds.rest.config.BinderUtils.bindClientAndAsyncClient;
|
import static org.jclouds.rest.config.BinderUtils.bindClientAndAsyncClient;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
|
||||||
|
import org.jclouds.Constants;
|
||||||
|
import org.jclouds.concurrent.SingleThreaded;
|
||||||
import org.jclouds.dynect.v3.DynECTApi;
|
import org.jclouds.dynect.v3.DynECTApi;
|
||||||
import org.jclouds.dynect.v3.DynECTAsyncApi;
|
import org.jclouds.dynect.v3.DynECTAsyncApi;
|
||||||
import org.jclouds.dynect.v3.features.SessionApi;
|
import org.jclouds.dynect.v3.features.SessionApi;
|
||||||
@ -29,13 +41,26 @@ import org.jclouds.dynect.v3.features.SessionAsyncApi;
|
|||||||
import org.jclouds.dynect.v3.features.ZoneApi;
|
import org.jclouds.dynect.v3.features.ZoneApi;
|
||||||
import org.jclouds.dynect.v3.features.ZoneAsyncApi;
|
import org.jclouds.dynect.v3.features.ZoneAsyncApi;
|
||||||
import org.jclouds.dynect.v3.filters.SessionManager;
|
import org.jclouds.dynect.v3.filters.SessionManager;
|
||||||
|
import org.jclouds.dynect.v3.handlers.DynECTErrorHandler;
|
||||||
import org.jclouds.dynect.v3.handlers.GetJobRedirectionRetryHandler;
|
import org.jclouds.dynect.v3.handlers.GetJobRedirectionRetryHandler;
|
||||||
|
import org.jclouds.http.HttpErrorHandler;
|
||||||
|
import org.jclouds.http.HttpResponse;
|
||||||
import org.jclouds.http.HttpRetryHandler;
|
import org.jclouds.http.HttpRetryHandler;
|
||||||
|
import org.jclouds.http.HttpUtils;
|
||||||
|
import org.jclouds.http.IOExceptionRetryHandler;
|
||||||
import org.jclouds.http.annotation.ClientError;
|
import org.jclouds.http.annotation.ClientError;
|
||||||
|
import org.jclouds.http.annotation.Redirection;
|
||||||
|
import org.jclouds.http.annotation.ServerError;
|
||||||
|
import org.jclouds.http.handlers.DelegatingErrorHandler;
|
||||||
|
import org.jclouds.http.handlers.DelegatingRetryHandler;
|
||||||
import org.jclouds.http.handlers.RedirectionRetryHandler;
|
import org.jclouds.http.handlers.RedirectionRetryHandler;
|
||||||
|
import org.jclouds.http.internal.HttpWire;
|
||||||
|
import org.jclouds.http.internal.JavaUrlHttpCommandExecutorService;
|
||||||
|
import org.jclouds.io.ContentMetadataCodec;
|
||||||
import org.jclouds.rest.ConfiguresRestClient;
|
import org.jclouds.rest.ConfiguresRestClient;
|
||||||
import org.jclouds.rest.config.RestClientModule;
|
import org.jclouds.rest.config.RestClientModule;
|
||||||
|
|
||||||
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,6 +69,8 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
@ConfiguresRestClient
|
@ConfiguresRestClient
|
||||||
|
// only one job at a time or error "This session already has a job running"
|
||||||
|
@SingleThreaded
|
||||||
public class DynECTRestClientModule extends RestClientModule<DynECTApi, DynECTAsyncApi> {
|
public class DynECTRestClientModule extends RestClientModule<DynECTApi, DynECTAsyncApi> {
|
||||||
|
|
||||||
public static final Map<Class<?>, Class<?>> DELEGATE_MAP = ImmutableMap.<Class<?>, Class<?>> builder()
|
public static final Map<Class<?>, Class<?>> DELEGATE_MAP = ImmutableMap.<Class<?>, Class<?>> builder()
|
||||||
@ -54,6 +81,13 @@ public class DynECTRestClientModule extends RestClientModule<DynECTApi, DynECTAs
|
|||||||
super(DELEGATE_MAP);
|
super(DELEGATE_MAP);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void bindErrorHandlers() {
|
||||||
|
bind(HttpErrorHandler.class).annotatedWith(Redirection.class).to(DynECTErrorHandler.class);
|
||||||
|
bind(HttpErrorHandler.class).annotatedWith(ClientError.class).to(DynECTErrorHandler.class);
|
||||||
|
bind(HttpErrorHandler.class).annotatedWith(ServerError.class).to(DynECTErrorHandler.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void bindRetryHandlers() {
|
protected void bindRetryHandlers() {
|
||||||
bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(SessionManager.class);
|
bind(HttpRetryHandler.class).annotatedWith(ClientError.class).to(SessionManager.class);
|
||||||
@ -67,6 +101,39 @@ public class DynECTRestClientModule extends RestClientModule<DynECTApi, DynECTAs
|
|||||||
super.configure();
|
super.configure();
|
||||||
// Bind apis that are used directly vs via DynECTApi
|
// Bind apis that are used directly vs via DynECTApi
|
||||||
bindClientAndAsyncClient(binder(), SessionApi.class, SessionAsyncApi.class);
|
bindClientAndAsyncClient(binder(), SessionApi.class, SessionAsyncApi.class);
|
||||||
|
|
||||||
|
// dynect returns the following as a 200.
|
||||||
|
// {"status": "failure", "data": {}, "job_id": 274509427, "msgs":
|
||||||
|
// [{"INFO": "token: This session already has a job running", "SOURCE":
|
||||||
|
// "API-B", "ERR_CD": "OPERATION_FAILED", "LVL": "ERROR"}]}
|
||||||
|
bind(JavaUrlHttpCommandExecutorService.class).to(SillyRabbit200sAreForSuccess.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
private static class SillyRabbit200sAreForSuccess extends JavaUrlHttpCommandExecutorService {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private SillyRabbit200sAreForSuccess(HttpUtils utils, ContentMetadataCodec contentMetadataCodec,
|
||||||
|
@Named(Constants.PROPERTY_IO_WORKER_THREADS) ExecutorService ioExecutor,
|
||||||
|
DelegatingRetryHandler retryHandler, IOExceptionRetryHandler ioRetryHandler,
|
||||||
|
DelegatingErrorHandler errorHandler, HttpWire wire, @Named("untrusted") HostnameVerifier verifier,
|
||||||
|
@Named("untrusted") Supplier<SSLContext> untrustedSSLContextProvider)
|
||||||
|
throws SecurityException, NoSuchFieldException {
|
||||||
|
super(utils, contentMetadataCodec, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire, verifier,
|
||||||
|
untrustedSSLContextProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpResponse invoke(HttpURLConnection connection) throws IOException, InterruptedException {
|
||||||
|
HttpResponse response = super.invoke(connection);
|
||||||
|
if (response.getStatusCode() == 200) {
|
||||||
|
byte[] data = closeClientButKeepContentStream(response);
|
||||||
|
String message = data != null ? new String(data, "UTF-8") : null;
|
||||||
|
if (message != null && !message.startsWith("{\"status\": \"success\"")) {
|
||||||
|
response = response.toBuilder().statusCode(400).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. jclouds 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.dynect.v3.handlers;
|
||||||
|
|
||||||
|
import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
|
||||||
|
import static org.jclouds.http.HttpUtils.releasePayload;
|
||||||
|
|
||||||
|
import org.jclouds.http.HttpCommand;
|
||||||
|
import org.jclouds.http.HttpErrorHandler;
|
||||||
|
import org.jclouds.http.HttpResponse;
|
||||||
|
import org.jclouds.http.HttpResponseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Adrian Cole
|
||||||
|
*/
|
||||||
|
public class DynECTErrorHandler implements HttpErrorHandler {
|
||||||
|
private static final String JOB_STILL_RUNNING = "This session already has a job running";
|
||||||
|
|
||||||
|
public void handleError(HttpCommand command, HttpResponse response) {
|
||||||
|
Exception exception = new HttpResponseException(command, response);
|
||||||
|
try {
|
||||||
|
byte[] data = closeClientButKeepContentStream(response);
|
||||||
|
String message = data != null ? new String(data) : null;
|
||||||
|
if (message != null) {
|
||||||
|
exception = new HttpResponseException(command, response, message);
|
||||||
|
if (message.indexOf(JOB_STILL_RUNNING) != -1)
|
||||||
|
exception = new IllegalStateException(JOB_STILL_RUNNING, exception);
|
||||||
|
} else {
|
||||||
|
exception = new HttpResponseException(command, response);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
releasePayload(response);
|
||||||
|
command.setException(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to jclouds, Inc. (jclouds) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. jclouds 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.dynect.v3;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.jclouds.ContextBuilder;
|
||||||
|
import org.jclouds.rest.RestContext;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import com.google.mockwebserver.MockResponse;
|
||||||
|
import com.google.mockwebserver.MockWebServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Adrian Cole
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public class DynectApiMockTest {
|
||||||
|
|
||||||
|
static RestContext<DynECTApi, DynECTAsyncApi> getContext(String uri) {
|
||||||
|
return ContextBuilder.newBuilder("dynect").credentials("jclouds:joe", "letmein").endpoint(uri).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
String session = "{\"status\": \"success\", \"data\": {\"token\": \"FFFFFFFFFF\", \"version\": \"3.3.7\"}, \"job_id\": 254417252, \"msgs\": [{\"INFO\": \"login: Login successful\", \"SOURCE\": \"BLL\", \"ERR_CD\": null, \"LVL\": \"INFO\"}]}";
|
||||||
|
String failure = "{\"status\": \"failure\", \"data\": {}, \"job_id\": 274509427, \"msgs\": [{\"INFO\": \"token: This session already has a job running\", \"SOURCE\": \"API-B\", \"ERR_CD\": \"OPERATION_FAILED\", \"LVL\": \"ERROR\"}]}";
|
||||||
|
|
||||||
|
@Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "This session already has a job running")
|
||||||
|
public void test200OnFailureThrowsExceptionWithoutRetry() throws IOException, InterruptedException {
|
||||||
|
MockWebServer server = new MockWebServer();
|
||||||
|
server.enqueue(new MockResponse().setResponseCode(200).setBody(session));
|
||||||
|
server.enqueue(new MockResponse().setResponseCode(200).setBody(failure));
|
||||||
|
server.play();
|
||||||
|
|
||||||
|
DynECTApi api = getContext(server.getUrl("/").toString()).getApi();
|
||||||
|
|
||||||
|
try {
|
||||||
|
api.getZoneApi().list();
|
||||||
|
} finally {
|
||||||
|
server.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,12 +20,17 @@ package org.jclouds.dynect.v3.internal;
|
|||||||
|
|
||||||
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
|
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
|
||||||
|
|
||||||
|
import org.jclouds.dynect.v3.config.DynECTRestClientModule;
|
||||||
import org.jclouds.http.HttpRequest;
|
import org.jclouds.http.HttpRequest;
|
||||||
import org.jclouds.http.HttpResponse;
|
import org.jclouds.http.HttpResponse;
|
||||||
|
import org.jclouds.http.config.SSLModule;
|
||||||
import org.jclouds.io.Payload;
|
import org.jclouds.io.Payload;
|
||||||
import org.jclouds.io.Payloads;
|
import org.jclouds.io.Payloads;
|
||||||
|
import org.jclouds.rest.ConfiguresRestClient;
|
||||||
import org.jclouds.rest.internal.BaseRestApiExpectTest;
|
import org.jclouds.rest.internal.BaseRestApiExpectTest;
|
||||||
|
|
||||||
|
import com.google.inject.Module;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for writing DynECT Expect tests
|
* Base class for writing DynECT Expect tests
|
||||||
*
|
*
|
||||||
@ -38,6 +43,20 @@ public class BaseDynECTExpectTest<T> extends BaseRestApiExpectTest<T> {
|
|||||||
credential = "letmein";
|
credential = "letmein";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Module createModule() {
|
||||||
|
return new TestDynECTRestClientModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConfiguresRestClient
|
||||||
|
private static final class TestDynECTRestClientModule extends DynECTRestClientModule {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
install(new SSLModule());
|
||||||
|
super.configure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Payload emptyJsonPayload() {
|
public static Payload emptyJsonPayload() {
|
||||||
Payload p = Payloads.newByteArrayPayload(new byte[] {});
|
Payload p = Payloads.newByteArrayPayload(new byte[] {});
|
||||||
p.getContentMetadata().setContentType(APPLICATION_JSON);
|
p.getContentMetadata().setContentType(APPLICATION_JSON);
|
||||||
@ -57,10 +76,14 @@ public class BaseDynECTExpectTest<T> extends BaseRestApiExpectTest<T> {
|
|||||||
|
|
||||||
protected String authToken = "FFFFFFFFFF";
|
protected String authToken = "FFFFFFFFFF";
|
||||||
|
|
||||||
protected HttpRequest createSession = HttpRequest.builder().method("POST")
|
protected HttpRequest createSession = HttpRequest
|
||||||
|
.builder()
|
||||||
|
.method("POST")
|
||||||
.endpoint("https://api2.dynect.net/REST/Session")
|
.endpoint("https://api2.dynect.net/REST/Session")
|
||||||
.addHeader("API-Version", "3.3.7")
|
.addHeader("API-Version", "3.3.7")
|
||||||
.payload(payloadFromStringWithContentType("{\"customer_name\":\"jclouds\",\"user_name\":\"joe\",\"password\":\"letmein\"}",APPLICATION_JSON))
|
.payload(
|
||||||
|
payloadFromStringWithContentType(
|
||||||
|
"{\"customer_name\":\"jclouds\",\"user_name\":\"joe\",\"password\":\"letmein\"}", APPLICATION_JSON))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
protected HttpResponse createSessionResponse = HttpResponse.builder().statusCode(200)
|
protected HttpResponse createSessionResponse = HttpResponse.builder().statusCode(200)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user