diff --git a/labs/dynect/pom.xml b/labs/dynect/pom.xml
index 3cc55b91a3..c2fc25291f 100644
--- a/labs/dynect/pom.xml
+++ b/labs/dynect/pom.xml
@@ -61,6 +61,11 @@
test-jar
test
+
+ com.google.mockwebserver
+ mockwebserver
+ test
+
org.jclouds.driver
jclouds-slf4j
diff --git a/labs/dynect/src/main/java/org/jclouds/dynect/v3/DynECTProviderMetadata.java b/labs/dynect/src/main/java/org/jclouds/dynect/v3/DynECTProviderMetadata.java
index c1c3a90fea..502726ff56 100644
--- a/labs/dynect/src/main/java/org/jclouds/dynect/v3/DynECTProviderMetadata.java
+++ b/labs/dynect/src/main/java/org/jclouds/dynect/v3/DynECTProviderMetadata.java
@@ -19,6 +19,7 @@
package org.jclouds.dynect.v3;
import static org.jclouds.Constants.PROPERTY_MAX_REDIRECTS;
+import static org.jclouds.Constants.PROPERTY_RETRY_DELAY_START;
import java.net.URI;
import java.util.Properties;
@@ -51,8 +52,9 @@ public class DynECTProviderMetadata extends BaseProviderMetadata {
public static Properties defaultProperties() {
Properties properties = new Properties();
- // job polling occurs via redirect loop
- properties.setProperty(PROPERTY_MAX_REDIRECTS, "20");
+ // job polling occurs via redirect loop and can take a while
+ properties.setProperty(PROPERTY_MAX_REDIRECTS, "100");
+ properties.setProperty(PROPERTY_RETRY_DELAY_START, "200");
return properties;
}
diff --git a/labs/dynect/src/main/java/org/jclouds/dynect/v3/config/DynECTRestClientModule.java b/labs/dynect/src/main/java/org/jclouds/dynect/v3/config/DynECTRestClientModule.java
index 770ed7dbf1..2cdd2a609e 100644
--- a/labs/dynect/src/main/java/org/jclouds/dynect/v3/config/DynECTRestClientModule.java
+++ b/labs/dynect/src/main/java/org/jclouds/dynect/v3/config/DynECTRestClientModule.java
@@ -18,10 +18,23 @@
*/
package org.jclouds.dynect.v3.config;
+import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
import static org.jclouds.rest.config.BinderUtils.bindHttpApi;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.Proxy;
+import java.net.URI;
import java.util.Map;
+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.DynECTAsyncApi;
import org.jclouds.dynect.v3.features.SessionApi;
@@ -29,14 +42,29 @@ import org.jclouds.dynect.v3.features.SessionAsyncApi;
import org.jclouds.dynect.v3.features.ZoneApi;
import org.jclouds.dynect.v3.features.ZoneAsyncApi;
import org.jclouds.dynect.v3.filters.SessionManager;
+import org.jclouds.dynect.v3.handlers.DynECTErrorHandler;
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.HttpUtils;
+import org.jclouds.http.IOExceptionRetryHandler;
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.internal.HttpWire;
+import org.jclouds.http.internal.JavaUrlHttpCommandExecutorService;
+import org.jclouds.io.ContentMetadataCodec;
import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.rest.config.RestClientModule;
+import com.google.common.base.Function;
+import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
+import com.google.common.util.concurrent.ListeningExecutorService;
/**
* Configures the DynECT connection.
@@ -44,6 +72,8 @@ import com.google.common.collect.ImmutableMap;
* @author Adrian Cole
*/
@ConfiguresRestClient
+// only one job at a time or error "This session already has a job running"
+@SingleThreaded
public class DynECTRestClientModule extends RestClientModule {
public static final Map, Class>> DELEGATE_MAP = ImmutableMap., Class>> builder()
@@ -54,6 +84,13 @@ public class DynECTRestClientModule extends RestClientModule untrustedSSLContextProvider, Function proxyForURI)
+ throws SecurityException, NoSuchFieldException {
+ super(utils, contentMetadataCodec, ioExecutor, retryHandler, ioRetryHandler, errorHandler, wire, verifier,
+ untrustedSSLContextProvider, proxyForURI);
+ }
+
+ @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;
+ }
+ }
}
diff --git a/labs/dynect/src/main/java/org/jclouds/dynect/v3/handlers/DynECTErrorHandler.java b/labs/dynect/src/main/java/org/jclouds/dynect/v3/handlers/DynECTErrorHandler.java
new file mode 100644
index 0000000000..b2f4afdabb
--- /dev/null
+++ b/labs/dynect/src/main/java/org/jclouds/dynect/v3/handlers/DynECTErrorHandler.java
@@ -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);
+ }
+ }
+}
diff --git a/labs/dynect/src/test/java/org/jclouds/dynect/v3/DynectApiMockTest.java b/labs/dynect/src/test/java/org/jclouds/dynect/v3/DynectApiMockTest.java
new file mode 100644
index 0000000000..09f53bb8cc
--- /dev/null
+++ b/labs/dynect/src/test/java/org/jclouds/dynect/v3/DynectApiMockTest.java
@@ -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 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();
+ }
+ }
+}
diff --git a/labs/dynect/src/test/java/org/jclouds/dynect/v3/internal/BaseDynECTExpectTest.java b/labs/dynect/src/test/java/org/jclouds/dynect/v3/internal/BaseDynECTExpectTest.java
index bafed021ee..6697af88e7 100644
--- a/labs/dynect/src/test/java/org/jclouds/dynect/v3/internal/BaseDynECTExpectTest.java
+++ b/labs/dynect/src/test/java/org/jclouds/dynect/v3/internal/BaseDynECTExpectTest.java
@@ -20,24 +20,43 @@ package org.jclouds.dynect.v3.internal;
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.HttpResponse;
+import org.jclouds.http.config.SSLModule;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
+import org.jclouds.rest.ConfiguresRestClient;
import org.jclouds.rest.internal.BaseRestApiExpectTest;
+import com.google.inject.Module;
+
/**
* Base class for writing DynECT Expect tests
*
* @author Adrian Cole
*/
public class BaseDynECTExpectTest extends BaseRestApiExpectTest {
- public BaseDynECTExpectTest() {
+ public BaseDynECTExpectTest() {
provider = "dynect";
identity = "jclouds:joe";
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() {
Payload p = Payloads.newByteArrayPayload(new byte[] {});
p.getContentMetadata().setContentType(APPLICATION_JSON);
@@ -57,12 +76,16 @@ public class BaseDynECTExpectTest extends BaseRestApiExpectTest {
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")
.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();
-
+
protected HttpResponse createSessionResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResourceWithContentType("/create_session.json", APPLICATION_JSON)).build();