From d2dffc91f7b8fbd6cdfe5d6410feb34f0d6140b4 Mon Sep 17 00:00:00 2001 From: adriancole Date: Wed, 12 Jun 2013 12:17:55 -0700 Subject: [PATCH] issue #87: implemented Weighted profile in route53 and removed weighted results from its basic listing --- CHANGES.md | 1 + .../GroupByRecordNameAndTypeIterator.java | 50 --- .../denominator/route53/Route53Provider.java | 49 ++- .../route53/Route53ResourceRecordSetApi.java | 39 +-- .../Route53WeightedResourceRecordSetApi.java | 191 ++++++++++++ .../ToDenominatorResourceRecordSet.java | 44 +++ .../route53/ToRoute53ResourceRecordSet.java | 19 +- .../Route53ResourceRecordSetApiMockTest.java | 46 +-- .../Route53WeightedReadOnlyLiveTest.java | 14 + ...3WeightedResourceRecordSetApiMockTest.java | 289 ++++++++++++++++++ .../Route53WeightedWriteCommandsLiveTest.java | 16 + 11 files changed, 632 insertions(+), 126 deletions(-) delete mode 100644 providers/denominator-route53/src/main/java/denominator/route53/GroupByRecordNameAndTypeIterator.java create mode 100644 providers/denominator-route53/src/main/java/denominator/route53/Route53WeightedResourceRecordSetApi.java create mode 100644 providers/denominator-route53/src/test/java/denominator/route53/Route53WeightedReadOnlyLiveTest.java create mode 100644 providers/denominator-route53/src/test/java/denominator/route53/Route53WeightedResourceRecordSetApiMockTest.java create mode 100644 providers/denominator-route53/src/test/java/denominator/route53/Route53WeightedWriteCommandsLiveTest.java diff --git a/CHANGES.md b/CHANGES.md index a966ab72..3eb6df64 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ ### Version 1.3.0 * Added `WeightedResourceRecordSetApi` to support reading and writing new `Weighted` (load balanced rrset) profile. +* Changed `Route53` to no longer return weighted record sets inside basic record set results. * Added `ResourceRecordSet.getQualifier()`, `ReadOnlyResourceRecordSetApi.getByNameTypeAndQualifier()` * Added `QualifiedResourceRecordSetApi` with `supportedTypes()`, `put()`, and `deleteByNameTypeAndQualifier()` * Added `-t/--type` to list, and `-q/--qualifier` to get record cli commands. diff --git a/providers/denominator-route53/src/main/java/denominator/route53/GroupByRecordNameAndTypeIterator.java b/providers/denominator-route53/src/main/java/denominator/route53/GroupByRecordNameAndTypeIterator.java deleted file mode 100644 index 8197b636..00000000 --- a/providers/denominator-route53/src/main/java/denominator/route53/GroupByRecordNameAndTypeIterator.java +++ /dev/null @@ -1,50 +0,0 @@ -package denominator.route53; - -import static com.google.common.base.Predicates.and; -import static com.google.common.collect.Iterators.peekingIterator; -import static denominator.model.ResourceRecordSets.nameEqualTo; -import static denominator.model.ResourceRecordSets.typeEqualTo; - -import java.util.Iterator; - -import com.google.common.collect.PeekingIterator; - -import denominator.model.ResourceRecordSet; - -/** - * used when there are server-side groups, such as weight or geo, which cause - * record sets to not be unique solely on name and type. - */ -class GroupByRecordNameAndTypeIterator implements Iterator> { - private final PeekingIterator> peekingIterator; - - public GroupByRecordNameAndTypeIterator(Iterator> sortedIterator) { - this.peekingIterator = peekingIterator(sortedIterator); - } - - @Override - public boolean hasNext() { - return peekingIterator.hasNext(); - } - - @Override - public ResourceRecordSet next() { - ResourceRecordSet rrset = peekingIterator.next(); - while (hasNext() && and(nameEqualTo(rrset.name()), typeEqualTo(rrset.type())).apply(peekingIterator.peek())) { - ResourceRecordSet next = peekingIterator.next(); - rrset = ResourceRecordSet.builder() - .name(rrset.name()) - .type(rrset.type()) - .ttl(rrset.ttl().or(next.ttl()).orNull()) - .addAll(rrset) - .addAll(next) - .build(); - } - return rrset; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } -} \ No newline at end of file diff --git a/providers/denominator-route53/src/main/java/denominator/route53/Route53Provider.java b/providers/denominator-route53/src/main/java/denominator/route53/Route53Provider.java index 33d32118..8bd11e70 100644 --- a/providers/denominator-route53/src/main/java/denominator/route53/Route53Provider.java +++ b/providers/denominator-route53/src/main/java/denominator/route53/Route53Provider.java @@ -8,6 +8,8 @@ import java.net.URI; import java.util.List; import java.util.Properties; +import java.util.Set; +import java.util.SortedSet; import javax.inject.Inject; import javax.inject.Singleton; @@ -21,9 +23,12 @@ import org.jclouds.route53.Route53ApiMetadata; import com.google.common.base.Supplier; +import com.google.common.collect.ContiguousSet; +import com.google.common.collect.DiscreteDomain; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; +import com.google.common.collect.Range; import dagger.Provides; import denominator.BasicProvider; @@ -31,11 +36,12 @@ import denominator.Credentials.ListCredentials; import denominator.DNSApiManager; import denominator.Provider; +import denominator.QualifiedResourceRecordSetApi; import denominator.ResourceRecordSetApi; import denominator.ZoneApi; +import denominator.config.ConcatBasicAndQualifiedResourceRecordSets; import denominator.config.GeoUnsupported; -import denominator.config.OnlyBasicResourceRecordSets; -import denominator.config.WeightedUnsupported; +import denominator.profile.WeightedResourceRecordSetApi; public class Route53Provider extends BasicProvider { private final String url; @@ -73,8 +79,7 @@ public Multimap credentialTypeToParameterNames() { @dagger.Module(injects = DNSApiManager.class, complete = false, // denominator.Provider includes = { GeoUnsupported.class, - WeightedUnsupported.class, - OnlyBasicResourceRecordSets.class, + ConcatBasicAndQualifiedResourceRecordSets.class, InstanceProfileCredentialsProvider.class }) public static final class Module { @@ -132,6 +137,42 @@ ResourceRecordSetApi.Factory provideResourceRecordSetApiFactory(Route53Api api) Closeable provideCloser(Route53Api api) { return api; } + + @Provides + WeightedResourceRecordSetApi.Factory provideWeightedResourceRecordSetApiFactory( + Route53WeightedResourceRecordSetApi.Factory in) { + return in; + } + + @Provides(type = Provides.Type.SET) + QualifiedResourceRecordSetApi.Factory provideWeightedResourceRecordSetApiFactory( + WeightedResourceRecordSetApi.Factory in) { + return in; + } + + /** + * @see valid + * types + */ + @Provides + @Singleton + @denominator.config.profile.Weighted + Set provideSupportedWeightedRecordTypes() { + return ImmutableSet.of("A", "AAAA", "CNAME", "MX", "PTR", "SPF", "SRV", "TXT"); + } + + /** + * @see valid + * weights + */ + @Provides + @Singleton + @denominator.config.profile.Weighted + SortedSet provideSupportedWeights() { + return ContiguousSet.create(Range.closed(0, 255), DiscreteDomain.integers()); + } } static final class ConvertToJcloudsCredentials implements Supplier { diff --git a/providers/denominator-route53/src/main/java/denominator/route53/Route53ResourceRecordSetApi.java b/providers/denominator-route53/src/main/java/denominator/route53/Route53ResourceRecordSetApi.java index f7fde7ea..2250ec08 100644 --- a/providers/denominator-route53/src/main/java/denominator/route53/Route53ResourceRecordSetApi.java +++ b/providers/denominator-route53/src/main/java/denominator/route53/Route53ResourceRecordSetApi.java @@ -3,8 +3,10 @@ import static com.google.common.base.Predicates.and; import static com.google.common.base.Predicates.in; import static com.google.common.base.Predicates.not; +import static com.google.common.base.Predicates.or; import static com.google.common.collect.Iterables.filter; import static denominator.route53.ToDenominatorResourceRecordSet.isAlias; +import static denominator.route53.ToDenominatorResourceRecordSet.isSubset; import static denominator.route53.ToRoute53ResourceRecordSet.toTextFormat; import java.util.Iterator; @@ -44,11 +46,10 @@ public Iterator> list() { */ @Override public Iterator> iterator() { - Iterator> iterator = route53RRsetApi.list().concat() - .filter(not(isAlias())) - .transform(ToDenominatorResourceRecordSet.INSTANCE) - .iterator(); - return new GroupByRecordNameAndTypeIterator(iterator); + return route53RRsetApi.list().concat() + .filter(not(or(isAlias(), isSubset()))) + .transform(ToDenominatorResourceRecordSet.INSTANCE) + .iterator(); } @Override @@ -63,24 +64,16 @@ public Iterator> listByName(String name) { */ @Override public Iterator> iterateByName(String name) { - Iterator> iterator = route53RRsetApi.listAt(NextRecord.name(name)) - .filter(and(not(isAlias()), nameEqualTo(name))) - .transform(ToDenominatorResourceRecordSet.INSTANCE) - .iterator(); - return new GroupByRecordNameAndTypeIterator(iterator); + return route53RRsetApi.listAt(NextRecord.name(name)) + .filter(and(not(or(isAlias(), isSubset())), nameEqualTo(name))) + .transform(ToDenominatorResourceRecordSet.INSTANCE) + .iterator(); } @Override public Optional> getByNameAndType(String name, String type) { - List> matches = filterRoute53RRSByNameAndType(name, type).transform( - ToDenominatorResourceRecordSet.INSTANCE).toList(); - switch (matches.size()) { - case 0: - return Optional.absent(); - case 1: - return Optional.> of(matches.get(0)); - } - return Optional.> of(new GroupByRecordNameAndTypeIterator(matches.iterator()).next()); + return filterRoute53RRSByNameAndType(name, type) + .transform(ToDenominatorResourceRecordSet.INSTANCE).first(); } /** @@ -89,8 +82,8 @@ public Optional> getByNameAndType(String name, String type) */ @SuppressWarnings("unchecked") FluentIterable filterRoute53RRSByNameAndType(String name, String type) { - return route53RRsetApi.listAt(NextRecord.nameAndType(name, type)).filter( - and(not(isAlias()), nameEqualTo(name), typeEqualTo(type))); + return route53RRsetApi.listAt(NextRecord.nameAndType(name, type)) + .filter(and(not(or(isAlias(), isSubset())), nameEqualTo(name), typeEqualTo(type))); } /** @@ -218,7 +211,7 @@ public ResourceRecordSetApi create(String id) { } } - public static Predicate nameEqualTo(String name) { + static Predicate nameEqualTo(String name) { return new Route53NameEqualToPredicate(name); } @@ -240,7 +233,7 @@ public String toString() { } } - public static Predicate typeEqualTo(String type) { + static Predicate typeEqualTo(String type) { return new Route53TypeEqualToPredicate(type); } diff --git a/providers/denominator-route53/src/main/java/denominator/route53/Route53WeightedResourceRecordSetApi.java b/providers/denominator-route53/src/main/java/denominator/route53/Route53WeightedResourceRecordSetApi.java new file mode 100644 index 00000000..411bc2d1 --- /dev/null +++ b/providers/denominator-route53/src/main/java/denominator/route53/Route53WeightedResourceRecordSetApi.java @@ -0,0 +1,191 @@ +package denominator.route53; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Predicates.and; +import static denominator.model.ResourceRecordSets.profileContainsType; +import static denominator.route53.Route53ResourceRecordSetApi.nameEqualTo; +import static denominator.route53.Route53ResourceRecordSetApi.typeEqualTo; +import static denominator.route53.ToDenominatorResourceRecordSet.isWeighted; + +import java.util.Iterator; +import java.util.Set; +import java.util.SortedSet; + +import javax.inject.Inject; + +import org.jclouds.route53.Route53Api; +import org.jclouds.route53.domain.ChangeBatch; +import org.jclouds.route53.domain.ResourceRecordSet.RecordSubset; +import org.jclouds.route53.domain.ResourceRecordSet.RecordSubset.Weighted; +import org.jclouds.route53.domain.ResourceRecordSetIterable.NextRecord; +import org.jclouds.route53.features.ResourceRecordSetApi; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; + +import denominator.model.ResourceRecordSet; +import denominator.profile.WeightedResourceRecordSetApi; + +final class Route53WeightedResourceRecordSetApi implements WeightedResourceRecordSetApi { + private static final Predicate> IS_WEIGHTED = + profileContainsType(denominator.model.profile.Weighted.class); + + private final ResourceRecordSetApi route53RRsetApi; + private final Set supportedTypes; + private final SortedSet supportedWeights; + + Route53WeightedResourceRecordSetApi(ResourceRecordSetApi route53RRsetApi, Set supportedTypes, + SortedSet supportedWeights) { + this.route53RRsetApi = route53RRsetApi; + this.supportedTypes = supportedTypes; + this.supportedWeights = supportedWeights; + } + + @Override + public Set supportedTypes() { + return supportedTypes; + } + + @Override + public SortedSet supportedWeights() { + return supportedWeights; + } + + @Deprecated + @Override + public Iterator> list() { + return iterator(); + } + + @Override + public Iterator> iterator() { + return route53RRsetApi.list().concat().filter(isWeighted()).transform(ToDenominatorResourceRecordSet.INSTANCE) + .iterator(); + } + + @Override + @Deprecated + public Iterator> listByName(String name) { + return iterateByName(name); + } + + @Override + public Iterator> iterateByName(String name) { + return route53RRsetApi.listAt(NextRecord.name(name)).filter(and(isWeighted(), nameEqualTo(name))) + .transform(ToDenominatorResourceRecordSet.INSTANCE).iterator(); + } + + @Override + @Deprecated + public Iterator> listByNameAndType(String name, String type) { + return iterateByNameAndType(name, type); + } + + @SuppressWarnings("unchecked") + @Override + public Iterator> iterateByNameAndType(String name, String type) { + return route53RRsetApi.listAt(NextRecord.nameAndType(name, type)) + .filter(and(isWeighted(), nameEqualTo(name), typeEqualTo(type))) + .transform(ToDenominatorResourceRecordSet.INSTANCE).iterator(); + } + + @Override + public Optional> getByNameTypeAndQualifier(String name, String type, String qualifier) { + return filterRoute53RRSByNameTypeAndId(name, type, qualifier) + .transform(ToDenominatorResourceRecordSet.INSTANCE).first(); + } + + @SuppressWarnings("unchecked") + FluentIterable filterRoute53RRSByNameTypeAndId(String name, String type, String id) { + return route53RRsetApi.listAt(NextRecord.nameTypeAndIdentifier(name, type, id)) + .filter(and(isWeighted(), nameEqualTo(name), typeEqualTo(type), idEqualTo(id))) + .transform(new Function() { + + @Override + public RecordSubset.Weighted apply(org.jclouds.route53.domain.ResourceRecordSet input) { + return RecordSubset.Weighted.class.cast(input); + } + + }); + } + + @Override + public void put(ResourceRecordSet rrset) { + checkNotNull(rrset, "rrset was null"); + checkArgument(rrset.qualifier().isPresent(), "no qualifier on: %s", rrset); + checkArgument(IS_WEIGHTED.apply(rrset), "%s failed on: %s", IS_WEIGHTED, rrset); + + ChangeBatch.Builder changes = ChangeBatch.builder(); + + Weighted replacement = Weighted.class.cast(ToRoute53ResourceRecordSet.INSTANCE.apply(rrset)); + + Optional oldRRS = filterRoute53RRSByNameTypeAndId(rrset.name(), rrset.type(), rrset.qualifier().get()) + .first(); + if (oldRRS.isPresent()) { + if (oldRRS.get().getWeight() == replacement.getWeight() + && oldRRS.get().getTTL().equals(replacement.getTTL()) + && oldRRS.get().getValues().equals(replacement.getValues())) + return; + changes.delete(oldRRS.get()); + } + + changes.create(replacement); + route53RRsetApi.apply(changes.build()); + } + + @Override + public void deleteByNameTypeAndQualifier(String name, String type, String qualifier) { + Optional oldRRS = filterRoute53RRSByNameTypeAndId(name, type, qualifier).first(); + if (!oldRRS.isPresent()) + return; + route53RRsetApi.delete(oldRRS.get()); + } + + static final class Factory implements WeightedResourceRecordSetApi.Factory { + + private final Route53Api api; + private final Set supportedTypes; + private final SortedSet supportedWeights; + + @Inject + Factory(Route53Api api, @denominator.config.profile.Weighted Set supportedTypes, + @denominator.config.profile.Weighted SortedSet supportedWeights) { + this.api = api; + this.supportedTypes = supportedTypes; + this.supportedWeights = supportedWeights; + } + + @Override + public Optional create(String id) { + return Optional. of(new Route53WeightedResourceRecordSetApi(api + .getResourceRecordSetApiForHostedZone(id), supportedTypes, supportedWeights)); + } + } + + static Predicate idEqualTo(String id) { + return new Route53IdEqualToPredicate(id); + } + + private static class Route53IdEqualToPredicate implements Predicate { + private final String id; + + private Route53IdEqualToPredicate(String id) { + this.id = checkNotNull(id, "id"); + } + + @Override + public boolean apply(org.jclouds.route53.domain.ResourceRecordSet input) { + if (!(input instanceof RecordSubset)) + return false; + return id.equals(RecordSubset.class.cast(input).getId()); + } + + @Override + public String toString() { + return "IdEqualTo(" + id + ")"; + } + } +} diff --git a/providers/denominator-route53/src/main/java/denominator/route53/ToDenominatorResourceRecordSet.java b/providers/denominator-route53/src/main/java/denominator/route53/ToDenominatorResourceRecordSet.java index 010e9128..70255c26 100644 --- a/providers/denominator-route53/src/main/java/denominator/route53/ToDenominatorResourceRecordSet.java +++ b/providers/denominator-route53/src/main/java/denominator/route53/ToDenominatorResourceRecordSet.java @@ -2,6 +2,8 @@ import java.util.Map; +import org.jclouds.route53.domain.ResourceRecordSet.RecordSubset; + import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Splitter; @@ -10,6 +12,7 @@ import denominator.model.ResourceRecordSet; import denominator.model.ResourceRecordSet.Builder; +import denominator.model.profile.Weighted; import denominator.model.rdata.AAAAData; import denominator.model.rdata.AData; import denominator.model.rdata.CNAMEData; @@ -35,6 +38,14 @@ public ResourceRecordSet apply(org.jclouds.route53.domain.ResourceRecordSet i Builder> builder = ResourceRecordSet.builder() .name(input.getName()) .type(input.getType()); + if (input instanceof RecordSubset) { + builder.qualifier(RecordSubset.class.cast(input).getId()); + } + + if (input instanceof RecordSubset.Weighted) { + builder.addProfile(Weighted.create(RecordSubset.Weighted.class.cast(input).getWeight())); + } + if (input.getTTL().isPresent()) builder.ttl(input.getTTL().get().intValue()); for (String rdata : input.getValues()) { @@ -65,6 +76,39 @@ public String toString() { }; } + static final Predicate isSubset() { + return new Predicate() { + @Override + public boolean apply(org.jclouds.route53.domain.ResourceRecordSet input) { + return input instanceof RecordSubset; + } + + @Override + public String toString() { + return "isSubset()"; + } + }; + } + + /** + * @see weighted + * RRSet + */ + static final Predicate isWeighted() { + return new Predicate() { + @Override + public boolean apply(org.jclouds.route53.domain.ResourceRecordSet input) { + return input instanceof RecordSubset.Weighted; + } + + @Override + public String toString() { + return "isWeighted()"; + } + }; + } + /** * @see supported diff --git a/providers/denominator-route53/src/main/java/denominator/route53/ToRoute53ResourceRecordSet.java b/providers/denominator-route53/src/main/java/denominator/route53/ToRoute53ResourceRecordSet.java index 3fff78ae..93a3bd95 100644 --- a/providers/denominator-route53/src/main/java/denominator/route53/ToRoute53ResourceRecordSet.java +++ b/providers/denominator-route53/src/main/java/denominator/route53/ToRoute53ResourceRecordSet.java @@ -1,4 +1,5 @@ package denominator.route53; +import static denominator.model.ResourceRecordSets.toProfile; import static java.lang.String.format; import java.util.List; @@ -11,17 +12,25 @@ import com.google.common.collect.ImmutableSet; import denominator.model.ResourceRecordSet; +import denominator.model.profile.Weighted; enum ToRoute53ResourceRecordSet implements Function, org.jclouds.route53.domain.ResourceRecordSet> { INSTANCE; @Override public org.jclouds.route53.domain.ResourceRecordSet apply(ResourceRecordSet rrset) { - return org.jclouds.route53.domain.ResourceRecordSet.builder() - .name(rrset.name()) - .type(rrset.type()) - .ttl(rrset.ttl().or(300)) - .addAll(toTextFormat(rrset)).build(); + org.jclouds.route53.domain.ResourceRecordSet.Builder builder = + org.jclouds.route53.domain.ResourceRecordSet.builder() + .name(rrset.name()) + .type(rrset.type()) + .id(rrset.qualifier().orNull()) + .ttl(rrset.ttl().or(300)) + .addAll(toTextFormat(rrset)); + Weighted weighted = toProfile(Weighted.class).apply(rrset); + if (weighted != null) { + builder.weight(weighted.weight()); + } + return builder.build(); } static List toTextFormat(ResourceRecordSet rrset) { diff --git a/providers/denominator-route53/src/test/java/denominator/route53/Route53ResourceRecordSetApiMockTest.java b/providers/denominator-route53/src/test/java/denominator/route53/Route53ResourceRecordSetApiMockTest.java index 414e3e22..35fcc274 100644 --- a/providers/denominator-route53/src/test/java/denominator/route53/Route53ResourceRecordSetApiMockTest.java +++ b/providers/denominator-route53/src/test/java/denominator/route53/Route53ResourceRecordSetApiMockTest.java @@ -2,7 +2,6 @@ import static denominator.CredentialsConfiguration.credentials; import static denominator.model.ResourceRecordSets.a; -import static denominator.model.ResourceRecordSets.cname; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; @@ -27,55 +26,14 @@ public class Route53ResourceRecordSetApiMockTest { String weightedRecords = "www.denominator.io.CNAMERoute53Service:us-east-1:PLATFORMSERVICE:i-7f0aec0d:2013031320501710www1.denominator.io.www.denominator.io.CNAMERoute53Service:us-east-1:PLATFORMSERVICE:i-fbe41089:2013031220341810www2.denominator.io."; @Test - public void listWeightedRecordSubsetsAggregateOnNameAndType() throws IOException, InterruptedException { + public void weighedRecordSetsAreFilteredOut() throws IOException, InterruptedException { MockWebServer server = new MockWebServer(); server.enqueue(new MockResponse().setResponseCode(200).setBody(weightedRecords)); server.play(); try { ResourceRecordSetApi api = mockApi(server.getUrl("/")); - assertEquals(api.iterator().next(), - cname("www.denominator.io.", 0, ImmutableList.of("www1.denominator.io.", "www2.denominator.io."))); - - assertEquals(server.getRequestCount(), 1); - - assertEquals(server.takeRequest().getRequestLine(), - "GET /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset HTTP/1.1"); - } finally { - server.shutdown(); - } - } - - @Test - public void iterateByNameWeightedRecordSubsetsAggregateOnNameAndType() throws IOException, InterruptedException { - MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody(weightedRecords)); - server.play(); - - try { - ResourceRecordSetApi api = mockApi(server.getUrl("/")); - assertEquals(api.iterateByName("www.denominator.io.").next(), - cname("www.denominator.io.", 0, ImmutableList.of("www1.denominator.io.", "www2.denominator.io."))); - - assertEquals(server.getRequestCount(), 1); - - assertEquals(server.takeRequest().getRequestLine(), - "GET /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=www.denominator.io. HTTP/1.1"); - } finally { - server.shutdown(); - } - } - - @Test - public void getByNameAndTypeWeightedRecordSubsetsAggregateOnNameAndType() throws IOException, InterruptedException { - MockWebServer server = new MockWebServer(); - server.enqueue(new MockResponse().setResponseCode(200).setBody(weightedRecords)); - server.play(); - - try { - ResourceRecordSetApi api = mockApi(server.getUrl("/")); - assertEquals(api.getByNameAndType("www.denominator.io.", "CNAME").get(), - cname("www.denominator.io.", 0, ImmutableList.of("www1.denominator.io.", "www2.denominator.io."))); + assertFalse(api.getByNameAndType("www.denominator.io.", "CNAME").isPresent()); assertEquals(server.getRequestCount(), 1); diff --git a/providers/denominator-route53/src/test/java/denominator/route53/Route53WeightedReadOnlyLiveTest.java b/providers/denominator-route53/src/test/java/denominator/route53/Route53WeightedReadOnlyLiveTest.java new file mode 100644 index 00000000..302a1044 --- /dev/null +++ b/providers/denominator-route53/src/test/java/denominator/route53/Route53WeightedReadOnlyLiveTest.java @@ -0,0 +1,14 @@ +package denominator.route53; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import denominator.profile.BaseWeightedReadOnlyLiveTest; + +@Test +public class Route53WeightedReadOnlyLiveTest extends BaseWeightedReadOnlyLiveTest { + @BeforeClass + private void setUp() { + manager = new Route53Connection().manager; + } +} diff --git a/providers/denominator-route53/src/test/java/denominator/route53/Route53WeightedResourceRecordSetApiMockTest.java b/providers/denominator-route53/src/test/java/denominator/route53/Route53WeightedResourceRecordSetApiMockTest.java new file mode 100644 index 00000000..077007c9 --- /dev/null +++ b/providers/denominator-route53/src/test/java/denominator/route53/Route53WeightedResourceRecordSetApiMockTest.java @@ -0,0 +1,289 @@ +package denominator.route53; + +import static denominator.CredentialsConfiguration.credentials; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; + +import java.io.IOException; +import java.net.URL; +import java.util.Iterator; + +import org.testng.annotations.Test; + +import com.google.common.base.Optional; +import com.google.mockwebserver.MockResponse; +import com.google.mockwebserver.MockWebServer; +import com.google.mockwebserver.RecordedRequest; + +import denominator.Denominator; +import denominator.model.ResourceRecordSet; +import denominator.model.profile.Weighted; +import denominator.model.rdata.CNAMEData; +import denominator.profile.WeightedResourceRecordSetApi; + +@Test(singleThreaded = true) +public class Route53WeightedResourceRecordSetApiMockTest { + + private String noRecords = ""; + private String oneRecord = "www.denominator.io.CNAMEMyService-East10www1.denominator.io."; + private String twoRecords = "www.denominator.io.CNAMEMyService-East10www1.denominator.io.www.denominator.io.CNAMEMyService-West50www2.denominator.io."; + + private String identifier1 = "MyService-East"; + + private ResourceRecordSet rrset1 = ResourceRecordSet. builder()// + .name("www.denominator.io.")// + .type("CNAME")// + .qualifier(identifier1)// + .ttl(0)// + .addProfile(Weighted.create(1))// + .add(CNAMEData.create("www1.denominator.io.")).build(); + + private String identifier2 = "MyService-West"; + + private ResourceRecordSet rrset2 = ResourceRecordSet. builder()// + .name("www.denominator.io.")// + .type("CNAME")// + .qualifier(identifier2)// + .ttl(0)// + .addProfile(Weighted.create(5))// + .add(CNAMEData.create("www2.denominator.io.")).build(); + + @Test + public void iterateByNameWhenPresent() throws IOException, InterruptedException { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody(twoRecords)); + server.enqueue(new MockResponse().setResponseCode(200).setBody(oneRecord)); + server.enqueue(new MockResponse().setResponseCode(200).setBody(oneRecord)); + server.enqueue(new MockResponse().setResponseCode(200).setBody(oneRecord)); + + server.play(); + + try { + WeightedResourceRecordSetApi api = mockApi(server.getUrl("/")); + Iterator> iterator = api.iterateByName("www.denominator.io."); + assertEquals(iterator.next(), rrset1); + assertEquals(iterator.next(), rrset2); + + assertEquals(server.getRequestCount(), 1); + + assertEquals(server.takeRequest().getRequestLine(), + "GET /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=www.denominator.io. HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + @Test + public void iterateByNameWhenAbsent() throws IOException, InterruptedException { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody(noRecords)); + server.enqueue(new MockResponse().setResponseCode(200).setBody(oneRecord)); + server.enqueue(new MockResponse().setResponseCode(200).setBody(oneRecord)); + server.enqueue(new MockResponse().setResponseCode(200).setBody(oneRecord)); + + server.play(); + + try { + WeightedResourceRecordSetApi api = mockApi(server.getUrl("/")); + assertFalse(api.iterateByName("www.denominator.io.").hasNext()); + + assertEquals(server.getRequestCount(), 1); + + assertEquals(server.takeRequest().getRequestLine(), + "GET /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=www.denominator.io. HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + @Test + public void iterateByNameAndTypeWhenPresent() throws IOException, InterruptedException { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody(twoRecords)); + + server.play(); + + try { + WeightedResourceRecordSetApi api = mockApi(server.getUrl("/")); + assertEquals(api.iterateByNameAndType("www.denominator.io.", "CNAME").next(), rrset1); + + assertEquals(server.getRequestCount(), 1); + + assertEquals(server.takeRequest().getRequestLine(), + "GET /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=www.denominator.io.&type=CNAME HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + @Test + public void iterateByNameAndTypeWhenAbsent() throws IOException, InterruptedException { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody(noRecords)); + + server.play(); + + try { + WeightedResourceRecordSetApi api = mockApi(server.getUrl("/")); + assertFalse(api.iterateByNameAndType("www.denominator.io.", "CNAME").hasNext()); + + assertEquals(server.getRequestCount(), 1); + + assertEquals(server.takeRequest().getRequestLine(), + "GET /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=www.denominator.io.&type=CNAME HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + @Test + public void getByNameTypeAndQualifierWhenPresent() throws IOException, InterruptedException { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody(twoRecords)); + + server.play(); + + try { + WeightedResourceRecordSetApi api = mockApi(server.getUrl("/")); + assertEquals(api.getByNameTypeAndQualifier("www.denominator.io.", "CNAME", identifier1).get(), rrset1); + + assertEquals(server.getRequestCount(), 1); + + assertEquals( + server.takeRequest().getRequestLine(), + "GET /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=www.denominator.io.&type=CNAME&identifier=MyService-East HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + @Test + public void getByNameTypeAndQualifierWhenAbsent() throws IOException, InterruptedException { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody(noRecords)); + server.enqueue(new MockResponse().setResponseCode(200).setBody(oneRecord)); + server.enqueue(new MockResponse().setResponseCode(200).setBody(oneRecord)); + + server.play(); + + try { + WeightedResourceRecordSetApi api = mockApi(server.getUrl("/")); + assertEquals(api.getByNameTypeAndQualifier("www.denominator.io.", "CNAME", identifier1), Optional.absent()); + + assertEquals(server.getRequestCount(), 1); + + assertEquals( + server.takeRequest().getRequestLine(), + "GET /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=www.denominator.io.&type=CNAME&identifier=MyService-East HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + private String createCNAMERecordSet = "CREATEwww.denominator.io.CNAMEMyService-East10www1.denominator.io."; + private String changeSynced = "/change/C2682N5HXP0BZ4INSYNC2011-09-10T01:36:41.958Z"; + + @Test + public void putRecordSet() throws IOException, InterruptedException { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody(noRecords)); + server.enqueue(new MockResponse().setResponseCode(200).setBody(changeSynced)); + + server.play(); + + try { + WeightedResourceRecordSetApi api = mockApi(server.getUrl("/")); + api.put(rrset1); + + assertEquals(server.getRequestCount(), 2); + + assertEquals( + server.takeRequest().getRequestLine(), + "GET /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=www.denominator.io.&type=CNAME&identifier=MyService-East HTTP/1.1"); + + RecordedRequest createRRSet = server.takeRequest(); + assertEquals(createRRSet.getRequestLine(), "POST /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset HTTP/1.1"); + assertEquals(new String(createRRSet.getBody()), createCNAMERecordSet); + } finally { + server.shutdown(); + } + } + + @Test + public void putRecordSetSkipsWhenEqual() throws IOException, InterruptedException { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody(oneRecord)); + + server.play(); + + try { + WeightedResourceRecordSetApi api = mockApi(server.getUrl("/")); + api.put(rrset1); + + assertEquals(server.getRequestCount(), 1); + + assertEquals( + server.takeRequest().getRequestLine(), + "GET /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=www.denominator.io.&type=CNAME&identifier=MyService-East HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + private String deleteQualifier1 = "DELETEwww.denominator.io.CNAMEMyService-East10www1.denominator.io."; + + @Test + public void deleteDoesntAffectOtherQualifiers() throws IOException, InterruptedException { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody(twoRecords)); + server.enqueue(new MockResponse().setResponseCode(200).setBody(changeSynced)); + + server.play(); + + try { + WeightedResourceRecordSetApi api = mockApi(server.getUrl("/")); + api.deleteByNameTypeAndQualifier("www.denominator.io.", "CNAME", identifier1); + + assertEquals(server.getRequestCount(), 2); + + assertEquals( + server.takeRequest().getRequestLine(), + "GET /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=www.denominator.io.&type=CNAME&identifier=MyService-East HTTP/1.1"); + + RecordedRequest deleteQualifier1 = server.takeRequest(); + assertEquals(deleteQualifier1.getRequestLine(), "POST /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset HTTP/1.1"); + assertEquals(new String(deleteQualifier1.getBody()), this.deleteQualifier1); + } finally { + server.shutdown(); + } + } + + @Test + public void deleteAbsentRRSDoesNothing() throws IOException, InterruptedException { + MockWebServer server = new MockWebServer(); + server.enqueue(new MockResponse().setResponseCode(200).setBody(oneRecord)); + server.play(); + + try { + WeightedResourceRecordSetApi api = mockApi(server.getUrl("/")); + api.deleteByNameTypeAndQualifier("www.denominator.io.", "CNAME", identifier2); + + assertEquals(server.getRequestCount(), 1); + + assertEquals( + server.takeRequest().getRequestLine(), + "GET /2012-02-29/hostedzone/Z1PA6795UKMFR9/rrset?name=www.denominator.io.&type=CNAME&identifier=MyService-West HTTP/1.1"); + } finally { + server.shutdown(); + } + } + + private static WeightedResourceRecordSetApi mockApi(final URL url) { + return Denominator.create(new Route53Provider() { + @Override + public String url() { + return url.toString(); + } + }, credentials("accessKey", "secretKey")).api().weightedRecordSetsInZone("Z1PA6795UKMFR9").get(); + } +} diff --git a/providers/denominator-route53/src/test/java/denominator/route53/Route53WeightedWriteCommandsLiveTest.java b/providers/denominator-route53/src/test/java/denominator/route53/Route53WeightedWriteCommandsLiveTest.java new file mode 100644 index 00000000..5af00cbc --- /dev/null +++ b/providers/denominator-route53/src/test/java/denominator/route53/Route53WeightedWriteCommandsLiveTest.java @@ -0,0 +1,16 @@ +package denominator.route53; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import denominator.profile.BaseWeightedWriteCommandsLiveTest; + +@Test +public class Route53WeightedWriteCommandsLiveTest extends BaseWeightedWriteCommandsLiveTest { + @BeforeClass + private void setUp() { + Route53Connection connection = new Route53Connection(); + manager = connection.manager; + setMutableZoneIfPresent(connection.mutableZone); + } +}