diff --git a/labs/aws-route53/src/test/java/org/jclouds/aws/route53/features/AWSResourceRecordSetApiLiveTest.java b/labs/aws-route53/src/test/java/org/jclouds/aws/route53/features/AWSRecordSetApiLiveTest.java similarity index 83% rename from labs/aws-route53/src/test/java/org/jclouds/aws/route53/features/AWSResourceRecordSetApiLiveTest.java rename to labs/aws-route53/src/test/java/org/jclouds/aws/route53/features/AWSRecordSetApiLiveTest.java index 7d6e517f4d..407e3e9aad 100644 --- a/labs/aws-route53/src/test/java/org/jclouds/aws/route53/features/AWSResourceRecordSetApiLiveTest.java +++ b/labs/aws-route53/src/test/java/org/jclouds/aws/route53/features/AWSRecordSetApiLiveTest.java @@ -18,15 +18,15 @@ */ package org.jclouds.aws.route53.features; -import org.jclouds.route53.features.ResourceRecordSetApiLiveTest; +import org.jclouds.route53.features.RecordSetApiLiveTest; import org.testng.annotations.Test; /** * @author Adrian Cole */ @Test(groups = "live", testName = "AWSResourceRecordSetApiLiveTest") -public class AWSResourceRecordSetApiLiveTest extends ResourceRecordSetApiLiveTest { - public AWSResourceRecordSetApiLiveTest() { +public class AWSRecordSetApiLiveTest extends RecordSetApiLiveTest { + public AWSRecordSetApiLiveTest() { provider = "aws-route53"; } } diff --git a/labs/route53/src/main/java/org/jclouds/route53/InvalidChangeBatchException.java b/labs/route53/src/main/java/org/jclouds/route53/InvalidChangeBatchException.java new file mode 100644 index 0000000000..2a2c088577 --- /dev/null +++ b/labs/route53/src/main/java/org/jclouds/route53/InvalidChangeBatchException.java @@ -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.route53; + +import org.jclouds.http.HttpResponseException; + +import com.google.common.collect.ImmutableList; + +/** + * @see + * + * @author Adrian Cole + */ +public class InvalidChangeBatchException extends IllegalArgumentException { + private static final long serialVersionUID = 1L; + + private final ImmutableList messages; + + public InvalidChangeBatchException(ImmutableList messages, HttpResponseException cause) { + super(messages.toString(), cause); + this.messages = messages; + } + + public ImmutableList getMessages() { + return messages; + } +} diff --git a/labs/route53/src/main/java/org/jclouds/route53/Route53Api.java b/labs/route53/src/main/java/org/jclouds/route53/Route53Api.java index f981f7c595..a06770b2c7 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/Route53Api.java +++ b/labs/route53/src/main/java/org/jclouds/route53/Route53Api.java @@ -25,7 +25,7 @@ import javax.ws.rs.PathParam; import org.jclouds.concurrent.Timeout; import org.jclouds.rest.annotations.Delegate; import org.jclouds.route53.domain.Change; -import org.jclouds.route53.features.ResourceRecordSetApi; +import org.jclouds.route53.features.RecordSetApi; import org.jclouds.route53.features.ZoneApi; /** @@ -56,8 +56,8 @@ public interface Route53Api { ZoneApi getZoneApi(); /** - * Provides synchronous access to ResourceRecordSet features. + * Provides synchronous access to record set features. */ @Delegate - ResourceRecordSetApi getResourceRecordSetApiForZone(@PathParam("zoneId") String zoneId); + RecordSetApi getRecordSetApiForZone(@PathParam("zoneId") String zoneId); } diff --git a/labs/route53/src/main/java/org/jclouds/route53/Route53AsyncApi.java b/labs/route53/src/main/java/org/jclouds/route53/Route53AsyncApi.java index edf09ee1b1..c08cc9e1a1 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/Route53AsyncApi.java +++ b/labs/route53/src/main/java/org/jclouds/route53/Route53AsyncApi.java @@ -30,7 +30,7 @@ import org.jclouds.rest.annotations.VirtualHost; import org.jclouds.rest.annotations.XMLResponseParser; import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; import org.jclouds.route53.domain.Change; -import org.jclouds.route53.features.ResourceRecordSetAsyncApi; +import org.jclouds.route53.features.RecordSetAsyncApi; import org.jclouds.route53.features.ZoneAsyncApi; import org.jclouds.route53.filters.RestAuthentication; import org.jclouds.route53.xml.ChangeHandler; @@ -67,8 +67,8 @@ public interface Route53AsyncApi { ZoneAsyncApi getZoneApi(); /** - * Provides asynchronous access to ResourceRecordSet features. + * Provides asynchronous access to record set features. */ @Delegate - ResourceRecordSetAsyncApi getResourceRecordSetApiForZone(@PathParam("zoneId") String zoneId); + RecordSetAsyncApi getRecordSetApiForZone(@PathParam("zoneId") String zoneId); } diff --git a/labs/route53/src/main/java/org/jclouds/route53/binders/BindChangeBatch.java b/labs/route53/src/main/java/org/jclouds/route53/binders/BindChangeBatch.java new file mode 100644 index 0000000000..58b4ccf3d0 --- /dev/null +++ b/labs/route53/src/main/java/org/jclouds/route53/binders/BindChangeBatch.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.route53.binders; + +import static org.jclouds.io.Payloads.newStringPayload; + +import javax.inject.Singleton; + +import org.jclouds.http.HttpRequest; +import org.jclouds.io.Payload; +import org.jclouds.rest.Binder; +import org.jclouds.route53.domain.ChangeBatch; +import org.jclouds.route53.domain.ChangeBatch.ActionOnResourceRecordSet; +import org.jclouds.route53.functions.SerializeRRS; + +/** + * + * @author Adrian Cole + */ +@Singleton +public class BindChangeBatch implements Binder { + + private static final SerializeRRS xml = new SerializeRRS(); + + @SuppressWarnings("unchecked") + @Override + public R bindToRequest(R request, Object payload) { + ChangeBatch from = ChangeBatch.class.cast(payload); + StringBuilder b = new StringBuilder(); + b.append(""); + if (from.getComment().isPresent()) + b.append("").append(from.getComment().get()).append(""); + b.append(""); + for (ActionOnResourceRecordSet change : from) + b.append("").append("").append(change.getAction()).append("") + .append(xml.apply(change.getRRS())).append(""); + b.append(""); + b.append(""); + Payload xmlPayload = newStringPayload(b.toString()); + xmlPayload.getContentMetadata().setContentType("application/xml"); + return (R) request.toBuilder().payload(xmlPayload).build(); + } +} diff --git a/labs/route53/src/main/java/org/jclouds/route53/binders/BindNextRecord.java b/labs/route53/src/main/java/org/jclouds/route53/binders/BindNextRecord.java index ef024c88b4..2a5327b825 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/binders/BindNextRecord.java +++ b/labs/route53/src/main/java/org/jclouds/route53/binders/BindNextRecord.java @@ -23,7 +23,7 @@ import javax.inject.Singleton; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest.Builder; import org.jclouds.rest.Binder; -import org.jclouds.route53.domain.ResourceRecordSetIterable.NextRecord; +import org.jclouds.route53.domain.RecordSetIterable.NextRecord; /** * diff --git a/labs/route53/src/main/java/org/jclouds/route53/config/Route53RestClientModule.java b/labs/route53/src/main/java/org/jclouds/route53/config/Route53RestClientModule.java index adca0446d1..823b377bc8 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/config/Route53RestClientModule.java +++ b/labs/route53/src/main/java/org/jclouds/route53/config/Route53RestClientModule.java @@ -26,15 +26,20 @@ import javax.inject.Singleton; import org.jclouds.aws.config.AWSRestClientModule; import org.jclouds.date.DateService; import org.jclouds.date.TimeStamp; +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.annotation.ClientError; +import org.jclouds.http.annotation.Redirection; +import org.jclouds.http.annotation.ServerError; import org.jclouds.rest.ConfiguresRestClient; import org.jclouds.rest.RequestSigner; import org.jclouds.route53.Route53Api; import org.jclouds.route53.Route53AsyncApi; -import org.jclouds.route53.features.ResourceRecordSetApi; -import org.jclouds.route53.features.ResourceRecordSetAsyncApi; +import org.jclouds.route53.features.RecordSetApi; +import org.jclouds.route53.features.RecordSetAsyncApi; import org.jclouds.route53.features.ZoneApi; import org.jclouds.route53.features.ZoneAsyncApi; import org.jclouds.route53.filters.RestAuthentication; +import org.jclouds.route53.handlers.Route53ErrorHandler; import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; @@ -49,7 +54,7 @@ import com.google.inject.Provides; public class Route53RestClientModule extends AWSRestClientModule { public static final Map, Class> DELEGATE_MAP = ImmutableMap., Class> builder()// .put(ZoneApi.class, ZoneAsyncApi.class) - .put(ResourceRecordSetApi.class, ResourceRecordSetAsyncApi.class).build(); + .put(RecordSetApi.class, RecordSetAsyncApi.class).build(); public Route53RestClientModule() { super(TypeToken.of(Route53Api.class), TypeToken.of(Route53AsyncApi.class), DELEGATE_MAP); @@ -66,4 +71,11 @@ public class Route53RestClientModule extends AWSRestClientModule { + + public static ChangeBatch createAll(Iterable toCreate) { + return builder().createAll(toCreate).build(); + } + + public static ChangeBatch deleteAll(Iterable toDelete) { + return builder().deleteAll(toDelete).build(); + } + + private final Optional comment; + private final List changes; + + public static enum Action { + CREATE, DELETE; + } + + public static class ActionOnResourceRecordSet { + private final Action action; + private final RecordSet rrs; + + private ActionOnResourceRecordSet(Action action, RecordSet rrs) { + this.action = action; + this.rrs = rrs; + } + + public Action getAction() { + return action; + } + + public RecordSet getRRS() { + return rrs; + } + + @Override + public int hashCode() { + return Objects.hashCode(action, rrs); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + ActionOnResourceRecordSet that = ActionOnResourceRecordSet.class.cast(obj); + return equal(this.action, that.rrs); + } + + @Override + public String toString() { + return toStringHelper("").omitNullValues().add("action", action).add("rrs", rrs).toString(); + } + } + + private ChangeBatch(Optional comment, ImmutableList changes) { + this.comment = checkNotNull(comment, "comment"); + this.changes = checkNotNull(changes, "changes%s", comment.isPresent() ? " for %s " + comment.get() : ""); + checkArgument(!changes.isEmpty(), "no changes%s", comment.isPresent() ? " for %s " + comment.get() : ""); + } + + /** + * Any comments you want to include about the changes in this change batch. + */ + public Optional getComment() { + return comment; + } + + @Override + protected List delegate() { + return changes; + } + + @Override + public int hashCode() { + return Objects.hashCode(comment, changes); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + ChangeBatch that = ChangeBatch.class.cast(obj); + return equal(this.comment, that.comment) && equal(this.changes, that.changes); + } + + @Override + public String toString() { + return toStringHelper("").omitNullValues().add("comment", comment.orNull()).add("changes", changes).toString(); + } + + public static Builder builder() { + return new Builder(); + } + + public final static class Builder { + private Optional comment = Optional.absent(); + private ImmutableList.Builder changes = ImmutableList.builder(); + + /** + * @see ChangeBatch#getComment() + */ + public Builder comment(String comment) { + this.comment = Optional.fromNullable(comment); + return this; + } + + public Builder create(RecordSet rrs) { + this.changes.add(new ActionOnResourceRecordSet(Action.CREATE, rrs)); + return this; + } + + public Builder createAll(Iterable toCreate) { + for (RecordSet rrs : toCreate) + create(rrs); + return this; + } + + public Builder delete(RecordSet rrs) { + this.changes.add(new ActionOnResourceRecordSet(Action.DELETE, rrs)); + return this; + } + + public Builder deleteAll(Iterable toDelete) { + for (RecordSet rrs : toDelete) + delete(rrs); + return this; + } + + public ChangeBatch build() { + return new ChangeBatch(comment, changes.build()); + } + } +} diff --git a/labs/route53/src/main/java/org/jclouds/route53/domain/RecordSet.java b/labs/route53/src/main/java/org/jclouds/route53/domain/RecordSet.java new file mode 100644 index 0000000000..89aaaa8757 --- /dev/null +++ b/labs/route53/src/main/java/org/jclouds/route53/domain/RecordSet.java @@ -0,0 +1,394 @@ +/** + * 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.route53.domain; + +import static com.google.common.base.Objects.equal; +import static com.google.common.base.Objects.toStringHelper; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Set; + +import org.jclouds.route53.domain.RecordSet.RecordSubset.Latency; +import org.jclouds.route53.domain.RecordSet.RecordSubset.Weighted; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableSet; + +/** + * + * @author Adrian Cole + */ +public class RecordSet { + + protected final String name; + protected final Type type; + protected final Optional ttl; + protected final Set values; + protected final Optional aliasTarget; + + public enum Type { + A, AAAA, CNAME, MX, NS, PTR, SOA, SPF, SRV, TXT; + } + + /** + * In this case, the rrs is an alias, and it points to another Route53 hosted + * resource, such as an ELB, S3 bucket, or zone. + */ + public static class AliasTarget { + + public static AliasTarget dnsNameInZone(String dnsName, String zoneId) { + return new AliasTarget(dnsName, zoneId); + } + + private final String dnsName; + private final String zoneId; + + private AliasTarget(String dnsName, String zoneId) { + this.dnsName = checkNotNull(dnsName, "dnsName"); + this.zoneId = checkNotNull(zoneId, "zoneId of %s", dnsName); + } + + public String getDNSName() { + return dnsName; + } + + public String getZoneId() { + return zoneId; + } + + @Override + public int hashCode() { + return Objects.hashCode(zoneId, dnsName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + AliasTarget other = AliasTarget.class.cast(obj); + return equal(this.dnsName, other.dnsName) && equal(this.zoneId, other.zoneId); + } + + @Override + public String toString() { + return toStringHelper("").omitNullValues().add("dnsName", dnsName).add("zoneId", zoneId).toString(); + } + } + + /** + * A portion of a RRs who share the same name and type + */ + public static abstract class RecordSubset extends RecordSet { + public static final class Weighted extends RecordSubset { + + private final int weight; + + private Weighted(String id, String name, Type type, int weight, Optional ttl, Set values, + Optional aliasTarget) { + super(id, name, type, ttl, values, aliasTarget); + this.weight = weight; + } + + /** + * determines what portion of traffic for the current resource record + * set is routed to this subset. + */ + public int getWeight() { + return weight; + } + + @Override + ToStringHelper differentiate(ToStringHelper in) { + return in.add("weight", weight); + } + } + + public static final class Latency extends RecordSubset { + + private final String region; + + private Latency(String id, String name, Type type, String region, Optional ttl, Set values, + Optional aliasTarget) { + super(id, name, type, ttl, values, aliasTarget); + this.region = checkNotNull(region, "region of %s", name); + } + + /** + * The Amazon EC2 region where the resource that is specified in this + * resource record set resides. + */ + public String getRegion() { + return region; + } + + @Override + ToStringHelper differentiate(ToStringHelper in) { + return in.add("region", region); + } + } + + private final String id; + + private RecordSubset(String id, String name, Type type, Optional ttl, Set values, + Optional aliasTarget) { + super(name, type, ttl, values, aliasTarget); + this.id = checkNotNull(id, "id of %s", name); + } + + /** + * The identifier that differentiates beyond {@code name} and {@code type} + */ + public String getId() { + return id; + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), id); + } + + @Override + public boolean equals(Object obj) { + if (super.equals(obj) && obj instanceof RecordSubset) { + RecordSubset that = RecordSubset.class.cast(obj); + return equal(this.id, that.id); + } + return false; + } + + abstract ToStringHelper differentiate(ToStringHelper in); + + @Override + public String toString() { + return differentiate(toStringHelper("").omitNullValues().add("id", id).add("name", name).add("type", type)) + .add("ttl", ttl.orNull()).add("values", values.isEmpty() ? null : values).add("aliasTarget", aliasTarget.orNull()) + .toString(); + } + } + + private RecordSet(String name, Type type, Optional ttl, Set values, Optional aliasTarget) { + this.name = checkNotNull(name, "name"); + this.type = checkNotNull(type, "type of %s", name); + this.ttl = checkNotNull(ttl, "ttl for %s", name); + this.values = checkNotNull(values, "values for %s", name); + this.aliasTarget = checkNotNull(aliasTarget, "aliasTarget for %s", aliasTarget); + } + + /** + * The name of the domain. + */ + public String getName() { + return name; + } + + /** + * The resource record set type. + */ + public Type getType() { + return type; + } + + /** + * Present in all resource record sets except aliases. The resource record + * cache time to live (TTL), in seconds. + */ + public Optional getTTL() { + return ttl; + } + + /** + * Type-specific values that differentiates the RRs in this set. Empty if + * {@link #getType()} is {@link Type#A} or {@link Type#AAAA} and + * {@link #getAliasTarget} is present. + */ + public Set getValues() { + return values; + } + + /** + * When present, {@link #getType()} is {@link Type#A} or {@link Type#AAAA}. + * Instead of {@link #getValues()} containing the corresponding IP addresses, + * the server will follow this link and resolve one on-demand. + */ + public Optional getAliasTarget() { + return aliasTarget; + } + + @Override + public int hashCode() { + return Objects.hashCode(name, type); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + RecordSet other = RecordSet.class.cast(obj); + return equal(this.name, other.name) && equal(this.type, other.type); + } + + @Override + public String toString() { + return toStringHelper("").omitNullValues().add("name", name).add("type", type).add("ttl", ttl.orNull()) + .add("values", values.isEmpty() ? null : values).add("aliasTarget", aliasTarget.orNull()).toString(); + } + + public static Builder builder() { + return new Builder(); + } + + public Builder toBuilder() { + return builder().from(this); + } + + public final static class Builder { + private String id; + private String name; + private Type type; + private Optional ttl = Optional.absent(); + private ImmutableSet.Builder values = ImmutableSet. builder(); + private String dnsName; + private String zoneId; + private Integer weight; + private String region; + + /** + * @see RecordSubset#getId() + */ + public Builder id(String id) { + this.id = id; + return this; + } + + /** + * @see RecordSet#getName() + */ + public Builder name(String name) { + this.name = name; + return this; + } + + /** + * @see RecordSet#getType() + */ + public Builder type(Type type) { + this.type = type; + return this; + } + + /** + * @see RecordSet#getTTL() + */ + public Builder ttl(Integer ttl) { + this.ttl = Optional.fromNullable(ttl); + return this; + } + + /** + * @see RecordSet#getAliasTarget() + */ + public Builder dnsName(String dnsName) { + this.dnsName = dnsName; + return this; + } + + /** + * @see RecordSet#getAliasTarget() + */ + public Builder zoneId(String zoneId) { + this.zoneId = zoneId; + return this; + } + + /** + * @see RecordSet#getAliasTarget() + */ + public Builder aliasTarget(AliasTarget aliasTarget) { + if (aliasTarget == null) { + dnsName = null; + zoneId = null; + } else { + dnsName = aliasTarget.dnsName; + zoneId = aliasTarget.zoneId; + } + return this; + } + + /** + * @see RecordSet#getValues() + */ + public Builder add(String values) { + this.values.add(values); + return this; + } + + /** + * @see RecordSet#getValues() + */ + public Builder addAll(Iterable values) { + this.values.addAll(values); + return this; + } + + /** + * @see RecordSubset.Weighted + */ + public Builder weight(Integer weight) { + this.weight = weight; + return this; + } + + /** + * @see RecordSubset.Latency + */ + public Builder region(String region) { + this.region = region; + return this; + } + + public RecordSet build() { + Optional aliasTarget = dnsName != null ? Optional.fromNullable(AliasTarget.dnsNameInZone(dnsName, zoneId)) + : Optional. absent(); + if (weight != null) { + return new RecordSubset.Weighted(id, name, type, weight, ttl, values.build(), aliasTarget); + } else if (region != null) { + return new RecordSubset.Latency(id, name, type, region, ttl, values.build(), aliasTarget); + } + return new RecordSet(name, type, ttl, values.build(), aliasTarget); + } + + public Builder from(RecordSet in) { + if (in instanceof RecordSubset) + id(RecordSubset.class.cast(in).id); + if (in instanceof Weighted) { + weight(Weighted.class.cast(in).weight); + } else if (in instanceof Latency) { + region(Latency.class.cast(in).region); + } + return this.name(in.name).type(in.type).ttl(in.ttl.orNull()).addAll(in.values) + .aliasTarget(in.aliasTarget.orNull()); + } + } +} diff --git a/labs/route53/src/main/java/org/jclouds/route53/domain/ResourceRecordSetIterable.java b/labs/route53/src/main/java/org/jclouds/route53/domain/RecordSetIterable.java similarity index 83% rename from labs/route53/src/main/java/org/jclouds/route53/domain/ResourceRecordSetIterable.java rename to labs/route53/src/main/java/org/jclouds/route53/domain/RecordSetIterable.java index 0e7448a4e2..8173f03b5f 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/domain/ResourceRecordSetIterable.java +++ b/labs/route53/src/main/java/org/jclouds/route53/domain/RecordSetIterable.java @@ -27,7 +27,7 @@ import java.util.Iterator; import org.jclouds.collect.IterableWithMarker; import org.jclouds.javax.annotation.Nullable; -import org.jclouds.route53.domain.ResourceRecordSet.Type; +import org.jclouds.route53.domain.RecordSet.Type; import com.google.common.base.Objects; import com.google.common.base.Optional; @@ -37,12 +37,12 @@ import com.google.common.collect.ImmutableList; * * @author Adrian Cole */ -public class ResourceRecordSetIterable extends IterableWithMarker { +public class RecordSetIterable extends IterableWithMarker { - private final Iterable items; + private final Iterable items; private final Optional nextRecord; - private ResourceRecordSetIterable(Iterable items, @Nullable NextRecord nextRecord) { + private RecordSetIterable(Iterable items, @Nullable NextRecord nextRecord) { this.items = checkNotNull(items, "items"); this.nextRecord = Optional.fromNullable(nextRecord); } @@ -61,7 +61,7 @@ public class ResourceRecordSetIterable extends IterableWithMarker iterator() { + public Iterator iterator() { return items.iterator(); } @@ -76,7 +76,7 @@ public class ResourceRecordSetIterable extends IterableWithMarker items = ImmutableList. builder(); + private ImmutableList.Builder items = ImmutableList. builder(); private String nextRecordName; private Type nextRecordType; private String nextRecordIdentifier; - public Builder add(ResourceRecordSet item) { + public Builder add(RecordSet item) { this.items.add(item); return this; } - public Builder addAll(Iterable items) { + public Builder addAll(Iterable items) { this.items.addAll(items); return this; } @@ -187,10 +187,10 @@ public class ResourceRecordSetIterable extends IterableWithMarker { - - private final String name; - private final Type type; - private final Optional ttl; - private final ImmutableList items; - - private ResourceRecordSet(String name, Type type, Optional ttl, ImmutableList items) { - this.name = checkNotNull(name, "name"); - this.type = checkNotNull(type, "type of %s", name); - this.ttl = checkNotNull(ttl, "ttl for %s", name); - this.items = checkNotNull(items, "items for %s", name); - } - - /** - * The name of the domain. - */ - public String getName() { - return name; - } - - /** - * The resource record set type. - */ - public Type getType() { - return type; - } - - /** - * Present in all resource record sets except aliases. The resource record - * cache time to live (TTL), in seconds. - */ - public Optional getTTL() { - return ttl; - } - - @Override - public int hashCode() { - return Objects.hashCode(name, type, ttl, items); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null || getClass() != obj.getClass()) - return false; - ResourceRecordSet other = ResourceRecordSet.class.cast(obj); - return equal(this.name, other.name) && equal(this.type, other.type) && equal(this.ttl, other.ttl) - && equal(this.items, other.items); - } - - @Override - public String toString() { - return toStringHelper("").omitNullValues().add("name", name).add("type", type).add("ttl", ttl.orNull()) - .add("resourceRecords", items).toString(); - } - - @Override - protected List delegate() { - return items; - } - - public enum Type { - A, AAAA, CNAME, MX, NS, PTR, SOA, SPF, SRV, TXT, UNRECOGNIZED; - - public static Type fromValue(String type) { - try { - return valueOf(checkNotNull(type, "type")); - } catch (IllegalArgumentException e) { - return UNRECOGNIZED; - } - } - } - - public static Builder builder() { - return new Builder(); - } - - public Builder toBuilder() { - return builder().from(this); - } - - public final static class Builder { - private String name; - private Type type; - private Optional ttl = Optional.absent(); - private ImmutableList.Builder items = ImmutableList. builder(); - - /** - * @see ResourceRecordSet#getName() - */ - public Builder name(String name) { - this.name = name; - return this; - } - - /** - * @see ResourceRecordSet#getType() - */ - public Builder type(Type type) { - this.type = type; - return this; - } - - /** - * @see ResourceRecordSet#getTTL() - */ - public Builder ttl(Integer ttl) { - this.ttl = Optional.fromNullable(ttl); - return this; - } - - public Builder add(String item) { - this.items.add(item); - return this; - } - - public Builder addAll(Iterable items) { - this.items.addAll(items); - return this; - } - - public ResourceRecordSet build() { - return new ResourceRecordSet(name, type, ttl, items.build()); - } - - public Builder from(ResourceRecordSet in) { - return this.name(in.name).type(in.type).ttl(in.ttl.orNull()).addAll(in.items); - } - } -} diff --git a/labs/route53/src/main/java/org/jclouds/route53/domain/Zone.java b/labs/route53/src/main/java/org/jclouds/route53/domain/Zone.java index 226e6ff0b5..8a7d02ac23 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/domain/Zone.java +++ b/labs/route53/src/main/java/org/jclouds/route53/domain/Zone.java @@ -88,9 +88,9 @@ public final class Zone { return true; if (obj == null || getClass() != obj.getClass()) return false; - Zone other = Zone.class.cast(obj); - return equal(this.id, other.id) && equal(this.name, other.name) - && equal(this.callerReference, other.callerReference); + Zone that = Zone.class.cast(obj); + return equal(this.id, that.id) && equal(this.name, that.name) + && equal(this.callerReference, that.callerReference); } @Override diff --git a/labs/route53/src/main/java/org/jclouds/route53/features/ResourceRecordSetApi.java b/labs/route53/src/main/java/org/jclouds/route53/features/RecordSetApi.java similarity index 59% rename from labs/route53/src/main/java/org/jclouds/route53/features/ResourceRecordSetApi.java rename to labs/route53/src/main/java/org/jclouds/route53/features/RecordSetApi.java index cd24e2b751..143ae6787f 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/features/ResourceRecordSetApi.java +++ b/labs/route53/src/main/java/org/jclouds/route53/features/RecordSetApi.java @@ -22,33 +22,56 @@ import java.util.concurrent.TimeUnit; import org.jclouds.collect.PagedIterable; import org.jclouds.concurrent.Timeout; -import org.jclouds.route53.domain.ResourceRecordSet; -import org.jclouds.route53.domain.ResourceRecordSetIterable; -import org.jclouds.route53.domain.ResourceRecordSetIterable.NextRecord; +import org.jclouds.javax.annotation.Nullable; +import org.jclouds.route53.domain.Change; +import org.jclouds.route53.domain.ChangeBatch; +import org.jclouds.route53.domain.RecordSet; +import org.jclouds.route53.domain.RecordSetIterable; +import org.jclouds.route53.domain.RecordSetIterable.NextRecord; /** - * @see ResourceRecordSetAsyncApi + * @see RecordSetAsyncApi * @see * @author Adrian Cole */ @Timeout(duration = 30, timeUnit = TimeUnit.SECONDS) -public interface ResourceRecordSetApi { +public interface RecordSetApi { + + /** + * schedules creation of the resource record set. + */ + Change create(RecordSet rrs); + + /** + * applies a batch of changes atomically. + */ + Change apply(ChangeBatch changes); /** * returns all resource record sets in order. */ - PagedIterable list(); + PagedIterable list(); /** * retrieves up to 100 resource record sets in order. */ - ResourceRecordSetIterable listFirstPage(); + RecordSetIterable listFirstPage(); /** - * retrieves up to 100 resource record sets in order, starting at {@code nextRecord} + * retrieves up to 100 resource record sets in order, starting at + * {@code nextRecord} */ - ResourceRecordSetIterable listAt(NextRecord nextRecord); + RecordSetIterable listAt(NextRecord nextRecord); + /** + * This action deletes a resource record set. + * + * @param rrs + * the resource record set to delete + * @return null if not found or the change in progress + */ + @Nullable + Change delete(RecordSet rrs); } diff --git a/labs/route53/src/main/java/org/jclouds/route53/features/RecordSetAsyncApi.java b/labs/route53/src/main/java/org/jclouds/route53/features/RecordSetAsyncApi.java new file mode 100644 index 0000000000..782e70a5c7 --- /dev/null +++ b/labs/route53/src/main/java/org/jclouds/route53/features/RecordSetAsyncApi.java @@ -0,0 +1,126 @@ +/** + * 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.route53.features; + +import static javax.ws.rs.core.MediaType.APPLICATION_XML; + +import javax.inject.Named; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import org.jclouds.collect.PagedIterable; +import org.jclouds.rest.annotations.BinderParam; +import org.jclouds.rest.annotations.ExceptionParser; +import org.jclouds.rest.annotations.ParamParser; +import org.jclouds.rest.annotations.Payload; +import org.jclouds.rest.annotations.PayloadParam; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.Transform; +import org.jclouds.rest.annotations.VirtualHost; +import org.jclouds.rest.annotations.XMLResponseParser; +import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404; +import org.jclouds.route53.binders.BindChangeBatch; +import org.jclouds.route53.binders.BindNextRecord; +import org.jclouds.route53.domain.Change; +import org.jclouds.route53.domain.ChangeBatch; +import org.jclouds.route53.domain.RecordSet; +import org.jclouds.route53.domain.RecordSetIterable; +import org.jclouds.route53.domain.RecordSetIterable.NextRecord; +import org.jclouds.route53.filters.RestAuthentication; +import org.jclouds.route53.functions.RecordSetIterableToPagedIterable; +import org.jclouds.route53.functions.SerializeRRS; +import org.jclouds.route53.xml.ChangeHandler; +import org.jclouds.route53.xml.ListResourceRecordSetsResponseHandler; + +import com.google.common.util.concurrent.ListenableFuture; + +/** + * @see RecordSetApi + * @see + * @author Adrian Cole + */ +@RequestFilters(RestAuthentication.class) +@VirtualHost +@Path("/{jclouds.api-version}/hostedzone/{zoneId}") +public interface RecordSetAsyncApi { + /** + * @see RecordSetApi#create + */ + @Named("ChangeResourceRecordSets") + @POST + @Produces(APPLICATION_XML) + @Path("/rrset") + @Payload("CREATE{rrs}") + @XMLResponseParser(ChangeHandler.class) + ListenableFuture create(@PayloadParam("rrs") @ParamParser(SerializeRRS.class) RecordSet rrs); + + /** + * @see RecordSetApi#apply + */ + @Named("ChangeResourceRecordSets") + @POST + @Produces(APPLICATION_XML) + @Path("/rrset") + @XMLResponseParser(ChangeHandler.class) + ListenableFuture apply(@BinderParam(BindChangeBatch.class) ChangeBatch changes); + + /** + * @see RecordSetApi#list() + */ + @Named("ListResourceRecordSets") + @GET + @Path("/rrset") + @XMLResponseParser(ListResourceRecordSetsResponseHandler.class) + @Transform(RecordSetIterableToPagedIterable.class) + ListenableFuture> list(); + + /** + * @see RecordSetApi#listFirstPage + */ + @Named("ListResourceRecordSets") + @GET + @Path("/rrset") + @XMLResponseParser(ListResourceRecordSetsResponseHandler.class) + ListenableFuture listFirstPage(); + + /** + * @see RecordSetApi#listAt(NextRecord) + */ + @Named("ListResourceRecordSets") + @GET + @Path("/rrset") + @XMLResponseParser(ListResourceRecordSetsResponseHandler.class) + ListenableFuture listAt(@BinderParam(BindNextRecord.class) NextRecord nextRecord); + + /** + * @see RecordSetApi#delete + */ + @Named("ChangeResourceRecordSets") + @POST + @Produces(APPLICATION_XML) + @Path("/rrset") + @Payload("DELETE{rrs}") + @XMLResponseParser(ChangeHandler.class) + @ExceptionParser(ReturnNullOnNotFoundOr404.class) + ListenableFuture delete(@PayloadParam("rrs") @ParamParser(SerializeRRS.class) RecordSet rrs); +} diff --git a/labs/route53/src/main/java/org/jclouds/route53/features/ResourceRecordSetAsyncApi.java b/labs/route53/src/main/java/org/jclouds/route53/features/ResourceRecordSetAsyncApi.java deleted file mode 100644 index cf0d87aa6a..0000000000 --- a/labs/route53/src/main/java/org/jclouds/route53/features/ResourceRecordSetAsyncApi.java +++ /dev/null @@ -1,80 +0,0 @@ -/** - * 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.route53.features; - -import javax.inject.Named; -import javax.ws.rs.GET; -import javax.ws.rs.Path; - -import org.jclouds.collect.PagedIterable; -import org.jclouds.rest.annotations.BinderParam; -import org.jclouds.rest.annotations.RequestFilters; -import org.jclouds.rest.annotations.Transform; -import org.jclouds.rest.annotations.VirtualHost; -import org.jclouds.rest.annotations.XMLResponseParser; -import org.jclouds.route53.binders.BindNextRecord; -import org.jclouds.route53.domain.ResourceRecordSet; -import org.jclouds.route53.domain.ResourceRecordSetIterable; -import org.jclouds.route53.domain.ResourceRecordSetIterable.NextRecord; -import org.jclouds.route53.filters.RestAuthentication; -import org.jclouds.route53.functions.ResourceRecordSetsToPagedIterable; -import org.jclouds.route53.xml.ListResourceRecordSetsResponseHandler; - -import com.google.common.util.concurrent.ListenableFuture; - -/** - * @see ResourceRecordSetApi - * @see - * @author Adrian Cole - */ -@RequestFilters(RestAuthentication.class) -@VirtualHost -@Path("/{jclouds.api-version}/hostedzone/{zoneId}") -public interface ResourceRecordSetAsyncApi { - - /** - * @see ResourceRecordSetApi#list() - */ - @Named("ListResourceRecordSets") - @GET - @Path("/rrset") - @XMLResponseParser(ListResourceRecordSetsResponseHandler.class) - @Transform(ResourceRecordSetsToPagedIterable.class) - ListenableFuture> list(); - - /** - * @see ResourceRecordSetApi#listFirstPage - */ - @Named("ListResourceRecordSets") - @GET - @Path("/rrset") - @XMLResponseParser(ListResourceRecordSetsResponseHandler.class) - ListenableFuture listFirstPage(); - - /** - * @see ResourceRecordSetApi#listAt(NextRecord) - */ - @Named("ListResourceRecordSets") - @GET - @Path("/rrset") - @XMLResponseParser(ListResourceRecordSetsResponseHandler.class) - ListenableFuture listAt(@BinderParam(BindNextRecord.class) NextRecord nextRecord); -} diff --git a/labs/route53/src/main/java/org/jclouds/route53/features/ZoneApi.java b/labs/route53/src/main/java/org/jclouds/route53/features/ZoneApi.java index a7a5a3246c..8be8dd9686 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/features/ZoneApi.java +++ b/labs/route53/src/main/java/org/jclouds/route53/features/ZoneApi.java @@ -29,7 +29,6 @@ import org.jclouds.route53.domain.Change.Status; import org.jclouds.route53.domain.NewZone; import org.jclouds.route53.domain.Zone; import org.jclouds.route53.domain.ZoneAndNameServers; -import org.jclouds.route53.options.ListZonesOptions; /** * @see ZoneAsyncApi @@ -64,23 +63,21 @@ public interface ZoneApi { */ NewZone createWithReferenceAndComment(String name, String callerReference, String comment); - /** - * The action retrieves a specified number of zones in order. - * - *
- * You can paginate the results using the - * {@link ListZonesOptions parameter} - * - * @param options - * the options describing the zones query - */ - IterableWithMarker list(ListZonesOptions options); - /** * returns all zones in order. */ PagedIterable list(); + /** + * retrieves up to 100 zones in order. + */ + IterableWithMarker listFirstPage(); + + /** + * retrieves up to 100 zones in order, starting at {@code nextMarker} + */ + IterableWithMarker listAt(String nextMarker); + /** * Retrieves information about the specified zone, including its nameserver * configuration diff --git a/labs/route53/src/main/java/org/jclouds/route53/features/ZoneAsyncApi.java b/labs/route53/src/main/java/org/jclouds/route53/features/ZoneAsyncApi.java index 92c6f31db5..86853bacec 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/features/ZoneAsyncApi.java +++ b/labs/route53/src/main/java/org/jclouds/route53/features/ZoneAsyncApi.java @@ -27,6 +27,7 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import org.jclouds.collect.IterableWithMarker; import org.jclouds.collect.PagedIterable; @@ -44,7 +45,6 @@ import org.jclouds.route53.domain.Zone; import org.jclouds.route53.domain.ZoneAndNameServers; import org.jclouds.route53.filters.RestAuthentication; import org.jclouds.route53.functions.ZonesToPagedIterable; -import org.jclouds.route53.options.ListZonesOptions; import org.jclouds.route53.xml.ChangeHandler; import org.jclouds.route53.xml.CreateHostedZoneResponseHandler; import org.jclouds.route53.xml.GetHostedZoneResponseHandler; @@ -98,13 +98,22 @@ public interface ZoneAsyncApi { ListenableFuture> list(); /** - * @see ZoneApi#list(ListZonesOptions) + * @see ZoneApi#listFirstPage */ @Named("ListHostedZones") @GET @Path("/hostedzone") @XMLResponseParser(ListHostedZonesResponseHandler.class) - ListenableFuture> list(ListZonesOptions options); + ListenableFuture> listFirstPage(); + + /** + * @see ZoneApi#listAt(String) + */ + @Named("ListHostedZones") + @GET + @Path("/hostedzone") + @XMLResponseParser(ListHostedZonesResponseHandler.class) + ListenableFuture> listAt(@QueryParam("marker") String nextMarker); /** * @see ZoneApi#get() diff --git a/labs/route53/src/main/java/org/jclouds/route53/functions/ResourceRecordSetsToPagedIterable.java b/labs/route53/src/main/java/org/jclouds/route53/functions/RecordSetIterableToPagedIterable.java similarity index 67% rename from labs/route53/src/main/java/org/jclouds/route53/functions/ResourceRecordSetsToPagedIterable.java rename to labs/route53/src/main/java/org/jclouds/route53/functions/RecordSetIterableToPagedIterable.java index 2f4fd295f9..01077954f9 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/functions/ResourceRecordSetsToPagedIterable.java +++ b/labs/route53/src/main/java/org/jclouds/route53/functions/RecordSetIterableToPagedIterable.java @@ -25,9 +25,9 @@ import javax.inject.Inject; import org.jclouds.collect.IterableWithMarker; import org.jclouds.collect.internal.CallerArg0ToPagedIterable; import org.jclouds.route53.Route53Api; -import org.jclouds.route53.domain.ResourceRecordSet; -import org.jclouds.route53.domain.ResourceRecordSetIterable.NextRecord; -import org.jclouds.route53.features.ResourceRecordSetApi; +import org.jclouds.route53.domain.RecordSet; +import org.jclouds.route53.domain.RecordSetIterable.NextRecord; +import org.jclouds.route53.features.RecordSetApi; import com.google.common.annotations.Beta; import com.google.common.base.Function; @@ -36,23 +36,23 @@ import com.google.common.base.Function; * @author Adrian Cole */ @Beta -public class ResourceRecordSetsToPagedIterable extends - CallerArg0ToPagedIterable { +public class RecordSetIterableToPagedIterable extends + CallerArg0ToPagedIterable { private final Route53Api api; @Inject - protected ResourceRecordSetsToPagedIterable(Route53Api api) { + protected RecordSetIterableToPagedIterable(Route53Api api) { this.api = checkNotNull(api, "api"); } @Override - protected Function> markerToNextForCallingArg0(String zoneId) { - final ResourceRecordSetApi resourceRecordSetApi = api.getResourceRecordSetApiForZone(zoneId); - return new Function>() { + protected Function> markerToNextForCallingArg0(String zoneId) { + final RecordSetApi resourceRecordSetApi = api.getRecordSetApiForZone(zoneId); + return new Function>() { @Override - public IterableWithMarker apply(Object input) { + public IterableWithMarker apply(Object input) { return resourceRecordSetApi.listAt(NextRecord.class.cast(input)); } diff --git a/labs/route53/src/main/java/org/jclouds/route53/functions/SerializeRRS.java b/labs/route53/src/main/java/org/jclouds/route53/functions/SerializeRRS.java new file mode 100644 index 0000000000..cd7adccc7b --- /dev/null +++ b/labs/route53/src/main/java/org/jclouds/route53/functions/SerializeRRS.java @@ -0,0 +1,65 @@ +/** + * 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.route53.functions; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.rest.annotations.ParamParser; +import org.jclouds.route53.domain.RecordSet; +import org.jclouds.route53.domain.RecordSet.RecordSubset; +import org.jclouds.route53.domain.RecordSet.RecordSubset.Latency; +import org.jclouds.route53.domain.RecordSet.RecordSubset.Weighted; + +import com.google.common.base.Function; + +/** + * @author Adrian Cole + * @see ParamParser + */ +public class SerializeRRS implements Function { + @Override + public String apply(Object in) { + RecordSet rrs = RecordSet.class.cast(checkNotNull(in, "rrs")); + StringBuilder builder = new StringBuilder().append(""); + builder.append("").append(rrs.getName()).append(""); + builder.append("").append(rrs.getType()).append(""); + if (rrs instanceof RecordSubset) { + String id = RecordSubset.class.cast(rrs).getId(); + builder.append("").append(id).append(""); + } + if (rrs instanceof Weighted) + builder.append("").append(Weighted.class.cast(rrs).getWeight()).append(""); + if (rrs instanceof Latency) + builder.append("").append(Latency.class.cast(rrs).getRegion()).append(""); + if (rrs.getAliasTarget().isPresent()) { + builder.append(""); + builder.append("").append(rrs.getAliasTarget().get().getZoneId()).append(""); + builder.append("").append(rrs.getAliasTarget().get().getDNSName()).append(""); + builder.append(""); + } else { + builder.append("").append(rrs.getTTL().or(0)).append(""); + builder.append(""); + for (String record : rrs.getValues()) + builder.append("").append("").append(record).append("") + .append(""); + builder.append(""); + } + return builder.append("").toString(); + } +} diff --git a/labs/route53/src/main/java/org/jclouds/route53/functions/ZonesToPagedIterable.java b/labs/route53/src/main/java/org/jclouds/route53/functions/ZonesToPagedIterable.java index 5278b959ae..f3d97c9e6a 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/functions/ZonesToPagedIterable.java +++ b/labs/route53/src/main/java/org/jclouds/route53/functions/ZonesToPagedIterable.java @@ -27,7 +27,6 @@ import org.jclouds.collect.internal.CallerArg0ToPagedIterable; import org.jclouds.route53.Route53Api; import org.jclouds.route53.domain.Zone; import org.jclouds.route53.features.ZoneApi; -import org.jclouds.route53.options.ListZonesOptions; import com.google.common.annotations.Beta; import com.google.common.base.Function; @@ -52,7 +51,7 @@ public class ZonesToPagedIterable extends CallerArg0ToPagedIterable apply(Object input) { - return zoneApi.list(ListZonesOptions.Builder.afterMarker(input.toString())); + return zoneApi.listAt(input.toString()); } @Override diff --git a/labs/route53/src/main/java/org/jclouds/route53/handlers/Route53ErrorHandler.java b/labs/route53/src/main/java/org/jclouds/route53/handlers/Route53ErrorHandler.java new file mode 100644 index 0000000000..2f9ab0e631 --- /dev/null +++ b/labs/route53/src/main/java/org/jclouds/route53/handlers/Route53ErrorHandler.java @@ -0,0 +1,102 @@ +/** + * 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.route53.handlers; + +import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream; +import static org.jclouds.http.HttpUtils.releasePayload; + +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.jclouds.aws.AWSResponseException; +import org.jclouds.aws.domain.AWSError; +import org.jclouds.aws.xml.ErrorHandler; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; +import org.jclouds.http.functions.ParseSax.Factory; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.InsufficientResourcesException; +import org.jclouds.rest.ResourceNotFoundException; +import org.jclouds.route53.InvalidChangeBatchException; +import org.jclouds.route53.xml.InvalidChangeBatchHandler; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +/** + * @author Adrian Cole + */ +@Singleton +public class Route53ErrorHandler implements HttpErrorHandler { + + private final Factory factory; + private final Provider handlers; + private final Provider batchHandlers; + + @Inject + Route53ErrorHandler(Factory factory, Provider handlers, + Provider batchHandlers) { + this.factory = factory; + this.handlers = handlers; + this.batchHandlers = batchHandlers; + } + + 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("ErrorResponse") != -1) { + AWSError error = factory.create(handlers.get()).parse(message); + exception = refineException(new AWSResponseException(command, response, error)); + } else if (message.indexOf("InvalidChangeBatch") != -1) { + ImmutableList errors = factory.create(batchHandlers.get()).parse(message); + exception = new InvalidChangeBatchException(errors, new HttpResponseException(command, response)); + } + } + } finally { + releasePayload(response); + command.setException(exception); + } + } + + private Exception refineException(AWSResponseException in) { + int statusCode = in.getResponse().getStatusCode(); + String errorCode = in.getError().getCode(); + String message = in.getError().getMessage(); + + if (statusCode == 403 || "RequestExpired".equals(errorCode)) + return new AuthorizationException(message, in); + if (statusCode == 400) { + if (ImmutableSet.of("InvalidAction", "AccessDenied").contains(errorCode)) + return new UnsupportedOperationException(message, in); + else if ("Throttling".equals(errorCode)) + return new InsufficientResourcesException(message, in); + else if (message.indexOf("not found") != -1) + return new ResourceNotFoundException(message, in); + return new IllegalArgumentException(message, in); + } + return in; + } +} diff --git a/labs/route53/src/main/java/org/jclouds/route53/options/ListZonesOptions.java b/labs/route53/src/main/java/org/jclouds/route53/options/ListZonesOptions.java deleted file mode 100644 index f2f01d3b59..0000000000 --- a/labs/route53/src/main/java/org/jclouds/route53/options/ListZonesOptions.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * 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.route53.options; - -import org.jclouds.http.options.BaseHttpRequestOptions; - -import com.google.common.base.Objects; -import com.google.common.collect.Multimap; - -/** - * Options used to list available zones. - * - * @see
- * - * @author Adrian Cole - */ -public class ListZonesOptions extends BaseHttpRequestOptions implements Cloneable { - - private Integer maxItems; - private Object afterMarker; - - /** - * corresponds to {@link org.jclouds.collect.IterableWithMarker#nextMarker} - */ - public ListZonesOptions afterMarker(Object afterMarker) { - this.afterMarker = afterMarker; - return this; - } - - /** - * Use this parameter only when paginating results to indicate the maximum - * number of zone names you want in the response. If there are - * additional zone names beyond the maximum you specify, the - * IsTruncated response element is true. - */ - public ListZonesOptions maxItems(Integer maxItems) { - this.maxItems = maxItems; - return this; - } - - public static class Builder { - - /** - * @see ListZonesOptions#afterMarker - */ - public static ListZonesOptions afterMarker(Object afterMarker) { - return new ListZonesOptions().afterMarker(afterMarker); - } - - /** - * @see ListZonesOptions#maxItems - */ - public static ListZonesOptions maxItems(Integer maxItems) { - return new ListZonesOptions().maxItems(maxItems); - } - } - - @Override - public Multimap buildQueryParameters() { - Multimap params = super.buildQueryParameters(); - if (afterMarker != null) - params.put("marker", afterMarker.toString()); - if (maxItems != null) - params.put("maxitems", maxItems.toString()); - return params; - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return Objects.hashCode(afterMarker, maxItems); - } - - @Override - public ListZonesOptions clone() { - return new ListZonesOptions().afterMarker(afterMarker).maxItems(maxItems); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - ListZonesOptions other = ListZonesOptions.class.cast(obj); - return Objects.equal(this.afterMarker, other.afterMarker) && Objects.equal(this.maxItems, other.maxItems); - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return Objects.toStringHelper(this).omitNullValues().add("afterMarker", afterMarker).add("maxItems", maxItems).toString(); - } -} diff --git a/labs/route53/src/main/java/org/jclouds/route53/predicates/RecordSetPredicates.java b/labs/route53/src/main/java/org/jclouds/route53/predicates/RecordSetPredicates.java new file mode 100644 index 0000000000..fd896ed7d3 --- /dev/null +++ b/labs/route53/src/main/java/org/jclouds/route53/predicates/RecordSetPredicates.java @@ -0,0 +1,53 @@ +/** + * 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.route53.predicates; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.route53.domain.RecordSet; +import org.jclouds.route53.domain.RecordSet.Type; + +import com.google.common.base.Predicate; + +/** + * Predicates handy when working with ResourceRecordSet Types + * + * @author Adrian Cole + */ +public class RecordSetPredicates { + + /** + * matches zones of the given type + */ + public static Predicate typeEquals(final Type type) { + checkNotNull(type, "type must be defined"); + + return new Predicate() { + @Override + public boolean apply(RecordSet zone) { + return type.equals(zone.getType()); + } + + @Override + public String toString() { + return "typeEquals(" + type + ")"; + } + }; + } +} diff --git a/labs/route53/src/main/java/org/jclouds/route53/predicates/ZonePredicates.java b/labs/route53/src/main/java/org/jclouds/route53/predicates/ZonePredicates.java new file mode 100644 index 0000000000..a68c788e21 --- /dev/null +++ b/labs/route53/src/main/java/org/jclouds/route53/predicates/ZonePredicates.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.route53.predicates; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.jclouds.route53.domain.Zone; + +import com.google.common.base.Predicate; + +/** + * Predicates handy when working with Zones + * + * @author Adrian Cole + */ +public class ZonePredicates { + + /** + * matches zones of the given name + */ + public static Predicate nameEquals(final String name) { + checkNotNull(name, "name must be defined"); + + return new Predicate() { + @Override + public boolean apply(Zone zone) { + return name.equals(zone.getName()); + } + + @Override + public String toString() { + return "nameEquals(" + name + ")"; + } + }; + } +} diff --git a/labs/route53/src/main/java/org/jclouds/route53/xml/InvalidChangeBatchHandler.java b/labs/route53/src/main/java/org/jclouds/route53/xml/InvalidChangeBatchHandler.java new file mode 100644 index 0000000000..2d03c0a9ee --- /dev/null +++ b/labs/route53/src/main/java/org/jclouds/route53/xml/InvalidChangeBatchHandler.java @@ -0,0 +1,53 @@ +/** + * 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.route53.xml; + +import static org.jclouds.util.SaxUtils.currentOrNull; + +import org.jclouds.http.functions.ParseSax; + +import com.google.common.collect.ImmutableList; + +/** + * + * @author Adrian Cole + */ +public class InvalidChangeBatchHandler extends ParseSax.HandlerForGeneratedRequestWithResult> { + + private StringBuilder currentText = new StringBuilder(); + private ImmutableList.Builder builder = ImmutableList.builder(); + + @Override + public ImmutableList getResult() { + return builder.build(); + } + + @Override + public void endElement(String uri, String name, String qName) { + if (qName.equals("Message")) { + builder.add(currentOrNull(currentText)); + } + currentText = new StringBuilder(); + } + + @Override + public void characters(char ch[], int start, int length) { + currentText.append(ch, start, length); + } +} diff --git a/labs/route53/src/main/java/org/jclouds/route53/xml/ListResourceRecordSetsResponseHandler.java b/labs/route53/src/main/java/org/jclouds/route53/xml/ListResourceRecordSetsResponseHandler.java index 246c4c18c1..d9f993c68a 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/xml/ListResourceRecordSetsResponseHandler.java +++ b/labs/route53/src/main/java/org/jclouds/route53/xml/ListResourceRecordSetsResponseHandler.java @@ -22,9 +22,9 @@ import static org.jclouds.util.SaxUtils.currentOrNull; import static org.jclouds.util.SaxUtils.equalsOrSuffix; import org.jclouds.http.functions.ParseSax; -import org.jclouds.route53.domain.ResourceRecordSet.Type; -import org.jclouds.route53.domain.ResourceRecordSetIterable; -import org.jclouds.route53.domain.ResourceRecordSetIterable.Builder; +import org.jclouds.route53.domain.RecordSet.Type; +import org.jclouds.route53.domain.RecordSetIterable; +import org.jclouds.route53.domain.RecordSetIterable.Builder; import org.xml.sax.Attributes; import com.google.inject.Inject; @@ -37,12 +37,12 @@ import com.google.inject.Inject; * @author Adrian Cole */ public class ListResourceRecordSetsResponseHandler extends - ParseSax.HandlerForGeneratedRequestWithResult { + ParseSax.HandlerForGeneratedRequestWithResult { private final ResourceRecordSetHandler resourceRecordSetHandler; private StringBuilder currentText = new StringBuilder(); - private Builder builder = ResourceRecordSetIterable.builder(); + private Builder builder = RecordSetIterable.builder(); private boolean inResourceRecordSets; @@ -52,11 +52,11 @@ public class ListResourceRecordSetsResponseHandler extends } @Override - public ResourceRecordSetIterable getResult() { + public RecordSetIterable getResult() { try { return builder.build(); } finally { - builder = ResourceRecordSetIterable.builder(); + builder = RecordSetIterable.builder(); } } diff --git a/labs/route53/src/main/java/org/jclouds/route53/xml/ResourceRecordSetHandler.java b/labs/route53/src/main/java/org/jclouds/route53/xml/ResourceRecordSetHandler.java index 763c4ca9ae..64acd4c739 100644 --- a/labs/route53/src/main/java/org/jclouds/route53/xml/ResourceRecordSetHandler.java +++ b/labs/route53/src/main/java/org/jclouds/route53/xml/ResourceRecordSetHandler.java @@ -21,25 +21,25 @@ package org.jclouds.route53.xml; import static org.jclouds.util.SaxUtils.currentOrNull; import org.jclouds.http.functions.ParseSax; -import org.jclouds.route53.domain.ResourceRecordSet; -import org.jclouds.route53.domain.ResourceRecordSet.Type; +import org.jclouds.route53.domain.RecordSet; +import org.jclouds.route53.domain.RecordSet.Type; import org.xml.sax.Attributes; /** * * @author Adrian Cole */ -public class ResourceRecordSetHandler extends ParseSax.HandlerForGeneratedRequestWithResult { +public class ResourceRecordSetHandler extends ParseSax.HandlerForGeneratedRequestWithResult { private StringBuilder currentText = new StringBuilder(); - private ResourceRecordSet.Builder builder = ResourceRecordSet.builder(); + private RecordSet.Builder builder = RecordSet.builder(); @Override - public ResourceRecordSet getResult() { + public RecordSet getResult() { try { return builder.build(); } finally { - builder = ResourceRecordSet.builder(); + builder = RecordSet.builder(); } } @@ -57,6 +57,16 @@ public class ResourceRecordSetHandler extends ParseSax.HandlerForGeneratedReques builder.ttl(Integer.parseInt(currentOrNull(currentText))); } else if (qName.equals("Value")) { builder.add(currentOrNull(currentText)); + } else if (qName.equals("HostedZoneId")) { + builder.zoneId(currentOrNull(currentText)); + } else if (qName.equals("SetIdentifier")) { + builder.id(currentOrNull(currentText)); + } else if (qName.equals("DNSName")) { + builder.dnsName(currentOrNull(currentText)); + } else if (qName.equals("Weight")) { + builder.weight(Integer.parseInt(currentOrNull(currentText))); + } else if (qName.equals("Region")) { + builder.region(currentOrNull(currentText)); } currentText = new StringBuilder(); } diff --git a/labs/route53/src/test/java/org/jclouds/route53/Route53ApiExpectTest.java b/labs/route53/src/test/java/org/jclouds/route53/Route53ApiExpectTest.java index e79b0d0004..ce12d872c5 100644 --- a/labs/route53/src/test/java/org/jclouds/route53/Route53ApiExpectTest.java +++ b/labs/route53/src/test/java/org/jclouds/route53/Route53ApiExpectTest.java @@ -38,7 +38,7 @@ public class Route53ApiExpectTest extends BaseRoute53ApiExpectTest { .addHeader("Host", "route53.amazonaws.com") .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") .addHeader("X-Amzn-Authorization", - "AWS3-HTTPS AWSAccessKeyId=identity,Algorithm=HmacSHA256,Signature=pylxNiLcrsjNRZOsxyT161JCwytVPHyc2rFfmNCuZKI=") + authForDate) .build(); HttpResponse getChangeResponse = HttpResponse.builder().statusCode(200) diff --git a/labs/route53/src/test/java/org/jclouds/route53/features/RecordSetApiExpectTest.java b/labs/route53/src/test/java/org/jclouds/route53/features/RecordSetApiExpectTest.java new file mode 100644 index 0000000000..762d420007 --- /dev/null +++ b/labs/route53/src/test/java/org/jclouds/route53/features/RecordSetApiExpectTest.java @@ -0,0 +1,147 @@ +/** + * 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 + * + * Unles 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 expres or implied. See the License for the + * specific language governing permisions and limitations + * under the License. + */ +package org.jclouds.route53.features; + +import static org.testng.Assert.assertEquals; + +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.rest.ResourceNotFoundException; +import org.jclouds.route53.InvalidChangeBatchException; +import org.jclouds.route53.Route53Api; +import org.jclouds.route53.domain.ChangeBatch; +import org.jclouds.route53.domain.RecordSet; +import org.jclouds.route53.domain.RecordSet.Type; +import org.jclouds.route53.domain.RecordSetIterable.NextRecord; +import org.jclouds.route53.internal.BaseRoute53ApiExpectTest; +import org.jclouds.route53.parse.GetChangeResponseTest; +import org.jclouds.route53.parse.ListResourceRecordSetsResponseTest; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "ResourceRecordSetApiExpectTest") +public class RecordSetApiExpectTest extends BaseRoute53ApiExpectTest { + + HttpRequest create = HttpRequest.builder().method("POST") + .endpoint("https://route53.amazonaws.com/2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset") + .addHeader("Host", "route53.amazonaws.com") + .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") + .addHeader("X-Amzn-Authorization", authForDate) + .payload(payloadFromResourceWithContentType("/create_rrs_request.xml", "application/xml")).build(); + + HttpResponse jobResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/change.xml", "text/xml")).build(); + + public void testCreateWhenResponseIs2xx() { + Route53Api success = requestSendsResponse(create, jobResponse); + assertEquals(success.getRecordSetApiForZone("Z1PA6795UKMFR9").create(RecordSet.builder().name("jclouds.org.").type(Type.TXT).add("my texts").build()).toString(), + new GetChangeResponseTest().expected().toString()); + } + + HttpRequest apply = HttpRequest.builder().method("POST") + .endpoint("https://route53.amazonaws.com/2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset") + .addHeader("Host", "route53.amazonaws.com") + .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") + .addHeader("X-Amzn-Authorization", authForDate) + .payload(payloadFromResourceWithContentType("/batch_rrs_request.xml", "application/xml")).build(); + + public void testApplyWhenResponseIs2xx() { + Route53Api success = requestSendsResponse(apply, jobResponse); + assertEquals(success.getRecordSetApiForZone("Z1PA6795UKMFR9").apply( + ChangeBatch.builder() + .delete(RecordSet.builder().name("jclouds.org.").type(Type.TXT).add("my texts").build()) + .create(RecordSet.builder().name("jclouds.org.").type(Type.TXT).add("my better texts").build()) + .build()).toString(), + new GetChangeResponseTest().expected().toString()); + } + + @Test(expectedExceptions = InvalidChangeBatchException.class, expectedExceptionsMessageRegExp = "\\[Tried to create resource record set duplicate.example.com. type A, but it already exists, Tried to delete resource record set noexist.example.com. type A, but it was not found\\]") + public void testApplyWhenResponseIs4xx() { + HttpResponse batchErrorFound = HttpResponse.builder().statusCode(400) + .payload(payloadFromResourceWithContentType("/invalid_change_batch.xml", "application/xml")).build(); + + Route53Api fails = requestSendsResponse(apply, batchErrorFound); + fails.getRecordSetApiForZone("Z1PA6795UKMFR9").apply( + ChangeBatch.builder() + .delete(RecordSet.builder().name("jclouds.org.").type(Type.TXT).add("my texts").build()) + .create(RecordSet.builder().name("jclouds.org.").type(Type.TXT).add("my better texts").build()) + .build()); + } + + HttpRequest list = HttpRequest.builder().method("GET") + .endpoint("https://route53.amazonaws.com/2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset") + .addHeader("Host", "route53.amazonaws.com") + .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") + .addHeader("X-Amzn-Authorization", authForDate).build(); + + HttpResponse listResponse = HttpResponse.builder().statusCode(200) + .payload(payloadFromResourceWithContentType("/rrsets.xml", "text/xml")).build(); + + public void testListWhenResponseIs2xx() { + Route53Api success = requestSendsResponse(list, listResponse); + assertEquals(success.getRecordSetApiForZone("Z1PA6795UKMFR9").list().get(0).toString(), new ListResourceRecordSetsResponseTest().expected() + .toString()); + } + + // TODO: this should really be an empty set + @Test(expectedExceptions = ResourceNotFoundException.class) + public void testListWhenResponseIs404() { + Route53Api fail = requestSendsResponse(list, notFound); + assertEquals(fail.getRecordSetApiForZone("Z1PA6795UKMFR9").list().get(0).toImmutableSet(), ImmutableSet.of()); + } + + HttpRequest listAt = HttpRequest.builder().method("GET") + .endpoint("https://route53.amazonaws.com/2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=testdoc2.example.com&type=NS") + .addHeader("Host", "route53.amazonaws.com") + .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") + .addHeader("X-Amzn-Authorization", authForDate).build(); + + public void testListAtWhenResponseIs2xx() { + Route53Api apiWhenAtExist = requestSendsResponse(listAt, listResponse); + NextRecord next = NextRecord.nameAndType("testdoc2.example.com", Type.NS); + assertEquals(apiWhenAtExist.getRecordSetApiForZone("Z1PA6795UKMFR9").listAt(next).toString(), + new ListResourceRecordSetsResponseTest().expected().toString()); + } + + public void testList2PagesWhenResponseIs2xx() { + HttpResponse noMore = HttpResponse.builder().statusCode(200) + .payload(payloadFromStringWithContentType("", "text/xml")).build(); + + Route53Api success = requestsSendResponses(list, listResponse, listAt, noMore); + assertEquals(success.getRecordSetApiForZone("Z1PA6795UKMFR9").list().concat().toImmutableSet(), new ListResourceRecordSetsResponseTest().expected() + .toImmutableSet()); + } + + HttpRequest delete = HttpRequest.builder().method("POST") + .endpoint("https://route53.amazonaws.com/2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset") + .addHeader("Host", "route53.amazonaws.com") + .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") + .addHeader("X-Amzn-Authorization", authForDate) + .payload(payloadFromResourceWithContentType("/delete_rrs_request.xml", "application/xml")).build(); + + public void testDeleteWhenResponseIs2xx() { + Route53Api success = requestSendsResponse(delete, jobResponse); + assertEquals(success.getRecordSetApiForZone("Z1PA6795UKMFR9").create(RecordSet.builder().name("jclouds.org.").type(Type.TXT).add("my texts").build()).toString(), + new GetChangeResponseTest().expected().toString()); + } +} diff --git a/labs/route53/src/test/java/org/jclouds/route53/features/RecordSetApiLiveTest.java b/labs/route53/src/test/java/org/jclouds/route53/features/RecordSetApiLiveTest.java new file mode 100644 index 0000000000..ca0991295a --- /dev/null +++ b/labs/route53/src/test/java/org/jclouds/route53/features/RecordSetApiLiveTest.java @@ -0,0 +1,172 @@ +/** + * 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.route53.features; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.not; +import static com.google.common.base.Predicates.or; +import static java.util.logging.Logger.getAnonymousLogger; +import static org.jclouds.route53.domain.ChangeBatch.createAll; +import static org.jclouds.route53.domain.ChangeBatch.deleteAll; +import static org.jclouds.route53.domain.RecordSet.Type.NS; +import static org.jclouds.route53.domain.RecordSet.Type.SOA; +import static org.jclouds.route53.domain.RecordSet.Type.TXT; +import static org.jclouds.route53.predicates.RecordSetPredicates.typeEquals; +import static org.jclouds.route53.predicates.ZonePredicates.nameEquals; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +import org.jclouds.JcloudsVersion; +import org.jclouds.collect.PagedIterable; +import org.jclouds.route53.domain.Change; +import org.jclouds.route53.domain.NewZone; +import org.jclouds.route53.domain.RecordSet; +import org.jclouds.route53.domain.RecordSet.RecordSubset; +import org.jclouds.route53.domain.RecordSet.RecordSubset.Latency; +import org.jclouds.route53.domain.RecordSet.RecordSubset.Weighted; +import org.jclouds.route53.domain.Zone; +import org.jclouds.route53.internal.BaseRoute53ApiLiveTest; +import org.testng.SkipException; +import org.testng.annotations.Test; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; + +/** + * @author Adrian Cole + */ +@Test(groups = "live", testName = "ResourceRecordSetApiLiveTest") +public class RecordSetApiLiveTest extends BaseRoute53ApiLiveTest { + + private void checkRRS(RecordSet rrs) { + checkNotNull(rrs.getName(), "Name: ResourceRecordSet %s", rrs); + checkNotNull(rrs.getType(), "Type: ResourceRecordSet %s", rrs); + checkNotNull(rrs.getTTL(), "TTL: ResourceRecordSet %s", rrs); + checkNotNull(rrs.getAliasTarget(), "AliasTarget: ResourceRecordSet %s", rrs); + if (rrs.getAliasTarget().isPresent()) { + assertTrue(rrs.getValues().isEmpty(), "Values present on aliasTarget ResourceRecordSet: " + rrs); + } else { + assertTrue(!rrs.getValues().isEmpty(), "Values absent on ResourceRecordSet: " + rrs); + } + if (rrs instanceof RecordSubset) { + checkNotNull(RecordSubset.class.cast(rrs).getId(), "Id: ResourceRecordSubset %s", rrs); + } + if (rrs instanceof Weighted) { + assertTrue(Weighted.class.cast(rrs).getWeight() >= 0, "Weight negative: ResourceRecordSubset " + rrs); + } + if (rrs instanceof Latency) { + checkNotNull(Latency.class.cast(rrs).getRegion(), "Region: ResourceRecordSubset %s", rrs); + } + } + + @Test + private void testListRRSs() { + for (Zone zone : zones().concat()) { + checkAllRRs(zone.getId()); + } + } + + private void checkAllRRs(String zoneId) { + Zone zone = context.getApi().getZoneApi().get(zoneId).getZone(); + List records = api(zone.getId()).list().concat().toImmutableList(); + assertEquals(zone.getResourceRecordSetCount(), records.size()); + + for (RecordSet rrs : records) { + checkRRS(rrs); + } + } + + @Test + public void testDeleteRRSNotFound() { + for (Zone zone : zones().concat()) { + assertNull(api(zone.getId()).delete( + RecordSet.builder().name("krank.foo.bar.").type(TXT).add("kranko").build())); + } + } + + /** + * cannot delete a zone without at least one of each + */ + private static final Predicate requiredRRTypes = or(typeEquals(SOA), typeEquals(NS)); + + @Test + public void testCreateAndDeleteBulkRecords() { + String name = System.getProperty("user.name").replace('.', '-') + ".bulk.route53test.jclouds.org."; + clearAndDeleteZonesNamed(name); + + ImmutableList records = ImmutableList. builder() + .add(RecordSet.builder().name("dom1." + name).type(TXT).add("\"somehow\" \" somewhere\"").build()) + .add(RecordSet.builder().name("dom2." + name).type(TXT).add("\"goodies\"").build()).build(); + + String nonce = name + " @ " + new Date(); + String comment = name + " for " + JcloudsVersion.get(); + NewZone newZone = context.getApi().getZoneApi().createWithReferenceAndComment(name, nonce, comment); + String zoneId = newZone.getZone().getId(); + getAnonymousLogger().info("created zone: " + newZone); + try { + assertTrue(inSync.apply(newZone.getChange()), "zone didn't sync " + newZone); + sync(api(zoneId).apply(createAll(records))); + + checkAllRRs(zoneId); + + sync(api(zoneId).apply(deleteAll(records))); + + PagedIterable refreshed = refresh(zoneId); + assertTrue(refreshed.concat().filter(not(requiredRRTypes)).isEmpty(), "zone still has optional records: " + + refreshed); + + } finally { + clearAndDeleteZonesNamed(name); + } + } + + private void clearAndDeleteZonesNamed(String name) { + for (Zone zone : context.getApi().getZoneApi().list().concat().filter(nameEquals(name))) { + getAnonymousLogger().info("clearing and deleting zone: " + zone); + Set remaining = refresh(zone.getId()).concat().filter(not(requiredRRTypes)).toImmutableSet(); + if (!remaining.isEmpty()) + sync(api(zone.getId()).apply(deleteAll(remaining))); + sync(context.getApi().getZoneApi().delete(zone.getId())); + } + } + + private void sync(Change job) { + assertTrue(inSync.apply(job), "job didn't sync " + job); + } + + private PagedIterable refresh(String zoneId) { + return api(zoneId).list(); + } + + private PagedIterable zones() { + PagedIterable zones = context.getApi().getZoneApi().list(); + if (zones.get(0).isEmpty()) + throw new SkipException("no zones in context: " + context); + return zones; + } + + private RecordSetApi api(String zoneId) { + return context.getApi().getRecordSetApiForZone(zoneId); + } +} diff --git a/labs/route53/src/test/java/org/jclouds/route53/features/ResourceRecordSetApiExpectTest.java b/labs/route53/src/test/java/org/jclouds/route53/features/ResourceRecordSetApiExpectTest.java deleted file mode 100644 index 9950bd8fec..0000000000 --- a/labs/route53/src/test/java/org/jclouds/route53/features/ResourceRecordSetApiExpectTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/** - * 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 - * - * Unles 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 expres or implied. See the License for the - * specific language governing permisions and limitations - * under the License. - */ -package org.jclouds.route53.features; - -import static org.testng.Assert.assertEquals; - -import org.jclouds.http.HttpRequest; -import org.jclouds.http.HttpResponse; -import org.jclouds.rest.ResourceNotFoundException; -import org.jclouds.route53.Route53Api; -import org.jclouds.route53.domain.ResourceRecordSet.Type; -import org.jclouds.route53.domain.ResourceRecordSetIterable.NextRecord; -import org.jclouds.route53.internal.BaseRoute53ApiExpectTest; -import org.jclouds.route53.parse.ListResourceRecordSetsResponseTest; -import org.testng.annotations.Test; - -import com.google.common.collect.ImmutableSet; - -/** - * @author Adrian Cole - */ -@Test(groups = "unit", testName = "ResourceRecordSetApiExpectTest") -public class ResourceRecordSetApiExpectTest extends BaseRoute53ApiExpectTest { - - HttpRequest list = HttpRequest.builder().method("GET") - .endpoint("https://route53.amazonaws.com/2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset") - .addHeader("Host", "route53.amazonaws.com") - .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") - .addHeader("X-Amzn-Authorization", - "AWS3-HTTPS AWSAccessKeyId=identity,Algorithm=HmacSHA256,Signature=pylxNiLcrsjNRZOsxyT161JCwytVPHyc2rFfmNCuZKI=") - .build(); - - HttpResponse listResponse = HttpResponse.builder().statusCode(200) - .payload(payloadFromResourceWithContentType("/rrsets.xml", "text/xml")).build(); - - public void testListWhenResponseIs2xx() { - Route53Api success = requestSendsResponse(list, listResponse); - assertEquals(success.getResourceRecordSetApiForZone("Z1PA6795UKMFR9").list().get(0).toString(), new ListResourceRecordSetsResponseTest().expected() - .toString()); - } - - // TODO: this should really be an empty set - @Test(expectedExceptions = ResourceNotFoundException.class) - public void testListWhenResponseIs404() { - Route53Api fail = requestSendsResponse(list, notFound); - assertEquals(fail.getResourceRecordSetApiForZone("Z1PA6795UKMFR9").list().get(0).toImmutableSet(), ImmutableSet.of()); - } - - HttpRequest listAt = HttpRequest.builder().method("GET") - .endpoint("https://route53.amazonaws.com/2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=testdoc2.example.com&type=NS") - .addHeader("Host", "route53.amazonaws.com") - .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") - .addHeader("X-Amzn-Authorization", - "AWS3-HTTPS AWSAccessKeyId=identity,Algorithm=HmacSHA256,Signature=pylxNiLcrsjNRZOsxyT161JCwytVPHyc2rFfmNCuZKI=") - .build(); - - public void testListAtWhenResponseIs2xx() { - Route53Api apiWhenAtExist = requestSendsResponse(listAt, listResponse); - NextRecord next = NextRecord.nameAndType("testdoc2.example.com", Type.NS); - assertEquals(apiWhenAtExist.getResourceRecordSetApiForZone("Z1PA6795UKMFR9").listAt(next).toString(), - new ListResourceRecordSetsResponseTest().expected().toString()); - } - - public void testList2PagesWhenResponseIs2xx() { - HttpResponse noMore = HttpResponse.builder().statusCode(200) - .payload(payloadFromStringWithContentType("", "text/xml")).build(); - - Route53Api success = requestsSendResponses(list, listResponse, listAt, noMore); - assertEquals(success.getResourceRecordSetApiForZone("Z1PA6795UKMFR9").list().concat().toImmutableSet(), new ListResourceRecordSetsResponseTest().expected() - .toImmutableSet()); - } -} diff --git a/labs/route53/src/test/java/org/jclouds/route53/features/ResourceRecordSetApiLiveTest.java b/labs/route53/src/test/java/org/jclouds/route53/features/ResourceRecordSetApiLiveTest.java deleted file mode 100644 index 3b403e22bd..0000000000 --- a/labs/route53/src/test/java/org/jclouds/route53/features/ResourceRecordSetApiLiveTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * 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.route53.features; - -import static com.google.common.base.Preconditions.checkNotNull; -import static org.testng.Assert.assertEquals; - -import java.util.List; - -import org.jclouds.collect.IterableWithMarker; -import org.jclouds.route53.domain.ResourceRecordSet; -import org.jclouds.route53.domain.Zone; -import org.jclouds.route53.internal.BaseRoute53ApiLiveTest; -import org.testng.SkipException; -import org.testng.annotations.Test; - -/** - * @author Adrian Cole - */ -@Test(groups = "live", testName = "ResourceRecordSetApiLiveTest") -public class ResourceRecordSetApiLiveTest extends BaseRoute53ApiLiveTest { - - private void checkResourceRecordSet(ResourceRecordSet resourceRecordSet) { - checkNotNull(resourceRecordSet.getName(), "Id cannot be null for a ResourceRecordSet %s", resourceRecordSet); - checkNotNull(resourceRecordSet.getType(), "Type cannot be null for a ResourceRecordSet %s", resourceRecordSet); - checkNotNull(resourceRecordSet.getTTL(), - "While TTL can be null for a ResourceRecordSet, its Optional wrapper cannot %s", resourceRecordSet); - } - - @Test - protected void testListResourceRecordSets() { - IterableWithMarker zones = context.getApi().getZoneApi().list().get(0); - if (zones.isEmpty()) - throw new SkipException("no zones in context: " + context); - - Zone zone = zones.first().get(); - List records = api(zone.getId()).list().concat().toImmutableList(); - assertEquals(zone.getResourceRecordSetCount(), records.size()); - - for (ResourceRecordSet resourceRecordSet : records) { - checkResourceRecordSet(resourceRecordSet); - } - } - - protected ResourceRecordSetApi api(String zoneId) { - return context.getApi().getResourceRecordSetApiForZone(zoneId); - } -} diff --git a/labs/route53/src/test/java/org/jclouds/route53/features/ZoneApiExpectTest.java b/labs/route53/src/test/java/org/jclouds/route53/features/ZoneApiExpectTest.java index 4f8a5f9653..72c0d20e46 100644 --- a/labs/route53/src/test/java/org/jclouds/route53/features/ZoneApiExpectTest.java +++ b/labs/route53/src/test/java/org/jclouds/route53/features/ZoneApiExpectTest.java @@ -18,7 +18,6 @@ */ package org.jclouds.route53.features; -import static org.jclouds.route53.options.ListZonesOptions.Builder.afterMarker; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; @@ -45,7 +44,7 @@ public class ZoneApiExpectTest extends BaseRoute53ApiExpectTest { .addHeader("Host", "route53.amazonaws.com") .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") .addHeader("X-Amzn-Authorization", - "AWS3-HTTPS AWSAccessKeyId=identity,Algorithm=HmacSHA256,Signature=pylxNiLcrsjNRZOsxyT161JCwytVPHyc2rFfmNCuZKI=") + authForDate) .payload( payloadFromStringWithContentType( "jclouds.org.expect", @@ -65,7 +64,7 @@ public class ZoneApiExpectTest extends BaseRoute53ApiExpectTest { .addHeader("Host", "route53.amazonaws.com") .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") .addHeader("X-Amzn-Authorization", - "AWS3-HTTPS AWSAccessKeyId=identity,Algorithm=HmacSHA256,Signature=pylxNiLcrsjNRZOsxyT161JCwytVPHyc2rFfmNCuZKI=") + authForDate) .payload( payloadFromStringWithContentType( "jclouds.org.expectcomment", @@ -82,7 +81,7 @@ public class ZoneApiExpectTest extends BaseRoute53ApiExpectTest { .addHeader("Host", "route53.amazonaws.com") .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") .addHeader("X-Amzn-Authorization", - "AWS3-HTTPS AWSAccessKeyId=identity,Algorithm=HmacSHA256,Signature=pylxNiLcrsjNRZOsxyT161JCwytVPHyc2rFfmNCuZKI=") + authForDate) .build(); HttpResponse getResponse = HttpResponse.builder().statusCode(200) @@ -104,7 +103,7 @@ public class ZoneApiExpectTest extends BaseRoute53ApiExpectTest { .addHeader("Host", "route53.amazonaws.com") .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") .addHeader("X-Amzn-Authorization", - "AWS3-HTTPS AWSAccessKeyId=identity,Algorithm=HmacSHA256,Signature=pylxNiLcrsjNRZOsxyT161JCwytVPHyc2rFfmNCuZKI=") + authForDate) .build(); HttpResponse listResponse = HttpResponse.builder().statusCode(200) @@ -123,17 +122,17 @@ public class ZoneApiExpectTest extends BaseRoute53ApiExpectTest { assertEquals(fail.getZoneApi().list().get(0).toImmutableSet(), ImmutableSet.of()); } - HttpRequest listWithOptions = HttpRequest.builder().method("GET") + HttpRequest listAt = HttpRequest.builder().method("GET") .endpoint("https://route53.amazonaws.com/2012-02-29/hostedzone?marker=Z333333YYYYYYY") .addHeader("Host", "route53.amazonaws.com") .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") .addHeader("X-Amzn-Authorization", - "AWS3-HTTPS AWSAccessKeyId=identity,Algorithm=HmacSHA256,Signature=pylxNiLcrsjNRZOsxyT161JCwytVPHyc2rFfmNCuZKI=") + authForDate) .build(); - public void testListWithOptionsWhenResponseIs2xx() { - Route53Api apiWhenWithOptionsExist = requestSendsResponse(listWithOptions, listResponse); - assertEquals(apiWhenWithOptionsExist.getZoneApi().list(afterMarker("Z333333YYYYYYY")).toString(), + public void testListAtWhenResponseIs2xx() { + Route53Api apiWhenAtExist = requestSendsResponse(listAt, listResponse); + assertEquals(apiWhenAtExist.getZoneApi().listAt("Z333333YYYYYYY").toString(), new ListHostedZonesResponseTest().expected().toString()); } @@ -141,7 +140,7 @@ public class ZoneApiExpectTest extends BaseRoute53ApiExpectTest { HttpResponse noMore = HttpResponse.builder().statusCode(200) .payload(payloadFromStringWithContentType("", "text/xml")).build(); - Route53Api success = requestsSendResponses(list, listResponse, listWithOptions, noMore); + Route53Api success = requestsSendResponses(list, listResponse, listAt, noMore); assertEquals(success.getZoneApi().list().concat().toString(), new ListHostedZonesResponseTest().expected() .toString()); } @@ -151,7 +150,7 @@ public class ZoneApiExpectTest extends BaseRoute53ApiExpectTest { .addHeader("Host", "route53.amazonaws.com") .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") .addHeader("X-Amzn-Authorization", - "AWS3-HTTPS AWSAccessKeyId=identity,Algorithm=HmacSHA256,Signature=pylxNiLcrsjNRZOsxyT161JCwytVPHyc2rFfmNCuZKI=") + authForDate) .build(); HttpResponse deleteResponse = HttpResponse.builder().statusCode(200) diff --git a/labs/route53/src/test/java/org/jclouds/route53/features/ZoneApiLiveTest.java b/labs/route53/src/test/java/org/jclouds/route53/features/ZoneApiLiveTest.java index e29924bb38..29d4bdfeb8 100644 --- a/labs/route53/src/test/java/org/jclouds/route53/features/ZoneApiLiveTest.java +++ b/labs/route53/src/test/java/org/jclouds/route53/features/ZoneApiLiveTest.java @@ -17,74 +17,49 @@ * under the License. */ package org.jclouds.route53.features; - import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.jclouds.route53.domain.Change.Status.INSYNC; +import static java.lang.String.format; +import static java.util.logging.Logger.getAnonymousLogger; import static org.jclouds.route53.domain.Change.Status.PENDING; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import java.util.Date; -import java.util.logging.Logger; import org.jclouds.JcloudsVersion; -import org.jclouds.collect.IterableWithMarker; -import org.jclouds.predicates.RetryablePredicate; import org.jclouds.route53.domain.Change; import org.jclouds.route53.domain.NewZone; import org.jclouds.route53.domain.Zone; import org.jclouds.route53.internal.BaseRoute53ApiLiveTest; -import org.jclouds.route53.options.ListZonesOptions; -import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; /** * @author Adrian Cole */ @Test(groups = "live", testName = "ZoneApiLiveTest") public class ZoneApiLiveTest extends BaseRoute53ApiLiveTest { - private Predicate inSync; - - @BeforeClass(groups = "live") - @Override - public void setupContext() { - super.setupContext(); - inSync = new RetryablePredicate(new Predicate() { - public boolean apply(Change input) { - return context.getApi().getChange(input.getId()).getStatus() == INSYNC; - } - }, 600, 1, 5, SECONDS); - } private void checkZone(Zone zone) { - checkNotNull(zone.getId(), "Id cannot be null for a Zone %s", zone); - checkNotNull(zone.getName(), "Name cannot be null for a Zone %s", zone); - checkNotNull(zone.getCallerReference(), "CallerReference cannot be null for a Zone %s", zone); + getAnonymousLogger().info(format("zone %s rrs: %s", zone.getName(), zone.getResourceRecordSetCount())); + + checkNotNull(zone.getId(), "Id: Zone %s", zone); + checkNotNull(zone.getName(), "Name: Zone %s", zone); + checkNotNull(zone.getCallerReference(), "CallerReference: Zone %s", zone); checkNotNull(zone.getComment(), "While Comment can be null for a Zone, its Optional wrapper cannot %s", zone); } @Test protected void testListZones() { - IterableWithMarker response = api().list().get(0); + ImmutableList zones = api().list().concat().toImmutableList(); + getAnonymousLogger().info("zones: " + zones.size()); - for (Zone zone : response) { + for (Zone zone : zones) { checkZone(zone); - } - - if (response.size() > 0) { - Zone zone = response.iterator().next(); assertEquals(api().get(zone.getId()).getZone(), zone); } - - // Test with a Marker, even if it's null - response = api().list(ListZonesOptions.Builder.afterMarker(response.nextMarker().orNull())); - for (Zone zone : response) { - checkZone(zone); - } } @Test @@ -103,7 +78,7 @@ public class ZoneApiLiveTest extends BaseRoute53ApiLiveTest { String nonce = name + " @ " + new Date(); String comment = name + " for " + JcloudsVersion.get(); NewZone newZone = api().createWithReferenceAndComment(name, nonce, comment); - Logger.getAnonymousLogger().info("created zone: " + newZone); + getAnonymousLogger().info("created zone: " + newZone); try { checkZone(newZone.getZone()); assertEquals(newZone.getChange().getStatus(), PENDING, "invalid status on zone " + newZone); diff --git a/labs/route53/src/test/java/org/jclouds/route53/handlers/Route53ErrorHandlerTest.java b/labs/route53/src/test/java/org/jclouds/route53/handlers/Route53ErrorHandlerTest.java new file mode 100644 index 0000000000..491dbf0491 --- /dev/null +++ b/labs/route53/src/test/java/org/jclouds/route53/handlers/Route53ErrorHandlerTest.java @@ -0,0 +1,152 @@ +/** + * 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.route53.handlers; + +import static com.google.common.base.Throwables.propagate; +import static org.jclouds.rest.internal.BaseRestApiExpectTest.payloadFromStringWithContentType; +import static org.jclouds.util.Strings2.toStringAndClose; +import static org.testng.Assert.assertEquals; + +import java.io.IOException; + +import org.jclouds.aws.AWSResponseException; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.functions.config.SaxParserModule; +import org.jclouds.io.Payload; +import org.jclouds.rest.ResourceNotFoundException; +import org.jclouds.route53.InvalidChangeBatchException; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Guice; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit" ) +public class Route53ErrorHandlerTest { + Route53ErrorHandler function = Guice.createInjector(new SaxParserModule()).getInstance(Route53ErrorHandler.class); + + HttpRequest request = HttpRequest.builder().method("POST") + .endpoint("https://route53.amazonaws.com/2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset") + .addHeader("Host", "route53.amazonaws.com") + .addHeader("Date", "Mon, 21 Jan 02013 19:29:03 -0800") + .addHeader("X-Amzn-Authorization", "AWS3-HTTPS AWSAccessKeyId=identity,Algorithm=HmacSHA256,Signature=pylxNiLcrsjNRZOsxyT161JCwytVPHyc2rFfmNCuZKI=") + .payload(payloadFromResource("/batch_rrs_request.xml")).build(); + HttpCommand command = command(request); + + @Test + public void testInvalidChangeBatchException() throws IOException { + HttpResponse response = HttpResponse.builder().statusCode(400) + .payload(payloadFromResource("/invalid_change_batch.xml")).build(); + function.handleError(command, response); + + InvalidChangeBatchException exception = InvalidChangeBatchException.class.cast(command.getException()); + + assertEquals(exception.getMessages(), ImmutableSet.of( + "Tried to create resource record set duplicate.example.com. type A, but it already exists", + "Tried to delete resource record set noexist.example.com. type A, but it was not found")); + } + + @Test + public void testDeleteNotFound() throws IOException { + + HttpResponse response = HttpResponse.builder().statusCode(400) + .payload( + payloadFromStringWithContentType( + "SenderInvalidChangeBatch" + + "Tried to delete resource record set krank.foo.bar., type TXT but it was not found" + + "", "application/xml")).build(); + + function.handleError(command, response); + + assertEquals(command.getException().getClass(), ResourceNotFoundException.class); + assertEquals(command.getException().getMessage(), "Tried to delete resource record set krank.foo.bar., type TXT but it was not found"); + + AWSResponseException exception = AWSResponseException.class.cast(command.getException().getCause()); + + assertEquals(exception.getError().getCode(), "InvalidChangeBatch"); + } + + private static HttpCommand command(final HttpRequest request) { + return new HttpCommand() { + + private Exception exception; + + @Override + public int getRedirectCount() { + return 0; + } + + @Override + public int incrementRedirectCount() { + return 0; + } + + @Override + public boolean isReplayable() { + return false; + } + + @Override + public Exception getException() { + return exception; + } + + @Override + public int getFailureCount() { + return 0; + } + + @Override + public int incrementFailureCount() { + return 0; + } + + @Override + public void setException(Exception exception) { + this.exception = exception; + } + + @Override + public HttpRequest getCurrentRequest() { + return request; + } + + @Override + public void setCurrentRequest(HttpRequest request) { + + } + + }; + } + + private Payload payloadFromResource(String resource) { + try { + return payloadFromStringWithContentType(toStringAndClose(getClass().getResourceAsStream(resource)), + "application/xml"); + } catch (IOException e) { + throw propagate(e); + } + } +} diff --git a/labs/route53/src/test/java/org/jclouds/route53/internal/BaseRoute53ApiExpectTest.java b/labs/route53/src/test/java/org/jclouds/route53/internal/BaseRoute53ApiExpectTest.java index ca538ba739..c9c30fd59f 100644 --- a/labs/route53/src/test/java/org/jclouds/route53/internal/BaseRoute53ApiExpectTest.java +++ b/labs/route53/src/test/java/org/jclouds/route53/internal/BaseRoute53ApiExpectTest.java @@ -18,13 +18,7 @@ */ package org.jclouds.route53.internal; -import org.jclouds.http.HttpResponse; import org.jclouds.route53.Route53Api; -/** - * - * @author Adrian Cole - */ public class BaseRoute53ApiExpectTest extends BaseRoute53ExpectTest { - protected HttpResponse notFound = HttpResponse.builder().statusCode(404).build(); } diff --git a/labs/route53/src/test/java/org/jclouds/route53/internal/BaseRoute53ApiLiveTest.java b/labs/route53/src/test/java/org/jclouds/route53/internal/BaseRoute53ApiLiveTest.java index 4b1cd086ac..ca011d0f19 100644 --- a/labs/route53/src/test/java/org/jclouds/route53/internal/BaseRoute53ApiLiveTest.java +++ b/labs/route53/src/test/java/org/jclouds/route53/internal/BaseRoute53ApiLiveTest.java @@ -18,13 +18,21 @@ */ package org.jclouds.route53.internal; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.jclouds.route53.domain.Change.Status.INSYNC; + import org.jclouds.apis.BaseContextLiveTest; +import org.jclouds.collect.IterableWithMarker; +import org.jclouds.predicates.RetryablePredicate; import org.jclouds.route53.Route53ApiMetadata; import org.jclouds.route53.Route53AsyncApi; import org.jclouds.route53.Route53Api; +import org.jclouds.route53.domain.Change; import org.jclouds.rest.RestContext; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import com.google.common.base.Predicate; import com.google.common.reflect.TypeToken; /** @@ -39,9 +47,21 @@ public class BaseRoute53ApiLiveTest extends provider = "route53"; } + protected Predicate inSync; + + @BeforeClass(groups = "live") + @Override + public void setupContext() { + super.setupContext(); + inSync = new RetryablePredicate(new Predicate() { + public boolean apply(Change input) { + return context.getApi().getChange(input.getId()).getStatus() == INSYNC; + } + }, 600, 1, 5, SECONDS); + } + @Override protected TypeToken> contextType() { return Route53ApiMetadata.CONTEXT_TOKEN; } - } diff --git a/labs/route53/src/test/java/org/jclouds/route53/internal/BaseRoute53ExpectTest.java b/labs/route53/src/test/java/org/jclouds/route53/internal/BaseRoute53ExpectTest.java index 59a869d1f7..3b0239982e 100644 --- a/labs/route53/src/test/java/org/jclouds/route53/internal/BaseRoute53ExpectTest.java +++ b/labs/route53/src/test/java/org/jclouds/route53/internal/BaseRoute53ExpectTest.java @@ -19,9 +19,10 @@ package org.jclouds.route53.internal; import org.jclouds.date.DateService; -import org.jclouds.route53.config.Route53RestClientModule; +import org.jclouds.http.HttpResponse; import org.jclouds.rest.ConfiguresRestClient; import org.jclouds.rest.internal.BaseRestApiExpectTest; +import org.jclouds.route53.config.Route53RestClientModule; import com.google.inject.Module; @@ -44,6 +45,9 @@ public class BaseRoute53ExpectTest extends BaseRestApiExpectTest { } } + protected final HttpResponse notFound = HttpResponse.builder().statusCode(404).build(); + protected final String authForDate = "AWS3-HTTPS AWSAccessKeyId=identity,Algorithm=HmacSHA256,Signature=pylxNiLcrsjNRZOsxyT161JCwytVPHyc2rFfmNCuZKI="; + @Override protected Module createModule() { return new TestRoute53RestClientModule(); diff --git a/labs/route53/src/test/java/org/jclouds/route53/options/ListZonesOptionsTest.java b/labs/route53/src/test/java/org/jclouds/route53/options/ListZonesOptionsTest.java deleted file mode 100644 index 9270443341..0000000000 --- a/labs/route53/src/test/java/org/jclouds/route53/options/ListZonesOptionsTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * 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.route53.options; - -import static org.jclouds.route53.options.ListZonesOptions.Builder.afterMarker; -import static org.jclouds.route53.options.ListZonesOptions.Builder.maxItems; -import static org.testng.Assert.assertEquals; - -import org.testng.annotations.Test; - -import com.google.common.collect.ImmutableSet; - -/** - * Tests behavior of {@code ListZonesOptions} - * - * @author Adrian Cole - */ -@Test(groups = "unit", testName = "ListZonesOptionsTest") -public class ListZonesOptionsTest { - - public void testMarker() { - ListZonesOptions options = new ListZonesOptions().afterMarker("FFFFF"); - assertEquals(ImmutableSet.of("FFFFF"), options.buildQueryParameters().get("marker")); - } - - public void testMarkerStatic() { - ListZonesOptions options = afterMarker("FFFFF"); - assertEquals(ImmutableSet.of("FFFFF"), options.buildQueryParameters().get("marker")); - } - - public void testMaxItems() { - ListZonesOptions options = new ListZonesOptions().maxItems(1000); - assertEquals(ImmutableSet.of("1000"), options.buildQueryParameters().get("maxitems")); - } - - public void testMaxItemsStatic() { - ListZonesOptions options = maxItems(1000); - assertEquals(ImmutableSet.of("1000"), options.buildQueryParameters().get("maxitems")); - } -} diff --git a/labs/route53/src/test/java/org/jclouds/route53/parse/InvalidChangeBatchResponseTest.java b/labs/route53/src/test/java/org/jclouds/route53/parse/InvalidChangeBatchResponseTest.java new file mode 100644 index 0000000000..36d291b802 --- /dev/null +++ b/labs/route53/src/test/java/org/jclouds/route53/parse/InvalidChangeBatchResponseTest.java @@ -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.route53.parse; + +import static org.testng.Assert.assertEquals; + +import java.io.InputStream; + +import org.jclouds.http.functions.BaseHandlerTest; +import org.jclouds.route53.xml.InvalidChangeBatchHandler; +import org.testng.annotations.Test; + +import com.google.common.collect.ImmutableList; + +/** + * @author Adrian Cole + */ +// NOTE:without testName, this will not call @Before* and fail w/NPE during +// surefire +@Test(groups = "unit", testName = "InvalidChangeBatchResponseTest") +public class InvalidChangeBatchResponseTest extends BaseHandlerTest { + + public void test() { + InputStream is = getClass().getResourceAsStream("/invalid_change_batch.xml"); + + InvalidChangeBatchHandler handler = injector.getInstance(InvalidChangeBatchHandler.class); + ImmutableList result = factory.create(handler).parse(is); + + assertEquals(result, ImmutableList.of( + "Tried to create resource record set duplicate.example.com. type A, but it already exists", + "Tried to delete resource record set noexist.example.com. type A, but it was not found")); + } +} diff --git a/labs/route53/src/test/java/org/jclouds/route53/parse/ListResourceRecordSetsResponseTest.java b/labs/route53/src/test/java/org/jclouds/route53/parse/ListResourceRecordSetsResponseTest.java index 4006c1b665..61585ede43 100644 --- a/labs/route53/src/test/java/org/jclouds/route53/parse/ListResourceRecordSetsResponseTest.java +++ b/labs/route53/src/test/java/org/jclouds/route53/parse/ListResourceRecordSetsResponseTest.java @@ -23,9 +23,9 @@ import static org.testng.Assert.assertEquals; import java.io.InputStream; import org.jclouds.http.functions.BaseHandlerTest; -import org.jclouds.route53.domain.ResourceRecordSet; -import org.jclouds.route53.domain.ResourceRecordSet.Type; -import org.jclouds.route53.domain.ResourceRecordSetIterable; +import org.jclouds.route53.domain.RecordSet; +import org.jclouds.route53.domain.RecordSet.Type; +import org.jclouds.route53.domain.RecordSetIterable; import org.jclouds.route53.xml.ListResourceRecordSetsResponseHandler; import org.testng.annotations.Test; @@ -40,24 +40,24 @@ public class ListResourceRecordSetsResponseTest extends BaseHandlerTest { public void test() { InputStream is = getClass().getResourceAsStream("/rrsets.xml"); - ResourceRecordSetIterable expected = expected(); + RecordSetIterable expected = expected(); ListResourceRecordSetsResponseHandler handler = injector.getInstance(ListResourceRecordSetsResponseHandler.class); - ResourceRecordSetIterable result = factory.create(handler).parse(is); + RecordSetIterable result = factory.create(handler).parse(is); assertEquals(result.toString(), expected.toString()); } - public ResourceRecordSetIterable expected() { - return ResourceRecordSetIterable.builder() - .add(ResourceRecordSet.builder() + public RecordSetIterable expected() { + return RecordSetIterable.builder() + .add(RecordSet.builder() .name("example.com.") .type(Type.SOA) .ttl(900) .add("ns-2048.awsdns-64.net. hostmaster.awsdns.com. 1 7200 900 1209600 86400") .build()) - .add(ResourceRecordSet.builder() + .add(RecordSet.builder() .name("example.com.") .type(Type.NS) .ttl(172800) diff --git a/labs/route53/src/test/java/org/jclouds/route53/predicates/RecordSetPredicatesTest.java b/labs/route53/src/test/java/org/jclouds/route53/predicates/RecordSetPredicatesTest.java new file mode 100644 index 0000000000..de5413ed66 --- /dev/null +++ b/labs/route53/src/test/java/org/jclouds/route53/predicates/RecordSetPredicatesTest.java @@ -0,0 +1,46 @@ +/** + * 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.route53.predicates; + +import static org.jclouds.route53.domain.RecordSet.Type.AAAA; +import static org.jclouds.route53.domain.RecordSet.Type.NS; +import static org.jclouds.route53.predicates.RecordSetPredicates.typeEquals; + +import org.jclouds.route53.domain.RecordSet; +import org.testng.annotations.Test; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "ResourceRecordSetPredicatesTest") +public class RecordSetPredicatesTest { + RecordSet rrs = RecordSet.builder().name("jclouds.org.").type(NS).add("ns-119.awsdns-14.com.") + .build(); + + @Test + public void testTypeEqualsWhenEqual() { + assert typeEquals(NS).apply(rrs); + } + + @Test + public void testTypeEqualsWhenNotEqual() { + assert !typeEquals(AAAA).apply(rrs); + } +} diff --git a/labs/route53/src/test/java/org/jclouds/route53/predicates/ZonePredicatesTest.java b/labs/route53/src/test/java/org/jclouds/route53/predicates/ZonePredicatesTest.java new file mode 100644 index 0000000000..f9ba9702de --- /dev/null +++ b/labs/route53/src/test/java/org/jclouds/route53/predicates/ZonePredicatesTest.java @@ -0,0 +1,43 @@ +/** + * 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.route53.predicates; + +import static org.jclouds.route53.predicates.ZonePredicates.nameEquals; + +import org.jclouds.route53.domain.Zone; +import org.testng.annotations.Test; + +/** + * + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "ZonePredicatesTest") +public class ZonePredicatesTest { + Zone zone = Zone.builder().id("EEEFFFEEE").callerReference("goog").name("jclouds.org.").build(); + + @Test + public void testNameEqualsWhenEqual() { + assert nameEquals("jclouds.org.").apply(zone); + } + + @Test + public void testNameEqualsWhenNotEqual() { + assert !nameEquals("kclouds.org.").apply(zone); + } +} diff --git a/labs/route53/src/test/resources/batch_rrs_request.xml b/labs/route53/src/test/resources/batch_rrs_request.xml new file mode 100644 index 0000000000..c6997fdc87 --- /dev/null +++ b/labs/route53/src/test/resources/batch_rrs_request.xml @@ -0,0 +1 @@ +DELETEjclouds.org.TXT0my textsCREATEjclouds.org.TXT0my better texts \ No newline at end of file diff --git a/labs/route53/src/test/resources/create_rrs_request.xml b/labs/route53/src/test/resources/create_rrs_request.xml new file mode 100644 index 0000000000..0003889aa0 --- /dev/null +++ b/labs/route53/src/test/resources/create_rrs_request.xml @@ -0,0 +1 @@ +CREATEjclouds.org.TXT0my texts \ No newline at end of file diff --git a/labs/route53/src/test/resources/delete_rrs_request.xml b/labs/route53/src/test/resources/delete_rrs_request.xml new file mode 100644 index 0000000000..0003889aa0 --- /dev/null +++ b/labs/route53/src/test/resources/delete_rrs_request.xml @@ -0,0 +1 @@ +CREATEjclouds.org.TXT0my texts \ No newline at end of file diff --git a/labs/route53/src/test/resources/invalid_change_batch.xml b/labs/route53/src/test/resources/invalid_change_batch.xml new file mode 100644 index 0000000000..83eb6add62 --- /dev/null +++ b/labs/route53/src/test/resources/invalid_change_batch.xml @@ -0,0 +1,7 @@ + + + + Tried to create resource record set duplicate.example.com. type A, but it already exists + Tried to delete resource record set noexist.example.com. type A, but it was not found + + \ No newline at end of file