Merge pull request #1324 from jclouds/dynect-hardening

error handling hardening for dynect
This commit is contained in:
Adrian Cole 2013-02-19 21:02:04 -08:00
commit a997d10d16
22 changed files with 296 additions and 91 deletions

View File

@ -35,7 +35,7 @@
<properties>
<test.dynect.endpoint>https://api2.dynect.net/REST</test.dynect.endpoint>
<test.dynect.api-version>3.3.7</test.dynect.api-version>
<test.dynect.api-version>3.3.8</test.dynect.api-version>
<test.dynect.build-version />
<test.dynect.identity />
<test.dynect.credential />
@ -95,6 +95,8 @@
<goal>test</goal>
</goals>
<configuration>
<!-- TODO: remove when multiple jobs/session are supported -->
<threadCount>1</threadCount>
<systemPropertyVariables>
<test.dynect.endpoint>${test.dynect.endpoint}</test.dynect.endpoint>
<test.dynect.api-version>${test.dynect.api-version}</test.dynect.api-version>

View File

@ -73,7 +73,7 @@ public class DynECTApiMetadata extends BaseRestApiMetadata {
.credentialName("Password")
.defaultCredential(ANONYMOUS_IDENTITY)
.documentation(URI.create("https://manage.dynect.net/help/docs/api2/rest/"))
.version("3.3.7")
.version("3.3.8")
.defaultEndpoint("https://api2.dynect.net/REST")
.defaultProperties(DynECTApiMetadata.defaultProperties())
.defaultModules(ImmutableSet.<Class<? extends Module>>builder()

View File

@ -0,0 +1,49 @@
/**
* 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;
/**
* Exceptions likely to be encountered when using {@link DynECTApi}
*
* @author Adrian Cole
*/
public interface DynECTExceptions {
/**
* You must wait until another job is finished before attempting this command
* again
*/
public static class JobStillRunningException extends IllegalStateException {
private static final long serialVersionUID = 1L;
public JobStillRunningException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* The Zone or other resource already exists
*/
public static class TargetExistsException extends IllegalStateException {
private static final long serialVersionUID = 1L;
public TargetExistsException(String message, Throwable cause) {
super(message, cause);
}
}
}

View File

@ -26,18 +26,19 @@ import java.util.Map;
import javax.inject.Named;
import com.google.common.base.Objects.ToStringHelper;
import com.google.common.primitives.UnsignedInteger;
/**
* @author Adrian Cole
*/
public class Record<D extends Map<String, Object>> extends RecordId {
private final int ttl;
private final UnsignedInteger ttl;
@Named("rdata")
private final D rdata;
@ConstructorProperties({ "zone", "fqdn", "record_type", "record_id", "ttl", "rdata" })
protected Record(String zone, String fqdn, String type, long id, int ttl, D rdata) {
protected Record(String zone, String fqdn, String type, long id, UnsignedInteger ttl, D rdata) {
super(zone, fqdn, type, id);
this.ttl = checkNotNull(ttl, "ttl of %s", id);
this.rdata = checkNotNull(rdata, "rdata of %s", id);
@ -46,7 +47,7 @@ public class Record<D extends Map<String, Object>> extends RecordId {
/**
* The current ttl of the record or zero if default for the zone
*/
public int getTTL() {
public UnsignedInteger getTTL() {
return ttl;
}
@ -73,17 +74,24 @@ public class Record<D extends Map<String, Object>> extends RecordId {
public abstract static class Builder<D extends Map<String, Object>, B extends Builder<D, B>> extends RecordId.Builder<B> {
protected int ttl;
protected UnsignedInteger ttl;
protected D rdata;
/**
* @see Record#getTTL()
*/
public B ttl(int ttl) {
public B ttl(UnsignedInteger ttl) {
this.ttl = ttl;
return self();
}
/**
* @see Record#getTTL()
*/
public B ttl(int ttl) {
return ttl(UnsignedInteger.fromIntBits(ttl));
}
/**
* @see Record#getRData()
*/

View File

@ -28,6 +28,7 @@ import org.jclouds.dynect.v3.domain.Zone.SerialStyle;
import org.jclouds.dynect.v3.domain.rdata.SOAData;
import com.google.common.base.Objects.ToStringHelper;
import com.google.common.primitives.UnsignedInteger;
/**
* Start of Authority per RFC 1035
@ -40,7 +41,7 @@ public final class SOARecord extends Record<SOAData> {
private final SerialStyle serialStyle;
@ConstructorProperties({ "zone", "fqdn", "record_type", "record_id", "ttl", "rdata", "serial_style" })
private SOARecord(String zone, String fqdn, String type, long id, int ttl, SOAData rdata, SerialStyle serialStyle) {
private SOARecord(String zone, String fqdn, String type, long id, UnsignedInteger ttl, SOAData rdata, SerialStyle serialStyle) {
super(zone, fqdn, type, id, ttl, rdata);
this.serialStyle = checkNotNull(serialStyle, "serialStyle of %s", id);
}

View File

@ -20,6 +20,7 @@ package org.jclouds.dynect.v3.features;
import java.util.Map;
import org.jclouds.dynect.v3.DynECTExceptions.JobStillRunningException;
import org.jclouds.dynect.v3.domain.Record;
import org.jclouds.dynect.v3.domain.RecordId;
import org.jclouds.dynect.v3.domain.SOARecord;
@ -41,16 +42,16 @@ import com.google.common.collect.FluentIterable;
public interface RecordApi {
/**
* Retrieves a list of resource record ids for all records of any type in the
* given zone;
* given zone throws JobStillRunningException;
*/
FluentIterable<RecordId> list();
FluentIterable<RecordId> list() throws JobStillRunningException;
/**
* retrieves a resource record without regard to type
*
* @return null if not found
*/
Record<? extends Map<String, Object>> get(RecordId recordId);
Record<? extends Map<String, Object>> get(RecordId recordId) throws JobStillRunningException;
/**
* Gets the {@link AAAARecord} or null if not present.
@ -61,7 +62,7 @@ public interface RecordApi {
* {@link RecordId#getId()}
* @return null if not found
*/
Record<AAAAData> getAAAA(String fqdn, long recordId);
Record<AAAAData> getAAAA(String fqdn, long recordId) throws JobStillRunningException;
/**
* Gets the {@link ARecord} or null if not present.
@ -72,7 +73,7 @@ public interface RecordApi {
* {@link RecordId#getId()}
* @return null if not found
*/
Record<AData> getA(String fqdn, long recordId);
Record<AData> getA(String fqdn, long recordId) throws JobStillRunningException;
/**
* Gets the {@link CNAMERecord} or null if not present.
@ -83,7 +84,7 @@ public interface RecordApi {
* {@link RecordId#getId()}
* @return null if not found
*/
Record<CNAMEData> getCNAME(String fqdn, long recordId);
Record<CNAMEData> getCNAME(String fqdn, long recordId) throws JobStillRunningException;
/**
* Gets the {@link MXRecord} or null if not present.
@ -94,7 +95,7 @@ public interface RecordApi {
* {@link RecordId#getId()}
* @return null if not found
*/
Record<MXData> getMX(String fqdn, long recordId);
Record<MXData> getMX(String fqdn, long recordId) throws JobStillRunningException;
/**
* Gets the {@link NSRecord} or null if not present.
@ -105,7 +106,7 @@ public interface RecordApi {
* {@link RecordId#getId()}
* @return null if not found
*/
Record<NSData> getNS(String fqdn, long recordId);
Record<NSData> getNS(String fqdn, long recordId) throws JobStillRunningException;
/**
* Gets the {@link PTRRecord} or null if not present.
@ -116,7 +117,7 @@ public interface RecordApi {
* {@link RecordId#getId()}
* @return null if not found
*/
Record<PTRData> getPTR(String fqdn, long recordId);
Record<PTRData> getPTR(String fqdn, long recordId) throws JobStillRunningException;
/**
* Gets the {@link SOARecord} or null if not present.
@ -127,7 +128,7 @@ public interface RecordApi {
* {@link RecordId#getId()}
* @return null if not found
*/
SOARecord getSOA(String fqdn, long recordId);
SOARecord getSOA(String fqdn, long recordId) throws JobStillRunningException;
/**
* Gets the {@link SRVRecord} or null if not present.
@ -138,7 +139,7 @@ public interface RecordApi {
* {@link RecordId#getId()}
* @return null if not found
*/
Record<SRVData> getSRV(String fqdn, long recordId);
Record<SRVData> getSRV(String fqdn, long recordId) throws JobStillRunningException;
/**
* Gets the {@link TXTRecord} or null if not present.
@ -149,5 +150,5 @@ public interface RecordApi {
* {@link RecordId#getId()}
* @return null if not found
*/
Record<TXTData> getTXT(String fqdn, long recordId);
Record<TXTData> getTXT(String fqdn, long recordId) throws JobStillRunningException;
}

View File

@ -32,6 +32,7 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import org.jclouds.Fallbacks.NullOnNotFoundOr404;
import org.jclouds.dynect.v3.DynECTExceptions.JobStillRunningException;
import org.jclouds.dynect.v3.domain.Record;
import org.jclouds.dynect.v3.domain.RecordId;
import org.jclouds.dynect.v3.domain.SOARecord;
@ -78,7 +79,7 @@ public interface RecordAsyncApi {
@GET
@Path("/AllRecord/{zone}")
@ResponseParser(ToRecordIds.class)
ListenableFuture<FluentIterable<RecordId>> list();
ListenableFuture<FluentIterable<RecordId>> list() throws JobStillRunningException;
/**
* @see RecordApi#get
@ -87,7 +88,7 @@ public interface RecordAsyncApi {
@GET
@SelectJson("data")
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<Record<? extends Map<String, Object>>> get(@BinderParam(RecordIdBinder.class) RecordId recordId);
ListenableFuture<Record<? extends Map<String, Object>>> get(@BinderParam(RecordIdBinder.class) RecordId recordId) throws JobStillRunningException;
static class RecordIdBinder implements Binder {
@SuppressWarnings("unchecked")
@ -113,7 +114,7 @@ public interface RecordAsyncApi {
@Path("/AAAARecord/{zone}/{fqdn}/{id}")
@SelectJson("data")
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<Record<AAAAData>> getAAAA(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId);
ListenableFuture<Record<AAAAData>> getAAAA(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException;
/**
* @see RecordApi#getA
@ -123,7 +124,7 @@ public interface RecordAsyncApi {
@Path("/ARecord/{zone}/{fqdn}/{id}")
@SelectJson("data")
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<Record<AData>> getA(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId);
ListenableFuture<Record<AData>> getA(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException;
/**
* @see RecordApi#getCNAME
@ -133,7 +134,7 @@ public interface RecordAsyncApi {
@Path("/CNAMERecord/{zone}/{fqdn}/{id}")
@SelectJson("data")
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<Record<CNAMEData>> getCNAME(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId);
ListenableFuture<Record<CNAMEData>> getCNAME(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException;
/**
* @see RecordApi#getMX
@ -143,7 +144,7 @@ public interface RecordAsyncApi {
@Path("/MXRecord/{zone}/{fqdn}/{id}")
@SelectJson("data")
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<Record<MXData>> getMX(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId);
ListenableFuture<Record<MXData>> getMX(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException;
/**
* @see RecordApi#getNS
@ -153,7 +154,7 @@ public interface RecordAsyncApi {
@Path("/NSRecord/{zone}/{fqdn}/{id}")
@SelectJson("data")
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<Record<NSData>> getNS(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId);
ListenableFuture<Record<NSData>> getNS(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException;
/**
* @see RecordApi#getPTR
@ -163,7 +164,7 @@ public interface RecordAsyncApi {
@Path("/PTRRecord/{zone}/{fqdn}/{id}")
@SelectJson("data")
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<Record<PTRData>> getPTR(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId);
ListenableFuture<Record<PTRData>> getPTR(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException;
/**
* @see RecordApi#getSOA
@ -173,7 +174,7 @@ public interface RecordAsyncApi {
@Path("/SOARecord/{zone}/{fqdn}/{id}")
@SelectJson("data")
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<SOARecord> getSOA(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId);
ListenableFuture<SOARecord> getSOA(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException;
/**
* @see RecordApi#getSRV
@ -183,7 +184,7 @@ public interface RecordAsyncApi {
@Path("/SRVRecord/{zone}/{fqdn}/{id}")
@SelectJson("data")
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<Record<SRVData>> getSRV(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId);
ListenableFuture<Record<SRVData>> getSRV(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException;
/**
* @see RecordApi#getTXT
@ -193,5 +194,5 @@ public interface RecordAsyncApi {
@Path("/TXTRecord/{zone}/{fqdn}/{id}")
@SelectJson("data")
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<Record<TXTData>> getTXT(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId);
ListenableFuture<Record<TXTData>> getTXT(@PathParam("fqdn") String fqdn, @PathParam("id") long recordId) throws JobStillRunningException;
}

View File

@ -18,6 +18,8 @@
*/
package org.jclouds.dynect.v3.features;
import org.jclouds.dynect.v3.DynECTExceptions.TargetExistsException;
import org.jclouds.dynect.v3.DynECTExceptions.JobStillRunningException;
import org.jclouds.dynect.v3.domain.CreatePrimaryZone;
import org.jclouds.dynect.v3.domain.Job;
import org.jclouds.dynect.v3.domain.Zone;
@ -34,7 +36,7 @@ public interface ZoneApi {
/**
* Lists all zone ids.
*/
FluentIterable<String> list();
FluentIterable<String> list() throws JobStillRunningException;
/**
* Creates a new primary zone.
@ -43,7 +45,7 @@ public interface ZoneApi {
* required parameters to create the zone.
* @return unpublished zone
*/
Zone create(CreatePrimaryZone zone);
Zone create(CreatePrimaryZone zone) throws JobStillRunningException, TargetExistsException;
/**
* Creates a new primary zone with one hour default TTL and
@ -55,7 +57,7 @@ public interface ZoneApi {
* email address of the contact
* @return unpublished zone
*/
Zone createWithContact(String fqdn, String contact);
Zone createWithContact(String fqdn, String contact) throws JobStillRunningException, TargetExistsException;
/**
* Retrieves information about the specified zone.
@ -66,7 +68,7 @@ public interface ZoneApi {
* @return null if not found
*/
@Nullable
Zone get(String fqdn);
Zone get(String fqdn) throws JobStillRunningException;
/**
* deletes the specified zone.
@ -76,7 +78,16 @@ public interface ZoneApi {
* @return null if not found
*/
@Nullable
Job delete(String fqdn);
Job delete(String fqdn) throws JobStillRunningException;
/**
* Deletes changes to the specified zone that have been created during the
* current session but not yet published to the zone.
*
* @param fqdn
* fqdn of the zone to delete changes from ex {@code jclouds.org}
*/
Job deleteChanges(String fqdn) throws JobStillRunningException;
/**
* Publishes the current zone
@ -85,7 +96,7 @@ public interface ZoneApi {
* fqdn of the zone to publish. ex
* {@code jclouds.org}
*/
Zone publish(String fqdn);
Zone publish(String fqdn) throws JobStillRunningException;
/**
* freezes the specified zone.
@ -93,7 +104,7 @@ public interface ZoneApi {
* @param fqdn
* fqdn of the zone to freeze ex {@code jclouds.org}
*/
Job freeze(String fqdn);
Job freeze(String fqdn) throws JobStillRunningException;
/**
* thaws the specified zone.
@ -101,5 +112,5 @@ public interface ZoneApi {
* @param fqdn
* fqdn of the zone to thaw ex {@code jclouds.org}
*/
Job thaw(String fqdn);
Job thaw(String fqdn) throws JobStillRunningException;
}

View File

@ -31,6 +31,8 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import org.jclouds.Fallbacks.NullOnNotFoundOr404;
import org.jclouds.dynect.v3.DynECTExceptions.TargetExistsException;
import org.jclouds.dynect.v3.DynECTExceptions.JobStillRunningException;
import org.jclouds.dynect.v3.domain.CreatePrimaryZone;
import org.jclouds.dynect.v3.domain.CreatePrimaryZone.ToFQDN;
import org.jclouds.dynect.v3.domain.Job;
@ -61,7 +63,6 @@ import com.google.common.util.concurrent.ListenableFuture;
// required for all calls
@Produces(APPLICATION_JSON)
@Headers(keys = "API-Version", values = "{jclouds.api-version}")
@Path("/Zone")
@RequestFilters(SessionManager.class)
public interface ZoneAsyncApi {
@ -70,29 +71,31 @@ public interface ZoneAsyncApi {
*/
@Named("ListZones")
@GET
@Path("/Zone")
@SelectJson("data")
@Transform(ExtractZoneNames.class)
ListenableFuture<FluentIterable<String>> list();
ListenableFuture<FluentIterable<String>> list() throws JobStillRunningException;
/**
* @see ZoneApi#get
*/
@Named("GetZone")
@GET
@Path("/{fqdn}")
@Path("/Zone/{fqdn}")
@SelectJson("data")
@Fallback(NullOnNotFoundOr404.class)
ListenableFuture<Zone> get(@PathParam("fqdn") String fqdn);
ListenableFuture<Zone> get(@PathParam("fqdn") String fqdn) throws JobStillRunningException;
/**
* @see ZoneApi#create
*/
@Named("CreatePrimaryZone")
@POST
@Path("/{fqdn}")
@Path("/Zone/{fqdn}")
@SelectJson("data")
ListenableFuture<Zone> create(
@PathParam("fqdn") @ParamParser(ToFQDN.class) @BinderParam(BindToJsonPayload.class) CreatePrimaryZone createZone);
@PathParam("fqdn") @ParamParser(ToFQDN.class) @BinderParam(BindToJsonPayload.class) CreatePrimaryZone createZone)
throws JobStillRunningException, TargetExistsException;
/**
* @see ZoneApi#createWithContact
@ -100,19 +103,29 @@ public interface ZoneAsyncApi {
@Named("CreatePrimaryZone")
@POST
@Payload("%7B\"rname\":\"{contact}\",\"serial_style\":\"increment\",\"ttl\":3600%7D")
@Path("/{fqdn}")
@Path("/Zone/{fqdn}")
@SelectJson("data")
ListenableFuture<Zone> createWithContact(@PathParam("fqdn") String fqdn, @PayloadParam("contact") String contact);
ListenableFuture<Zone> createWithContact(@PathParam("fqdn") String fqdn, @PayloadParam("contact") String contact)
throws JobStillRunningException, TargetExistsException;
/**
* @see ZoneApi#delete
*/
@Named("DeleteZone")
@DELETE
@Path("/{fqdn}")
@Path("/Zone/{fqdn}")
@Fallback(NullOnNotFoundOr404.class)
@Consumes(APPLICATION_JSON)
ListenableFuture<Job> delete(@PathParam("fqdn") String fqdn);
ListenableFuture<Job> delete(@PathParam("fqdn") String fqdn) throws JobStillRunningException;
/**
* @see ZoneApi#deleteChanges
*/
@Named("DeleteZoneChanges")
@DELETE
@Path("/ZoneChanges/{fqdn}")
@Consumes(APPLICATION_JSON)
ListenableFuture<Job> deleteChanges(@PathParam("fqdn") String fqdn) throws JobStillRunningException;
/**
* @see ZoneApi#publish
@ -120,27 +133,27 @@ public interface ZoneAsyncApi {
@Named("PublishZone")
@PUT
@Payload("{\"publish\":true}")
@Path("/{fqdn}")
@Path("/Zone/{fqdn}")
@SelectJson("data")
ListenableFuture<Zone> publish(@PathParam("fqdn") String fqdn);
ListenableFuture<Zone> publish(@PathParam("fqdn") String fqdn) throws JobStillRunningException;
/**
* @see ZoneApi#freeze
*/
@Named("FreezeZone")
@PUT
@Path("/{fqdn}")
@Path("/Zone/{fqdn}")
@Payload("{\"freeze\":true}")
@Consumes(APPLICATION_JSON)
ListenableFuture<Job> freeze(@PathParam("fqdn") String fqdn);
ListenableFuture<Job> freeze(@PathParam("fqdn") String fqdn) throws JobStillRunningException;
/**
* @see ZoneApi#thaw
*/
@Named("ThawZone")
@PUT
@Path("/{fqdn}")
@Path("/Zone/{fqdn}")
@Payload("{\"thaw\":true}")
@Consumes(APPLICATION_JSON)
ListenableFuture<Job> thaw(@PathParam("fqdn") String fqdn);
ListenableFuture<Job> thaw(@PathParam("fqdn") String fqdn) throws JobStillRunningException;
}

View File

@ -21,6 +21,8 @@ package org.jclouds.dynect.v3.handlers;
import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
import static org.jclouds.http.HttpUtils.releasePayload;
import org.jclouds.dynect.v3.DynECTExceptions.TargetExistsException;
import org.jclouds.dynect.v3.DynECTExceptions.JobStillRunningException;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpErrorHandler;
import org.jclouds.http.HttpResponse;
@ -31,6 +33,8 @@ import org.jclouds.http.HttpResponseException;
*/
public class DynECTErrorHandler implements HttpErrorHandler {
private static final String JOB_STILL_RUNNING = "This session already has a job running";
private static final String OPERATION_BLOCKED = "Operation blocked by current task";
private static final String TARGET_EXISTS = "Name already exists";
public void handleError(HttpCommand command, HttpResponse response) {
Exception exception = new HttpResponseException(command, response);
@ -40,7 +44,11 @@ public class DynECTErrorHandler implements HttpErrorHandler {
if (message != null) {
exception = new HttpResponseException(command, response, message);
if (message.indexOf(JOB_STILL_RUNNING) != -1)
exception = new IllegalStateException(JOB_STILL_RUNNING, exception);
exception = new JobStillRunningException(JOB_STILL_RUNNING, exception);
else if (message.indexOf(OPERATION_BLOCKED) != -1)
exception = new JobStillRunningException(OPERATION_BLOCKED, exception);
else if (message.indexOf(TARGET_EXISTS) != -1)
exception = new TargetExistsException(TARGET_EXISTS, exception);
} else {
exception = new HttpResponseException(command, response);
}

View File

@ -21,6 +21,8 @@ package org.jclouds.dynect.v3;
import java.io.IOException;
import org.jclouds.ContextBuilder;
import org.jclouds.dynect.v3.DynECTExceptions.TargetExistsException;
import org.jclouds.dynect.v3.DynECTExceptions.JobStillRunningException;
import org.jclouds.rest.RestContext;
import org.testng.annotations.Test;
@ -31,21 +33,59 @@ import com.google.mockwebserver.MockWebServer;
*
* @author Adrian Cole
*/
@Test
@Test(singleThreaded = true)
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\"}]}";
String session = "{\"status\": \"success\", \"data\": {\"token\": \"FFFFFFFFFF\", \"version\": \"3.3.8\"}, \"job_id\": 254417252, \"msgs\": [{\"INFO\": \"login: Login successful\", \"SOURCE\": \"BLL\", \"ERR_CD\": null, \"LVL\": \"INFO\"}]}";
@Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "This session already has a job running")
public void test200OnFailureThrowsExceptionWithoutRetry() throws IOException, InterruptedException {
String running = "{\"status\": \"running\", \"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 = JobStillRunningException.class, expectedExceptionsMessageRegExp = "This session already has a job running")
public void test200OnFailureThrowsExceptionWithoutRetryWhenJobRunning() throws IOException, InterruptedException {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setResponseCode(200).setBody(session));
server.enqueue(new MockResponse().setResponseCode(200).setBody(failure));
server.enqueue(new MockResponse().setResponseCode(200).setBody(running));
server.play();
DynECTApi api = getContext(server.getUrl("/").toString()).getApi();
try {
api.getZoneApi().list();
} finally {
server.shutdown();
}
}
String taskBlocking = "[{\"status\": \"failure\", \"data\": {}, \"job_id\": 275545493, \"msgs\": [{\"INFO\": \"zone: Operation blocked by current task\", \"SOURCE\": \"BLL\", \"ERR_CD\": \"ILLEGAL_OPERATION\", \"LVL\": \"ERROR\"}, {\"INFO\": \"task_name: ProvisionZone\", \"SOURCE\": \"BLL\", \"ERR_CD\": null, \"LVL\": \"INFO\"}, {\"INFO\": \"task_id: 39120953\", \"SOURCE\": \"BLL\", \"ERR_CD\": null, \"LVL\": \"INFO\"}]}]";
@Test(expectedExceptions = JobStillRunningException.class, expectedExceptionsMessageRegExp = "Operation blocked by current task")
public void test200OnFailureThrowsExceptionWithoutRetryWhenOperationBlocked() throws IOException,
InterruptedException {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setResponseCode(200).setBody(session));
server.enqueue(new MockResponse().setResponseCode(200).setBody(taskBlocking));
server.play();
DynECTApi api = getContext(server.getUrl("/").toString()).getApi();
try {
api.getZoneApi().list();
} finally {
server.shutdown();
}
}
String targetExists = "[{\"status\": \"failure\", \"data\": {}, \"job_id\": 275533917, \"msgs\": [{\"INFO\": \"name: Name already exists\", \"SOURCE\": \"BLL\", \"ERR_CD\": \"TARGET_EXISTS\", \"LVL\": \"ERROR\"}, {\"INFO\": \"create: You already have this zone.\", \"SOURCE\": \"BLL\", \"ERR_CD\": null, \"LVL\": \"INFO\"}]}]";
@Test(expectedExceptions = TargetExistsException.class, expectedExceptionsMessageRegExp = "Name already exists")
public void test200OnFailureThrowsExceptionWithoutRetryOnNameExists() throws IOException, InterruptedException {
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setResponseCode(200).setBody(session));
server.enqueue(new MockResponse().setResponseCode(200).setBody(targetExists));
server.play();
DynECTApi api = getContext(server.getUrl("/").toString()).getApi();

View File

@ -48,7 +48,7 @@ import org.testng.annotations.Test;
public class RecordApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest getSOA = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/SOARecord/adrianc.zone.dynecttest.jclouds.org/adrianc.zone.dynecttest.jclouds.org/50976579")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload()).build();
@ -74,7 +74,7 @@ public class RecordApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest getAAAA = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/AAAARecord/adrianc.zone.dynecttest.jclouds.org/adrianc.zone.dynecttest.jclouds.org/50976579")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload()).build();
@ -100,7 +100,7 @@ public class RecordApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest getA = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/ARecord/adrianc.zone.dynecttest.jclouds.org/adrianc.zone.dynecttest.jclouds.org/50976579")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload()).build();
@ -126,7 +126,7 @@ public class RecordApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest getCNAME = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/CNAMERecord/adrianc.zone.dynecttest.jclouds.org/adrianc.zone.dynecttest.jclouds.org/50976579")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload()).build();
@ -152,7 +152,7 @@ public class RecordApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest getMX = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/MXRecord/adrianc.zone.dynecttest.jclouds.org/adrianc.zone.dynecttest.jclouds.org/50976579")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload()).build();
@ -178,7 +178,7 @@ public class RecordApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest getNS = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/NSRecord/adrianc.zone.dynecttest.jclouds.org/adrianc.zone.dynecttest.jclouds.org/50976579")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload()).build();
@ -204,7 +204,7 @@ public class RecordApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest getPTR = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/PTRRecord/adrianc.zone.dynecttest.jclouds.org/adrianc.zone.dynecttest.jclouds.org/50976579")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload()).build();
@ -241,7 +241,7 @@ public class RecordApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest getSRV = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/SRVRecord/adrianc.zone.dynecttest.jclouds.org/adrianc.zone.dynecttest.jclouds.org/50976579")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload()).build();
@ -262,7 +262,7 @@ public class RecordApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest getTXT = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/TXTRecord/adrianc.zone.dynecttest.jclouds.org/adrianc.zone.dynecttest.jclouds.org/50976579")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload()).build();
@ -288,7 +288,7 @@ public class RecordApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest list = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/AllRecord/adrianc.zone.dynecttest.jclouds.org")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload()).build();

View File

@ -52,14 +52,14 @@ public class RecordApiLiveTest extends BaseDynECTApiLiveTest {
assertTrue(record.getId() > 0, "Id cannot be zero for RecordId: " + record);
checkNotNull(record.getType(), "Type cannot be null for RecordId: %s", record);
checkNotNull(record.getFQDN(), "FQDN cannot be null for RecordId: %s", record);
checkNotNull(record.getZone(), "Serial cannot be null for RecordId: %s", record);
checkNotNull(record.getZone(), "Zone cannot be null for RecordId: %s", record);
}
private void checkRecord(Record<? extends Map<String, Object>> record) {
checkRecordId(record);
assertTrue(record.getRData().size() > 0, "RData entries should be present for cannot be zero for Record: "
+ record);
assertTrue(record.getTTL() > -1, "TTL cannot be negative for RecordId: " + record);
checkNotNull(record.getTTL(), "TTL cannot be null for RecordId: %s", record);
}
@Test

View File

@ -48,7 +48,7 @@ public class SessionApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest isValid = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/Session")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload())
.build();
@ -71,7 +71,7 @@ public class SessionApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest logout = HttpRequest.builder().method("DELETE")
.endpoint("https://api2.dynect.net/REST/Session")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload())
.build();

View File

@ -26,6 +26,7 @@ import static org.testng.Assert.assertNull;
import org.jclouds.dynect.v3.DynECTApi;
import org.jclouds.dynect.v3.domain.CreatePrimaryZone;
import org.jclouds.dynect.v3.internal.BaseDynECTApiExpectTest;
import org.jclouds.dynect.v3.parse.DeleteZoneChangesResponseTest;
import org.jclouds.dynect.v3.parse.DeleteZoneResponseTest;
import org.jclouds.dynect.v3.parse.GetZoneResponseTest;
import org.jclouds.dynect.v3.parse.ListZonesResponseTest;
@ -40,7 +41,7 @@ import org.testng.annotations.Test;
public class ZoneApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest get = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/Zone/jclouds.org")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload())
.build();
@ -56,7 +57,7 @@ public class ZoneApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest create = HttpRequest.builder().method("POST")
.endpoint("https://api2.dynect.net/REST/Zone/jclouds.org")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(stringPayload("{\"rname\":\"jimmy@jclouds.org\",\"serial_style\":\"increment\",\"ttl\":3600}"))
.build();
@ -83,7 +84,7 @@ public class ZoneApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest list = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/Zone")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload())
.build();
@ -97,9 +98,26 @@ public class ZoneApiExpectTest extends BaseDynECTApiExpectTest {
new ListZonesResponseTest().expected().toString());
}
HttpRequest deleteChanges = HttpRequest.builder().method("DELETE")
.endpoint("https://api2.dynect.net/REST/ZoneChanges/jclouds.org")
.addHeader("API-Version", "3.3.8")
.addHeader(ACCEPT, APPLICATION_JSON)
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload())
.build();
HttpResponse deleteChangesResponse = HttpResponse.builder().statusCode(200)
.payload(payloadFromResourceWithContentType("/delete_zone_changes.json", APPLICATION_JSON)).build();
public void testDeleteChangesWhenResponseIs2xx() {
DynECTApi success = requestsSendResponses(createSession, createSessionResponse, deleteChanges, deleteChangesResponse);
assertEquals(success.getZoneApi().deleteChanges("jclouds.org").toString(),
new DeleteZoneChangesResponseTest().expected().toString());
}
HttpRequest delete = HttpRequest.builder().method("DELETE")
.endpoint("https://api2.dynect.net/REST/Zone/jclouds.org")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader(ACCEPT, APPLICATION_JSON)
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload())
@ -121,7 +139,7 @@ public class ZoneApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest publish = HttpRequest.builder().method("PUT")
.endpoint("https://api2.dynect.net/REST/Zone/jclouds.org")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader("Auth-Token", authToken)
.payload(stringPayload("{\"publish\":true}"))
.build();
@ -134,7 +152,7 @@ public class ZoneApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest freeze = HttpRequest.builder().method("PUT")
.endpoint("https://api2.dynect.net/REST/Zone/jclouds.org")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader(ACCEPT, APPLICATION_JSON)
.addHeader("Auth-Token", authToken)
.payload(stringPayload("{\"freeze\":true}"))
@ -148,7 +166,7 @@ public class ZoneApiExpectTest extends BaseDynECTApiExpectTest {
HttpRequest thaw = HttpRequest.builder().method("PUT")
.endpoint("https://api2.dynect.net/REST/Zone/jclouds.org")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader(ACCEPT, APPLICATION_JSON)
.addHeader("Auth-Token", authToken)
.payload(stringPayload("{\"thaw\":true}"))

View File

@ -36,7 +36,7 @@ import com.google.common.collect.ImmutableList;
/**
* @author Adrian Cole
*/
@Test(groups = "live", testName = "ZoneApiLiveTest")
@Test(groups = "live", singleThreaded = true, testName = "ZoneApiLiveTest")
public class ZoneApiLiveTest extends BaseDynECTApiLiveTest {
private void checkZone(Zone zone) {
@ -102,6 +102,13 @@ public class ZoneApiLiveTest extends BaseDynECTApiLiveTest {
}
@Test(dependsOnMethods = "testThawZone")
public void testDeleteZoneChanges() {
Job job = api().deleteChanges(fqdn);
assertEquals(job.getStatus(), Status.SUCCESS);
assertEquals(context.getApi().getJob(job.getId()), job);
}
@Test(dependsOnMethods = "testDeleteZoneChanges")
public void testDeleteZone() {
Job job = api().delete(fqdn);
assertEquals(job.getStatus(), Status.SUCCESS);

View File

@ -39,7 +39,7 @@ public class GetJobRedirectionRetryHandlerExpectTest extends BaseDynECTApiExpect
HttpRequest thaw = HttpRequest.builder().method("PUT")
.endpoint("https://api2.dynect.net/REST/Zone/jclouds.org")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader(ACCEPT, APPLICATION_JSON)
.addHeader("Auth-Token", authToken)
.payload(stringPayload("{\"thaw\":true}"))
@ -52,7 +52,7 @@ public class GetJobRedirectionRetryHandlerExpectTest extends BaseDynECTApiExpect
HttpRequest job = HttpRequest.builder().method("GET")
.endpoint("https://api2.dynect.net/REST/Job/1234")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.addHeader(ACCEPT, APPLICATION_JSON)
.addHeader("Auth-Token", authToken)
.payload(emptyJsonPayload())

View File

@ -80,7 +80,7 @@ public class BaseDynECTExpectTest<T> extends BaseRestApiExpectTest<T> {
.builder()
.method("POST")
.endpoint("https://api2.dynect.net/REST/Session")
.addHeader("API-Version", "3.3.7")
.addHeader("API-Version", "3.3.8")
.payload(
payloadFromStringWithContentType(
"{\"customer_name\":\"jclouds\",\"user_name\":\"joe\",\"password\":\"letmein\"}", APPLICATION_JSON))

View File

@ -42,6 +42,6 @@ public class CreateSessionResponseTest extends BaseDynECTParseTest<Session> {
@SelectJson("data")
@Consumes(MediaType.APPLICATION_JSON)
public Session expected() {
return Session.forTokenAndVersion("FFFFFFFFFF", "3.3.7");
return Session.forTokenAndVersion("FFFFFFFFFF", "3.3.8");
}
}

View File

@ -0,0 +1,45 @@
/**
* 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.parse;
import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
import org.jclouds.dynect.v3.domain.Job;
import org.jclouds.dynect.v3.internal.BaseDynECTParseTest;
import org.testng.annotations.Test;
/**
* @author Adrian Cole
*/
@Test(groups = "unit")
public class DeleteZoneChangesResponseTest extends BaseDynECTParseTest<Job> {
@Override
public String resource() {
return "/delete_zone_changes.json";
}
@Override
@Consumes(MediaType.APPLICATION_JSON)
public Job expected() {
return Job.success(275625520l);
}
}

View File

@ -1 +1 @@
{"status": "success", "data": {"token": "FFFFFFFFFF", "version": "3.3.7"}, "job_id": 254417252, "msgs": [{"INFO": "login: Login successful", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}
{"status": "success", "data": {"token": "FFFFFFFFFF", "version": "3.3.8"}, "job_id": 254417252, "msgs": [{"INFO": "login: Login successful", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}

View File

@ -0,0 +1 @@
{"status": "success", "data": {}, "job_id": 275625520, "msgs": [{"INFO": "discard: 0 zone changes discarded", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}