From b02ef485301b0b211a626b64a37f65b4b0d1e51e Mon Sep 17 00:00:00 2001 From: Dan James Date: Tue, 27 Oct 2015 14:32:42 -0400 Subject: [PATCH 1/4] Add provider for Verisign's managed DNS service --- cli/supportedProviders.txt | 3 +- .../denominator/assertj/ModelAssertions.java | 2 +- settings.gradle | 2 +- verisigndns/README.md | 5 + verisigndns/build.gradle | 23 ++ verisigndns/config/checkstyle/checkstyle.xml | 188 +++++++++ .../verisigndns/HostedZonesReadable.java | 31 ++ .../ResourceRecordByNameAndTypeIterator.java | 121 ++++++ .../denominator/verisigndns/VerisignDns.java | 44 +++ ...signDnsAllProfileResourceRecordSetApi.java | 166 ++++++++ .../VerisignDnsContentHandlers.java | 225 +++++++++++ .../verisigndns/VerisignDnsEncoder.java | 366 ++++++++++++++++++ .../verisigndns/VerisignDnsErrorDecoder.java | 85 ++++ .../verisigndns/VerisignDnsException.java | 28 ++ .../verisigndns/VerisignDnsProvider.java | 146 +++++++ .../VerisignDnsResourceRecordSetApi.java | 60 +++ .../verisigndns/VerisignDnsTarget.java | 102 +++++ .../verisigndns/VerisignDnsZoneApi.java | 101 +++++ .../META-INF/services/denominator.Provider | 1 + .../HostedZonesReadableMockTest.java | 39 ++ .../verisigndns/MockVerisignDnsServer.java | 102 +++++ ...isignDnsProviderDynamicUpdateMockTest.java | 84 ++++ .../verisigndns/VerisignDnsProviderTest.java | 79 ++++ .../VerisignDnsReadOnlyLiveTest.java | 9 + ...risignDnsResourceRecordSetApiMockTest.java | 133 +++++++ .../verisigndns/VerisignDnsTest.java | 224 +++++++++++ .../verisigndns/VerisignDnsTestGraph.java | 15 + .../VerisignDnsWriteCommandsLiveTest.java | 9 + .../VerisignDnsZoneApiMockTest.java | 97 +++++ .../VerisignDnsZoneWriteCommandsLiveTest.java | 9 + 30 files changed, 2496 insertions(+), 3 deletions(-) create mode 100644 verisigndns/README.md create mode 100644 verisigndns/build.gradle create mode 100644 verisigndns/config/checkstyle/checkstyle.xml create mode 100644 verisigndns/src/main/java/denominator/verisigndns/HostedZonesReadable.java create mode 100644 verisigndns/src/main/java/denominator/verisigndns/ResourceRecordByNameAndTypeIterator.java create mode 100644 verisigndns/src/main/java/denominator/verisigndns/VerisignDns.java create mode 100644 verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java create mode 100644 verisigndns/src/main/java/denominator/verisigndns/VerisignDnsContentHandlers.java create mode 100644 verisigndns/src/main/java/denominator/verisigndns/VerisignDnsEncoder.java create mode 100644 verisigndns/src/main/java/denominator/verisigndns/VerisignDnsErrorDecoder.java create mode 100644 verisigndns/src/main/java/denominator/verisigndns/VerisignDnsException.java create mode 100644 verisigndns/src/main/java/denominator/verisigndns/VerisignDnsProvider.java create mode 100644 verisigndns/src/main/java/denominator/verisigndns/VerisignDnsResourceRecordSetApi.java create mode 100644 verisigndns/src/main/java/denominator/verisigndns/VerisignDnsTarget.java create mode 100644 verisigndns/src/main/java/denominator/verisigndns/VerisignDnsZoneApi.java create mode 100644 verisigndns/src/main/resources/META-INF/services/denominator.Provider create mode 100644 verisigndns/src/test/java/denominator/verisigndns/HostedZonesReadableMockTest.java create mode 100644 verisigndns/src/test/java/denominator/verisigndns/MockVerisignDnsServer.java create mode 100644 verisigndns/src/test/java/denominator/verisigndns/VerisignDnsProviderDynamicUpdateMockTest.java create mode 100644 verisigndns/src/test/java/denominator/verisigndns/VerisignDnsProviderTest.java create mode 100644 verisigndns/src/test/java/denominator/verisigndns/VerisignDnsReadOnlyLiveTest.java create mode 100644 verisigndns/src/test/java/denominator/verisigndns/VerisignDnsResourceRecordSetApiMockTest.java create mode 100644 verisigndns/src/test/java/denominator/verisigndns/VerisignDnsTest.java create mode 100644 verisigndns/src/test/java/denominator/verisigndns/VerisignDnsTestGraph.java create mode 100644 verisigndns/src/test/java/denominator/verisigndns/VerisignDnsWriteCommandsLiveTest.java create mode 100644 verisigndns/src/test/java/denominator/verisigndns/VerisignDnsZoneApiMockTest.java create mode 100644 verisigndns/src/test/java/denominator/verisigndns/VerisignDnsZoneWriteCommandsLiveTest.java diff --git a/cli/supportedProviders.txt b/cli/supportedProviders.txt index 7c56aad2..ec68b99d 100644 --- a/cli/supportedProviders.txt +++ b/cli/supportedProviders.txt @@ -7,4 +7,5 @@ denominator.designate.DesignateProvider denominator.dynect.DynECTProvider denominator.mock.MockProvider denominator.route53.Route53Provider -denominator.ultradns.UltraDNSProvider \ No newline at end of file +denominator.ultradns.UltraDNSProvider +denominator.verisigndns.VerisignDnsProvider \ No newline at end of file diff --git a/model/src/test/java/denominator/assertj/ModelAssertions.java b/model/src/test/java/denominator/assertj/ModelAssertions.java index ac47e342..3fca70bc 100644 --- a/model/src/test/java/denominator/assertj/ModelAssertions.java +++ b/model/src/test/java/denominator/assertj/ModelAssertions.java @@ -7,7 +7,7 @@ public class ModelAssertions extends Assertions { - public static ResourceRecordSetAssert assertThat(ResourceRecordSet actual) { + public static ResourceRecordSetAssert assertThat(ResourceRecordSet actual) { return new ResourceRecordSetAssert(actual); } diff --git a/settings.gradle b/settings.gradle index b3b967ff..31a48d0c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,6 @@ rootProject.name='denominator' -include 'model', 'core', 'route53', 'ultradns', 'dynect', 'clouddns', 'designate', 'cli' +include 'model', 'core', 'route53', 'ultradns', 'verisigndns', 'dynect', 'clouddns', 'designate', 'cli' rootProject.children.each { childProject -> childProject.name = 'denominator-' + childProject.name diff --git a/verisigndns/README.md b/verisigndns/README.md new file mode 100644 index 00000000..6c094449 --- /dev/null +++ b/verisigndns/README.md @@ -0,0 +1,5 @@ +## Notable Behaviors +The following are notable when compared to different providers. +* `Zone.id()` is the `Zone.name()` +* Zone lists are 1 + N requests in order to zip with the SOA's ttl and rname. +* The default ttl for record sets is 86400. diff --git a/verisigndns/build.gradle b/verisigndns/build.gradle new file mode 100644 index 00000000..340474e8 --- /dev/null +++ b/verisigndns/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'java' +apply plugin: 'checkstyle' + +sourceCompatibility = 1.6 + +test { + systemProperty 'verisigndns.url', System.getProperty('verisignmdns.url', '') + systemProperty 'verisigndns.username', System.getProperty('verisignmdns.username', '') + systemProperty 'verisigndns.password', System.getProperty('verisignmdns.password', '') + systemProperty 'verisigndns.zone', System.getProperty('verisignmdns.zone', '') +} + +dependencies { + compile project(':denominator-core') + compile 'com.netflix.feign:feign-core:8.10.0' + compile 'com.netflix.feign:feign-sax:8.10.0' + compile 'com.google.guava:guava:18.0' + testCompile project(':denominator-model').sourceSets.test.output + testCompile project(':denominator-core').sourceSets.test.output + testCompile 'junit:junit:4.12' + testCompile 'org.assertj:assertj-core:1.7.1' // last version supporting JDK 7 + testCompile 'com.squareup.okhttp:mockwebserver:2.5.0' +} diff --git a/verisigndns/config/checkstyle/checkstyle.xml b/verisigndns/config/checkstyle/checkstyle.xml new file mode 100644 index 00000000..47c01a2e --- /dev/null +++ b/verisigndns/config/checkstyle/checkstyle.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/verisigndns/src/main/java/denominator/verisigndns/HostedZonesReadable.java b/verisigndns/src/main/java/denominator/verisigndns/HostedZonesReadable.java new file mode 100644 index 00000000..0634b129 --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/HostedZonesReadable.java @@ -0,0 +1,31 @@ +package denominator.verisigndns; + +import javax.inject.Inject; + +import denominator.CheckConnection; +import denominator.verisigndns.VerisignDnsEncoder.Paging; + +public class HostedZonesReadable implements CheckConnection { + + private final VerisignDns api; + + @Inject + HostedZonesReadable(VerisignDns api) { + this.api = api; + } + + @Override + public boolean ok() { + try { + api.getZones(new Paging(1, 1)); + return true; + } catch (RuntimeException e) { + return false; + } + } + + @Override + public String toString() { + return "HostedZonesReadable"; + } +} diff --git a/verisigndns/src/main/java/denominator/verisigndns/ResourceRecordByNameAndTypeIterator.java b/verisigndns/src/main/java/denominator/verisigndns/ResourceRecordByNameAndTypeIterator.java new file mode 100644 index 00000000..b0c79fc4 --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/ResourceRecordByNameAndTypeIterator.java @@ -0,0 +1,121 @@ +package denominator.verisigndns; + +import static denominator.common.Util.peekingIterator; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import denominator.common.PeekingIterator; +import denominator.common.Util; +import denominator.model.ResourceRecordSet; +import denominator.model.ResourceRecordSet.Builder; +import denominator.verisigndns.VerisignDnsContentHandlers.Page; +import denominator.verisigndns.VerisignDnsContentHandlers.ResourceRecord; +import denominator.verisigndns.VerisignDnsEncoder.GetRRList; + +final class ResourceRecordByNameAndTypeIterator implements Iterator> { + + private final VerisignDns api; + private final GetRRList getRRList; + private final String zoneSuffix; + private PeekingIterator peekingIterator; + + public ResourceRecordByNameAndTypeIterator(VerisignDns api, GetRRList getRRList) { + this.api = api; + this.getRRList = getRRList; + zoneSuffix = "." + getRRList.getZoneName() + "."; + } + + @Override + public boolean hasNext() { + if (peekingIterator == null || !peekingIterator.hasNext()) { + nextPeekingIterator(); + } + return peekingIterator.hasNext(); + } + + private void nextPeekingIterator() { + if (getRRList.nextPage()) { + Page rrPage = api.getResourceRecords(getRRList.getZoneName(), getRRList); + getRRList.setTotal(rrPage.getCount()); + peekingIterator = peekingIterator(rrPage.getList().iterator()); + } + } + + private String relativeName(String name, String root) { + if (name.endsWith(root)) { + name = name.substring(0, name.length() - root.length()); + } + return name; + } + + @Override + public ResourceRecordSet next() { + if (peekingIterator == null) { + nextPeekingIterator(); + } + ResourceRecord record = peekingIterator.next(); + if (record == null) { + return null; + } + + String owner = relativeName(record.getName(), zoneSuffix); + String type = record.getType(); + Builder> builder = + ResourceRecordSet.builder().name(owner).type(type).ttl(record.getTtl()); + builder.add(getRRTypeAndRdata(type, record.getRdata())); + + while (hasNext()) { + ResourceRecord next = peekingIterator.peek(); + if (fqdnAndTypeEquals(next, record)) { + peekingIterator.next(); + builder.add(getRRTypeAndRdata(type, next.getRdata())); + } else { + break; + } + } + return builder.build(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + private static boolean fqdnAndTypeEquals(ResourceRecord actual, ResourceRecord expected) { + return actual.getName().equals(expected.getName()) && actual.getType().equals(expected.getType()); + } + + private static final int NAPTR_FIELD_FLAGS = 2; + private static final int NAPTR_FIELD_SERVICE = 3; + + private static Map getRRTypeAndRdata(String type, String rdata) { + + rdata = rdata.replace("\"", ""); + try { + if ("AAAA".equals(type)) { + rdata = rdata.toUpperCase(); + } else if ("NAPTR".equals(type)) { + List parts = Util.split(' ', rdata); + + if (parts.size() > NAPTR_FIELD_SERVICE) { + parts.set(NAPTR_FIELD_FLAGS, parts.get(NAPTR_FIELD_FLAGS).toUpperCase()); + + String service = parts.get(NAPTR_FIELD_SERVICE); + List serviceParts = Util.split('+', service); + serviceParts.set(0, serviceParts.get(0).toUpperCase()); + parts.set(NAPTR_FIELD_SERVICE, Util.join('+', serviceParts.toArray())); + + rdata = Util.join(' ', parts.toArray()); + } + } + return Util.toMap(type, rdata); + } catch (IllegalArgumentException e) { + Map map = new LinkedHashMap(); + map.put(type, rdata); + return map; + } + } +} diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDns.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDns.java new file mode 100644 index 00000000..ec2e4bfd --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDns.java @@ -0,0 +1,44 @@ +package denominator.verisigndns; + +import denominator.model.ResourceRecordSet; +import denominator.model.Zone; +import denominator.verisigndns.VerisignDnsContentHandlers.Page; +import denominator.verisigndns.VerisignDnsContentHandlers.ResourceRecord; +import denominator.verisigndns.VerisignDnsEncoder.GetRRList; +import denominator.verisigndns.VerisignDnsEncoder.Paging; +import feign.Param; +import feign.RequestLine; + +interface VerisignDns { + + @RequestLine("POST") + void createZone(@Param("createZone") Zone zone); + + @RequestLine("POST") + void updateSoa(@Param("updateSoa") Zone zone); + + @RequestLine("POST") + void deleteZone(@Param("deleteZone") String zone); + + @RequestLine("POST") + Page getZones(@Param("getZoneList") Paging paging); + + @RequestLine("POST") + Zone getZone(@Param("getZone") String zone); + + @RequestLine("POST") + void createResourceRecords(@Param("zone") String zone, + @Param("rrSet") ResourceRecordSet rrSet, @Param("oldRRSet") ResourceRecordSet oldRRSet); + + @RequestLine("POST") + void updateResourceRecords(@Param("zone") String zone, + @Param("rrSet") ResourceRecordSet rrSet, @Param("oldRRSet") ResourceRecordSet oldRRSet); + + @RequestLine("POST") + Page getResourceRecords(@Param("zone") String zone, + @Param("getRRList") GetRRList rrRequest); + + @RequestLine("POST") + void deleteResourceRecords(@Param("zone") String zone, + @Param("deleteRRSet") ResourceRecordSet rrSet); +} diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java new file mode 100644 index 00000000..74b9c90c --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java @@ -0,0 +1,166 @@ +package denominator.verisigndns; + +import static com.google.common.base.Predicates.in; +import static com.google.common.base.Predicates.not; +import static com.google.common.collect.Iterables.filter; +import static denominator.common.Preconditions.checkNotNull; +import static denominator.common.Util.equal; +import static denominator.common.Util.nextOrNull; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; + +import denominator.AllProfileResourceRecordSetApi; +import denominator.model.ResourceRecordSet; +import denominator.model.ResourceRecordSet.Builder; +import denominator.verisigndns.VerisignDnsEncoder.GetRRList; + +final class VerisignDnsAllProfileResourceRecordSetApi implements AllProfileResourceRecordSetApi { + + private final VerisignDns api; + private final String zoneName; + + VerisignDnsAllProfileResourceRecordSetApi(VerisignDns api, String zoneName) { + this.api = api; + this.zoneName = zoneName; + } + + @Override + public Iterator> iterator() { + GetRRList getRRList = new GetRRList(zoneName); + + return new ResourceRecordByNameAndTypeIterator(api, getRRList); + } + + @Override + public Iterator> iterateByName(String name) { + checkNotNull(name, "name"); + + GetRRList getRRList = new GetRRList(zoneName, name); + + return new ResourceRecordByNameAndTypeIterator(api, getRRList); + } + + @Override + public Iterator> iterateByNameAndType(String name, String type) { + checkNotNull(name, "name"); + checkNotNull(type, "type"); + + GetRRList getRRList = new GetRRList(zoneName, name, type); + + return new ResourceRecordByNameAndTypeIterator(api, getRRList); + } + + @Override + public ResourceRecordSet getByNameTypeAndQualifier(String name, String type, String qualifier) { + checkNotNull(name, "name"); + checkNotNull(type, "type"); + checkNotNull(qualifier, "qualifier"); + + GetRRList getRRList = new GetRRList(zoneName, name, type, qualifier); + + return nextOrNull(new ResourceRecordByNameAndTypeIterator(api, getRRList)); + } + + @Override + public void put(ResourceRecordSet rrset) { + checkNotNull(rrset, "rrset was null"); + + Integer ttlToApply = rrset.ttl() != null ? rrset.ttl() : 86400; + + ResourceRecordSet oldRRSet = null; + if (rrset.qualifier() != null) { + oldRRSet = getByNameTypeAndQualifier(rrset.name(), rrset.type(), rrset.qualifier()); + } else { + oldRRSet = nextOrNull(iterateByNameAndType(rrset.name(), rrset.type())); + } + + List> newRRData = null; + List> oldRRData = null; + if (oldRRSet != null) { + newRRData = Lists.newArrayList(filter(rrset.records(), not(in(oldRRSet.records())))); + if (newRRData.isEmpty() && !equal(oldRRSet.ttl(), ttlToApply)) { + oldRRData = new ArrayList>(); + oldRRData.addAll(oldRRSet.records()); + } else if (newRRData.isEmpty() && equal(oldRRSet.ttl(), ttlToApply)) { + return; + } else { + List> oldRRDataList = + ImmutableList.copyOf(filter(oldRRSet.records(), in(rrset.records()))); + + if (!oldRRDataList.isEmpty()) { + oldRRData = new ArrayList>(); + oldRRData.addAll(oldRRDataList); + newRRData.addAll(oldRRDataList); + } + } + } else { + newRRData = ImmutableList.copyOf(rrset.records()); + } + + Builder> newRRSetBuilder = ResourceRecordSet.builder(); + if (newRRData != null && !newRRData.isEmpty()) { + rrset = + newRRSetBuilder.name(rrset.name()).type(rrset.type()).ttl(ttlToApply).addAll(newRRData) + .build(); + } + + Builder> deleteRRSetBuilder = ResourceRecordSet.builder(); + ResourceRecordSet> rrsetToBeDeleted = null; + if (oldRRData != null) { + deleteRRSetBuilder.ttl(oldRRSet.ttl()); + deleteRRSetBuilder.name(oldRRSet.name()); + deleteRRSetBuilder.type(oldRRSet.type()); + deleteRRSetBuilder.addAll(oldRRData); + rrsetToBeDeleted = deleteRRSetBuilder.build(); + } + + api.updateResourceRecords(zoneName, rrset, rrsetToBeDeleted); + } + + @Override + public void deleteByNameAndType(String name, String type) { + checkNotNull(name, "name"); + checkNotNull(type, "type"); + + ResourceRecordSet rrSet = nextOrNull(iterateByNameAndType(name, type)); + if (rrSet != null) { + api.deleteResourceRecords(zoneName, rrSet); + } + } + + @Override + public void deleteByNameTypeAndQualifier(String name, String type, String qualifier) { + checkNotNull(name, "name"); + checkNotNull(type, "type"); + checkNotNull(qualifier, "rdata for the record"); + + ResourceRecordSet rrSet = getByNameTypeAndQualifier(name, type, qualifier); + if (rrSet != null) { + api.deleteResourceRecords(zoneName, rrSet); + } + } + + static final class Factory implements denominator.AllProfileResourceRecordSetApi.Factory { + + private final VerisignDns api; + + @Inject + Factory(VerisignDns api) { + this.api = api; + } + + @Override + public VerisignDnsAllProfileResourceRecordSetApi create(String name) { + return new VerisignDnsAllProfileResourceRecordSetApi(api, name); + } + } + +} diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsContentHandlers.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsContentHandlers.java new file mode 100644 index 00000000..12d96358 --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsContentHandlers.java @@ -0,0 +1,225 @@ +package denominator.verisigndns; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import denominator.model.Zone; +import feign.sax.SAXDecoder.ContentHandlerWithResult; + +final class VerisignDnsContentHandlers { + + private VerisignDnsContentHandlers() { + } + + abstract static class ElementHandler extends DefaultHandler { + + private Deque elements = null; + private String parentEl; + + protected ElementHandler(String parentEl) { + this.parentEl = parentEl; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + + if (parentEl.equals(qName)) { + elements = new ArrayDeque(); + } + + if (elements != null) { + elements.push(qName); + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + + if (elements != null) { + elements.pop(); + } + + if (parentEl.equals(qName)) { + elements = null; + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (elements == null) { + return; + } + + processElValue(elements.peek(), ch, start, length); + } + + protected abstract void processElValue(String currentEl, char[] ch, int start, int length); + } + + static class ZoneHandler extends ElementHandler implements ContentHandlerWithResult { + private String domainName; + private String email; + private int ttl; + + ZoneHandler() { + super("ns3:getZoneInfoRes"); + } + + @Override + protected void processElValue(String currentEl, char[] ch, int start, int length) { + if ("ns3:domainName".equals(currentEl)) { + domainName = val(ch, start, length); + } else if ("ns3:email".equals(currentEl)) { + email = val(ch, start, length); + } else if ("ns3:ttl".equals(currentEl)) { + ttl = Integer.valueOf(val(ch, start, length)); + } + } + + @Override + public Zone result() { + Zone zone = null; + + if (domainName != null) { + zone = Zone.create(domainName, domainName, ttl, email); + } + + return zone; + } + } + + static class ZoneListHandler extends ElementHandler implements ContentHandlerWithResult> { + private int count = 0; + private List zones = new ArrayList(); + + ZoneListHandler() { + super("ns3:getZoneListRes"); + } + + @Override + protected void processElValue(String currentEl, char[] ch, int start, int length) { + + if ("ns3:totalCount".equals(currentEl)) { + String value = val(ch, start, length); + count = Integer.valueOf(value); + } else if ("ns3:domainName".equals(currentEl)) { + String value = val(ch, start, length); + zones.add(Zone.create(value, value, 86400, "mdnshelp@verisign.com")); + } + } + + @Override + public Page result() { + return new Page(zones, count); + } + } + + static class RRHandler extends ElementHandler implements ContentHandlerWithResult> { + private int count = 0; + private List rrList = new ArrayList(); + + RRHandler() { + super("ns3:getResourceRecordListRes"); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + super.startElement(uri, localName, qName, attributes); + if ("ns3:resourceRecord".equals(qName)) { + rrList.add(new ResourceRecord()); + } + } + + @Override + protected void processElValue(String currentEl, char[] ch, int start, int length) { + if (rrList.isEmpty()) { + return; + } + + ResourceRecord resourceRecord = rrList.get(rrList.size() - 1); + String value = val(ch, start, length); + if ("ns3:totalCount".equals(currentEl)) { + count = Integer.valueOf(value); + } else if ("ns3:resourceRecordId".equals(currentEl)) { + resourceRecord.id = value; + } else if ("ns3:owner".equals(currentEl)) { + resourceRecord.name = value; + } else if ("ns3:type".equals(currentEl)) { + resourceRecord.type = value; + } else if ("ns3:rData".equals(currentEl)) { + resourceRecord.rdata = value; + } else if ("ns3:ttl".equals(currentEl)) { + resourceRecord.ttl = Integer.valueOf(value); + } + } + + @Override + public Page result() { + return new Page(rrList, count); + } + } + + static String val(char[] ch, int start, int length) { + return new String(ch, start, length).trim(); + } + + static class Page { + private final List list; + private final int count; + + Page(List list, int count) { + this.list = list; + this.count = count; + } + + List getList() { + return list; + } + + int getCount() { + return count; + } + + @Override + public String toString() { + return String.format("page[count=%d total=%d]", list != null ? list.size() : 0, count); + } + } + + static class ResourceRecord { + private String id; + private String name; + private String type; + private String rdata; + private Integer ttl; + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public String getRdata() { + return rdata; + } + + public Integer getTtl() { + return ttl; + } + + @Override + public String toString() { + return String.format("rr[id=%s name=%s type=%s rdata=\"%s\" ttl=%d]", id, name, type, rdata, ttl); + } + } +} diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsEncoder.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsEncoder.java new file mode 100644 index 00000000..9ef98ce9 --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsEncoder.java @@ -0,0 +1,366 @@ +package denominator.verisigndns; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import denominator.common.Util; +import denominator.model.ResourceRecordSet; +import denominator.model.Zone; +import feign.RequestTemplate; +import feign.codec.EncodeException; +import feign.codec.Encoder; + +final class VerisignDnsEncoder implements Encoder { + + private static final String NS_API_1 = "api1"; + private static final String NS_API_2 = "api2"; + + @SuppressWarnings("unchecked") + @Override + public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException { + + Map params = Map.class.cast(object); + + Node node = null; + if (params.containsKey("rrSet")) { + node = encodeRRSet(params); + } else if (params.containsKey("createZone")) { + node = encodeCreateZone(params); + } else if (params.containsKey("updateSoa")) { + node = encodeUpdateSoa(params); + } else if (params.containsKey("getZone")) { + node = encodeGetZone(params); + } else if (params.containsKey("getZoneList")) { + node = encodeGetZoneList(params); + } else if (params.containsKey("deleteZone")) { + node = encodeDeleteZone(params); + } else if (params.containsKey("getRRList")) { + node = encodeGetRRList(params); + } else if (params.containsKey("deleteRRSet")) { + node = encodeDeleteRRSet(params); + } else { + throw new EncodeException("Unsupported param key"); + } + + template.body(node.toXml()); + } + + private String normalize(String email) { + email = email.replace("@", "."); + if (!email.endsWith(".")) { + email += "."; + } + return email; + } + + private Node encodeCreateZone(Map params) { + + Zone zone = Zone.class.cast(params.get("createZone")); + if (zone == null) { + return null; + } + + TagNode zoneNode = new TagNode(NS_API_1, "createZone"); + zoneNode.add(NS_API_1, "domainName", zone.name()); + zoneNode.add(NS_API_1, "type", "DNS Hosting"); + return zoneNode; + } + + private Node encodeUpdateSoa(Map params) { + + Zone zone = Zone.class.cast(params.get("updateSoa")); + if (zone == null) { + return null; + } + + TagNode soaNode = new TagNode(NS_API_1, "zoneSOAInfo"); + soaNode.add(NS_API_1, "email", normalize(zone.email())); + soaNode.add(NS_API_1, "retry", 7200); + soaNode.add(NS_API_1, "ttl", zone.ttl()); + soaNode.add(NS_API_1, "refresh", 86400); + soaNode.add(NS_API_1, "expire", 1209600); + + TagNode zoneNode = new TagNode(NS_API_1, "updateSOA"); + zoneNode.add(NS_API_1, "domainName", zone.name()); + zoneNode.add(soaNode); + return zoneNode; + } + + private Node encodeGetZone(Map params) { + + String zoneName = String.class.cast(params.get("getZone")); + if (zoneName == null) { + return null; + } + + TagNode zoneNode = new TagNode(NS_API_1, "getZoneInfo"); + zoneNode.add(NS_API_1, "domainName", zoneName); + return zoneNode; + } + + private Node encodeGetZoneList(Map params) { + + Paging paging = Paging.class.cast(params.get("getZoneList")); + if (paging == null) { + return null; + } + + TagNode zoneListNode = new TagNode(NS_API_1, "getZoneList"); + zoneListNode.add(toPagingNode(paging)); + return zoneListNode; + } + + private Node encodeDeleteZone(Map params) { + + String zoneName = (String) params.get("deleteZone"); + if (zoneName == null) { + return null; + } + + return new TagNode(NS_API_1, "deleteZone").add(NS_API_1, "domainName", zoneName); + } + + private Node encodeDeleteRRSet(Map params) { + + ResourceRecordSet oldRRSet = ResourceRecordSet.class.cast(params.get("deleteRRSet")); + if (oldRRSet == null) { + return null; + } + + String zoneName = String.class.cast(params.get("zone")); + + TagNode bulkUpdateZoneNode = new TagNode(NS_API_2, "bulkUpdateSingleZone"); + bulkUpdateZoneNode.add(NS_API_2, "domainName", zoneName); + bulkUpdateZoneNode.add(toRRNode(NS_API_2, "deleteResourceRecords", oldRRSet, false)); + return bulkUpdateZoneNode; + } + + private Node toPagingNode(Paging paging) { + TagNode pagingNode = null; + + if (paging != null) { + pagingNode = new TagNode(NS_API_1, "listPagingInfo"); + + pagingNode.add(NS_API_1, "pageNumber", paging.pageNumber); + pagingNode.add(NS_API_1, "pageSize", paging.pageSize); + } + + return pagingNode; + } + + private Node encodeGetRRList(Map params) { + + GetRRList getRRList = GetRRList.class.cast(params.get("getRRList")); + if (getRRList == null) { + return null; + } + + String zoneName = String.class.cast(params.get("zone")); + + TagNode getRRListNode = new TagNode(NS_API_1, "getResourceRecordList"); + getRRListNode.add(NS_API_1, "domainName", zoneName); + getRRListNode.add(NS_API_1, "owner", getRRList.ownerName); + getRRListNode.add(NS_API_1, "resourceRecordType", getRRList.type); + getRRListNode.add(toPagingNode(getRRList.paging)); + + return getRRListNode; + } + + private Node encodeRRSet(Map params) { + + ResourceRecordSet rrSet = ResourceRecordSet.class.cast(params.get("rrSet")); + if (rrSet == null) { + return null; + } + + String zoneName = String.class.cast(params.get("zone")); + ResourceRecordSet oldRRSet = ResourceRecordSet.class.cast(params.get("oldRRSet")); + + TagNode bulkUpdateZoneNode = new TagNode(NS_API_2, "bulkUpdateSingleZone"); + bulkUpdateZoneNode.add(NS_API_2, "domainName", zoneName); + bulkUpdateZoneNode.add(toRRNode(NS_API_2, "createResourceRecords", rrSet, true)); + if (oldRRSet != null) { + bulkUpdateZoneNode.add(toRRNode(NS_API_2, "deleteResourceRecords", oldRRSet, false)); + } + + return bulkUpdateZoneNode; + } + + private Node toRRNode(String ns, String tag, ResourceRecordSet rrSet, boolean includeTtl) { + + String name = rrSet.name(); + String type = rrSet.type(); + Integer ttl = rrSet.ttl(); + TagNode rrsNode = new TagNode(ns, tag); + + for (Map record : rrSet.records()) { + TagNode rrNode = new TagNode(ns, "resourceRecord"); + rrNode.add(ns, "owner", name); + rrNode.add(ns, "type", type); + rrNode.add(ns, "rData", Util.flatten(record)); + if (includeTtl && ttl != null) { + rrNode.add(ns, "ttl", ttl.intValue()); + } + rrsNode.add(rrNode); + } + return rrsNode; + } + + static class GetRRList { + private String zoneName; + private String ownerName; + private String type; + private String viewName; + private Paging paging; + + public GetRRList(String zoneName) { + this.zoneName = zoneName; + } + + public GetRRList(String zoneName, String ownerName) { + this.zoneName = zoneName; + this.ownerName = ownerName; + } + + public GetRRList(String zoneName, String ownerName, String type) { + this.zoneName = zoneName; + this.ownerName = ownerName; + this.type = type; + } + + public GetRRList(String zoneName, String ownerName, String type, String viewName) { + this.zoneName = zoneName; + this.ownerName = ownerName; + this.type = type; + this.viewName = viewName; + } + + public String getZoneName() { + return zoneName; + } + + public boolean nextPage() { + if (paging == null) { + paging = new Paging(1); + return true; + } else { + return paging.nextPage(); + } + } + + public void setTotal(int total) { + paging.setTotal(total); + } + + @Override + public String toString() { + return String.format("getrrlist[zone=%s owner=%s type=%s view=%s paging=%s]", + zoneName, ownerName, type, viewName, paging); + } + } + + static class Paging { + private int pageNumber; + private int pageSize; + private int total; + + Paging(int pageNumber) { + this.pageNumber = pageNumber; + this.pageSize = 100; + } + + Paging(int pageNumber, int pageSize) { + this.pageNumber = pageNumber; + this.pageSize = pageSize; + } + + void setTotal(int total) { + this.total = total; + } + + int getPages() { + return (total / pageSize) + ((total % pageSize) == 0 ? 0 : 1); + } + + boolean nextPage() { + return ++pageNumber < total; + } + + @Override + public String toString() { + return String.format("paging[page=%d size=%d total=%d pages=%d]", pageNumber, pageSize, total, getPages()); + } + } + + interface Node { + String toXml(); + } + + class TextNode implements Node { + + private final String value; + + TextNode(String value) { + this.value = value; + } + + @Override + public String toXml() { + return value; + } + } + + class TagNode implements Node { + + private final String ns; + private final String tag; + private final List children; + + TagNode(String ns, String tag) { + this.ns = ns; + this.tag = tag; + this.children = new ArrayList(); + } + + String getTag() { + return tag; + } + + List getChildren() { + return children; + } + + TagNode add(Node node) { + if (node != null) { + children.add(node); + } + return this; + } + + TagNode add(String cns, String ctag, String value) { + if (value != null) { + children.add(new TagNode(cns, ctag).add(new TextNode(value))); + } + return this; + } + + TagNode add(String cns, String ctag, int value) { + return add(cns, ctag, Integer.toString(value)); + } + + @Override + public String toXml() { + + StringBuilder sb = new StringBuilder(); + sb.append("<").append(ns).append(":").append(tag).append(">"); + for (Node child : children) { + sb.append(child.toXml()); + } + sb.append(""); + + return sb.toString(); + } + } +} diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsErrorDecoder.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsErrorDecoder.java new file mode 100644 index 00000000..06fd269d --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsErrorDecoder.java @@ -0,0 +1,85 @@ +package denominator.verisigndns; + +import java.io.IOException; + +import javax.inject.Inject; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import feign.FeignException; +import feign.Response; +import feign.codec.Decoder; +import feign.codec.ErrorDecoder; +import feign.sax.SAXDecoder.ContentHandlerWithResult; + +final class VerisignDnsErrorDecoder implements ErrorDecoder { + + private final Decoder decoder; + + VerisignDnsErrorDecoder(Decoder decoder) { + this.decoder = decoder; + } + + @Override + public Exception decode(String methodKey, Response response) { + + try { + Object errorObject = decoder.decode(response, VerisignDnsError.class); + VerisignDnsError error = VerisignDnsError.class.cast(errorObject); + if (error == null) { + return FeignException.errorStatus(methodKey, response); + } + + StringBuilder message = new StringBuilder(); + message.append(methodKey).append(" failed"); + if (error.code != null) { + message.append(" with error ").append(error.code); + } + if (error.description != null) { + message.append(": ").append(error.description); + } + + int errorCode = -1; + if (error.description.equalsIgnoreCase("The domain name could not be found.")) { + errorCode = VerisignDnsException.DOMAIN_NOT_FOUND; + } else if (error.description + .equalsIgnoreCase("Domain already exists. Please verify your domain name.")) { + errorCode = VerisignDnsException.DOMAIN_ALREADY_EXISTS; + } + + return new VerisignDnsException(message.toString(), errorCode, error.description); + } catch (IOException ignored) { + return FeignException.errorStatus(methodKey, response); + } catch (RuntimeException propagate) { + return propagate; + } + } + + static class VerisignDnsError extends DefaultHandler implements + ContentHandlerWithResult { + + private String description; + private String code; + + @Inject + VerisignDnsError() { + } + + @Override + public VerisignDnsError result() { + return (code == null && description == null) ? null : this; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + + if ("ns3:reason".equals(qName)) { + description = attributes.getValue("description"); + code = attributes.getValue("code"); + } + } + } +} diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsException.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsException.java new file mode 100644 index 00000000..076def96 --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsException.java @@ -0,0 +1,28 @@ +package denominator.verisigndns; + +import feign.FeignException; + +final class VerisignDnsException extends FeignException { + + static final int SYSTEM_ERROR = -1; + static final int DOMAIN_NOT_FOUND = 1; + static final int DOMAIN_ALREADY_EXISTS = 2; + + private static final long serialVersionUID = 1L; + private final int code; + private final String description; + + VerisignDnsException(String message, int code, String description) { + super(message); + this.code = code; + this.description = description; + } + + public int code() { + return code; + } + + public String description() { + return description; + } +} diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsProvider.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsProvider.java new file mode 100644 index 00000000..0354e0fe --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsProvider.java @@ -0,0 +1,146 @@ +package denominator.verisigndns; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import javax.inject.Singleton; + +import dagger.Provides; +import denominator.AllProfileResourceRecordSetApi; +import denominator.BasicProvider; +import denominator.CheckConnection; +import denominator.DNSApiManager; +import denominator.ResourceRecordSetApi; +import denominator.ZoneApi; +import denominator.config.GeoUnsupported; +import denominator.config.NothingToClose; +import denominator.config.WeightedUnsupported; +import denominator.verisigndns.VerisignDnsContentHandlers.RRHandler; +import denominator.verisigndns.VerisignDnsContentHandlers.ZoneHandler; +import denominator.verisigndns.VerisignDnsContentHandlers.ZoneListHandler; +import denominator.verisigndns.VerisignDnsErrorDecoder.VerisignDnsError; +import feign.Feign; +import feign.Logger; +import feign.Request.Options; +import feign.codec.Decoder; +import feign.sax.SAXDecoder; + +public class VerisignDnsProvider extends BasicProvider { + private static final String DEFAULT_URL = "https://api.verisigndns.com/dnsa-ws/V2.0/dnsaapi?wsdl"; + private final String url; + + public VerisignDnsProvider() { + this(null); + } + + /** + * Construct a new provider for the Verisign Dns service. + * + * @param url if empty or null use default + */ + public VerisignDnsProvider(String url) { + this.url = url == null || url.isEmpty() ? DEFAULT_URL : url; + } + + @Override + public String url() { + return url; + } + + @Override + public Set basicRecordTypes() { + Set types = new LinkedHashSet(); + types.addAll(Arrays.asList("A", "AAAA", "CNAME", "MX", "NAPTR", "NS", "PTR", "SRV", "TXT")); + return types; + } + + @Override + public Map> credentialTypeToParameterNames() { + Map> options = new LinkedHashMap>(); + options.put("password", Arrays.asList("username", "password")); + return options; + } + + @dagger.Module(injects = { DNSApiManager.class }, + complete = false, + includes = { NothingToClose.class, WeightedUnsupported.class, GeoUnsupported.class, FeignModule.class }) + public static final class Module { + + @Provides + CheckConnection checkConnection(HostedZonesReadable checkConnection) { + return checkConnection; + } + + @Provides + @Singleton + ZoneApi provideZoneApi(VerisignDnsZoneApi api) { + return api; + } + + + @Provides + @Singleton + ResourceRecordSetApi.Factory provideResourceRecordSetApiFactory( + VerisignDnsResourceRecordSetApi.Factory factory) { + return factory; + } + + @Provides + @Singleton + AllProfileResourceRecordSetApi.Factory provideAllProfileResourceRecordSetApiFactory( + VerisignDnsAllProfileResourceRecordSetApi.Factory factory) { + return factory; + } + + } + + @dagger.Module(injects = VerisignDnsResourceRecordSetApi.Factory.class, complete = false) + public static final class FeignModule { + + @Provides + @Singleton + VerisignDns verisignDns(Feign feign, VerisignDnsTarget target) { + return feign.newInstance(target); + } + + @Provides + Logger logger() { + return new Logger.NoOpLogger(); + } + + @Provides + Logger.Level logLevel() { + return Logger.Level.NONE; + } + + @Provides + @Singleton + Feign feign(Logger logger, Logger.Level logLevel) { + + Options options = new Options(10 * 1000, 10 * 60 * 1000); + Decoder decoder = decoder(); + + return Feign.builder() + .logger(logger) + .logLevel(logLevel) + .options(options) + .encoder(new VerisignDnsEncoder()) + .decoder(decoder) + .errorDecoder(new VerisignDnsErrorDecoder(decoder)) + .build(); + } + + static Decoder decoder() { + return SAXDecoder.builder() + .registerContentHandler(RRHandler.class) + .registerContentHandler(ZoneHandler.class) + .registerContentHandler(ZoneListHandler.class) + .registerContentHandler(VerisignDnsError.class) + .build(); + } + } +} diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsResourceRecordSetApi.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsResourceRecordSetApi.java new file mode 100644 index 00000000..30ad8ee0 --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsResourceRecordSetApi.java @@ -0,0 +1,60 @@ +package denominator.verisigndns; + +import static denominator.common.Util.nextOrNull; + +import java.util.Iterator; + +import javax.inject.Inject; + +import denominator.ResourceRecordSetApi; +import denominator.model.ResourceRecordSet; + +final class VerisignDnsResourceRecordSetApi implements ResourceRecordSetApi { + + private final VerisignDnsAllProfileResourceRecordSetApi allApi; + + public VerisignDnsResourceRecordSetApi(VerisignDnsAllProfileResourceRecordSetApi allApi) { + this.allApi = allApi; + } + + @Override + public Iterator> iterator() { + return allApi.iterator(); + } + + @Override + public Iterator> iterateByName(String name) { + return allApi.iterateByName(name); + } + + @Override + public ResourceRecordSet getByNameAndType(String name, String type) { + return nextOrNull(allApi.iterateByNameAndType(name, type)); + } + + @Override + public void put(ResourceRecordSet rrset) { + allApi.put(rrset); + } + + @Override + public void deleteByNameAndType(String name, String type) { + allApi.deleteByNameAndType(name, type); + } + + static final class Factory implements ResourceRecordSetApi.Factory { + + private final VerisignDnsAllProfileResourceRecordSetApi.Factory allApi; + + @Inject + Factory(VerisignDnsAllProfileResourceRecordSetApi.Factory allApi) { + this.allApi = allApi; + } + + @Override + public ResourceRecordSetApi create(String name) { + return new VerisignDnsResourceRecordSetApi(allApi.create(name)); + } + } + +} diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsTarget.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsTarget.java new file mode 100644 index 00000000..87536c0f --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsTarget.java @@ -0,0 +1,102 @@ +package denominator.verisigndns; + +import static denominator.common.Preconditions.checkNotNull; +import static feign.Util.UTF_8; +import static java.lang.String.format; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.inject.Inject; + +import denominator.Credentials; +import denominator.Provider; +import feign.Request; +import feign.RequestTemplate; +import feign.Target; + +final class VerisignDnsTarget implements Target { + static final AtomicInteger MSGID = new AtomicInteger(); + // formatter:off + static final String SOAP_TEMPLATE = + "\n" + + " \n" + + " \n" + + " %d\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " %s\n" + + " %s\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " %s\n" + + " \n" + + ""; + // formatter:on + + private final Provider provider; + private final javax.inject.Provider credentials; + + @Inject + VerisignDnsTarget(Provider provider, javax.inject.Provider credentials) { + this.provider = provider; + this.credentials = credentials; + } + + @Override + public Class type() { + return VerisignDns.class; + } + + @Override + public String name() { + return provider.name(); + } + + @Override + public String url() { + return provider.url(); + } + + @Override + public Request apply(RequestTemplate in) { + String username; + String password; + + Credentials creds = credentials.get(); + if (creds instanceof List) { + @SuppressWarnings("unchecked") + List listCreds = (List) creds; + username = listCreds.get(0).toString(); + password = listCreds.get(1).toString(); + } else if (creds instanceof Map) { + @SuppressWarnings("unchecked") + Map mapCreds = (Map) creds; + username = checkNotNull(mapCreds.get("username"), "username").toString(); + password = checkNotNull(mapCreds.get("password"), "password").toString(); + } else { + throw new IllegalArgumentException("Unsupported credential type: " + creds); + } + + in.insert(0, url()); + in.body(xml(username, password, new String(in.body(), UTF_8))); + in.header("Host", URI.create(in.url()).getHost()); + in.header("Content-Type", "application/soap+xml"); + return in.request(); + } + + private String xml(String username, String password, String body) { + return format(SOAP_TEMPLATE, MSGID.getAndIncrement(), username, password, body); + } +} diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsZoneApi.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsZoneApi.java new file mode 100644 index 00000000..e2cadcb8 --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsZoneApi.java @@ -0,0 +1,101 @@ +package denominator.verisigndns; + +import static denominator.common.Util.singletonIterator; + +import java.util.Iterator; + +import javax.inject.Inject; + +import denominator.model.Zone; +import denominator.verisigndns.VerisignDnsContentHandlers.Page; +import denominator.verisigndns.VerisignDnsEncoder.Paging; + +final class VerisignDnsZoneApi implements denominator.ZoneApi { + + private final VerisignDns api; + + @Inject + VerisignDnsZoneApi(VerisignDns api) { + this.api = api; + } + + @Override + public Iterator iterator() { + return new Iterator() { + private Paging paging; + private Iterator current; + + private void check() { + boolean nextPage = false; + + if (paging == null) { + paging = new Paging(1); + nextPage = true; + } else if (!current.hasNext() && paging.nextPage()) { + nextPage = true; + } + + if (nextPage) { + Page page = api.getZones(paging); + paging.setTotal(page.getCount()); + current = page.getList().iterator(); + } + } + + @Override + public boolean hasNext() { + check(); + return current.hasNext(); + } + + @Override + public Zone next() { + check(); + return api.getZone(current.next().name()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public Iterator iterateByName(String name) { + Zone zone = null; + try { + zone = api.getZone(name); + } catch (VerisignDnsException e) { + if (e.code() != VerisignDnsException.DOMAIN_NOT_FOUND) { + throw e; + } + } + return singletonIterator(zone); + } + + @Override + public String put(Zone zone) { + try { + api.createZone(zone); + } catch (VerisignDnsException e) { + if (e.code() != VerisignDnsException.DOMAIN_ALREADY_EXISTS) { + throw e; + } + } + + api.updateSoa(zone); + return zone.name(); + } + + @Override + public void delete(String zone) { + try { + api.deleteZone(zone); + } catch (VerisignDnsException e) { + if (e.code() != VerisignDnsException.DOMAIN_NOT_FOUND) { + throw e; + } + } + } +} diff --git a/verisigndns/src/main/resources/META-INF/services/denominator.Provider b/verisigndns/src/main/resources/META-INF/services/denominator.Provider new file mode 100644 index 00000000..dd32b01d --- /dev/null +++ b/verisigndns/src/main/resources/META-INF/services/denominator.Provider @@ -0,0 +1 @@ +denominator.verisigndns.VerisignDnsProvider diff --git a/verisigndns/src/test/java/denominator/verisigndns/HostedZonesReadableMockTest.java b/verisigndns/src/test/java/denominator/verisigndns/HostedZonesReadableMockTest.java new file mode 100644 index 00000000..541b23db --- /dev/null +++ b/verisigndns/src/test/java/denominator/verisigndns/HostedZonesReadableMockTest.java @@ -0,0 +1,39 @@ +package denominator.verisigndns; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Rule; +import org.junit.Test; + +import com.squareup.okhttp.mockwebserver.MockResponse; + +import static denominator.verisigndns.VerisignDnsTest.getZoneListRes; + +import denominator.DNSApiManager; + +public class HostedZonesReadableMockTest { + + @Rule + public final MockVerisignDnsServer server = new MockVerisignDnsServer(); + + @Test + public void singleRequestOnSuccess() throws Exception { + server.enqueue(getZoneListRes); + + DNSApiManager api = server.connect(); + assertTrue(api.checkConnection()); + + server.assertRequest(); + } + + @Test + public void singleRequestOnFailure() throws Exception { + server.enqueue(new MockResponse().setResponseCode(500)); + + DNSApiManager api = server.connect(); + assertFalse(api.checkConnection()); + + server.assertRequest(); + } +} diff --git a/verisigndns/src/test/java/denominator/verisigndns/MockVerisignDnsServer.java b/verisigndns/src/test/java/denominator/verisigndns/MockVerisignDnsServer.java new file mode 100644 index 00000000..d4134c95 --- /dev/null +++ b/verisigndns/src/test/java/denominator/verisigndns/MockVerisignDnsServer.java @@ -0,0 +1,102 @@ +package denominator.verisigndns; + +import static denominator.assertj.MockWebServerAssertions.assertThat; +import static denominator.verisigndns.VerisignDnsTarget.SOAP_TEMPLATE; +import static java.lang.String.format; + +import java.io.IOException; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; + +import denominator.Credentials; +import denominator.CredentialsConfiguration; +import denominator.DNSApiManager; +import denominator.Denominator; +import denominator.Credentials.ListCredentials; +import denominator.assertj.RecordedRequestAssert; + +public class MockVerisignDnsServer extends VerisignDnsProvider implements TestRule { + + private final MockWebServer delegate = new MockWebServer(); + private String username; + private String password; + private String soapTemplate; + + MockVerisignDnsServer() { + credentials("testuser", "password"); + } + + @Override + public String url() { + return "http://localhost:" + delegate.getPort(); + } + + DNSApiManager connect() { + return Denominator.create(this, CredentialsConfiguration.credentials(credentials())); + } + + Credentials credentials() { + return ListCredentials.from(username, password); + } + + MockVerisignDnsServer credentials(String username, String password) { + this.username = username; + this.password = password; + this.soapTemplate = format(SOAP_TEMPLATE, System.currentTimeMillis(), username, password, "%s"); + return this; + } + + void enqueue(MockResponse mockResponse) { + delegate.enqueue(mockResponse); + } + + void enqueueError(String code, String description) { + delegate.enqueue(new MockResponse().setResponseCode(500).setBody( + format(FAULT_TEMPLATE, description, code, description))); + } + + RecordedRequestAssert assertRequest() throws InterruptedException { + return assertThat(delegate.takeRequest()); + } + + RecordedRequestAssert assertSoapBody(String soapBody) throws InterruptedException { + return assertThat(delegate.takeRequest()).hasMethod("POST").hasPath("/") + .hasBody(format(soapTemplate, soapBody)); + } + + void shutdown() throws IOException { + delegate.shutdown(); + } + + @Override + public Statement apply(Statement base, Description description) { + return delegate.apply(base, description); + } + + @dagger.Module(injects = DNSApiManager.class, complete = false, + includes = VerisignDnsProvider.Module.class) + static final class Module { + + } + + static final String FAULT_TEMPLATE = + "" + + " " + + " ns3:Receiver" + + " " + + " " + + " %s" + + " " + + " " + + " " + + " false" + + " " + + " " + + " " + + ""; +} diff --git a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsProviderDynamicUpdateMockTest.java b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsProviderDynamicUpdateMockTest.java new file mode 100644 index 00000000..db6ed781 --- /dev/null +++ b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsProviderDynamicUpdateMockTest.java @@ -0,0 +1,84 @@ +package denominator.verisigndns; + +import static denominator.CredentialsConfiguration.credentials; + +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Rule; +import org.junit.Test; + +import com.squareup.okhttp.mockwebserver.MockResponse; + +import dagger.Module; +import dagger.Provides; +import denominator.Credentials; +import denominator.Credentials.ListCredentials; +import denominator.DNSApi; +import denominator.Denominator; + +public class VerisignDnsProviderDynamicUpdateMockTest { + + @Rule + public MockVerisignDnsServer server = new MockVerisignDnsServer(); + + @Test + public void dynamicEndpointUpdates() throws Exception { + final AtomicReference url = new AtomicReference(server.url()); + server.enqueue(new MockResponse().setBody("")); + + DNSApi api = Denominator.create(new VerisignDnsProvider() { + @Override + public String url() { + return url.get(); + } + }, credentials(server.credentials())).api(); + + api.zones().iterator().hasNext(); + server.assertRequest(); + + MockVerisignDnsServer server2 = new MockVerisignDnsServer(); + url.set(server2.url()); + server2.enqueue(new MockResponse().setBody("")); + + api.zones().iterator().hasNext(); + server2.assertRequest(); + server2.shutdown(); + } + + @Test + public void dynamicCredentialUpdates() throws Exception { + server.enqueue(new MockResponse() + .setBody("")); + + AtomicReference dynamicCredentials = + new AtomicReference(server.credentials()); + + DNSApi api = Denominator.create(server, new OverrideCredentials(dynamicCredentials)).api(); + + api.zones().iterator().hasNext(); + server.assertRequest(); + + dynamicCredentials.set(ListCredentials.from("bob", "comeon")); + server.credentials("bob", "comeon"); + server.enqueue(new MockResponse() + .setBody("")); + + api.zones().iterator().hasNext(); + server.assertRequest(); + } + + @Module(complete = false, library = true, overrides = true) + static class OverrideCredentials { + + final AtomicReference dynamicCredentials; + + OverrideCredentials(AtomicReference dynamicCredentials) { + this.dynamicCredentials = dynamicCredentials; + } + + @Provides + public Credentials get() { + return dynamicCredentials.get(); + } + } +} diff --git a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsProviderTest.java b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsProviderTest.java new file mode 100644 index 00000000..dbf38534 --- /dev/null +++ b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsProviderTest.java @@ -0,0 +1,79 @@ +package denominator.verisigndns; + +import static denominator.CredentialsConfiguration.credentials; +import static denominator.Denominator.create; +import static denominator.Providers.list; +import static denominator.Providers.provide; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import dagger.ObjectGraph; +import denominator.Credentials.MapCredentials; +import denominator.DNSApiManager; +import denominator.Provider; + +public class VerisignDnsProviderTest { + + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + private static final Provider PROVIDER = new VerisignDnsProvider(); + + @Test + public void testVerisignDnsMetadata() { + assertThat(PROVIDER.name()).isEqualTo("verisigndns"); + assertThat(PROVIDER.supportsDuplicateZoneNames()).isFalse(); + assertThat(PROVIDER.credentialTypeToParameterNames()).containsEntry("password", + Arrays.asList("username", "password")); + } + + @Test + public void testVerisignDnsRegistered() { + assertThat(list()).contains(PROVIDER); + } + + @Test + public void testProviderWiresVerisignDnsZoneApi() { + DNSApiManager manager = create(PROVIDER, credentials("username", "password")); + assertThat(manager.api().zones()).isInstanceOf(VerisignDnsZoneApi.class); + manager = create("verisigndns", credentials("username", "password")); + assertThat(manager.api().zones()).isInstanceOf(VerisignDnsZoneApi.class); + + Map map = new LinkedHashMap(); + map.put("username", "U"); + map.put("password", "P"); + manager = create("verisigndns", credentials(MapCredentials.from(map))); + assertThat(manager.api().zones()).isInstanceOf(VerisignDnsZoneApi.class); + } + + @Test + public void testCredentialsRequired() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("no credentials supplied. " + PROVIDER.name() + " requires username,password"); + + create(PROVIDER).api().zones().iterator().hasNext(); + } + + @Test + public void testTwoPartCredentialsRequired() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("incorrect credentials supplied. " + PROVIDER.name() + " requires username,password"); + + create(PROVIDER, credentials("customer", "username", "password")).api().zones().iterator().hasNext(); + } + + @Test + public void testViaDagger() { + DNSApiManager manager = + ObjectGraph.create(provide(new VerisignDnsProvider()), new VerisignDnsProvider.Module(), + credentials("username", "password")).get(DNSApiManager.class); + assertThat(manager.api().zones()).isInstanceOf(VerisignDnsZoneApi.class); + } +} diff --git a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsReadOnlyLiveTest.java b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsReadOnlyLiveTest.java new file mode 100644 index 00000000..6cd1d2bf --- /dev/null +++ b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsReadOnlyLiveTest.java @@ -0,0 +1,9 @@ +package denominator.verisigndns; + +import denominator.ReadOnlyLiveTest; +import denominator.Live.UseTestGraph; + +@UseTestGraph(VerisignDnsTestGraph.class) +public class VerisignDnsReadOnlyLiveTest extends ReadOnlyLiveTest { + +} diff --git a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsResourceRecordSetApiMockTest.java b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsResourceRecordSetApiMockTest.java new file mode 100644 index 00000000..234f085c --- /dev/null +++ b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsResourceRecordSetApiMockTest.java @@ -0,0 +1,133 @@ +package denominator.verisigndns; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Rule; +import org.junit.Test; + +import com.squareup.okhttp.mockwebserver.MockResponse; + +import denominator.AllProfileResourceRecordSetApi; +import denominator.common.Util; +import denominator.model.ResourceRecordSet; + +import static denominator.verisigndns.VerisignDnsTest.getResourceRecordListRes; +import static denominator.verisigndns.VerisignDnsTest.twoResourceRecordRes; + +public class VerisignDnsResourceRecordSetApiMockTest { + + @Rule + public final MockVerisignDnsServer server = new MockVerisignDnsServer(); + + @Test + public void iteratorWhenPresent() throws Exception { + + server.enqueue(getResourceRecordListRes); + AllProfileResourceRecordSetApi recordSetsInZoneApi = + server.connect().api().recordSetsInZone("denominator.io"); + + assertThat(recordSetsInZoneApi.iterator()).containsExactly( + ResourceRecordSet.builder().name("www").type("A").ttl(86400) + .add(Util.toMap("A", "127.0.0.1")).build()); + } + + @Test + public void iteratorWhenAbsent() throws Exception { + server.enqueue(new MockResponse() + .setBody("")); + + AllProfileResourceRecordSetApi recordSetsInZoneApi = + server.connect().api().recordSetsInZone("denominator.io"); + assertThat(recordSetsInZoneApi.iterator()).isEmpty(); + } + + @Test + public void iterateByNameWhenPresent() throws Exception { + + server.enqueue(getResourceRecordListRes); + + AllProfileResourceRecordSetApi recordSetsInZoneApi = + server.connect().api().recordSetsInZone("denominator.io"); + + assertThat(recordSetsInZoneApi.iterateByName("www")).containsExactly( + ResourceRecordSet.builder().name("www").type("A").ttl(86400) + .add(Util.toMap("A", "127.0.0.1")).build()); + } + + @Test + public void iterateByNameWhenAbsent() throws Exception { + server.enqueue(new MockResponse() + .setBody("")); + + AllProfileResourceRecordSetApi recordSetsInZoneApi = + server.connect().api().recordSetsInZone("denominator.io"); + assertThat(recordSetsInZoneApi.iterator()).isEmpty(); + } + + @Test + public void putFirstRecordCreatesNewRRSet() throws Exception { + server + .enqueue(new MockResponse() + .setBody("" + + " true" + + " 0" + "")); + server.enqueue(getResourceRecordListRes); + + AllProfileResourceRecordSetApi recordSetsInZoneApi = + server.connect().api().recordSetsInZone("denominator.io"); + assertThat(recordSetsInZoneApi.iterator()).isEmpty(); + + recordSetsInZoneApi.put(ResourceRecordSet.builder().name("www").type("A").ttl(86400) + .add(Util.toMap("A", "127.0.0.1")).build()); + } + + @Test + public void putSameRecordNoOp() throws Exception { + server.enqueue(getResourceRecordListRes); + server.enqueue(getResourceRecordListRes); + + AllProfileResourceRecordSetApi recordSetsInZoneApi = + server.connect().api().recordSetsInZone("denominator.io"); + + recordSetsInZoneApi.put(ResourceRecordSet.builder().name("www").type("A").ttl(86400) + .add(Util.toMap("A", "127.0.0.1")).build()); + + assertThat(recordSetsInZoneApi.iterator()).hasSize(1); + } + + @Test + public void putOneRecordReplacesRRSet() throws Exception { + server.enqueue(twoResourceRecordRes); + server.enqueue(getResourceRecordListRes); + + AllProfileResourceRecordSetApi recordSetsInZoneApi = + server.connect().api().recordSetsInZone("denominator.io"); + assertThat(recordSetsInZoneApi.iterator()).hasSize(2); + + recordSetsInZoneApi.put(ResourceRecordSet.builder().name("www").type("A").ttl(86400) + .add(Util.toMap("A", "127.0.0.1")).build()); + } + + @Test + public void deleteWhenPresent() throws Exception { + server.enqueue(getResourceRecordListRes); + server + .enqueue(new MockResponse() + .setBody("" + + " true" + "")); + + AllProfileResourceRecordSetApi recordSetsInZoneApi = + server.connect().api().recordSetsInZone("denominator.io"); + recordSetsInZoneApi.deleteByNameAndType("www.denominator.io.", "A"); + } + + @Test + public void deleteWhenAbsent() throws Exception { + server.enqueue(getResourceRecordListRes); + server.enqueue(new MockResponse()); + + AllProfileResourceRecordSetApi recordSetsInZoneApi = + server.connect().api().recordSetsInZone("denominator.io"); + recordSetsInZoneApi.deleteByNameAndType("www", "A"); + } +} diff --git a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsTest.java b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsTest.java new file mode 100644 index 00000000..46386581 --- /dev/null +++ b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsTest.java @@ -0,0 +1,224 @@ +package denominator.verisigndns; + +import java.util.Iterator; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized.Parameter; + +import com.squareup.okhttp.mockwebserver.MockResponse; + +import denominator.AllProfileResourceRecordSetApi; +import denominator.DNSApiManager; +import denominator.Live; +import denominator.Live.UseTestGraph; +import denominator.ZoneApi; +import denominator.common.Util; +import denominator.model.ResourceRecordSet; +import denominator.model.Zone; + +@RunWith(Live.class) +@UseTestGraph(VerisignDnsTestGraph.class) +public class VerisignDnsTest { + + @Parameter + public DNSApiManager manager; + + @Test + public void zoneTest() { + ZoneApi zoneApi = manager.api().zones(); + + // Setup test data + String zoneName = "testzone-" + System.currentTimeMillis() + ".io"; + int ttl = 86400; + String email = "user@" + zoneName; + + // createZone + System.out.println("\nCreating zone..."); + zoneApi.put(Zone.create(null, zoneName, ttl, email)); + + // getZoneInfo + System.out.println("\nQuerying zone by name..."); + Iterator zoneIterator = zoneApi.iterateByName(zoneName); + while (zoneIterator.hasNext()) { + System.out.printf("\t%s", zoneIterator.next()); + System.out.println(); + } + + // getZoneList + System.out.println("\nQuerying zones for an account..."); + zoneIterator = zoneApi.iterator(); + int count = 0; + while (zoneIterator.hasNext()) { + zoneIterator.next(); + count++; + } + System.out.println("\tZone Size:" + count); + + // deleteZone + System.out.println("Deleting zone..."); + zoneApi.delete(zoneName); + } + + @Test + public void rrSetTest() { + + // Setup test data + String zoneName = "testzone-" + System.currentTimeMillis() + ".io"; + int ttl = 86400; + String email = "user@" + zoneName; + + // createZone + System.out.println("\nCreating zone..."); + ZoneApi zoneApi = manager.api().zones(); + String zoneId = zoneApi.put(Zone.create(null, zoneName, ttl, email)); + + AllProfileResourceRecordSetApi recordSetsInZoneApi = manager.api().recordSetsInZone(zoneId); + + // Add ResourceRecord record + System.out.println("\nAdding resource records..."); + + // Add A record + recordSetsInZoneApi.put(ResourceRecordSet.builder().name("www").type("A") + .add(Util.toMap("A", "127.0.0.1")).build()); + + // Add TLSA record + recordSetsInZoneApi.put(ResourceRecordSet + .builder() + .name("_443._tcp.www") + .type("TLSA") + .add( + Util.toMap("CERT", + "3 1 1 b760c12119c388736da724df1224d21dfd23bf03366c286de1a4125369ef7de0")).build()); + + // getResourceRecords + System.out.println("\nQuerying resource records..."); + Iterator> rrsIterator = recordSetsInZoneApi.iterator(); + while (rrsIterator.hasNext()) { + ResourceRecordSet rrs = rrsIterator.next(); + System.out.printf("\t%s", rrs.toString()); + System.out.println(); + } + + // getResourceRecordByName + System.out.println("\nQuerying resource record by name..."); + rrsIterator = recordSetsInZoneApi.iterateByName("www"); + while (rrsIterator.hasNext()) { + ResourceRecordSet rrs = rrsIterator.next(); + System.out.printf("\t%s", rrs.toString()); + System.out.println(); + } + + // getResourceRecordByNameAndType + System.out.println("\nQuerying resource record by name and rrType..."); + rrsIterator = recordSetsInZoneApi.iterateByNameAndType("www", "A"); + while (rrsIterator.hasNext()) { + ResourceRecordSet rrs = rrsIterator.next(); + System.out.printf("\t%s", rrs.toString()); + System.out.println(); + } + + // delete Resource Record + System.out.println("\nDeleting resource record..."); + recordSetsInZoneApi.deleteByNameAndType("www", "A"); + + // deleteZone + System.out.println("Deleting zone..."); + zoneApi.delete(zoneName); + } + + static MockResponse getZoneListRes = + new MockResponse() + .setBody("" + + " true" + + " 1" + + " " + + " denominator.io" + + " DNS Hosting" + + " ACTIVE" + + " 2015-09-29T01:55:39.000Z" + + " 2015-09-30T00:25:53.000Z" + + " No" + + " " + + ""); + + static MockResponse getZoneInfoRes = + new MockResponse() + .setBody(" " + + " true" + + " " + + " denominator.io" + + " DNS Hosting" + + " ACTIVE" + + " 2015-09-29T13:58:53.000Z" + + " 2015-09-29T14:41:11.000Z" + + " " + + " nil@denominator.io" + + " 7400" + + " 86400" + + " 30000" + + " 1234567" + + " 1443535137" + + " " + + " COMPLETE" + + " " + + " false" + + " " + + " " + + " 10" + + " a1.verisigndns.com" + + " 209.112.113.33" + + " 2001:500:7967::2:33" + + " Anycast Global" + + " " + + " " + + " 11" + + " a2.verisigndns.com" + + " 209.112.114.33" + + " 2620:74:19::33" + + " Anycast 1" + + " " + + " " + + " 12" + + " a3.verisigndns.com" + + " 69.36.145.33" + + " 2001:502:cbe4::33" + + " Anycast 2" + + " " + + " " + " "); + + static MockResponse getResourceRecordListRes = + new MockResponse() + .setBody("" + + " true" + + " 1" + + " " + + " 3194811" + + " www.denominator.io." + + " A" + + " 86400" + + " 127.0.0.1" + + " " + + ""); + + static MockResponse twoResourceRecordRes = + new MockResponse() + .setBody("" + + " true" + + " 2" + + " " + + " 3194802" + + " www.denominator.io." + + " A" + + " 86400" + + " 127.0.0.11" + + " " + + " " + + " 3194811" + + " www1.denominator.io." + + " A" + + " 86400" + + " 127.0.0.12" + + " " + + ""); +} diff --git a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsTestGraph.java b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsTestGraph.java new file mode 100644 index 00000000..1f4bb602 --- /dev/null +++ b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsTestGraph.java @@ -0,0 +1,15 @@ +package denominator.verisigndns; + +import static feign.Util.emptyToNull; +import static java.lang.System.getProperty; +import denominator.DNSApiManagerFactory; + +public class VerisignDnsTestGraph extends denominator.TestGraph { + + private static final String url = emptyToNull(getProperty("verisigndns.url")); + private static final String zone = emptyToNull(getProperty("verisigndns.zone")); + + public VerisignDnsTestGraph() { + super(DNSApiManagerFactory.create(new VerisignDnsProvider(url)), zone); + } +} diff --git a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsWriteCommandsLiveTest.java b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsWriteCommandsLiveTest.java new file mode 100644 index 00000000..6ecd4a9b --- /dev/null +++ b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsWriteCommandsLiveTest.java @@ -0,0 +1,9 @@ +package denominator.verisigndns; + +import denominator.WriteCommandsLiveTest; +import denominator.Live.UseTestGraph; + +@UseTestGraph(VerisignDnsTestGraph.class) +public class VerisignDnsWriteCommandsLiveTest extends WriteCommandsLiveTest { + +} diff --git a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsZoneApiMockTest.java b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsZoneApiMockTest.java new file mode 100644 index 00000000..76f6058b --- /dev/null +++ b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsZoneApiMockTest.java @@ -0,0 +1,97 @@ +package denominator.verisigndns; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Rule; +import org.junit.Test; + +import com.squareup.okhttp.mockwebserver.MockResponse; + +import denominator.ZoneApi; +import denominator.model.Zone; + +import static denominator.verisigndns.VerisignDnsTest.getZoneListRes; +import static denominator.verisigndns.VerisignDnsTest.getZoneInfoRes; + +public class VerisignDnsZoneApiMockTest { + + @Rule + public final MockVerisignDnsServer server = new MockVerisignDnsServer(); + + @Test + public void iteratorWhenPresent() throws Exception { + server.enqueue(getZoneListRes); + server.enqueue(getZoneInfoRes); + ZoneApi api = server.connect().api().zones(); + + assertThat(api.iterator()).containsExactly( + Zone.create("denominator.io", "denominator.io", 86400, "nil@denominator.io")); + } + + @Test + public void iteratorWhenAbsent() throws Exception { + server.enqueue(new MockResponse().setBody("")); + + ZoneApi api = server.connect().api().zones(); + assertThat(api.iterator()).isEmpty(); + } + + @Test + public void iterateByNameWhenPresent() throws Exception { + + server.enqueue(getZoneInfoRes); + ZoneApi api = server.connect().api().zones(); + + assertThat(api.iterateByName("denominator.io")).containsExactly( + Zone.create("denominator.io", "denominator.io", 86400, "nil@denominator.io")); + } + + @Test + public void iterateByNameWhenAbsent() throws Exception { + server.enqueue(new MockResponse().setBody("")); + + ZoneApi api = server.connect().api().zones(); + assertThat(api.iterateByName("denominator.io.")).isEmpty(); + } + + @Test + public void putWhenPresent() throws Exception { + server.enqueueError("ERROR_OPERATION_FAILURE", + "Domain already exists. Please verify your domain name."); + server.enqueue(new MockResponse()); + + ZoneApi api = server.connect().api().zones(); + + Zone zone = Zone.create("denominator.io", "denominator.io", 86400, "nil@denominator.io"); + api.put(zone); + } + + @Test + public void putWhenAbsent() throws Exception { + server.enqueue(new MockResponse()); + server.enqueue(new MockResponse()); + ZoneApi api = server.connect().api().zones(); + + Zone zone = Zone.create("denominator.io", "denominator.io", 86400, "nil@denominator.io"); + assertThat(api.put(zone)).isEqualTo(zone.name()); + } + + @Test + public void deleteWhenPresent() throws Exception { + server + .enqueue(new MockResponse() + .setBody("" + + " true" + "")); + + ZoneApi api = server.connect().api().zones(); + api.delete("denominator.io."); + } + + @Test + public void deleteWhenAbsent() throws Exception { + server.enqueue(new MockResponse()); + + ZoneApi api = server.connect().api().zones(); + api.delete("test.io"); + } +} diff --git a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsZoneWriteCommandsLiveTest.java b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsZoneWriteCommandsLiveTest.java new file mode 100644 index 00000000..44952def --- /dev/null +++ b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsZoneWriteCommandsLiveTest.java @@ -0,0 +1,9 @@ +package denominator.verisigndns; + +import denominator.ZoneWriteCommandsLiveTest; +import denominator.Live.UseTestGraph; + +@UseTestGraph(VerisignDnsTestGraph.class) +public class VerisignDnsZoneWriteCommandsLiveTest extends ZoneWriteCommandsLiveTest { + +} From 845e109153f496876bb023ffb908b668d5a42252 Mon Sep 17 00:00:00 2001 From: "Jain, Mahendra" Date: Thu, 29 Oct 2015 12:30:11 -0400 Subject: [PATCH 2/4] Implemented suggestions from pull request --- verisigndns/build.gradle | 2 - verisigndns/config/checkstyle/checkstyle.xml | 188 --------------- .../denominator/verisigndns/RecordFilter.java | 121 ++++++++++ ...signDnsAllProfileResourceRecordSetApi.java | 18 +- .../HostedZonesReadableMockTest.java | 16 +- ...risignDnsResourceRecordSetApiMockTest.java | 138 +++++++++-- .../verisigndns/VerisignDnsTest.java | 224 ------------------ .../VerisignDnsZoneApiMockTest.java | 111 ++++++++- 8 files changed, 371 insertions(+), 447 deletions(-) delete mode 100644 verisigndns/config/checkstyle/checkstyle.xml create mode 100644 verisigndns/src/main/java/denominator/verisigndns/RecordFilter.java delete mode 100644 verisigndns/src/test/java/denominator/verisigndns/VerisignDnsTest.java diff --git a/verisigndns/build.gradle b/verisigndns/build.gradle index 340474e8..38db092b 100644 --- a/verisigndns/build.gradle +++ b/verisigndns/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'java' -apply plugin: 'checkstyle' sourceCompatibility = 1.6 @@ -14,7 +13,6 @@ dependencies { compile project(':denominator-core') compile 'com.netflix.feign:feign-core:8.10.0' compile 'com.netflix.feign:feign-sax:8.10.0' - compile 'com.google.guava:guava:18.0' testCompile project(':denominator-model').sourceSets.test.output testCompile project(':denominator-core').sourceSets.test.output testCompile 'junit:junit:4.12' diff --git a/verisigndns/config/checkstyle/checkstyle.xml b/verisigndns/config/checkstyle/checkstyle.xml deleted file mode 100644 index 47c01a2e..00000000 --- a/verisigndns/config/checkstyle/checkstyle.xml +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/verisigndns/src/main/java/denominator/verisigndns/RecordFilter.java b/verisigndns/src/main/java/denominator/verisigndns/RecordFilter.java new file mode 100644 index 00000000..c7a026fb --- /dev/null +++ b/verisigndns/src/main/java/denominator/verisigndns/RecordFilter.java @@ -0,0 +1,121 @@ +package denominator.verisigndns; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import static denominator.common.Preconditions.checkNotNull; + +class RecordFilter { + + public static Iterable filter(final Iterable unfiltered, + final Predicate predicate) { + checkNotNull(unfiltered, "data was null"); + checkNotNull(predicate, "predicate was null"); + + final Iterator it = unfiltered.iterator(); + final List list = new ArrayList(); + return new Iterable() { + @Override + public Iterator iterator() { + while (it.hasNext()) { + final T element = it.next(); + if (predicate.apply(element)) { + list.add(element); + } + } + return list.iterator(); + } + }; + } + + interface Predicate { + boolean apply(T input); + boolean equals(Object object); + } + + static class InPredicate implements Predicate, Serializable { + private static final long serialVersionUID = 1L; + private final Collection target; + + InPredicate(Collection target) { + this.target = checkNotNull(target, "target was null"); + } + + @Override + public boolean apply(T element) { + try { + return target.contains(element); + } catch (NullPointerException e) { + return false; + } catch (ClassCastException e) { + return false; + } + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof InPredicate) { + InPredicate that = (InPredicate) obj; + return target.equals(that.target); + } + return false; + } + + @Override + public int hashCode() { + return target.hashCode(); + } + } + + static class NotPredicate implements Predicate, Serializable { + private static final long serialVersionUID = 1L; + final Predicate predicate; + + NotPredicate(Predicate predicate) { + this.predicate = checkNotNull(predicate, "predicate was null"); + } + + @Override + public boolean apply(T t) { + return !predicate.apply(t); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof NotPredicate) { + NotPredicate that = (NotPredicate) obj; + return predicate.equals(that.predicate); + } + return false; + } + + @Override + public int hashCode() { + return ~predicate.hashCode(); + } + } + + public static Predicate in(Collection target) { + return new InPredicate(target); + } + + public static Predicate not(Predicate predicate) { + return new NotPredicate(predicate); + } + + + public static ArrayList newArrayList(Iterable elements) { + return newArrayList(elements.iterator()); + } + + public static ArrayList newArrayList(Iterator elements) { + ArrayList list = new ArrayList(); + while (elements.hasNext()) { + list.add(elements.next()); + } + return list; + } +} diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java index 74b9c90c..9c622ab3 100644 --- a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java @@ -1,8 +1,5 @@ package denominator.verisigndns; -import static com.google.common.base.Predicates.in; -import static com.google.common.base.Predicates.not; -import static com.google.common.collect.Iterables.filter; import static denominator.common.Preconditions.checkNotNull; import static denominator.common.Util.equal; import static denominator.common.Util.nextOrNull; @@ -14,9 +11,10 @@ import javax.inject.Inject; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; - +import static denominator.verisigndns.RecordFilter.filter; +import static denominator.verisigndns.RecordFilter.in; +import static denominator.verisigndns.RecordFilter.not; +import static denominator.verisigndns.RecordFilter.newArrayList; import denominator.AllProfileResourceRecordSetApi; import denominator.model.ResourceRecordSet; import denominator.model.ResourceRecordSet.Builder; @@ -85,7 +83,8 @@ public void put(ResourceRecordSet rrset) { List> newRRData = null; List> oldRRData = null; if (oldRRSet != null) { - newRRData = Lists.newArrayList(filter(rrset.records(), not(in(oldRRSet.records())))); + newRRData = newArrayList(filter(rrset.records(), not(in(oldRRSet.records())))); + if (newRRData.isEmpty() && !equal(oldRRSet.ttl(), ttlToApply)) { oldRRData = new ArrayList>(); oldRRData.addAll(oldRRSet.records()); @@ -93,7 +92,7 @@ public void put(ResourceRecordSet rrset) { return; } else { List> oldRRDataList = - ImmutableList.copyOf(filter(oldRRSet.records(), in(rrset.records()))); + newArrayList(filter(oldRRSet.records(), in(rrset.records()))); if (!oldRRDataList.isEmpty()) { oldRRData = new ArrayList>(); @@ -102,7 +101,7 @@ public void put(ResourceRecordSet rrset) { } } } else { - newRRData = ImmutableList.copyOf(rrset.records()); + newRRData = newArrayList(rrset.records()); } Builder> newRRSetBuilder = ResourceRecordSet.builder(); @@ -125,6 +124,7 @@ public void put(ResourceRecordSet rrset) { api.updateResourceRecords(zoneName, rrset, rrsetToBeDeleted); } + @Override public void deleteByNameAndType(String name, String type) { checkNotNull(name, "name"); diff --git a/verisigndns/src/test/java/denominator/verisigndns/HostedZonesReadableMockTest.java b/verisigndns/src/test/java/denominator/verisigndns/HostedZonesReadableMockTest.java index 541b23db..33f7a9e2 100644 --- a/verisigndns/src/test/java/denominator/verisigndns/HostedZonesReadableMockTest.java +++ b/verisigndns/src/test/java/denominator/verisigndns/HostedZonesReadableMockTest.java @@ -8,8 +8,6 @@ import com.squareup.okhttp.mockwebserver.MockResponse; -import static denominator.verisigndns.VerisignDnsTest.getZoneListRes; - import denominator.DNSApiManager; public class HostedZonesReadableMockTest { @@ -19,7 +17,19 @@ public class HostedZonesReadableMockTest { @Test public void singleRequestOnSuccess() throws Exception { - server.enqueue(getZoneListRes); + server + .enqueue(new MockResponse() + .setBody("" + + " true" + + " 1" + + " " + + " denominator.io" + + " DNS Hosting" + + " ACTIVE" + + " 2015-09-29T01:55:39.000Z" + + " 2015-09-30T00:25:53.000Z" + + " No" + + " " + "")); DNSApiManager api = server.connect(); assertTrue(api.checkConnection()); diff --git a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsResourceRecordSetApiMockTest.java b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsResourceRecordSetApiMockTest.java index 234f085c..35d510b0 100644 --- a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsResourceRecordSetApiMockTest.java +++ b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsResourceRecordSetApiMockTest.java @@ -11,9 +11,6 @@ import denominator.common.Util; import denominator.model.ResourceRecordSet; -import static denominator.verisigndns.VerisignDnsTest.getResourceRecordListRes; -import static denominator.verisigndns.VerisignDnsTest.twoResourceRecordRes; - public class VerisignDnsResourceRecordSetApiMockTest { @Rule @@ -21,8 +18,19 @@ public class VerisignDnsResourceRecordSetApiMockTest { @Test public void iteratorWhenPresent() throws Exception { - - server.enqueue(getResourceRecordListRes); + server + .enqueue(new MockResponse() + .setBody("" + + " true" + + " 1" + + " " + + " 3194811" + + " www.denominator.io." + + " A" + + " 86400" + + " 127.0.0.1" + + " " + + "")); AllProfileResourceRecordSetApi recordSetsInZoneApi = server.connect().api().recordSetsInZone("denominator.io"); @@ -43,8 +51,19 @@ public void iteratorWhenAbsent() throws Exception { @Test public void iterateByNameWhenPresent() throws Exception { - - server.enqueue(getResourceRecordListRes); + server + .enqueue(new MockResponse() + .setBody("" + + " true" + + " 1" + + " " + + " 3194811" + + " www.denominator.io." + + " A" + + " 86400" + + " 127.0.0.1" + + " " + + "")); AllProfileResourceRecordSetApi recordSetsInZoneApi = server.connect().api().recordSetsInZone("denominator.io"); @@ -71,7 +90,19 @@ public void putFirstRecordCreatesNewRRSet() throws Exception { .setBody("" + " true" + " 0" + "")); - server.enqueue(getResourceRecordListRes); + server + .enqueue(new MockResponse() + .setBody("" + + " true" + + " 1" + + " " + + " 3194811" + + " www.denominator.io." + + " A" + + " 86400" + + " 127.0.0.1" + + " " + + "")); AllProfileResourceRecordSetApi recordSetsInZoneApi = server.connect().api().recordSetsInZone("denominator.io"); @@ -83,8 +114,32 @@ public void putFirstRecordCreatesNewRRSet() throws Exception { @Test public void putSameRecordNoOp() throws Exception { - server.enqueue(getResourceRecordListRes); - server.enqueue(getResourceRecordListRes); + server + .enqueue(new MockResponse() + .setBody("" + + " true" + + " 1" + + " " + + " 3194811" + + " www.denominator.io." + + " A" + + " 86400" + + " 127.0.0.1" + + " " + + "")); + server + .enqueue(new MockResponse() + .setBody("" + + " true" + + " 1" + + " " + + " 3194811" + + " www.denominator.io." + + " A" + + " 86400" + + " 127.0.0.1" + + " " + + "")); AllProfileResourceRecordSetApi recordSetsInZoneApi = server.connect().api().recordSetsInZone("denominator.io"); @@ -97,8 +152,39 @@ public void putSameRecordNoOp() throws Exception { @Test public void putOneRecordReplacesRRSet() throws Exception { - server.enqueue(twoResourceRecordRes); - server.enqueue(getResourceRecordListRes); + server + .enqueue(new MockResponse() + .setBody("" + + " true" + + " 2" + + " " + + " 3194802" + + " www.denominator.io." + + " A" + + " 86400" + + " 127.0.0.11" + + " " + + " " + + " 3194811" + + " www1.denominator.io." + + " A" + + " 86400" + + " 127.0.0.12" + + " " + + "")); + server + .enqueue(new MockResponse() + .setBody("" + + " true" + + " 1" + + " " + + " 3194811" + + " www.denominator.io." + + " A" + + " 86400" + + " 127.0.0.1" + + " " + + "")); AllProfileResourceRecordSetApi recordSetsInZoneApi = server.connect().api().recordSetsInZone("denominator.io"); @@ -110,7 +196,19 @@ public void putOneRecordReplacesRRSet() throws Exception { @Test public void deleteWhenPresent() throws Exception { - server.enqueue(getResourceRecordListRes); + server + .enqueue(new MockResponse() + .setBody("" + + " true" + + " 1" + + " " + + " 3194811" + + " www.denominator.io." + + " A" + + " 86400" + + " 127.0.0.1" + + " " + + "")); server .enqueue(new MockResponse() .setBody("" @@ -123,7 +221,19 @@ public void deleteWhenPresent() throws Exception { @Test public void deleteWhenAbsent() throws Exception { - server.enqueue(getResourceRecordListRes); + server + .enqueue(new MockResponse() + .setBody("" + + " true" + + " 1" + + " " + + " 3194811" + + " www.denominator.io." + + " A" + + " 86400" + + " 127.0.0.1" + + " " + + "")); server.enqueue(new MockResponse()); AllProfileResourceRecordSetApi recordSetsInZoneApi = diff --git a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsTest.java b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsTest.java deleted file mode 100644 index 46386581..00000000 --- a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsTest.java +++ /dev/null @@ -1,224 +0,0 @@ -package denominator.verisigndns; - -import java.util.Iterator; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized.Parameter; - -import com.squareup.okhttp.mockwebserver.MockResponse; - -import denominator.AllProfileResourceRecordSetApi; -import denominator.DNSApiManager; -import denominator.Live; -import denominator.Live.UseTestGraph; -import denominator.ZoneApi; -import denominator.common.Util; -import denominator.model.ResourceRecordSet; -import denominator.model.Zone; - -@RunWith(Live.class) -@UseTestGraph(VerisignDnsTestGraph.class) -public class VerisignDnsTest { - - @Parameter - public DNSApiManager manager; - - @Test - public void zoneTest() { - ZoneApi zoneApi = manager.api().zones(); - - // Setup test data - String zoneName = "testzone-" + System.currentTimeMillis() + ".io"; - int ttl = 86400; - String email = "user@" + zoneName; - - // createZone - System.out.println("\nCreating zone..."); - zoneApi.put(Zone.create(null, zoneName, ttl, email)); - - // getZoneInfo - System.out.println("\nQuerying zone by name..."); - Iterator zoneIterator = zoneApi.iterateByName(zoneName); - while (zoneIterator.hasNext()) { - System.out.printf("\t%s", zoneIterator.next()); - System.out.println(); - } - - // getZoneList - System.out.println("\nQuerying zones for an account..."); - zoneIterator = zoneApi.iterator(); - int count = 0; - while (zoneIterator.hasNext()) { - zoneIterator.next(); - count++; - } - System.out.println("\tZone Size:" + count); - - // deleteZone - System.out.println("Deleting zone..."); - zoneApi.delete(zoneName); - } - - @Test - public void rrSetTest() { - - // Setup test data - String zoneName = "testzone-" + System.currentTimeMillis() + ".io"; - int ttl = 86400; - String email = "user@" + zoneName; - - // createZone - System.out.println("\nCreating zone..."); - ZoneApi zoneApi = manager.api().zones(); - String zoneId = zoneApi.put(Zone.create(null, zoneName, ttl, email)); - - AllProfileResourceRecordSetApi recordSetsInZoneApi = manager.api().recordSetsInZone(zoneId); - - // Add ResourceRecord record - System.out.println("\nAdding resource records..."); - - // Add A record - recordSetsInZoneApi.put(ResourceRecordSet.builder().name("www").type("A") - .add(Util.toMap("A", "127.0.0.1")).build()); - - // Add TLSA record - recordSetsInZoneApi.put(ResourceRecordSet - .builder() - .name("_443._tcp.www") - .type("TLSA") - .add( - Util.toMap("CERT", - "3 1 1 b760c12119c388736da724df1224d21dfd23bf03366c286de1a4125369ef7de0")).build()); - - // getResourceRecords - System.out.println("\nQuerying resource records..."); - Iterator> rrsIterator = recordSetsInZoneApi.iterator(); - while (rrsIterator.hasNext()) { - ResourceRecordSet rrs = rrsIterator.next(); - System.out.printf("\t%s", rrs.toString()); - System.out.println(); - } - - // getResourceRecordByName - System.out.println("\nQuerying resource record by name..."); - rrsIterator = recordSetsInZoneApi.iterateByName("www"); - while (rrsIterator.hasNext()) { - ResourceRecordSet rrs = rrsIterator.next(); - System.out.printf("\t%s", rrs.toString()); - System.out.println(); - } - - // getResourceRecordByNameAndType - System.out.println("\nQuerying resource record by name and rrType..."); - rrsIterator = recordSetsInZoneApi.iterateByNameAndType("www", "A"); - while (rrsIterator.hasNext()) { - ResourceRecordSet rrs = rrsIterator.next(); - System.out.printf("\t%s", rrs.toString()); - System.out.println(); - } - - // delete Resource Record - System.out.println("\nDeleting resource record..."); - recordSetsInZoneApi.deleteByNameAndType("www", "A"); - - // deleteZone - System.out.println("Deleting zone..."); - zoneApi.delete(zoneName); - } - - static MockResponse getZoneListRes = - new MockResponse() - .setBody("" - + " true" - + " 1" - + " " - + " denominator.io" - + " DNS Hosting" - + " ACTIVE" - + " 2015-09-29T01:55:39.000Z" - + " 2015-09-30T00:25:53.000Z" - + " No" - + " " - + ""); - - static MockResponse getZoneInfoRes = - new MockResponse() - .setBody(" " - + " true" - + " " - + " denominator.io" - + " DNS Hosting" - + " ACTIVE" - + " 2015-09-29T13:58:53.000Z" - + " 2015-09-29T14:41:11.000Z" - + " " - + " nil@denominator.io" - + " 7400" - + " 86400" - + " 30000" - + " 1234567" - + " 1443535137" - + " " - + " COMPLETE" - + " " - + " false" - + " " - + " " - + " 10" - + " a1.verisigndns.com" - + " 209.112.113.33" - + " 2001:500:7967::2:33" - + " Anycast Global" - + " " - + " " - + " 11" - + " a2.verisigndns.com" - + " 209.112.114.33" - + " 2620:74:19::33" - + " Anycast 1" - + " " - + " " - + " 12" - + " a3.verisigndns.com" - + " 69.36.145.33" - + " 2001:502:cbe4::33" - + " Anycast 2" - + " " - + " " + " "); - - static MockResponse getResourceRecordListRes = - new MockResponse() - .setBody("" - + " true" - + " 1" - + " " - + " 3194811" - + " www.denominator.io." - + " A" - + " 86400" - + " 127.0.0.1" - + " " - + ""); - - static MockResponse twoResourceRecordRes = - new MockResponse() - .setBody("" - + " true" - + " 2" - + " " - + " 3194802" - + " www.denominator.io." - + " A" - + " 86400" - + " 127.0.0.11" - + " " - + " " - + " 3194811" - + " www1.denominator.io." - + " A" - + " 86400" - + " 127.0.0.12" - + " " - + ""); -} diff --git a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsZoneApiMockTest.java b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsZoneApiMockTest.java index 76f6058b..32d25b19 100644 --- a/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsZoneApiMockTest.java +++ b/verisigndns/src/test/java/denominator/verisigndns/VerisignDnsZoneApiMockTest.java @@ -10,9 +10,6 @@ import denominator.ZoneApi; import denominator.model.Zone; -import static denominator.verisigndns.VerisignDnsTest.getZoneListRes; -import static denominator.verisigndns.VerisignDnsTest.getZoneInfoRes; - public class VerisignDnsZoneApiMockTest { @Rule @@ -20,8 +17,64 @@ public class VerisignDnsZoneApiMockTest { @Test public void iteratorWhenPresent() throws Exception { - server.enqueue(getZoneListRes); - server.enqueue(getZoneInfoRes); + server + .enqueue(new MockResponse() + .setBody("" + + " true" + + " 1" + + " " + + " denominator.io" + + " DNS Hosting" + + " ACTIVE" + + " 2015-09-29T01:55:39.000Z" + + " 2015-09-30T00:25:53.000Z" + + " No" + + " " + "")); + + server + .enqueue(new MockResponse() + .setBody(" " + + " true" + + " " + + " denominator.io" + + " DNS Hosting" + + " ACTIVE" + + " 2015-09-29T13:58:53.000Z" + + " 2015-09-29T14:41:11.000Z" + + " " + + " nil@denominator.io" + + " 7400" + + " 86400" + + " 30000" + + " 1234567" + + " 1443535137" + + " " + + " COMPLETE" + + " " + + " false" + + " " + + " " + + " 10" + + " a1.verisigndns.com" + + " 209.112.113.33" + + " 2001:500:7967::2:33" + + " Anycast Global" + + " " + + " " + + " 11" + + " a2.verisigndns.com" + + " 209.112.114.33" + + " 2620:74:19::33" + + " Anycast 1" + + " " + + " " + + " 12" + + " a3.verisigndns.com" + + " 69.36.145.33" + + " 2001:502:cbe4::33" + + " Anycast 2" + + " " + + " " + " ")); ZoneApi api = server.connect().api().zones(); assertThat(api.iterator()).containsExactly( @@ -39,7 +92,50 @@ public void iteratorWhenAbsent() throws Exception { @Test public void iterateByNameWhenPresent() throws Exception { - server.enqueue(getZoneInfoRes); + server + .enqueue(new MockResponse() + .setBody(" " + + " true" + + " " + + " denominator.io" + + " DNS Hosting" + + " ACTIVE" + + " 2015-09-29T13:58:53.000Z" + + " 2015-09-29T14:41:11.000Z" + + " " + + " nil@denominator.io" + + " 7400" + + " 86400" + + " 30000" + + " 1234567" + + " 1443535137" + + " " + + " COMPLETE" + + " " + + " false" + + " " + + " " + + " 10" + + " a1.verisigndns.com" + + " 209.112.113.33" + + " 2001:500:7967::2:33" + + " Anycast Global" + + " " + + " " + + " 11" + + " a2.verisigndns.com" + + " 209.112.114.33" + + " 2620:74:19::33" + + " Anycast 1" + + " " + + " " + + " 12" + + " a3.verisigndns.com" + + " 69.36.145.33" + + " 2001:502:cbe4::33" + + " Anycast 2" + + " " + + " " + " ")); ZoneApi api = server.connect().api().zones(); assertThat(api.iterateByName("denominator.io")).containsExactly( @@ -81,7 +177,8 @@ public void deleteWhenPresent() throws Exception { server .enqueue(new MockResponse() .setBody("" - + " true" + "")); + + " true" + + "")); ZoneApi api = server.connect().api().zones(); api.delete("denominator.io."); From 21502cfce5a21c102d8493df253a203f035dd940 Mon Sep 17 00:00:00 2001 From: Dan James Date: Thu, 29 Oct 2015 15:21:54 -0400 Subject: [PATCH 3/4] Remove guava-like code and inline the delta calculation --- .../denominator/verisigndns/RecordFilter.java | 121 ------------------ ...signDnsAllProfileResourceRecordSetApi.java | 86 +++++++------ 2 files changed, 49 insertions(+), 158 deletions(-) delete mode 100644 verisigndns/src/main/java/denominator/verisigndns/RecordFilter.java diff --git a/verisigndns/src/main/java/denominator/verisigndns/RecordFilter.java b/verisigndns/src/main/java/denominator/verisigndns/RecordFilter.java deleted file mode 100644 index c7a026fb..00000000 --- a/verisigndns/src/main/java/denominator/verisigndns/RecordFilter.java +++ /dev/null @@ -1,121 +0,0 @@ -package denominator.verisigndns; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import static denominator.common.Preconditions.checkNotNull; - -class RecordFilter { - - public static Iterable filter(final Iterable unfiltered, - final Predicate predicate) { - checkNotNull(unfiltered, "data was null"); - checkNotNull(predicate, "predicate was null"); - - final Iterator it = unfiltered.iterator(); - final List list = new ArrayList(); - return new Iterable() { - @Override - public Iterator iterator() { - while (it.hasNext()) { - final T element = it.next(); - if (predicate.apply(element)) { - list.add(element); - } - } - return list.iterator(); - } - }; - } - - interface Predicate { - boolean apply(T input); - boolean equals(Object object); - } - - static class InPredicate implements Predicate, Serializable { - private static final long serialVersionUID = 1L; - private final Collection target; - - InPredicate(Collection target) { - this.target = checkNotNull(target, "target was null"); - } - - @Override - public boolean apply(T element) { - try { - return target.contains(element); - } catch (NullPointerException e) { - return false; - } catch (ClassCastException e) { - return false; - } - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof InPredicate) { - InPredicate that = (InPredicate) obj; - return target.equals(that.target); - } - return false; - } - - @Override - public int hashCode() { - return target.hashCode(); - } - } - - static class NotPredicate implements Predicate, Serializable { - private static final long serialVersionUID = 1L; - final Predicate predicate; - - NotPredicate(Predicate predicate) { - this.predicate = checkNotNull(predicate, "predicate was null"); - } - - @Override - public boolean apply(T t) { - return !predicate.apply(t); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof NotPredicate) { - NotPredicate that = (NotPredicate) obj; - return predicate.equals(that.predicate); - } - return false; - } - - @Override - public int hashCode() { - return ~predicate.hashCode(); - } - } - - public static Predicate in(Collection target) { - return new InPredicate(target); - } - - public static Predicate not(Predicate predicate) { - return new NotPredicate(predicate); - } - - - public static ArrayList newArrayList(Iterable elements) { - return newArrayList(elements.iterator()); - } - - public static ArrayList newArrayList(Iterator elements) { - ArrayList list = new ArrayList(); - while (elements.hasNext()) { - list.add(elements.next()); - } - return list; - } -} diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java index 9c622ab3..cf9d0345 100644 --- a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java @@ -1,5 +1,6 @@ package denominator.verisigndns; +import static denominator.common.Preconditions.checkArgument; import static denominator.common.Preconditions.checkNotNull; import static denominator.common.Util.equal; import static denominator.common.Util.nextOrNull; @@ -11,13 +12,8 @@ import javax.inject.Inject; -import static denominator.verisigndns.RecordFilter.filter; -import static denominator.verisigndns.RecordFilter.in; -import static denominator.verisigndns.RecordFilter.not; -import static denominator.verisigndns.RecordFilter.newArrayList; import denominator.AllProfileResourceRecordSetApi; import denominator.model.ResourceRecordSet; -import denominator.model.ResourceRecordSet.Builder; import denominator.verisigndns.VerisignDnsEncoder.GetRRList; final class VerisignDnsAllProfileResourceRecordSetApi implements AllProfileResourceRecordSetApi { @@ -67,9 +63,29 @@ public ResourceRecordSet getByNameTypeAndQualifier(String name, String type, return nextOrNull(new ResourceRecordByNameAndTypeIterator(api, getRRList)); } + private List notIn(List a, List b) { + List r = new ArrayList(); + + for (E i : a) { + if (!b.contains(i)) { + r.add(i); + } + } + + return r; + } + + private List copy(List a) { + List r = new ArrayList(); + + r.addAll(a); + + return r; + } + @Override public void put(ResourceRecordSet rrset) { - checkNotNull(rrset, "rrset was null"); + checkArgument(rrset != null && !rrset.records().isEmpty(), "rrset was empty"); Integer ttlToApply = rrset.ttl() != null ? rrset.ttl() : 86400; @@ -80,51 +96,47 @@ public void put(ResourceRecordSet rrset) { oldRRSet = nextOrNull(iterateByNameAndType(rrset.name(), rrset.type())); } - List> newRRData = null; - List> oldRRData = null; + List> toAdd; + List> toDel; if (oldRRSet != null) { - newRRData = newArrayList(filter(rrset.records(), not(in(oldRRSet.records())))); - - if (newRRData.isEmpty() && !equal(oldRRSet.ttl(), ttlToApply)) { - oldRRData = new ArrayList>(); - oldRRData.addAll(oldRRSet.records()); - } else if (newRRData.isEmpty() && equal(oldRRSet.ttl(), ttlToApply)) { - return; + if (equal(oldRRSet.ttl(), ttlToApply)) { + toDel = notIn(oldRRSet.records(), rrset.records()); + toAdd = notIn(rrset.records(), oldRRSet.records()); } else { - List> oldRRDataList = - newArrayList(filter(oldRRSet.records(), in(rrset.records()))); - - if (!oldRRDataList.isEmpty()) { - oldRRData = new ArrayList>(); - oldRRData.addAll(oldRRDataList); - newRRData.addAll(oldRRDataList); - } + toDel = copy(oldRRSet.records()); + toAdd = copy(rrset.records()); } } else { - newRRData = newArrayList(rrset.records()); + toDel = null; + toAdd = copy(rrset.records()); } - Builder> newRRSetBuilder = ResourceRecordSet.builder(); - if (newRRData != null && !newRRData.isEmpty()) { - rrset = - newRRSetBuilder.name(rrset.name()).type(rrset.type()).ttl(ttlToApply).addAll(newRRData) - .build(); + if (toAdd.isEmpty() && (toDel == null || toDel.isEmpty())) { + return; + } + + if (!toAdd.isEmpty()) { + rrset = ResourceRecordSet.builder() + .name(rrset.name()) + .type(rrset.type()) + .ttl(ttlToApply) + .addAll(toAdd) + .build(); } - Builder> deleteRRSetBuilder = ResourceRecordSet.builder(); ResourceRecordSet> rrsetToBeDeleted = null; - if (oldRRData != null) { - deleteRRSetBuilder.ttl(oldRRSet.ttl()); - deleteRRSetBuilder.name(oldRRSet.name()); - deleteRRSetBuilder.type(oldRRSet.type()); - deleteRRSetBuilder.addAll(oldRRData); - rrsetToBeDeleted = deleteRRSetBuilder.build(); + if (toDel != null && !toDel.isEmpty()) { + rrsetToBeDeleted = ResourceRecordSet.builder() + .name(oldRRSet.name()) + .type(oldRRSet.type()) + .ttl(oldRRSet.ttl()) + .addAll(toDel) + .build(); } api.updateResourceRecords(zoneName, rrset, rrsetToBeDeleted); } - @Override public void deleteByNameAndType(String name, String type) { checkNotNull(name, "name"); From c72ce2072897e9ffd745a42d7007f55ff79a3890 Mon Sep 17 00:00:00 2001 From: Dan James Date: Thu, 29 Oct 2015 16:01:10 -0400 Subject: [PATCH 4/4] Handle case where a put() is only deleting some records from the rest --- .../VerisignDnsAllProfileResourceRecordSetApi.java | 14 ++++++-------- .../verisigndns/VerisignDnsEncoder.java | 6 ++++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java index cf9d0345..2c9edd16 100644 --- a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsAllProfileResourceRecordSetApi.java @@ -115,14 +115,12 @@ public void put(ResourceRecordSet rrset) { return; } - if (!toAdd.isEmpty()) { - rrset = ResourceRecordSet.builder() - .name(rrset.name()) - .type(rrset.type()) - .ttl(ttlToApply) - .addAll(toAdd) - .build(); - } + rrset = ResourceRecordSet.builder() + .name(rrset.name()) + .type(rrset.type()) + .ttl(ttlToApply) + .addAll(toAdd) + .build(); ResourceRecordSet> rrsetToBeDeleted = null; if (toDel != null && !toDel.isEmpty()) { diff --git a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsEncoder.java b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsEncoder.java index 9ef98ce9..bdadfbc2 100644 --- a/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsEncoder.java +++ b/verisigndns/src/main/java/denominator/verisigndns/VerisignDnsEncoder.java @@ -180,8 +180,10 @@ private Node encodeRRSet(Map params) { TagNode bulkUpdateZoneNode = new TagNode(NS_API_2, "bulkUpdateSingleZone"); bulkUpdateZoneNode.add(NS_API_2, "domainName", zoneName); - bulkUpdateZoneNode.add(toRRNode(NS_API_2, "createResourceRecords", rrSet, true)); - if (oldRRSet != null) { + if (!rrSet.records().isEmpty()) { + bulkUpdateZoneNode.add(toRRNode(NS_API_2, "createResourceRecords", rrSet, true)); + } + if (oldRRSet != null && !oldRRSet.records().isEmpty()) { bulkUpdateZoneNode.add(toRRNode(NS_API_2, "deleteResourceRecords", oldRRSet, false)); }