├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── OSSMETADATA ├── README.md ├── build.gradle ├── buildViaTravis.sh ├── cli ├── README.md ├── build.gradle ├── src │ ├── main │ │ └── java │ │ │ └── denominator │ │ │ └── cli │ │ │ ├── Denominator.java │ │ │ ├── GeoResourceRecordSetCommands.java │ │ │ ├── ResourceRecordSetCommands.java │ │ │ └── ZoneCommands.java │ └── test │ │ ├── java │ │ └── denominator │ │ │ └── cli │ │ │ ├── DataFromInstanceMetadataHookTest.java │ │ │ └── DenominatorTest.java │ │ └── resources │ │ ├── test-config-cert.yml │ │ └── test-config.yml └── supportedProviders.txt ├── clouddns ├── README.md ├── build.gradle └── src │ ├── main │ ├── java │ │ └── denominator │ │ │ └── clouddns │ │ │ ├── CloudDNSFunctions.java │ │ │ ├── CloudDNSProvider.java │ │ │ ├── CloudDNSResourceRecordSetApi.java │ │ │ ├── CloudDNSTarget.java │ │ │ ├── CloudDNSZoneApi.java │ │ │ ├── GroupByRecordNameAndTypeIterator.java │ │ │ ├── InvalidatableAuthProvider.java │ │ │ ├── KeystoneAccessAdapter.java │ │ │ ├── LimitsReadable.java │ │ │ ├── RackspaceAdapters.java │ │ │ └── RackspaceApis.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── denominator.Provider │ └── test │ └── java │ └── denominator │ └── clouddns │ ├── CloudDNSCheckConnectionLiveTest.java │ ├── CloudDNSConnection.java │ ├── CloudDNSFunctionsTest.java │ ├── CloudDNSProviderDynamicUpdateMockTest.java │ ├── CloudDNSProviderTest.java │ ├── CloudDNSReadOnlyLiveTest.java │ ├── CloudDNSResourceRecordSetApiMockTest.java │ ├── CloudDNSTestGraph.java │ ├── CloudDNSWriteCommandsLiveTest.java │ ├── CloudDNSZoneApiMockTest.java │ ├── CloudDNSZoneWriteCommandsLiveTest.java │ ├── KeystoneAccessAdapterTest.java │ ├── LimitsReadableMockTest.java │ ├── MockCloudDNSServer.java │ └── RackspaceApisTest.java ├── codequality └── checkstyle.xml ├── core ├── build.gradle └── src │ ├── main │ ├── java │ │ └── denominator │ │ │ ├── AllProfileResourceRecordSetApi.java │ │ │ ├── BasicProvider.java │ │ │ ├── CheckConnection.java │ │ │ ├── Credentials.java │ │ │ ├── CredentialsConfiguration.java │ │ │ ├── DNSApi.java │ │ │ ├── DNSApiManager.java │ │ │ ├── Denominator.java │ │ │ ├── Provider.java │ │ │ ├── Providers.java │ │ │ ├── QualifiedResourceRecordSetApi.java │ │ │ ├── ReadOnlyResourceRecordSetApi.java │ │ │ ├── ResourceRecordSetApi.java │ │ │ ├── ZoneApi.java │ │ │ ├── config │ │ │ ├── ConcatBasicAndQualifiedResourceRecordSets.java │ │ │ ├── GeoUnsupported.java │ │ │ ├── NothingToClose.java │ │ │ ├── OnlyBasicResourceRecordSets.java │ │ │ ├── WeightedUnsupported.java │ │ │ └── package-info.java │ │ │ ├── hook │ │ │ └── InstanceMetadataHook.java │ │ │ ├── mock │ │ │ ├── MockAllProfileResourceRecordSetApi.java │ │ │ ├── MockGeoResourceRecordSetApi.java │ │ │ ├── MockProvider.java │ │ │ ├── MockResourceRecordSetApi.java │ │ │ ├── MockWeightedResourceRecordSetApi.java │ │ │ └── MockZoneApi.java │ │ │ └── profile │ │ │ ├── GeoResourceRecordSetApi.java │ │ │ └── WeightedResourceRecordSetApi.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── denominator.Provider │ └── test │ └── java │ └── denominator │ ├── CheckConnectionLiveTest.java │ ├── CredentialsConfigurationTest.java │ ├── CredentialsTest.java │ ├── DNSApiManagerFactory.java │ ├── DenominatorTest.java │ ├── DynamicCredentialsProviderExampleTest.java │ ├── Live.java │ ├── ProviderTest.java │ ├── ProvidersTest.java │ ├── ReadOnlyLiveTest.java │ ├── RoundRobinWriteCommandsLiveTest.java │ ├── TestGraph.java │ ├── WriteCommandsLiveTest.java │ ├── ZoneWriteCommandsLiveTest.java │ ├── assertj │ ├── MockWebServerAssertions.java │ └── RecordedRequestAssert.java │ ├── hook │ └── InstanceMetadataHookTest.java │ ├── mock │ └── MockProviderTest.java │ └── profile │ ├── GeoReadOnlyLiveTest.java │ ├── GeoWriteCommandsLiveTest.java │ ├── Regions.java │ ├── WeightedReadOnlyLiveTest.java │ └── WeightedWriteCommandsLiveTest.java ├── dagger.gradle ├── denominator.jpg ├── denominator.png ├── designate ├── README.md ├── build.gradle └── src │ ├── main │ ├── java │ │ └── denominator │ │ │ └── designate │ │ │ ├── Designate.java │ │ │ ├── DesignateAdapters.java │ │ │ ├── DesignateFunctions.java │ │ │ ├── DesignateProvider.java │ │ │ ├── DesignateResourceRecordSetApi.java │ │ │ ├── DesignateTarget.java │ │ │ ├── DesignateZoneApi.java │ │ │ ├── GroupByRecordNameAndTypeIterator.java │ │ │ ├── InvalidatableAuthProvider.java │ │ │ ├── KeystoneV2.java │ │ │ ├── KeystoneV2AccessAdapter.java │ │ │ └── LimitsReadable.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── denominator.Provider │ └── test │ └── java │ └── denominator │ └── designate │ ├── DesignateCheckConnectionLiveTest.java │ ├── DesignateFunctionsTest.java │ ├── DesignateProviderDynamicUpdateMockTest.java │ ├── DesignateProviderTest.java │ ├── DesignateReadOnlyLiveTest.java │ ├── DesignateResourceRecordSetApiMockTest.java │ ├── DesignateRoundRobinWriteCommandsLiveTest.java │ ├── DesignateTest.java │ ├── DesignateTestGraph.java │ ├── DesignateWriteCommandsLiveTest.java │ ├── DesignateZoneApiMockTest.java │ ├── DesignateZoneWriteCommandsLiveTest.java │ ├── KeystoneV2AccessAdapterTest.java │ ├── LimitsReadableMockTest.java │ └── MockDesignateServer.java ├── dynect ├── README.md ├── build.gradle └── src │ ├── main │ ├── java │ │ └── denominator │ │ │ └── dynect │ │ │ ├── CountryToRegions.java │ │ │ ├── DynECT.java │ │ │ ├── DynECTAdapters.java │ │ │ ├── DynECTErrorDecoder.java │ │ │ ├── DynECTException.java │ │ │ ├── DynECTGeoResourceRecordSetApi.java │ │ │ ├── DynECTProvider.java │ │ │ ├── DynECTResourceRecordSetApi.java │ │ │ ├── DynECTTarget.java │ │ │ ├── DynECTZoneApi.java │ │ │ ├── InvalidatableTokenProvider.java │ │ │ ├── ResourceRecordSetsAdapter.java │ │ │ ├── SessionTarget.java │ │ │ └── ToRecord.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── denominator.Provider │ └── test │ ├── java │ └── denominator │ │ └── dynect │ │ ├── DynECTCheckConnectionLiveTest.java │ │ ├── DynECTFunctionsTest.java │ │ ├── DynECTGeoReadOnlyLiveTest.java │ │ ├── DynECTGeoResourceRecordSetApiMockTest.java │ │ ├── DynECTProviderDynamicUpdateMockTest.java │ │ ├── DynECTProviderTest.java │ │ ├── DynECTReadOnlyLiveTest.java │ │ ├── DynECTResourceRecordSetApiMockTest.java │ │ ├── DynECTRoundRobinWriteCommandsLiveTest.java │ │ ├── DynECTTest.java │ │ ├── DynECTTestGraph.java │ │ ├── DynECTWriteCommandsLiveTest.java │ │ ├── DynECTZoneApiMockTest.java │ │ ├── DynECTZoneWriteCommandsLiveTest.java │ │ ├── InvalidatableTokenProviderMockTest.java │ │ └── MockDynECTServer.java │ └── resources │ ├── geoservice.json │ ├── records.json │ └── recordsByName.json ├── example-android ├── README.md ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ └── denominator │ │ └── example │ │ └── android │ │ ├── DenominatorApplication.java │ │ ├── ui │ │ ├── HomeActivity.java │ │ ├── PreferencesActivity.java │ │ └── ZoneListFragment.java │ │ └── zone │ │ ├── ZoneList.java │ │ ├── ZoneListModule.java │ │ ├── ZoneListQueue.java │ │ └── ZoneListTaskService.java │ └── res │ ├── drawable-hdpi │ └── icon.png │ ├── drawable-mdpi │ └── icon.png │ ├── drawable-xhdpi │ └── icon.png │ └── values │ └── strings.xml ├── example-daemon ├── README.md ├── build.gradle ├── src │ ├── main │ │ └── java │ │ │ └── denominator │ │ │ └── denominatord │ │ │ ├── DenominatorD.java │ │ │ ├── DenominatorDApi.java │ │ │ ├── DenominatorDispatcher.java │ │ │ ├── JsonCodec.java │ │ │ ├── Query.java │ │ │ ├── RecordSetDispatcher.java │ │ │ └── ZoneDispatcher.java │ └── test │ │ └── java │ │ └── denominator │ │ └── denominatord │ │ └── DenominatorDTest.java └── supportedProviders.txt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── installViaTravis.sh ├── model ├── README.md ├── build.gradle └── src │ ├── main │ └── java │ │ └── denominator │ │ ├── ResourceTypeToValue.java │ │ ├── common │ │ ├── Filter.java │ │ ├── PeekingIterator.java │ │ ├── Preconditions.java │ │ └── Util.java │ │ └── model │ │ ├── AbstractRecordSetBuilder.java │ │ ├── NumbersAreUnsignedIntsLinkedHashMap.java │ │ ├── ResourceRecordSet.java │ │ ├── ResourceRecordSets.java │ │ ├── StringRecordBuilder.java │ │ ├── Zone.java │ │ ├── Zones.java │ │ ├── profile │ │ ├── Geo.java │ │ ├── Geos.java │ │ └── Weighted.java │ │ └── rdata │ │ ├── AAAAData.java │ │ ├── AData.java │ │ ├── CERTData.java │ │ ├── CNAMEData.java │ │ ├── MXData.java │ │ ├── NAPTRData.java │ │ ├── NSData.java │ │ ├── PTRData.java │ │ ├── SOAData.java │ │ ├── SPFData.java │ │ ├── SRVData.java │ │ ├── SSHFPData.java │ │ ├── TXTData.java │ │ └── package-info.java │ └── test │ └── java │ └── denominator │ ├── ResourceTypeToValueTest.java │ ├── assertj │ ├── ModelAssertions.java │ ├── ResourceRecordSetAssert.java │ └── ZoneAssert.java │ ├── common │ ├── PeekingIteratorTest.java │ ├── PreconditionsTest.java │ └── UtilTest.java │ └── model │ ├── ResourceRecordSetTest.java │ ├── ResourceRecordSetsTest.java │ ├── ZoneTest.java │ ├── ZonesTest.java │ ├── profile │ ├── GeoTest.java │ ├── GeosTest.java │ └── WeightedTest.java │ └── rdata │ ├── AAAADataTest.java │ ├── ADataTest.java │ ├── CERTDataTest.java │ ├── CNAMEDataTest.java │ ├── MXDataTest.java │ ├── NAPTRDataTest.java │ ├── NSDataTest.java │ ├── SPFDataTest.java │ ├── SRVDataTest.java │ ├── SSHFPDataTest.java │ └── TXTDataTest.java ├── route53 ├── README.md ├── build.gradle └── src │ ├── main │ ├── java │ │ └── denominator │ │ │ └── route53 │ │ │ ├── AliasTarget.java │ │ │ ├── EncodeChanges.java │ │ │ ├── GetHostedZoneResponseHandler.java │ │ │ ├── HostedZonesReadable.java │ │ │ ├── InstanceProfileCredentialsProvider.java │ │ │ ├── InvalidChangeBatchException.java │ │ │ ├── InvalidatableAuthenticationHeadersProvider.java │ │ │ ├── ListHostedZonesResponseHandler.java │ │ │ ├── ListResourceRecordSetsResponseHandler.java │ │ │ ├── ResourceRecordSetHandler.java │ │ │ ├── Route53.java │ │ │ ├── Route53AllProfileResourceRecordSetApi.java │ │ │ ├── Route53ErrorDecoder.java │ │ │ ├── Route53Exception.java │ │ │ ├── Route53Provider.java │ │ │ ├── Route53ResourceRecordSetApi.java │ │ │ ├── Route53Target.java │ │ │ ├── Route53WeightedResourceRecordSetApi.java │ │ │ └── Route53ZoneApi.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── denominator.Provider │ └── test │ └── java │ └── denominator │ └── route53 │ ├── EncodeChangesTest.java │ ├── HostedZonesReadableMockTest.java │ ├── InstanceProfileCredentialsProviderTest.java │ ├── MockRoute53Server.java │ ├── Route53CheckConnectionLiveTest.java │ ├── Route53DecoderTest.java │ ├── Route53ErrorDecoderTest.java │ ├── Route53ProviderDynamicUpdateMockTest.java │ ├── Route53ProviderTest.java │ ├── Route53ReadOnlyLiveTest.java │ ├── Route53ResourceRecordSetApiMockTest.java │ ├── Route53RoundRobinWriteCommandsLiveTest.java │ ├── Route53Test.java │ ├── Route53TestGraph.java │ ├── Route53WeightedReadOnlyLiveTest.java │ ├── Route53WeightedResourceRecordSetApiMockTest.java │ ├── Route53WeightedWriteCommandsLiveTest.java │ ├── Route53WriteCommandsLiveTest.java │ ├── Route53ZoneApiMockTest.java │ └── Route53ZoneWriteCommandsLiveTest.java ├── settings.gradle └── ultradns ├── README.md ├── build.gradle └── src ├── main ├── java │ └── denominator │ │ └── ultradns │ │ ├── GroupByRecordNameAndTypeIterator.java │ │ ├── GroupGeoRecordByNameTypeIterator.java │ │ ├── InvalidatableAccountIdSupplier.java │ │ ├── NetworkStatusReadable.java │ │ ├── UltraDNS.java │ │ ├── UltraDNSContentHandlers.java │ │ ├── UltraDNSErrorDecoder.java │ │ ├── UltraDNSException.java │ │ ├── UltraDNSFilters.java │ │ ├── UltraDNSFormEncoder.java │ │ ├── UltraDNSGeoResourceRecordSetApi.java │ │ ├── UltraDNSGeoSupport.java │ │ ├── UltraDNSProvider.java │ │ ├── UltraDNSResourceRecordSetApi.java │ │ ├── UltraDNSRoundRobinPoolApi.java │ │ ├── UltraDNSTarget.java │ │ └── UltraDNSZoneApi.java └── resources │ └── META-INF │ └── services │ └── denominator.Provider └── test └── java └── denominator └── ultradns ├── MockUltraDNSServer.java ├── NetworkStatusReadableMockTest.java ├── UltraDNSCheckConnectionLiveTest.java ├── UltraDNSConnection.java ├── UltraDNSErrorDecoderTest.java ├── UltraDNSGeoReadOnlyLiveTest.java ├── UltraDNSGeoResourceRecordSetApiMockTest.java ├── UltraDNSGeoWriteCommandsLiveTest.java ├── UltraDNSPredicatesTest.java ├── UltraDNSProviderDynamicUpdateMockTest.java ├── UltraDNSProviderTest.java ├── UltraDNSReadOnlyLiveTest.java ├── UltraDNSResourceRecordSetApiMockTest.java ├── UltraDNSRoundRobinWriteCommandsLiveTest.java ├── UltraDNSTest.java ├── UltraDNSTestGraph.java ├── UltraDNSWriteCommandsLiveTest.java ├── UltraDNSZoneApiMockTest.java └── UltraDNSZoneWriteCommandsLiveTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | 27 | # OS generated files # 28 | ###################### 29 | .DS_Store* 30 | ehthumbs.db 31 | Icon? 32 | Thumbs.db 33 | 34 | # Editor Files # 35 | ################ 36 | *~ 37 | *.swp 38 | 39 | # Gradle Files # 40 | ################ 41 | .gradle 42 | local.properties 43 | 44 | # Build output directies 45 | /target 46 | **/test-output 47 | **/target 48 | **/bin 49 | build 50 | */build 51 | .m2 52 | 53 | # IntelliJ specific files/directories 54 | out 55 | .idea 56 | *.ipr 57 | *.iws 58 | *.iml 59 | atlassian-ide-plugin.xml 60 | 61 | # Eclipse specific files/directories 62 | .classpath 63 | .project 64 | .settings 65 | .metadata 66 | .factorypath 67 | .generated 68 | 69 | # NetBeans specific files/directories 70 | .nbattrs 71 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk7 4 | install: ./installViaTravis.sh 5 | script: ./buildViaTravis.sh 6 | env: 7 | global: 8 | - secure: TndmkHMNpM2RsrGC+nJfJLd5vcrn0krifi/ILLJBFiAceGFFNb7QQ8B/dgW/fETAmBfwT2YqNe2zXLDTEAuc5Vij7/RksXusgBuFJDkMQp+XbMLpva+FnHtdPtCYSPm/r/Vp/FaFXFp1Y0m0wRLo/C6PGTv5VnmdkmPFBls2o5o= 9 | - secure: itRjB+oK2KMa6EqSebJR8Yxq8U1q9uPIIZjo66usqYB3m1inYi5BAYDrRgriKAy7oLs6tMei9Sz6sejYcCEfEehbNUH1U/a8OaX+L3j6xSRbYHOLmAxDxWkQeWq4713RhgT/zN0n1KneP0Rcs9Ff23E7Fu77uBN9QYBTx+Ip5wM= 10 | - secure: mhYqGLbfUt0WA/qL+Tm4EbAE8DYbLWpuFdk2m0CTZmSPt24Hlg0Wl1xk32zwav+hdlXLkoNO5FPoUA+5JHTs0fe0Ibe82+d3o4j87ovpUr1US6s+6nd2NqJYb2vGfMI5BB+92vnPqrno7/gI4PPHiV6uoKSmiQZWt75y92wqbTQ= 11 | - secure: Ln16Jt+I9SnC0JArt4U1pAJHKUxRtOvOVrDrF3N9gwIpFoqYszDAU8j+VC0Rt1ICiwpGuCRkzFPAQ4ou1u5g8fq/yELPcqM8YwrS0m+uHld0qfD+1ERnvU73bKUoaJXp8Uw5/fIiY5a2mUsE+adWhGUrTfqkTnpzGkh1WBRWTp8= 12 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Denominator 2 | 3 | If you would like to contribute code you can do so through GitHub by forking the repository and sending a pull request (on a branch other than `master` or `gh-pages`). 4 | 5 | When submitting code, please ensure you follow the [Google Style Guide](http://google-styleguide.googlecode.com/svn/trunk/javaguide.html). For example, you can format code with Intellij using [this file](https://google-styleguide.googlecode.com/svn/trunk/intellij-java-google-style.xml). 6 | 7 | ## License 8 | 9 | By contributing your code, you agree to license your contribution under the terms of the APLv2: https://github.com/Netflix/Denominator/blob/master/LICENSE 10 | 11 | All files are released with the Apache 2.0 license. 12 | 13 | If you are adding a new file it should have a header like this: 14 | 15 | ``` 16 | /** 17 | * Copyright 2013 Netflix, Inc. 18 | * 19 | * Licensed under the Apache License, Version 2.0 (the "License"); 20 | * you may not use this file except in compliance with the License. 21 | * You may obtain a copy of the License at 22 | * 23 | * http://www.apache.org/licenses/LICENSE-2.0 24 | * 25 | * Unless required by applicable law or agreed to in writing, software 26 | * distributed under the License is distributed on an "AS IS" BASIS, 27 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28 | * See the License for the specific language governing permissions and 29 | * limitations under the License. 30 | */ 31 | ``` 32 | -------------------------------------------------------------------------------- /OSSMETADATA: -------------------------------------------------------------------------------- 1 | osslifecycle=archived 2 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { jcenter() } 3 | dependencies { 4 | classpath 'be.insaneprogramming.gradle:animalsniffer-gradle-plugin:1.4.0' 5 | } 6 | } 7 | 8 | plugins { 9 | id 'nebula.netflixoss' version '2.2.10' 10 | } 11 | 12 | ext { 13 | githubProjectName = rootProject.name // Change if github project name is not the same as the root project's name 14 | } 15 | 16 | subprojects { 17 | apply plugin: 'nebula.netflixoss' 18 | 19 | repositories { 20 | jcenter() 21 | } 22 | apply from: rootProject.file('dagger.gradle') 23 | group = "com.netflix.${githubProjectName}" // TEMPLATE: Set to organization of project 24 | apply plugin: 'be.insaneprogramming.gradle.animalsniffer' 25 | 26 | animalsniffer { // Don't use apis that may not be available on Android 27 | signature = "org.codehaus.mojo.signature:java16:+@signature" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /buildViaTravis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script will build the project. 3 | 4 | if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then 5 | echo -e "Build Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" 6 | ./gradlew build 7 | elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ]; then 8 | echo -e 'Build Branch with Snapshot => Branch ['$TRAVIS_BRANCH']' 9 | ./gradlew -Prelease.travisci=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" build snapshot 10 | elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then 11 | echo -e 'Build Branch for Release => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG']' 12 | case "$TRAVIS_TAG" in 13 | *-rc\.*) 14 | ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" candidate 15 | ;; 16 | *) 17 | ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true -PbintrayUser="${bintrayUser}" -PbintrayKey="${bintrayKey}" -PsonatypeUsername="${sonatypeUsername}" -PsonatypePassword="${sonatypePassword}" final 18 | ;; 19 | esac 20 | else 21 | echo -e 'WARN: Should not be here => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG'] Pull Request ['$TRAVIS_PULL_REQUEST']' 22 | ./gradlew build 23 | fi 24 | -------------------------------------------------------------------------------- /cli/src/test/resources/test-config.yml: -------------------------------------------------------------------------------- 1 | name: blah1 2 | provider: route53 3 | credentials: 4 | accessKey: foo1 5 | secretKey: foo2 6 | --- 7 | name: blah2 8 | provider: mock 9 | url: mem:mock2 10 | credentials: 11 | accessKey: foo3 12 | secretKey: foo4 13 | sessionToken: foo5 14 | 15 | -------------------------------------------------------------------------------- /cli/supportedProviders.txt: -------------------------------------------------------------------------------- 1 | # Update this file to add more default providers to the fat jar. 2 | # Note that these providers must be specified as a fatJarProviders dependency 3 | # At build time, this will become META-INF/services/denominator.Provider 4 | # see http://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html 5 | denominator.clouddns.CloudDNSProvider 6 | denominator.designate.DesignateProvider 7 | denominator.dynect.DynECTProvider 8 | denominator.mock.MockProvider 9 | denominator.route53.Route53Provider 10 | denominator.ultradns.UltraDNSProvider -------------------------------------------------------------------------------- /clouddns/README.md: -------------------------------------------------------------------------------- 1 | ## Notable Behaviors 2 | The following are notable when compared to different providers. 3 | * `Zone.id()` is opaque and `Zone.name()` doesn't include a trailing dot. 4 | * Zone lists are 1 + N requests in order to zip with the SOA's ttl. 5 | * `Zone.ttl()` is the default for new records. 6 | * `SOAData.refresh(),retry(),expire(), and minimum()` are invalid as they aren't exposed via the api. 7 | * 413 errors are common as the api is chatty, yet throttled. 8 | -------------------------------------------------------------------------------- /clouddns/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | sourceCompatibility = 1.6 4 | 5 | test { 6 | systemProperty 'clouddns.url', System.getProperty('clouddns.url', '') 7 | systemProperty 'clouddns.username', System.getProperty('clouddns.username', '') 8 | systemProperty 'clouddns.apiKey', System.getProperty('clouddns.apiKey', '') 9 | systemProperty 'clouddns.zone', System.getProperty('clouddns.zone', '') 10 | } 11 | 12 | dependencies { 13 | compile project(':denominator-core') 14 | compile 'com.netflix.feign:feign-core:8.10.0' 15 | compile 'com.netflix.feign:feign-gson:8.10.0' 16 | testCompile project(':denominator-model').sourceSets.test.output 17 | testCompile project(':denominator-core').sourceSets.test.output 18 | testCompile 'junit:junit:4.12' 19 | testCompile 'org.assertj:assertj-core:1.7.1' // last version supporting JDK 7 20 | testCompile 'com.squareup.okhttp:mockwebserver:2.5.0' 21 | } 22 | -------------------------------------------------------------------------------- /clouddns/src/main/java/denominator/clouddns/CloudDNSFunctions.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import denominator.clouddns.RackspaceApis.CloudDNS; 7 | import denominator.clouddns.RackspaceApis.Job; 8 | import denominator.clouddns.RackspaceApis.Record; 9 | import denominator.common.Util; 10 | import denominator.model.rdata.MXData; 11 | import denominator.model.rdata.SOAData; 12 | import denominator.model.rdata.SRVData; 13 | import denominator.model.rdata.TXTData; 14 | import feign.RetryableException; 15 | import feign.Retryer; 16 | 17 | import static denominator.common.Util.split; 18 | import static java.lang.String.format; 19 | 20 | final class CloudDNSFunctions { 21 | 22 | /** 23 | * Returns the ID of the object created or null. 24 | */ 25 | static String awaitComplete(CloudDNS api, Job job) { 26 | RetryableException retryableException = new RetryableException( 27 | format("Job %s did not complete. Check your logs.", job.id), null); 28 | Retryer retryer = new Retryer.Default(500, 1000, 30); 29 | 30 | while (true) { 31 | job = api.getStatus(job.id); 32 | 33 | if ("COMPLETED".equals(job.status)) { 34 | return job.resultId; 35 | } else if ("ERROR".equals(job.status)) { 36 | throw new IllegalStateException( 37 | format("Job %s failed with error: %s", job.id, job.errorDetails)); 38 | } 39 | 40 | retryer.continueOrPropagate(retryableException); 41 | } 42 | } 43 | 44 | /** 45 | * Special-cases priority field and the strange and incomplete SOA record. 46 | */ 47 | static Map toRDataMap(Record record) { 48 | if ("MX".equals(record.type)) { 49 | return MXData.create(record.priority, record.data()); 50 | } else if ("TXT".equals(record.type)) { 51 | return TXTData.create(record.data()); 52 | } else if ("SRV".equals(record.type)) { 53 | List rdata = split(' ', record.data()); 54 | return SRVData.builder() 55 | .priority(record.priority) 56 | .weight(Integer.valueOf(rdata.get(0))) 57 | .port(Integer.valueOf(rdata.get(1))) 58 | .target(rdata.get(2)).build(); 59 | } else if ("SOA".equals(record.type)) { 60 | List threeParts = split(' ', record.data()); 61 | return SOAData.builder() 62 | .mname(threeParts.get(0)) 63 | .rname(threeParts.get(1)) 64 | .serial(Integer.valueOf(threeParts.get(2))) 65 | .refresh(record.ttl) 66 | .retry(record.ttl) 67 | .expire(record.ttl).minimum(record.ttl).build(); 68 | } else { 69 | return Util.toMap(record.type, record.data()); 70 | } 71 | } 72 | 73 | private CloudDNSFunctions() { 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /clouddns/src/main/java/denominator/clouddns/CloudDNSTarget.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import javax.inject.Inject; 4 | 5 | import denominator.Provider; 6 | import denominator.clouddns.RackspaceApis.CloudDNS; 7 | import denominator.clouddns.RackspaceApis.TokenIdAndPublicURL; 8 | import feign.Request; 9 | import feign.RequestTemplate; 10 | import feign.Target; 11 | 12 | class CloudDNSTarget implements Target { 13 | 14 | private final Provider provider; 15 | private final InvalidatableAuthProvider lazyUrlAndToken; 16 | 17 | @Inject 18 | CloudDNSTarget(Provider provider, InvalidatableAuthProvider lazyUrlAndToken) { 19 | this.provider = provider; 20 | this.lazyUrlAndToken = lazyUrlAndToken; 21 | } 22 | 23 | @Override 24 | public Class type() { 25 | return CloudDNS.class; 26 | } 27 | 28 | @Override 29 | public String name() { 30 | return provider.name(); 31 | } 32 | 33 | @Override 34 | public String url() { 35 | return lazyUrlAndToken.get().publicURL; 36 | } 37 | 38 | @Override 39 | public Request apply(RequestTemplate input) { 40 | TokenIdAndPublicURL urlAndToken = lazyUrlAndToken.get(); 41 | if (input.url().indexOf("http") != 0) { 42 | input.insert(0, urlAndToken.publicURL); 43 | } 44 | input.header("X-Auth-Token", urlAndToken.tokenId); 45 | return input.request(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /clouddns/src/main/java/denominator/clouddns/GroupByRecordNameAndTypeIterator.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import java.util.Iterator; 4 | import java.util.Map; 5 | 6 | import denominator.clouddns.RackspaceApis.Record; 7 | import denominator.common.PeekingIterator; 8 | import denominator.model.ResourceRecordSet; 9 | import denominator.model.ResourceRecordSet.Builder; 10 | 11 | import static denominator.clouddns.CloudDNSFunctions.toRDataMap; 12 | import static denominator.common.Util.peekingIterator; 13 | 14 | class GroupByRecordNameAndTypeIterator implements Iterator> { 15 | 16 | private final PeekingIterator peekingIterator; 17 | 18 | public GroupByRecordNameAndTypeIterator(Iterator sortedIterator) { 19 | this.peekingIterator = peekingIterator(sortedIterator); 20 | } 21 | 22 | private static boolean nameAndTypeEquals(Record actual, Record expected) { 23 | return actual.name.equals(expected.name) && actual.type.equals(expected.type); 24 | } 25 | 26 | @Override 27 | public boolean hasNext() { 28 | return peekingIterator.hasNext(); 29 | } 30 | 31 | @Override 32 | public ResourceRecordSet next() { 33 | Record recordDetail = peekingIterator.next(); 34 | Builder> builder = ResourceRecordSet.builder() 35 | .name(recordDetail.name) 36 | .type(recordDetail.type) 37 | .ttl(recordDetail.ttl) 38 | .add(toRDataMap(recordDetail)); 39 | while (hasNext()) { 40 | Record next = peekingIterator.peek(); 41 | if (next == null) { 42 | continue; 43 | } 44 | if (nameAndTypeEquals(next, recordDetail)) { 45 | builder.add(toRDataMap(peekingIterator.next())); 46 | } else { 47 | break; 48 | } 49 | } 50 | return builder.build(); 51 | } 52 | 53 | @Override 54 | public void remove() { 55 | throw new UnsupportedOperationException(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /clouddns/src/main/java/denominator/clouddns/KeystoneAccessAdapter.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonIOException; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import com.google.gson.TypeAdapter; 8 | import com.google.gson.stream.JsonReader; 9 | import com.google.gson.stream.JsonWriter; 10 | 11 | import java.io.IOException; 12 | 13 | import denominator.clouddns.RackspaceApis.TokenIdAndPublicURL; 14 | 15 | import static denominator.common.Preconditions.checkNotNull; 16 | 17 | class KeystoneAccessAdapter extends TypeAdapter { 18 | 19 | // rax:dns 20 | private final String type; 21 | 22 | KeystoneAccessAdapter(String type) { 23 | this.type = checkNotNull(type, "type was null"); 24 | } 25 | 26 | static boolean isNull(JsonElement element) { 27 | return element == null || element.isJsonNull(); 28 | } 29 | 30 | @Override 31 | public TokenIdAndPublicURL read(JsonReader in) throws IOException { 32 | JsonObject access; 33 | try { 34 | access = new JsonParser().parse(in).getAsJsonObject().get("access").getAsJsonObject(); 35 | } catch (JsonIOException e) { 36 | if (e.getCause() != null && e.getCause() instanceof IOException) { 37 | throw IOException.class.cast(e.getCause()); 38 | } 39 | throw e; 40 | } 41 | JsonElement tokenField = access.get("token"); 42 | if (isNull(tokenField)) { 43 | return null; 44 | } 45 | JsonElement idField = tokenField.getAsJsonObject().get("id"); 46 | if (isNull(idField)) { 47 | return null; 48 | } 49 | 50 | TokenIdAndPublicURL tokenUrl = new TokenIdAndPublicURL(); 51 | tokenUrl.tokenId = idField.getAsString(); 52 | 53 | for (JsonElement s : access.get("serviceCatalog").getAsJsonArray()) { 54 | JsonObject service = s.getAsJsonObject(); 55 | JsonElement typeField = service.get("type"); 56 | JsonElement endpointsField = service.get("endpoints"); 57 | if (!isNull(typeField) && !isNull(endpointsField) && type.equals(typeField.getAsString())) { 58 | for (JsonElement e : endpointsField.getAsJsonArray()) { 59 | JsonObject endpoint = e.getAsJsonObject(); 60 | tokenUrl.publicURL = endpoint.get("publicURL").getAsString(); 61 | } 62 | } 63 | } 64 | return tokenUrl; 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return "KeystoneAccessAdapter(" + type + ")"; 70 | } 71 | 72 | @Override 73 | public void write(JsonWriter out, TokenIdAndPublicURL value) throws IOException { 74 | throw new UnsupportedOperationException(); 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /clouddns/src/main/java/denominator/clouddns/LimitsReadable.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import javax.inject.Inject; 4 | 5 | import denominator.CheckConnection; 6 | import denominator.clouddns.RackspaceApis.CloudDNS; 7 | 8 | class LimitsReadable implements CheckConnection { 9 | 10 | private final CloudDNS api; 11 | 12 | @Inject 13 | LimitsReadable(CloudDNS api) { 14 | this.api = api; 15 | } 16 | 17 | @Override 18 | public boolean ok() { 19 | try { 20 | return api.limits() != null; 21 | } catch (RuntimeException e) { 22 | return false; 23 | } 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "LimitsReadable"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /clouddns/src/main/resources/META-INF/services/denominator.Provider: -------------------------------------------------------------------------------- 1 | denominator.clouddns.CloudDNSProvider 2 | -------------------------------------------------------------------------------- /clouddns/src/test/java/denominator/clouddns/CloudDNSCheckConnectionLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import denominator.CheckConnectionLiveTest; 4 | import denominator.Live.UseTestGraph; 5 | 6 | @UseTestGraph(CloudDNSTestGraph.class) 7 | public class CloudDNSCheckConnectionLiveTest extends CheckConnectionLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /clouddns/src/test/java/denominator/clouddns/CloudDNSConnection.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import denominator.DNSApiManager; 8 | import denominator.Denominator; 9 | import feign.Logger; 10 | 11 | import static denominator.CredentialsConfiguration.credentials; 12 | import static feign.Util.emptyToNull; 13 | import static java.lang.System.getProperty; 14 | 15 | public class CloudDNSConnection { 16 | 17 | final DNSApiManager manager; 18 | final String mutableZone; 19 | 20 | CloudDNSConnection() { 21 | String username = emptyToNull(getProperty("clouddns.username")); 22 | String apiKey = emptyToNull(getProperty("clouddns.apiKey")); 23 | if (username != null && apiKey != null) { 24 | manager = create(username, apiKey); 25 | } else { 26 | manager = null; 27 | } 28 | mutableZone = emptyToNull(getProperty("clouddns.zone")); 29 | } 30 | 31 | static DNSApiManager create(String username, String apiKey) { 32 | CloudDNSProvider provider = new CloudDNSProvider(emptyToNull(getProperty("clouddns.url"))); 33 | return Denominator.create(provider, credentials(username, apiKey), new Overrides()); 34 | } 35 | 36 | @Module(overrides = true, library = true) 37 | static class Overrides { 38 | 39 | @Provides 40 | @Singleton 41 | Logger.Level provideLevel() { 42 | return Logger.Level.FULL; 43 | } 44 | 45 | @Provides 46 | @Singleton 47 | Logger provideLogger() { 48 | return new Logger.JavaLogger().appendToFile("build/http-wire.log"); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /clouddns/src/test/java/denominator/clouddns/CloudDNSFunctionsTest.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import org.junit.Test; 4 | 5 | import denominator.clouddns.RackspaceApis.Record; 6 | import denominator.model.rdata.NSData; 7 | import denominator.model.rdata.TXTData; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class CloudDNSFunctionsTest { 12 | 13 | @Test 14 | public void transformsNSRecordSet() { 15 | Record input = new Record(); 16 | input.name = "denominator.io"; 17 | input.type = "NS"; 18 | input.ttl = 3600; 19 | input.data("dns1.stabletransit.com"); 20 | 21 | assertThat(CloudDNSFunctions.toRDataMap(input)) 22 | .isEqualTo(NSData.create("dns1.stabletransit.com")); 23 | } 24 | 25 | @Test 26 | public void transformsTXTRecordSet() { 27 | Record input = new Record(); 28 | input.name = "denominator.io"; 29 | input.type = "TXT"; 30 | input.ttl = 3600; 31 | input.data("Hello DNS"); 32 | 33 | assertThat(CloudDNSFunctions.toRDataMap(input)) 34 | .isEqualTo(TXTData.create("Hello DNS")); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /clouddns/src/test/java/denominator/clouddns/CloudDNSReadOnlyLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.ReadOnlyLiveTest; 5 | 6 | @UseTestGraph(CloudDNSTestGraph.class) 7 | public class CloudDNSReadOnlyLiveTest extends ReadOnlyLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /clouddns/src/test/java/denominator/clouddns/CloudDNSTestGraph.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import denominator.DNSApiManagerFactory; 4 | import denominator.TestGraph; 5 | 6 | import static feign.Util.emptyToNull; 7 | import static java.lang.System.getProperty; 8 | 9 | public class CloudDNSTestGraph extends TestGraph { 10 | 11 | private static final String url = emptyToNull(getProperty("clouddns.url")); 12 | private static final String zone = emptyToNull(getProperty("clouddns.zone")); 13 | 14 | public CloudDNSTestGraph() { 15 | super(DNSApiManagerFactory.create(new CloudDNSProvider(url)), zone); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /clouddns/src/test/java/denominator/clouddns/CloudDNSWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.WriteCommandsLiveTest; 5 | 6 | @UseTestGraph(CloudDNSTestGraph.class) 7 | public class CloudDNSWriteCommandsLiveTest extends WriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /clouddns/src/test/java/denominator/clouddns/CloudDNSZoneWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.ZoneWriteCommandsLiveTest; 5 | 6 | @UseTestGraph(CloudDNSTestGraph.class) 7 | public class CloudDNSZoneWriteCommandsLiveTest extends ZoneWriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /clouddns/src/test/java/denominator/clouddns/LimitsReadableMockTest.java: -------------------------------------------------------------------------------- 1 | package denominator.clouddns; 2 | 3 | import com.squareup.okhttp.mockwebserver.MockResponse; 4 | 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | 8 | import denominator.DNSApiManager; 9 | 10 | import static denominator.clouddns.RackspaceApisTest.limitsResponse; 11 | import static org.junit.Assert.assertFalse; 12 | import static org.junit.Assert.assertTrue; 13 | 14 | public class LimitsReadableMockTest { 15 | 16 | @Rule 17 | public final MockCloudDNSServer server = new MockCloudDNSServer(); 18 | 19 | @Test 20 | public void implicitlyStartsSessionWhichIsReusedForLaterRequests() throws Exception { 21 | server.enqueueAuthResponse(); 22 | server.enqueue(new MockResponse().setBody("{ \"domains\": [] }")); 23 | server.enqueue(new MockResponse().setBody(limitsResponse)); 24 | server.enqueue(new MockResponse().setBody(limitsResponse)); 25 | 26 | DNSApiManager api = server.connect(); 27 | assertTrue(api.checkConnection()); 28 | assertTrue(api.checkConnection()); 29 | 30 | server.assertAuthRequest(); 31 | server.assertRequest().hasPath("/v1.0/123123/limits"); 32 | server.assertRequest().hasPath("/v1.0/123123/limits"); 33 | } 34 | 35 | @Test 36 | public void singleRequestOnFailure() throws Exception { 37 | server.enqueueAuthResponse(); 38 | server.enqueue(new MockResponse().setResponseCode(401)); 39 | 40 | DNSApiManager api = server.connect(); 41 | assertFalse(api.checkConnection()); 42 | 43 | server.assertAuthRequest(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'nebula-test-jar' 3 | 4 | sourceCompatibility = 1.6 5 | 6 | jar { 7 | manifest { 8 | attributes("Implementation-Title": "Denominator", "Specification-Version": version, "Implementation-Version": version) 9 | } 10 | } 11 | 12 | test { 13 | systemProperty 'mock.zone', 'denominator.io.' 14 | } 15 | 16 | dependencies { 17 | compile project(':denominator-model') 18 | testCompile 'junit:junit:4.12' 19 | testCompile 'org.assertj:assertj-core:1.7.1' // last version supporting JDK 7 20 | testCompile 'com.squareup.okhttp:mockwebserver:2.5.0' 21 | testCompile 'com.netflix.feign:feign-core:8.10.0' 22 | testCompile project(':denominator-model').sourceSets.test.output 23 | } 24 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/AllProfileResourceRecordSetApi.java: -------------------------------------------------------------------------------- 1 | package denominator; 2 | 3 | import denominator.model.ResourceRecordSet; 4 | 5 | /** 6 | * Controls all resource records in the provider 7 | */ 8 | public interface AllProfileResourceRecordSetApi extends QualifiedResourceRecordSetApi { 9 | 10 | /** 11 | * Idempotently deletes all resource record sets with the specified {@link 12 | * ResourceRecordSet#name() name} and {@link ResourceRecordSet#type()}. This does not error if no 13 | * record sets match. 14 | * 15 | * @param name {@link ResourceRecordSet#name() name} of the rrset 16 | * @param type {@link ResourceRecordSet#type() type} of the rrset 17 | * @throws IllegalArgumentException if the zone is not found. 18 | */ 19 | void deleteByNameAndType(String name, String type); 20 | 21 | static interface Factory { 22 | 23 | AllProfileResourceRecordSetApi create(String id); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/CheckConnection.java: -------------------------------------------------------------------------------- 1 | package denominator; 2 | 3 | /** 4 | * Answers the question: 5 | * 6 | * Are operations likely to succeed, given context of the {@link Provider provider} and currently 7 | * configured {@link Credentials credentials}? 8 | * 9 | * Implementations should make a remote connection, or consult a trusted source to derive the 10 | * result. They should use least resources possible to establish a meaningful result, and be safe to 11 | * call many times, possibly concurrently. 12 | */ 13 | public interface CheckConnection { 14 | 15 | boolean ok(); 16 | } 17 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/DNSApiManager.java: -------------------------------------------------------------------------------- 1 | package denominator; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | 6 | import javax.inject.Inject; 7 | 8 | /** 9 | * represents the connection between a {@link DNSApi} interface and the {@link Provider} that 10 | * implements it. 11 | */ 12 | public class DNSApiManager implements Closeable { 13 | 14 | private final Provider provider; 15 | private final DNSApi api; 16 | private final CheckConnection checkConnection; 17 | private final Closeable closer; 18 | 19 | @Inject 20 | DNSApiManager(Provider provider, DNSApi api, CheckConnection checkConnection, Closeable closer) { 21 | this.provider = provider; 22 | this.api = api; 23 | this.checkConnection = checkConnection; 24 | this.closer = closer; 25 | } 26 | 27 | /** 28 | * the currently configured {@link DNSApi} 29 | */ 30 | public DNSApi api() { 31 | return api; 32 | } 33 | 34 | /** 35 | * Get the provider associated with this instance 36 | */ 37 | public Provider provider() { 38 | return provider; 39 | } 40 | 41 | /** 42 | * Returns true, if api commands are likely to succeed. 43 | * 44 | * @see CheckConnection 45 | */ 46 | public boolean checkConnection() { 47 | return checkConnection.ok(); 48 | } 49 | 50 | /** 51 | * closes resources associated with the connections, such as thread pools or open files. 52 | */ 53 | @Override 54 | public void close() throws IOException { 55 | closer.close(); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return provider.toString(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/QualifiedResourceRecordSetApi.java: -------------------------------------------------------------------------------- 1 | package denominator; 2 | 3 | import denominator.model.ResourceRecordSet; 4 | 5 | /** 6 | * Write operations for {@link ResourceRecordSet record sets} who have a {@link 7 | * ResourceRecordSet#qualifier()}. 8 | * 9 | * @since 1.3 10 | */ 11 | public interface QualifiedResourceRecordSetApi extends ReadOnlyResourceRecordSetApi { 12 | 13 | /** 14 | * Idempotently replaces any existing records with {@link ResourceRecordSet#name() name}, {@link 15 | * ResourceRecordSet#type() type}, and {@link ResourceRecordSet#qualifier() qualifier} 16 | * corresponding to {@code rrset}. If no records exist, they will be added. 17 | * 18 | *
Example of replacing the {@code A} record set for {@code www.denominator.io.} qualified as 19 | * {@code US-West}: 20 | * 21 | *
22 |    * rrsApi.put(ResourceRecordSet.<AData> builder().name("www.denominator.io.").type("A").qualifier("US-West").ttl(3600)
23 |    *         .add(AData.create("192.0.2.1")).build());
24 |    * 
25 | * 26 | * @param rrset contains the {@code rdata} elements ensure exist. If {@link 27 | * ResourceRecordSet#ttl() ttl} is not present, zone default is used. 28 | * @throws IllegalArgumentException if the zone is not found. 29 | */ 30 | void put(ResourceRecordSet rrset); 31 | 32 | /** 33 | * Idempotently deletes a resource record set by {@link ResourceRecordSet#name() name}, {@link 34 | * ResourceRecordSet#type() type}, and {@link ResourceRecordSet#qualifier() qualifier}. This does 35 | * not error if the record set doesn't exist. 36 | * 37 | * @param name {@link ResourceRecordSet#name() name} of the rrset 38 | * @param type {@link ResourceRecordSet#type() type} of the rrset 39 | * @param qualifier {@link ResourceRecordSet#qualifier() qualifier} of the rrset 40 | * @throws IllegalArgumentException if the zone is not found. 41 | */ 42 | void deleteByNameTypeAndQualifier(String name, String type, String qualifier); 43 | 44 | static interface Factory { 45 | 46 | /** 47 | * @return null if this feature isn't supported on the provider. 48 | */ 49 | QualifiedResourceRecordSetApi create(String id); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/ReadOnlyResourceRecordSetApi.java: -------------------------------------------------------------------------------- 1 | package denominator; 2 | 3 | import java.util.Iterator; 4 | 5 | import denominator.model.ResourceRecordSet; 6 | 7 | public interface ReadOnlyResourceRecordSetApi extends Iterable> { 8 | 9 | /** 10 | * Iterates across all record sets in the zone. Implementations are lazy when possible. 11 | * 12 | * @return iterator which is lazy where possible 13 | * @throws IllegalArgumentException if the zone is not found. 14 | */ 15 | @Override 16 | Iterator> iterator(); 17 | 18 | /** 19 | * a listing of all resource record sets which have the specified name. 20 | * 21 | * @return iterator which is lazy where possible, empty if there are no records with that name. 22 | * @throws IllegalArgumentException if the zone is not found. 23 | * @since 1.3 24 | */ 25 | Iterator> iterateByName(String name); 26 | 27 | /** 28 | * a listing of all resource record sets by name and type. 29 | * 30 | * @param name {@link ResourceRecordSet#name() name} of the rrset 31 | * @param type {@link ResourceRecordSet#type() type} of the rrset 32 | * @return iterator which is lazy where possible, empty if there are no records with that name. 33 | * @throws IllegalArgumentException if the zone is not found. 34 | * @since 1.3 35 | */ 36 | Iterator> iterateByNameAndType(String name, String type); 37 | 38 | /** 39 | * retrieve a resource record set by name, type, and qualifier 40 | * 41 | * @param name {@link ResourceRecordSet#name() name} of the rrset 42 | * @param type {@link ResourceRecordSet#type() type} of the rrset 43 | * @param qualifier {@link ResourceRecordSet#qualifier() qualifier} of the rrset 44 | * @return null unless a resource record exists with the same {@code name}, {@code type}, and 45 | * {@code qualifier} 46 | * @throws IllegalArgumentException if the zone is not found. 47 | */ 48 | ResourceRecordSet getByNameTypeAndQualifier(String name, String type, String qualifier); 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/ResourceRecordSetApi.java: -------------------------------------------------------------------------------- 1 | package denominator; 2 | 3 | import java.util.Iterator; 4 | 5 | import denominator.model.ResourceRecordSet; 6 | 7 | public interface ResourceRecordSetApi extends Iterable> { 8 | 9 | /** 10 | * Iterates across all basic record sets in the zone (those with no profile). Implementations are 11 | * lazy when possible. 12 | * 13 | * @return iterator which is lazy where possible 14 | * @throws IllegalArgumentException if the zone is not found. 15 | */ 16 | @Override 17 | Iterator> iterator(); 18 | 19 | /** 20 | * a listing of all resource record sets which have the specified name. 21 | * 22 | * @return iterator which is lazy where possible, empty if there are no records with that name. 23 | * @throws IllegalArgumentException if the zone is not found. 24 | * @since 1.3 25 | */ 26 | Iterator> iterateByName(String name); 27 | 28 | /** 29 | * retrieve a resource record set by name and type. 30 | * 31 | * @param name {@link ResourceRecordSet#name() name} of the rrset 32 | * @param type {@link ResourceRecordSet#type() type} of the rrset 33 | * @return null unless a resource record exists with the same {@code name} and {@code type} 34 | * @throws IllegalArgumentException if the zone is not found. 35 | */ 36 | ResourceRecordSet getByNameAndType(String name, String type); 37 | 38 | /** 39 | * Idempotently replaces any existing records with {@link ResourceRecordSet#name() name} and 40 | * {@link ResourceRecordSet#type()} corresponding to {@code rrset}. If no records exists, they 41 | * will be added. 42 | * 43 | *
Example of replacing the {@code A} record set for {@code www.denominator.io.}: 44 | * 45 | *
46 |    * rrsApi.put(a("www.denominator.io.", "192.0.2.1"));
47 |    * 
48 | * 49 | * @param rrset contains the {@code rdata} elements ensure exist. If {@link 50 | * ResourceRecordSet#ttl() ttl} is not present, zone default is used. 51 | * @throws IllegalArgumentException if the zone is not found 52 | * @since 1.3 53 | */ 54 | void put(ResourceRecordSet rrset); 55 | 56 | /** 57 | * deletes a resource record set by name and type idempotently. This does not error if the record 58 | * set doesn't exist. 59 | * 60 | * @param name {@link ResourceRecordSet#name() name} of the rrset 61 | * @param type {@link ResourceRecordSet#type() type} of the rrset 62 | * @throws IllegalArgumentException if the zone is not found. 63 | */ 64 | void deleteByNameAndType(String name, String type); 65 | 66 | interface Factory { 67 | 68 | ResourceRecordSetApi create(String id); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/ZoneApi.java: -------------------------------------------------------------------------------- 1 | package denominator; 2 | 3 | import java.util.Iterator; 4 | 5 | import denominator.model.Zone; 6 | 7 | public interface ZoneApi extends Iterable { 8 | 9 | /** 10 | * Iterates across all zones, returning their name and id (when present). Implementations are lazy 11 | * when possible. 12 | */ 13 | @Override 14 | Iterator iterator(); 15 | 16 | /** 17 | * Returns a potentially empty iterator of zones with the supplied {@link Zone#name()}. This can 18 | * only have multiple results when {@link Provider#supportsDuplicateZoneNames()}. 19 | * 20 | * @since 4.5 21 | */ 22 | Iterator iterateByName(String name); 23 | 24 | /** 25 | * Creates or updates a zone with corresponding to {@link Zone#name()}. 26 | * 27 | *

When {@linkplain Provider#supportsDuplicateZoneNames()} and {@link Zone#id()} is set, the 28 | * corresponding zone will be updated. Otherwise, a new zone will be created. 29 | * 30 | *
Example adding a zone {@code denominator.io.}: 31 | * 32 | *

33 |    * zoneId = zoneApi.put(Zone.create(null, "denominator.io.", 86400, "nil@denominator.io");
34 |    * 
35 | * 36 | * @return the {@link Zone#id() id} of the new or affected zone. 37 | * @since 4.5 38 | */ 39 | String put(Zone zone); 40 | 41 | /** 42 | * Deletes a zone by id idempotently. This does not error if the zone doesn't exist. 43 | * 44 | * @param id {@link Zone#id() id} of the zone. 45 | * @since 4.5 46 | */ 47 | void delete(String id); 48 | } 49 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/config/GeoUnsupported.java: -------------------------------------------------------------------------------- 1 | package denominator.config; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import denominator.DNSApiManager; 8 | import denominator.profile.GeoResourceRecordSetApi; 9 | 10 | /** 11 | * Some providers do not yet support directional DNS. 12 | */ 13 | @Module(injects = DNSApiManager.class, complete = false) 14 | public class GeoUnsupported { 15 | 16 | @Provides 17 | @Singleton 18 | GeoResourceRecordSetApi.Factory provideGeoResourceRecordSetApiFactory() { 19 | return new GeoResourceRecordSetApi.Factory() { 20 | 21 | @Override 22 | public GeoResourceRecordSetApi create(String id) { 23 | return null; 24 | } 25 | 26 | }; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/config/NothingToClose.java: -------------------------------------------------------------------------------- 1 | package denominator.config; 2 | 3 | import java.io.Closeable; 4 | 5 | import javax.inject.Singleton; 6 | 7 | import dagger.Module; 8 | import dagger.Provides; 9 | import denominator.DNSApiManager; 10 | 11 | /** 12 | * In many providers, we would likely inject resources that need cleanup and call them inside the 13 | * {@link Closeable}. For example, shutting down thread pools, or syncing disk write. In this case, 14 | * there's nothing to close. 15 | */ 16 | @Module(injects = DNSApiManager.class, complete = false) 17 | public class NothingToClose implements Closeable { 18 | 19 | @Provides 20 | @Singleton 21 | Closeable provideCloser() { 22 | return this; 23 | } 24 | 25 | @Override 26 | public void close() { 27 | // nothing to close 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/config/OnlyBasicResourceRecordSets.java: -------------------------------------------------------------------------------- 1 | package denominator.config; 2 | 3 | import java.util.Collections; 4 | import java.util.Iterator; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import dagger.Module; 9 | import dagger.Provides; 10 | import denominator.AllProfileResourceRecordSetApi; 11 | import denominator.DNSApiManager; 12 | import denominator.ResourceRecordSetApi; 13 | import denominator.model.ResourceRecordSet; 14 | 15 | /** 16 | * Used when the backend doesn't support any record types except basic ones. 17 | */ 18 | @Module(injects = DNSApiManager.class, complete = false) 19 | public class OnlyBasicResourceRecordSets { 20 | 21 | @Provides 22 | @Singleton 23 | AllProfileResourceRecordSetApi.Factory provideAllProfileResourceRecordSetApi( 24 | final ResourceRecordSetApi.Factory factory) { 25 | return new AllProfileResourceRecordSetApi.Factory() { 26 | 27 | @Override 28 | public AllProfileResourceRecordSetApi create(String id) { 29 | return new OnlyBasicResourceRecordSetApi(factory.create(id)); 30 | } 31 | 32 | }; 33 | } 34 | 35 | private static class OnlyBasicResourceRecordSetApi implements AllProfileResourceRecordSetApi { 36 | 37 | private final ResourceRecordSetApi api; 38 | 39 | private OnlyBasicResourceRecordSetApi(ResourceRecordSetApi api) { 40 | this.api = api; 41 | } 42 | 43 | @Override 44 | public Iterator> iterator() { 45 | return api.iterator(); 46 | } 47 | 48 | @Override 49 | public Iterator> iterateByName(String name) { 50 | return api.iterateByName(name); 51 | } 52 | 53 | @Override 54 | public Iterator> iterateByNameAndType(String name, String type) { 55 | ResourceRecordSet rrs = api.getByNameAndType(name, type); 56 | if (rrs != null) { 57 | return Collections.>singleton(rrs).iterator(); 58 | } 59 | return Collections.>emptySet().iterator(); 60 | } 61 | 62 | @Override 63 | public ResourceRecordSet getByNameTypeAndQualifier(String name, String type, 64 | String qualifier) { 65 | return null; 66 | } 67 | 68 | @Override 69 | public void put(ResourceRecordSet rrset) { 70 | api.put(rrset); 71 | } 72 | 73 | @Override 74 | public void deleteByNameTypeAndQualifier(String name, String type, String qualifier) { 75 | api.deleteByNameAndType(name, type); 76 | } 77 | 78 | @Override 79 | public void deleteByNameAndType(String name, String type) { 80 | api.deleteByNameAndType(name, type); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/config/WeightedUnsupported.java: -------------------------------------------------------------------------------- 1 | package denominator.config; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import denominator.DNSApiManager; 8 | import denominator.profile.WeightedResourceRecordSetApi; 9 | 10 | /** 11 | * Some providers do not yet support weighted records. 12 | */ 13 | @Module(injects = DNSApiManager.class, complete = false) 14 | public class WeightedUnsupported { 15 | 16 | @Provides 17 | @Singleton 18 | WeightedResourceRecordSetApi.Factory provideWeightedResourceRecordSetApiFactory() { 19 | return new WeightedResourceRecordSetApi.Factory() { 20 | @Override 21 | public WeightedResourceRecordSetApi create(String id) { 22 | return null; 23 | } 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * {@link dagger.Module} classes that {@link denominator.Provider} {@link dagger.Module#includes includes}. 3 | */ 4 | package denominator.config; 5 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/mock/MockResourceRecordSetApi.java: -------------------------------------------------------------------------------- 1 | package denominator.mock; 2 | 3 | import java.util.Collection; 4 | import java.util.Iterator; 5 | import java.util.Map; 6 | 7 | import denominator.model.ResourceRecordSet; 8 | 9 | import static denominator.common.Preconditions.checkNotNull; 10 | import static denominator.common.Util.nextOrNull; 11 | import static denominator.model.ResourceRecordSets.alwaysVisible; 12 | 13 | final class MockResourceRecordSetApi implements denominator.ResourceRecordSetApi { 14 | 15 | private final MockAllProfileResourceRecordSetApi delegate; 16 | 17 | MockResourceRecordSetApi(Map>> data, String zoneName) { 18 | this.delegate = new MockAllProfileResourceRecordSetApi(data, zoneName, alwaysVisible()); 19 | } 20 | 21 | @Override 22 | public Iterator> iterator() { 23 | return delegate.iterator(); 24 | } 25 | 26 | @Override 27 | public Iterator> iterateByName(String name) { 28 | return delegate.iterateByName(name); 29 | } 30 | 31 | @Override 32 | public ResourceRecordSet getByNameAndType(String name, String type) { 33 | return nextOrNull(delegate.iterateByNameAndType(name, type)); 34 | } 35 | 36 | @Override 37 | public void put(ResourceRecordSet rrset) { 38 | checkNotNull(rrset, "rrset was null"); 39 | Collection> records = delegate.records(); 40 | synchronized (records) { 41 | removeByNameAndType(records.iterator(), rrset.name(), rrset.type()); 42 | records.add(rrset); 43 | } 44 | } 45 | 46 | private void removeByNameAndType(Iterator> i, String name, String type) { 47 | while (i.hasNext()) { 48 | ResourceRecordSet test = i.next(); 49 | if (test.name().equals(name) && test.type().equals(type) && test.qualifier() == null) { 50 | i.remove(); 51 | } 52 | } 53 | } 54 | 55 | @Override 56 | public void deleteByNameAndType(String name, String type) { 57 | Collection> records = delegate.records(); 58 | synchronized (records) { 59 | removeByNameAndType(records.iterator(), name, type); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/mock/MockWeightedResourceRecordSetApi.java: -------------------------------------------------------------------------------- 1 | package denominator.mock; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | import java.util.SortedSet; 6 | 7 | import denominator.common.Filter; 8 | import denominator.model.ResourceRecordSet; 9 | import denominator.profile.WeightedResourceRecordSetApi; 10 | 11 | final class MockWeightedResourceRecordSetApi extends MockAllProfileResourceRecordSetApi 12 | implements WeightedResourceRecordSetApi { 13 | 14 | private static final Filter> 15 | IS_WEIGHTED = 16 | new Filter>() { 17 | @Override 18 | public boolean apply(ResourceRecordSet in) { 19 | return in != null && in.weighted() != null; 20 | } 21 | }; 22 | 23 | private final SortedSet supportedWeights; 24 | 25 | MockWeightedResourceRecordSetApi(Map>> data, 26 | String zoneName, 27 | SortedSet supportedWeights) { 28 | super(data, zoneName, IS_WEIGHTED); 29 | this.supportedWeights = supportedWeights; 30 | } 31 | 32 | @Override 33 | public SortedSet supportedWeights() { 34 | return supportedWeights; 35 | } 36 | 37 | @Override 38 | public void put(ResourceRecordSet rrset) { 39 | put(IS_WEIGHTED, rrset); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/profile/GeoResourceRecordSetApi.java: -------------------------------------------------------------------------------- 1 | package denominator.profile; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | 6 | import denominator.QualifiedResourceRecordSetApi; 7 | 8 | /** 9 | * list operations are filtered to only include those which are geo record sets. 10 | */ 11 | public interface GeoResourceRecordSetApi extends QualifiedResourceRecordSetApi { 12 | 13 | /** 14 | * retrieve an organized list of regions by region. It is often the case that the keys correlate 15 | * to UN or otherwise defined regions such as {@code North America}. However, this can also 16 | * include special case keys, such as {@code Fallback} and {@code Anonymous Proxy}.
ex. 17 | * 18 | *
19 |    * {
20 |    *     "States and Provinces: Canada": ["ab", "bc", "mb", "nb", "nl", "nt", "ns", "nu", "on",
21 |    * "pe", "qc", "sk", "yt"],
22 |    *     "Fallback": ["@@"],
23 |    *     "Anonymous Proxy": ["A1"],
24 |    *     "Other Country": ["O1"],
25 |    *     "Satellite Provider": ["A2"]
26 |    * }
27 |    * 
28 | * 29 | *

Note
30 | * 31 | * The values of this are not guaranteed portable across providers. 32 | * 33 | * @since 1.3 34 | */ 35 | Map> supportedRegions(); 36 | 37 | static interface Factory extends QualifiedResourceRecordSetApi.Factory { 38 | 39 | /** 40 | * @return null if this feature isn't supported on the provider. 41 | */ 42 | @Override 43 | GeoResourceRecordSetApi create(String id); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/src/main/java/denominator/profile/WeightedResourceRecordSetApi.java: -------------------------------------------------------------------------------- 1 | package denominator.profile; 2 | 3 | import java.util.SortedSet; 4 | 5 | import denominator.QualifiedResourceRecordSetApi; 6 | import denominator.model.profile.Weighted; 7 | 8 | /** 9 | * list operations are filtered to only include those which are weighted record sets. 10 | * 11 | * @since 1.3 12 | */ 13 | public interface WeightedResourceRecordSetApi extends QualifiedResourceRecordSetApi { 14 | 15 | /** 16 | * the set of {@link Weighted#weight() weights} that are valid for this provider. If present, 17 | * {@code 0} implies always serve this record set. 18 | */ 19 | SortedSet supportedWeights(); 20 | 21 | static interface Factory extends QualifiedResourceRecordSetApi.Factory { 22 | 23 | /** 24 | * @return null if this feature isn't supported on the provider. 25 | */ 26 | @Override 27 | WeightedResourceRecordSetApi create(String id); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/resources/META-INF/services/denominator.Provider: -------------------------------------------------------------------------------- 1 | denominator.mock.MockProvider 2 | -------------------------------------------------------------------------------- /core/src/test/java/denominator/CheckConnectionLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.Parameterized; 6 | 7 | import java.util.Collection; 8 | import java.util.LinkedHashMap; 9 | import java.util.Map; 10 | 11 | import denominator.Credentials.MapCredentials; 12 | 13 | import static denominator.CredentialsConfiguration.credentials; 14 | import static org.junit.Assert.assertFalse; 15 | import static org.junit.Assert.assertTrue; 16 | import static org.junit.Assume.assumeFalse; 17 | 18 | @RunWith(Live.class) 19 | public class CheckConnectionLiveTest { 20 | 21 | @Parameterized.Parameter 22 | public DNSApiManager manager; 23 | 24 | @Test 25 | public void success() { 26 | assertTrue(manager.checkConnection()); 27 | } 28 | 29 | @Test 30 | public void failGracefullyOnIncorrectCredentials() { 31 | assumeFalse("This test only applies to providers that authenticate", 32 | manager.provider().credentialTypeToParameterNames().isEmpty()); 33 | 34 | Collection 35 | parameters = 36 | manager.provider().credentialTypeToParameterNames().values().iterator().next(); 37 | 38 | Map creds = new LinkedHashMap(parameters.size()); 39 | for (String parameter : parameters) { 40 | creds.put(parameter, "foo"); 41 | } 42 | 43 | DNSApiManager 44 | incorrectCredentials = 45 | Denominator.create(manager.provider(), credentials(MapCredentials.from(creds))); 46 | assertFalse(incorrectCredentials.checkConnection()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /core/src/test/java/denominator/CredentialsTest.java: -------------------------------------------------------------------------------- 1 | package denominator; 2 | 3 | import org.junit.Test; 4 | 5 | import denominator.Credentials.ListCredentials; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | public class CredentialsTest { 10 | 11 | @Test 12 | public void testTwoPartCredentialsEqualsHashCode() { 13 | Credentials original = ListCredentials.from("user", "pass"); 14 | Credentials copy = ListCredentials.from("user", "pass"); 15 | 16 | assertThat(original).isEqualTo(copy); 17 | assertThat(original.hashCode()).isEqualTo(copy.hashCode()); 18 | } 19 | 20 | @Test 21 | public void testThreePartCredentialsEqualsHashCode() { 22 | Credentials original = ListCredentials.from("customer", "user", "pass"); 23 | Credentials copy = ListCredentials.from("customer", "user", "pass"); 24 | 25 | assertThat(original).isEqualTo(copy); 26 | assertThat(original.hashCode()).isEqualTo(copy.hashCode()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/src/test/java/denominator/DNSApiManagerFactory.java: -------------------------------------------------------------------------------- 1 | package denominator; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.LinkedHashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import javax.inject.Singleton; 11 | 12 | import dagger.Module; 13 | import dagger.Provides; 14 | import feign.Logger; 15 | 16 | import static feign.Util.emptyToNull; 17 | 18 | public final class DNSApiManagerFactory { 19 | 20 | public static DNSApiManager create(denominator.Provider provider) { 21 | List modules = new ArrayList(2); 22 | modules.add(credentialsModule(provider)); 23 | for (Class inner : provider.getClass().getDeclaredClasses()) { 24 | if (inner.getSimpleName().equals("FeignModule")) { 25 | modules.add(new HttpLog()); 26 | } 27 | } 28 | return Denominator.create(provider, modules.toArray()); 29 | } 30 | 31 | /** 32 | * Looks for {@link denominator.Provider#credentialTypeToParameterNames() credential parameters} 33 | * in system properties as {@code ${provider.name}.${parameter}. 34 | */ 35 | static Object credentialsModule(denominator.Provider provider) { 36 | Map credentials = new LinkedHashMap(3); 37 | for (Collection parameters : provider.credentialTypeToParameterNames().values()) { 38 | for (String parameter : parameters) { 39 | String systemProperty = provider.name() + "." + parameter; 40 | String value = emptyToNull(System.getProperty(systemProperty)); 41 | if (value != null) { 42 | credentials.put(parameter, value); 43 | } 44 | } 45 | } 46 | return CredentialsConfiguration.credentials(Credentials.MapCredentials.from(credentials)); 47 | } 48 | 49 | @Module(overrides = true, library = true) 50 | public static final class HttpLog { 51 | 52 | @Provides 53 | @Singleton 54 | Logger.Level provideLevel() { 55 | return Logger.Level.FULL; 56 | } 57 | 58 | @Provides 59 | @Singleton 60 | Logger provideLogger() { 61 | new File(System.getProperty("user.dir"), "build").mkdirs(); 62 | return new Logger.JavaLogger().appendToFile("build/http-wire.log"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /core/src/test/java/denominator/assertj/MockWebServerAssertions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package denominator.assertj; 17 | 18 | import com.squareup.okhttp.mockwebserver.RecordedRequest; 19 | 20 | import org.assertj.core.api.Assertions; 21 | 22 | public class MockWebServerAssertions extends Assertions { 23 | 24 | public static RecordedRequestAssert assertThat(RecordedRequest actual) { 25 | return new RecordedRequestAssert(actual); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/test/java/denominator/assertj/RecordedRequestAssert.java: -------------------------------------------------------------------------------- 1 | package denominator.assertj; 2 | 3 | import com.squareup.okhttp.mockwebserver.RecordedRequest; 4 | 5 | import org.assertj.core.api.AbstractAssert; 6 | import org.assertj.core.internal.Objects; 7 | import org.assertj.core.internal.Strings; 8 | 9 | public final class RecordedRequestAssert 10 | extends AbstractAssert { 11 | 12 | Strings strings = Strings.instance(); 13 | Objects objects = Objects.instance(); 14 | 15 | public RecordedRequestAssert(RecordedRequest actual) { 16 | super(actual, RecordedRequestAssert.class); 17 | } 18 | 19 | public RecordedRequestAssert hasMethod(String expected) { 20 | isNotNull(); 21 | objects.assertEqual(info, actual.getMethod(), expected); 22 | return this; 23 | } 24 | 25 | public RecordedRequestAssert hasPath(String expected) { 26 | isNotNull(); 27 | objects.assertEqual(info, actual.getPath(), expected); 28 | return this; 29 | } 30 | 31 | public RecordedRequestAssert hasBody(String utf8Expected) { 32 | isNotNull(); 33 | objects.assertEqual(info, actual.getBody().readUtf8(), utf8Expected); 34 | return this; 35 | } 36 | 37 | public RecordedRequestAssert hasXMLBody(String utf8Expected) { 38 | isNotNull(); 39 | hasHeaderContaining("Content-Type", "application/xml"); 40 | strings.assertXmlEqualsTo(info, actual.getBody().readUtf8(), utf8Expected); 41 | return this; 42 | } 43 | 44 | public RecordedRequestAssert hasHeaderContaining(String name, String value) { 45 | isNotNull(); 46 | if (actual.getHeader(name) == null) { 47 | failWithMessage("\nExpecting request to have header:<%s>", name); 48 | } 49 | strings.assertContains(info, actual.getHeader(name), value); 50 | return this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/test/java/denominator/mock/MockProviderTest.java: -------------------------------------------------------------------------------- 1 | package denominator.mock; 2 | 3 | import org.junit.Test; 4 | 5 | import denominator.Provider; 6 | 7 | import static denominator.Denominator.create; 8 | import static denominator.Providers.list; 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class MockProviderTest { 12 | 13 | private static final Provider PROVIDER = new MockProvider(); 14 | 15 | @Test 16 | public void testMockMetadata() { 17 | assertThat(PROVIDER.name()).isEqualTo("mock"); 18 | assertThat(PROVIDER.supportsDuplicateZoneNames()).isFalse(); 19 | assertThat(PROVIDER.credentialTypeToParameterNames()).isEmpty(); 20 | } 21 | 22 | @Test 23 | public void testMockRegistered() { 24 | assertThat(list()).contains(PROVIDER); 25 | } 26 | 27 | @Test 28 | public void testProviderWiresMockZoneApi() { 29 | assertThat(create(PROVIDER).api().zones()).isInstanceOf(MockZoneApi.class); 30 | assertThat(create("mock").api().zones()).isInstanceOf(MockZoneApi.class); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/src/test/java/denominator/profile/Regions.java: -------------------------------------------------------------------------------- 1 | package denominator.profile; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collection; 6 | import java.util.LinkedHashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | final class Regions { 11 | 12 | private final Map> all; 13 | private final String lastRegion; 14 | private final String lastTerritory; 15 | private final List exceptLastTerritory; 16 | 17 | Regions(Map> input) { 18 | all = input; 19 | String[] names = input.keySet().toArray(new String[input.size()]); 20 | lastRegion = names[names.length - 1]; 21 | exceptLastTerritory = new ArrayList(input.get(lastRegion)); 22 | lastTerritory = exceptLastTerritory.remove(exceptLastTerritory.size() - 1); 23 | } 24 | 25 | Map> allButLastTerritory() { 26 | Map> result = new LinkedHashMap>(all); 27 | if (exceptLastTerritory.isEmpty()) { 28 | result.remove(lastRegion); 29 | } else { 30 | result.put(lastRegion, exceptLastTerritory); 31 | } 32 | return result; 33 | } 34 | 35 | Map> onlyLastTerritory() { 36 | Map> result = new LinkedHashMap>(1); 37 | result.put(lastRegion, Arrays.asList(lastTerritory)); 38 | return result; 39 | } 40 | 41 | Map> plusLastTerritory(Map> input) { 42 | Map> result = new LinkedHashMap>(input); 43 | if (result.containsKey(lastRegion)) { 44 | List moreTerritories = new ArrayList(result.get(lastRegion)); 45 | moreTerritories.add(lastTerritory); 46 | result.put(lastRegion, moreTerritories); 47 | } else { 48 | result.put(lastRegion, Arrays.asList(lastTerritory)); 49 | } 50 | return result; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /denominator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/denominator/c565e3b8c6043051252e0947029511f9ac5d306f/denominator.jpg -------------------------------------------------------------------------------- /denominator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/denominator/c565e3b8c6043051252e0947029511f9ac5d306f/denominator.png -------------------------------------------------------------------------------- /designate/README.md: -------------------------------------------------------------------------------- 1 | ## Notable Behaviors 2 | The following are notable when compared to different providers. 3 | * `Zone.id()` is opaque. 4 | * `Zone.ttl()` is the default for new records. 5 | * `ZoneApi.iterateByName()` is a client-side filter. 6 | * Each zone has provider-specific NS records that aren't visible to the api. 7 | -------------------------------------------------------------------------------- /designate/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | sourceCompatibility = 1.6 4 | 5 | test { 6 | systemProperty 'designate.url', System.getProperty('designate.url', '') 7 | systemProperty 'designate.tenantId', System.getProperty('designate.tenantId', '') 8 | systemProperty 'designate.username', System.getProperty('designate.username', '') 9 | systemProperty 'designate.password', System.getProperty('designate.password', '') 10 | systemProperty 'designate.zone', System.getProperty('designate.zone', '') 11 | } 12 | 13 | dependencies { 14 | compile project(':denominator-core') 15 | compile 'com.netflix.feign:feign-core:8.10.0' 16 | compile 'com.netflix.feign:feign-gson:8.10.0' 17 | testCompile project(':denominator-model').sourceSets.test.output 18 | testCompile project(':denominator-core').sourceSets.test.output 19 | testCompile 'junit:junit:4.12' 20 | testCompile 'org.assertj:assertj-core:1.7.1' // last version supporting JDK 7 21 | testCompile 'com.squareup.okhttp:mockwebserver:2.5.0' 22 | } 23 | -------------------------------------------------------------------------------- /designate/src/main/java/denominator/designate/Designate.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import denominator.model.Zone; 7 | import feign.Body; 8 | import feign.Headers; 9 | import feign.Param; 10 | import feign.RequestLine; 11 | 12 | // http://designate.readthedocs.org/en/latest/rest.html#v1-api 13 | public interface Designate { 14 | 15 | @RequestLine("GET /limits") 16 | Map limits(); 17 | 18 | @RequestLine("GET /domains") 19 | List domains(); 20 | 21 | @RequestLine("POST /domains") 22 | @Body("%7B\"name\":\"{name}\",\"ttl\":{ttl},\"email\":\"{email}\"%7D") 23 | @Headers("Content-Type: application/json") 24 | Zone createDomain(@Param("name") String name, @Param("email") String email, 25 | @Param("ttl") int ttl); 26 | 27 | @RequestLine("PUT /domains/{id}") 28 | @Body("%7B\"id\":\"{id}\",\"name\":\"{name}\",\"ttl\":{ttl},\"email\":\"{email}\"%7D") 29 | @Headers("Content-Type: application/json") 30 | Zone updateDomain(@Param("id") String id, @Param("name") String name, 31 | @Param("email") String email, @Param("ttl") int ttl); 32 | 33 | @RequestLine("DELETE /domains/{domainId}") 34 | void deleteDomain(@Param("domainId") String domainId); 35 | 36 | @RequestLine("GET /domains/{domainId}/records") 37 | List records(@Param("domainId") String domainId); 38 | 39 | @RequestLine("POST /domains/{domainId}/records") 40 | @Headers("Content-Type: application/json") 41 | Record createRecord(@Param("domainId") String domainId, Record record); 42 | 43 | @RequestLine("PUT /domains/{domainId}/records/{recordId}") 44 | @Headers("Content-Type: application/json") 45 | Record updateRecord(@Param("domainId") String domainId, @Param("recordId") String recordId, 46 | Record record); 47 | 48 | @RequestLine("DELETE /domains/{domainId}/records/{recordId}") 49 | void deleteRecord(@Param("domainId") String domainId, @Param("recordId") String recordId); 50 | 51 | class Record { 52 | 53 | String id; 54 | String name; 55 | String type; 56 | Integer ttl; 57 | String data; 58 | Integer priority; 59 | 60 | // toString ordering 61 | @Override 62 | public String toString() { 63 | return new StringBuilder(name).append(type).append(ttl).append(data).append(priority) 64 | .toString(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /designate/src/main/java/denominator/designate/DesignateFunctions.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import denominator.designate.Designate.Record; 7 | import denominator.model.rdata.AAAAData; 8 | import denominator.model.rdata.AData; 9 | import denominator.model.rdata.CNAMEData; 10 | import denominator.model.rdata.MXData; 11 | import denominator.model.rdata.NSData; 12 | import denominator.model.rdata.SRVData; 13 | import denominator.model.rdata.TXTData; 14 | 15 | import static denominator.common.Util.split; 16 | 17 | public final class DesignateFunctions { 18 | 19 | private DesignateFunctions() { /* */ 20 | } 21 | 22 | static Map toRDataMap(Record record) { 23 | if ("A".equals(record.type)) { 24 | return AData.create(record.data); 25 | } else if ("AAAA".equals(record.type)) { 26 | return AAAAData.create(record.data); 27 | } else if ("CNAME".equals(record.type)) { 28 | return CNAMEData.create(record.data); 29 | } else if ("MX".equals(record.type)) { 30 | return MXData.create(record.priority, record.data); 31 | } else if ("NS".equals(record.type)) { 32 | return NSData.create(record.data); 33 | } else if ("SRV".equals(record.type)) { 34 | List rdata = split(' ', record.data); 35 | return SRVData.builder() 36 | .priority(record.priority) 37 | .weight(Integer.valueOf(rdata.get(0))) 38 | .port(Integer.valueOf(rdata.get(1))) 39 | .target(rdata.get(2)).build(); 40 | } else if ("TXT".equals(record.type)) { 41 | return TXTData.create(record.data); 42 | } else { 43 | throw new UnsupportedOperationException("record type not yet supported" + record); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /designate/src/main/java/denominator/designate/DesignateTarget.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import javax.inject.Inject; 4 | 5 | import denominator.Provider; 6 | import denominator.designate.KeystoneV2.TokenIdAndPublicURL; 7 | import feign.Request; 8 | import feign.RequestTemplate; 9 | import feign.Target; 10 | 11 | class DesignateTarget implements Target { 12 | 13 | private final Provider provider; 14 | private final InvalidatableAuthProvider lazyUrlAndToken; 15 | 16 | @Inject 17 | DesignateTarget(Provider provider, InvalidatableAuthProvider lazyUrlAndToken) { 18 | this.provider = provider; 19 | this.lazyUrlAndToken = lazyUrlAndToken; 20 | } 21 | 22 | @Override 23 | public Class type() { 24 | return Designate.class; 25 | } 26 | 27 | @Override 28 | public String name() { 29 | return provider.name(); 30 | } 31 | 32 | @Override 33 | public String url() { 34 | return lazyUrlAndToken.get().publicURL; 35 | } 36 | 37 | @Override 38 | public Request apply(RequestTemplate input) { 39 | TokenIdAndPublicURL urlAndToken = lazyUrlAndToken.get(); 40 | if (input.url().indexOf("http") != 0) { 41 | input.insert(0, urlAndToken.publicURL); 42 | } 43 | input.header("X-Auth-Token", urlAndToken.tokenId); 44 | return input.request(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /designate/src/main/java/denominator/designate/DesignateZoneApi.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import java.util.Iterator; 4 | 5 | import javax.inject.Inject; 6 | 7 | import denominator.model.Zone; 8 | import feign.FeignException; 9 | 10 | import static denominator.common.Util.filter; 11 | import static denominator.model.Zones.nameEqualTo; 12 | 13 | class DesignateZoneApi implements denominator.ZoneApi { 14 | 15 | private final Designate api; 16 | 17 | @Inject 18 | DesignateZoneApi(Designate api) { 19 | this.api = api; 20 | } 21 | 22 | @Override 23 | public Iterator iterator() { 24 | return api.domains().iterator(); 25 | } 26 | 27 | /** 28 | * Designate V1 does not have a filter by name api. 29 | */ 30 | @Override 31 | public Iterator iterateByName(String name) { 32 | return filter(iterator(), nameEqualTo(name)); 33 | } 34 | 35 | @Override 36 | public String put(Zone zone) { 37 | if (zone.id() != null) { 38 | return api.updateDomain(zone.id(), zone.name(), zone.email(), zone.ttl()).id(); 39 | } 40 | try { 41 | return api.createDomain(zone.name(), zone.email(), zone.ttl()).id(); 42 | } catch (FeignException e) { 43 | if (e.getMessage().indexOf(" 409 ") == -1) { 44 | throw e; 45 | } 46 | String id = iterateByName(zone.name()).next().id(); 47 | return api.updateDomain(id, zone.name(), zone.email(), zone.ttl()).id(); 48 | } 49 | } 50 | 51 | @Override 52 | public void delete(String id) { 53 | try { 54 | api.deleteDomain(id); 55 | } catch (FeignException e) { 56 | if (e.getMessage().indexOf(" 404 ") == -1) { 57 | throw e; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /designate/src/main/java/denominator/designate/GroupByRecordNameAndTypeIterator.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import java.util.Iterator; 4 | import java.util.Map; 5 | 6 | import denominator.common.PeekingIterator; 7 | import denominator.designate.Designate.Record; 8 | import denominator.model.ResourceRecordSet; 9 | import denominator.model.ResourceRecordSet.Builder; 10 | 11 | import static denominator.common.Util.peekingIterator; 12 | import static denominator.designate.DesignateFunctions.toRDataMap; 13 | 14 | class GroupByRecordNameAndTypeIterator implements Iterator> { 15 | 16 | private final PeekingIterator peekingIterator; 17 | 18 | public GroupByRecordNameAndTypeIterator(Iterator sortedIterator) { 19 | this.peekingIterator = peekingIterator(sortedIterator); 20 | } 21 | 22 | private static boolean nameAndTypeEquals(Record actual, Record expected) { 23 | return actual.name.equals(expected.name) && actual.type.equals(expected.type); 24 | } 25 | 26 | @Override 27 | public boolean hasNext() { 28 | return peekingIterator.hasNext(); 29 | } 30 | 31 | @Override 32 | public ResourceRecordSet next() { 33 | Record recordDetail = peekingIterator.next(); 34 | Builder> builder = ResourceRecordSet.builder() 35 | .name(recordDetail.name) 36 | .type(recordDetail.type) 37 | .ttl(recordDetail.ttl) 38 | .add(toRDataMap(recordDetail)); 39 | while (hasNext()) { 40 | Record next = peekingIterator.peek(); 41 | if (next == null) { 42 | continue; 43 | } 44 | if (nameAndTypeEquals(next, recordDetail)) { 45 | builder.add(toRDataMap(peekingIterator.next())); 46 | } else { 47 | break; 48 | } 49 | } 50 | return builder.build(); 51 | } 52 | 53 | @Override 54 | public void remove() { 55 | throw new UnsupportedOperationException(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /designate/src/main/java/denominator/designate/KeystoneV2.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import java.net.URI; 4 | 5 | import feign.Body; 6 | import feign.Headers; 7 | import feign.Param; 8 | import feign.RequestLine; 9 | 10 | @Headers("Content-Type: application/json") 11 | interface KeystoneV2 { 12 | 13 | @RequestLine("POST /tokens") 14 | @Body("%7B\"auth\":%7B\"passwordCredentials\":%7B\"username\":\"{username}\",\"password\":\"{password}\"%7D,\"tenantId\":\"{tenantId}\"%7D%7D") 15 | TokenIdAndPublicURL passwordAuth(URI endpoint, @Param("tenantId") String tenantId, 16 | @Param("username") String username, 17 | @Param("password") String password); 18 | 19 | static class TokenIdAndPublicURL { 20 | String tokenId; 21 | String publicURL; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /designate/src/main/java/denominator/designate/KeystoneV2AccessAdapter.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonIOException; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonParser; 7 | import com.google.gson.TypeAdapter; 8 | import com.google.gson.stream.JsonReader; 9 | import com.google.gson.stream.JsonWriter; 10 | 11 | import java.io.IOException; 12 | 13 | import denominator.designate.KeystoneV2.TokenIdAndPublicURL; 14 | 15 | class KeystoneV2AccessAdapter extends TypeAdapter { 16 | 17 | private final String serviceTypeSuffix = ":dns"; 18 | 19 | static boolean isNull(JsonElement element) { 20 | return element == null || element.isJsonNull(); 21 | } 22 | 23 | @Override 24 | public TokenIdAndPublicURL read(JsonReader in) throws IOException { 25 | JsonObject access; 26 | try { 27 | access = new JsonParser().parse(in).getAsJsonObject().get("access").getAsJsonObject(); 28 | } catch (JsonIOException e) { 29 | if (e.getCause() != null && e.getCause() instanceof IOException) { 30 | throw IOException.class.cast(e.getCause()); 31 | } 32 | throw e; 33 | } 34 | JsonElement tokenField = access.get("token"); 35 | if (isNull(tokenField)) { 36 | return null; 37 | } 38 | JsonElement idField = tokenField.getAsJsonObject().get("id"); 39 | if (isNull(idField)) { 40 | return null; 41 | } 42 | 43 | TokenIdAndPublicURL tokenUrl = new TokenIdAndPublicURL(); 44 | tokenUrl.tokenId = idField.getAsString(); 45 | 46 | for (JsonElement s : access.get("serviceCatalog").getAsJsonArray()) { 47 | JsonObject service = s.getAsJsonObject(); 48 | JsonElement typeField = service.get("type"); 49 | JsonElement endpointsField = service.get("endpoints"); 50 | if (!isNull(typeField) && !isNull(endpointsField) && typeField.getAsString() 51 | .endsWith(serviceTypeSuffix)) { 52 | for (JsonElement e : endpointsField.getAsJsonArray()) { 53 | JsonObject endpoint = e.getAsJsonObject(); 54 | tokenUrl.publicURL = endpoint.get("publicURL").getAsString(); 55 | if (tokenUrl.publicURL.endsWith("/")) { 56 | tokenUrl.publicURL = tokenUrl.publicURL.substring(0, tokenUrl.publicURL.length() - 1); 57 | } 58 | } 59 | } 60 | } 61 | return tokenUrl; 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "KeystoneV2AccessAdapter(" + serviceTypeSuffix + ")"; 67 | } 68 | 69 | @Override 70 | public void write(JsonWriter out, TokenIdAndPublicURL value) throws IOException { 71 | throw new UnsupportedOperationException(); 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /designate/src/main/java/denominator/designate/LimitsReadable.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import javax.inject.Inject; 4 | 5 | import denominator.CheckConnection; 6 | 7 | class LimitsReadable implements CheckConnection { 8 | 9 | private final Designate api; 10 | 11 | @Inject 12 | LimitsReadable(Designate api) { 13 | this.api = api; 14 | } 15 | 16 | @Override 17 | public boolean ok() { 18 | try { 19 | return api.limits() != null; 20 | } catch (RuntimeException e) { 21 | return false; 22 | } 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "LimitsReadable"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /designate/src/main/resources/META-INF/services/denominator.Provider: -------------------------------------------------------------------------------- 1 | denominator.designate.DesignateProvider 2 | -------------------------------------------------------------------------------- /designate/src/test/java/denominator/designate/DesignateCheckConnectionLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import denominator.CheckConnectionLiveTest; 4 | import denominator.Live.UseTestGraph; 5 | 6 | @UseTestGraph(DesignateTestGraph.class) 7 | public class DesignateCheckConnectionLiveTest extends CheckConnectionLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /designate/src/test/java/denominator/designate/DesignateFunctionsTest.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import org.junit.Test; 4 | 5 | import denominator.designate.Designate.Record; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | 9 | public class DesignateFunctionsTest { 10 | 11 | @Test 12 | public void transformsNSRecordSet() { 13 | Record input = new Record(); 14 | input.name = "denominator.io"; 15 | input.type = "NS"; 16 | input.ttl = 3600; 17 | input.data = "dns1.stabletransit.com"; 18 | 19 | assertThat(DesignateFunctions.toRDataMap(input)) 20 | .containsEntry("nsdname", "dns1.stabletransit.com"); 21 | } 22 | 23 | @Test 24 | public void transformsTXTRecordSet() { 25 | Record input = new Record(); 26 | input.name = "denominator.io"; 27 | input.type = "TXT"; 28 | input.ttl = 3600; 29 | input.data = "Hello DNS"; 30 | 31 | assertThat(DesignateFunctions.toRDataMap(input)) 32 | .containsEntry("txtdata", "Hello DNS"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /designate/src/test/java/denominator/designate/DesignateReadOnlyLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.ReadOnlyLiveTest; 5 | 6 | @UseTestGraph(DesignateTestGraph.class) 7 | public class DesignateReadOnlyLiveTest extends ReadOnlyLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /designate/src/test/java/denominator/designate/DesignateRoundRobinWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.RoundRobinWriteCommandsLiveTest; 5 | 6 | @UseTestGraph(DesignateTestGraph.class) 7 | public class DesignateRoundRobinWriteCommandsLiveTest extends RoundRobinWriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /designate/src/test/java/denominator/designate/DesignateTestGraph.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import denominator.DNSApiManagerFactory; 4 | import denominator.TestGraph; 5 | 6 | import static feign.Util.emptyToNull; 7 | import static java.lang.System.getProperty; 8 | 9 | public class DesignateTestGraph extends TestGraph { 10 | 11 | private static final String url = emptyToNull(getProperty("designate.url")); 12 | private static final String zone = emptyToNull(getProperty("designate.zone")); 13 | 14 | public DesignateTestGraph() { 15 | super(DNSApiManagerFactory.create(new DesignateProvider(url)), zone); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /designate/src/test/java/denominator/designate/DesignateWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.WriteCommandsLiveTest; 5 | 6 | @UseTestGraph(DesignateTestGraph.class) 7 | public class DesignateWriteCommandsLiveTest extends WriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /designate/src/test/java/denominator/designate/DesignateZoneWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.ZoneWriteCommandsLiveTest; 5 | 6 | @UseTestGraph(DesignateTestGraph.class) 7 | public class DesignateZoneWriteCommandsLiveTest extends ZoneWriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /designate/src/test/java/denominator/designate/LimitsReadableMockTest.java: -------------------------------------------------------------------------------- 1 | package denominator.designate; 2 | 3 | import com.squareup.okhttp.mockwebserver.MockResponse; 4 | 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | 8 | import denominator.DNSApiManager; 9 | 10 | import static org.junit.Assert.assertFalse; 11 | import static org.junit.Assert.assertTrue; 12 | 13 | public class LimitsReadableMockTest { 14 | 15 | @Rule 16 | public MockDesignateServer server = new MockDesignateServer(); 17 | 18 | String limitsResponse = "{\n" 19 | + " \"limits\": {\n" 20 | + " \"absolute\": {\n" 21 | + " \"maxDomains\": 20,\n" 22 | + " \"maxDomainRecords\": 5000\n" 23 | + " }\n" 24 | + " }\n" 25 | + "}"; 26 | 27 | @Test 28 | public void implicitlyStartsSessionWhichIsReusedForLaterRequests() throws Exception { 29 | server.enqueueAuthResponse(); 30 | server.enqueue(new MockResponse().setBody("{ \"domains\": [] }")); 31 | server.enqueue(new MockResponse().setBody(limitsResponse)); 32 | server.enqueue(new MockResponse().setBody(limitsResponse)); 33 | 34 | DNSApiManager api = server.connect(); 35 | assertTrue(api.checkConnection()); 36 | assertTrue(api.checkConnection()); 37 | 38 | server.assertAuthRequest(); 39 | server.assertRequest().hasPath("/v1/limits"); 40 | server.assertRequest().hasPath("/v1/limits"); 41 | } 42 | 43 | @Test 44 | public void singleRequestOnFailure() throws Exception { 45 | server.enqueueAuthResponse(); 46 | server.enqueue(new MockResponse().setResponseCode(401)); 47 | 48 | DNSApiManager api = server.connect(); 49 | assertFalse(api.checkConnection()); 50 | 51 | server.assertAuthRequest(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /dynect/README.md: -------------------------------------------------------------------------------- 1 | ## Notable Behaviors 2 | The following are notable when compared to different providers. 3 | * `Zone.id()` is the `Zone.name()` 4 | * Zone lists are 1 + N requests in order to zip with the SOA's ttl and rname. 5 | * `Zone.ttl()` is the default for new records. 6 | * The Zone's NS record set includes 4 Primary Service Records. These cannot be removed, so `put` requests will silently retain them. 7 | -------------------------------------------------------------------------------- /dynect/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | sourceCompatibility = 1.6 4 | 5 | test { 6 | systemProperty 'dynect.url', System.getProperty('dynect.url', '') 7 | systemProperty 'dynect.customer', System.getProperty('dynect.customer', '') 8 | systemProperty 'dynect.username', System.getProperty('dynect.username', '') 9 | systemProperty 'dynect.password', System.getProperty('dynect.password', '') 10 | systemProperty 'dynect.zone', System.getProperty('dynect.zone', '') 11 | } 12 | 13 | dependencies { 14 | compile project(':denominator-core') 15 | compile 'com.netflix.feign:feign-core:8.10.0' 16 | compile 'com.netflix.feign:feign-gson:8.10.0' 17 | testCompile project(':denominator-model').sourceSets.test.output 18 | testCompile project(':denominator-core').sourceSets.test.output 19 | testCompile 'junit:junit:4.12' 20 | testCompile 'org.assertj:assertj-core:1.7.1' // last version supporting JDK 7 21 | testCompile 'com.squareup.okhttp:mockwebserver:2.5.0' 22 | } 23 | -------------------------------------------------------------------------------- /dynect/src/main/java/denominator/dynect/DynECTException.java: -------------------------------------------------------------------------------- 1 | package denominator.dynect; 2 | 3 | import java.util.List; 4 | 5 | import feign.FeignException; 6 | 7 | import static java.lang.String.format; 8 | 9 | public class DynECTException extends FeignException { 10 | 11 | private static final long serialVersionUID = 1L; 12 | private final String status; 13 | private final List messages; 14 | 15 | DynECTException(String status, List messages) { 16 | super(format("status %s: %s", status, messages)); 17 | this.status = status; 18 | this.messages = messages; 19 | } 20 | 21 | /** 22 | * For example: {@code running}. 23 | */ 24 | public String status() { 25 | return status; 26 | } 27 | 28 | /** 29 | * Messages corresponding to the changes. 30 | */ 31 | public List messages() { 32 | return messages; 33 | } 34 | 35 | public static class Message { 36 | 37 | String code; 38 | String info; 39 | 40 | /** 41 | * nullable 42 | */ 43 | public String code() { 44 | return code; 45 | } 46 | 47 | public String info() { 48 | return info; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | if (code != null) { 54 | return code + ": " + info; 55 | } 56 | return info; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /dynect/src/main/java/denominator/dynect/DynECTTarget.java: -------------------------------------------------------------------------------- 1 | package denominator.dynect; 2 | 3 | import javax.inject.Inject; 4 | 5 | import denominator.Provider; 6 | import feign.Request; 7 | import feign.RequestTemplate; 8 | import feign.Target; 9 | 10 | class DynECTTarget implements Target { 11 | 12 | private final Provider provider; 13 | private final InvalidatableTokenProvider lazyToken; 14 | 15 | @Inject 16 | DynECTTarget(Provider provider, InvalidatableTokenProvider lazyToken) { 17 | this.provider = provider; 18 | this.lazyToken = lazyToken; 19 | } 20 | 21 | @Override 22 | public Class type() { 23 | return DynECT.class; 24 | } 25 | 26 | @Override 27 | public String name() { 28 | return provider.name(); 29 | } 30 | 31 | @Override 32 | public String url() { 33 | return provider.url(); 34 | } 35 | 36 | @Override 37 | public Request apply(RequestTemplate input) { 38 | input.header("Auth-Token", lazyToken.get()); 39 | input.insert(0, url()); 40 | return input.request(); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /dynect/src/main/java/denominator/dynect/DynECTZoneApi.java: -------------------------------------------------------------------------------- 1 | package denominator.dynect; 2 | 3 | import java.util.Iterator; 4 | 5 | import javax.inject.Inject; 6 | 7 | import denominator.dynect.DynECT.Record; 8 | import denominator.model.Zone; 9 | import denominator.model.rdata.SOAData; 10 | 11 | import static denominator.common.Preconditions.checkState; 12 | import static denominator.common.Util.singletonIterator; 13 | 14 | public final class DynECTZoneApi implements denominator.ZoneApi { 15 | 16 | private final DynECT api; 17 | 18 | @Inject 19 | DynECTZoneApi(DynECT api) { 20 | this.api = api; 21 | } 22 | 23 | @Override 24 | public Iterator iterator() { 25 | final Iterator delegate = api.zones().data.iterator(); 26 | return new Iterator() { 27 | @Override 28 | public boolean hasNext() { 29 | return delegate.hasNext(); 30 | } 31 | 32 | @Override 33 | public Zone next() { 34 | return fromSOA(delegate.next()); 35 | } 36 | 37 | @Override 38 | public void remove() { 39 | throw new UnsupportedOperationException(); 40 | } 41 | }; 42 | } 43 | 44 | @Override 45 | public Iterator iterateByName(String name) { 46 | Zone zone = null; 47 | try { 48 | zone = fromSOA(name); 49 | } catch (DynECTException e) { 50 | if (e.getMessage().indexOf("No such zone") == -1) { 51 | throw e; 52 | } 53 | } 54 | return singletonIterator(zone); 55 | } 56 | 57 | @Override 58 | public String put(Zone zone) { 59 | try { 60 | api.createZone(zone.name(), zone.ttl(), zone.email()); 61 | } catch (DynECTException e) { 62 | if (e.getMessage().indexOf("already exists") == -1) { 63 | throw e; 64 | } 65 | long soaId = getSOA(zone.name()).id; 66 | api.scheduleUpdateSOA(zone.name(), soaId, zone.ttl(), zone.email()); 67 | } 68 | api.publish(zone.name()); 69 | return zone.name(); 70 | } 71 | 72 | @Override 73 | public void delete(String name) { 74 | try { 75 | api.deleteZone(name); 76 | } catch (DynECTException e) { 77 | if (e.getMessage().indexOf("No such zone") == -1) { 78 | throw e; 79 | } 80 | } 81 | } 82 | 83 | private Zone fromSOA(String name) { 84 | Record soa = getSOA(name); 85 | SOAData soaData = (SOAData) soa.rdata; 86 | return Zone.create(name, name, soa.ttl, soaData.rname()); 87 | } 88 | 89 | private Record getSOA(String name) { 90 | Iterator soa = api.recordsInZoneByNameAndType(name, name, "SOA").data; 91 | checkState(soa.hasNext(), "SOA record for zone %s was not present", name); 92 | return soa.next(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /dynect/src/main/java/denominator/dynect/SessionTarget.java: -------------------------------------------------------------------------------- 1 | package denominator.dynect; 2 | 3 | import javax.inject.Inject; 4 | 5 | import denominator.Provider; 6 | import denominator.dynect.InvalidatableTokenProvider.Session; 7 | import feign.Request; 8 | import feign.RequestTemplate; 9 | import feign.Target; 10 | 11 | class SessionTarget implements Target { 12 | 13 | private final Provider provider; 14 | 15 | @Inject 16 | SessionTarget(Provider provider) { 17 | this.provider = provider; 18 | } 19 | 20 | @Override 21 | public Class type() { 22 | return Session.class; 23 | } 24 | 25 | @Override 26 | public String name() { 27 | return provider.name(); 28 | } 29 | 30 | @Override 31 | public String url() { 32 | return provider.url(); 33 | } 34 | 35 | @Override 36 | public Request apply(RequestTemplate input) { 37 | input.header("API-Version", "3.5.10"); 38 | input.header("Content-Type", "application/json"); 39 | input.insert(0, url()); 40 | return input.request(); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /dynect/src/main/resources/META-INF/services/denominator.Provider: -------------------------------------------------------------------------------- 1 | denominator.dynect.DynECTProvider 2 | -------------------------------------------------------------------------------- /dynect/src/test/java/denominator/dynect/DynECTCheckConnectionLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.dynect; 2 | 3 | import denominator.CheckConnectionLiveTest; 4 | import denominator.Live.UseTestGraph; 5 | 6 | @UseTestGraph(DynECTTestGraph.class) 7 | public class DynECTCheckConnectionLiveTest extends CheckConnectionLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /dynect/src/test/java/denominator/dynect/DynECTGeoReadOnlyLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.dynect; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.profile.GeoReadOnlyLiveTest; 5 | 6 | @UseTestGraph(DynECTTestGraph.class) 7 | public class DynECTGeoReadOnlyLiveTest extends GeoReadOnlyLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /dynect/src/test/java/denominator/dynect/DynECTReadOnlyLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.dynect; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.ReadOnlyLiveTest; 5 | 6 | @UseTestGraph(DynECTTestGraph.class) 7 | public class DynECTReadOnlyLiveTest extends ReadOnlyLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /dynect/src/test/java/denominator/dynect/DynECTRoundRobinWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.dynect; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.RoundRobinWriteCommandsLiveTest; 5 | 6 | @UseTestGraph(DynECTTestGraph.class) 7 | public class DynECTRoundRobinWriteCommandsLiveTest extends RoundRobinWriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /dynect/src/test/java/denominator/dynect/DynECTTestGraph.java: -------------------------------------------------------------------------------- 1 | package denominator.dynect; 2 | 3 | import denominator.DNSApiManagerFactory; 4 | 5 | import static feign.Util.emptyToNull; 6 | import static java.lang.System.getProperty; 7 | 8 | public class DynECTTestGraph extends denominator.TestGraph { 9 | 10 | private static final String url = emptyToNull(getProperty("dynect.url")); 11 | private static final String zone = emptyToNull(getProperty("dynect.zone")); 12 | 13 | public DynECTTestGraph() { 14 | super(DNSApiManagerFactory.create(new DynECTProvider(url)), zone); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /dynect/src/test/java/denominator/dynect/DynECTWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.dynect; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.WriteCommandsLiveTest; 5 | 6 | @UseTestGraph(DynECTTestGraph.class) 7 | public class DynECTWriteCommandsLiveTest extends WriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /dynect/src/test/java/denominator/dynect/DynECTZoneWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.dynect; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.ZoneWriteCommandsLiveTest; 5 | 6 | @UseTestGraph(DynECTTestGraph.class) 7 | public class DynECTZoneWriteCommandsLiveTest extends ZoneWriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /dynect/src/test/java/denominator/dynect/InvalidatableTokenProviderMockTest.java: -------------------------------------------------------------------------------- 1 | package denominator.dynect; 2 | 3 | import com.squareup.okhttp.mockwebserver.MockResponse; 4 | 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | 8 | import denominator.DNSApiManager; 9 | 10 | import static denominator.dynect.DynECTProviderDynamicUpdateMockTest.sessionValid; 11 | import static org.junit.Assert.assertFalse; 12 | import static org.junit.Assert.assertTrue; 13 | 14 | public class InvalidatableTokenProviderMockTest { 15 | 16 | @Rule 17 | public MockDynECTServer server = new MockDynECTServer(); 18 | 19 | @Test 20 | public void successThenFailure() throws Exception { 21 | server.enqueueSessionResponse(); 22 | server.enqueue(new MockResponse().setBody(sessionValid)); 23 | server.enqueue(new MockResponse().setBody(sessionValid)); 24 | server.enqueue(new MockResponse().setResponseCode(400).setBody( 25 | "{\"status\": \"failure\", \"data\": {}, \"job_id\": 427275274, \"msgs\": [{\"INFO\": \"login: Bad or expired credentials\", \"SOURCE\": \"BLL\", \"ERR_CD\": \"INVALID_DATA\", \"LVL\": \"ERROR\"}, {\"INFO\": \"login: There was a problem with your credentials\", \"SOURCE\": \"BLL\", \"ERR_CD\": null, \"LVL\": \"INFO\"}]}")); 26 | 27 | DNSApiManager api = server.connect(); 28 | 29 | assertTrue(api.checkConnection()); 30 | assertTrue(api.checkConnection()); 31 | assertFalse(api.checkConnection()); 32 | 33 | server.assertSessionRequest(); 34 | server.assertRequest().hasMethod("GET").hasPath("/Session"); 35 | server.assertRequest().hasMethod("GET").hasPath("/Session"); 36 | } 37 | 38 | @Test 39 | public void singleRequestOnFailure() throws Exception { 40 | server.enqueue(new MockResponse().setResponseCode(401)); 41 | 42 | assertFalse(server.connect().checkConnection()); 43 | 44 | server.assertSessionRequest(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /dynect/src/test/resources/recordsByName.json: -------------------------------------------------------------------------------- 1 | {"status": "success", 2 | "data": { 3 | "cname_records": [], 4 | "cert_records": [], 5 | "dname_records": [], 6 | "aaaa_records": [], 7 | "ipseckey_records": [], 8 | "loc_records": [], 9 | "ptr_records": [], 10 | "soa_records": [], 11 | "kx_records": [], 12 | "dnskey_records": [], 13 | "naptr_records": [], 14 | "rp_records": [], 15 | "mx_records": [], 16 | "key_records": [], 17 | "px_records": [], 18 | "ds_records": [], 19 | "sshfp_records": [], 20 | "ns_records": [], 21 | "dhcid_records": [], 22 | "srv_records": [], 23 | "nsap_records": [], 24 | "txt_records": [], 25 | "spf_records": [], 26 | "a_records": [{ 27 | "zone": "denominator.io", 28 | "ttl": 3600, 29 | "fqdn": "www.denominator.io", 30 | "record_type": "A", 31 | "rdata": { 32 | "address": "192.0.2.1" 33 | }, 34 | "record_id": 1 35 | }, { 36 | "zone": "denominator.io", 37 | "ttl": 3600, 38 | "fqdn": "www.denominator.io", 39 | "record_type": "A", 40 | "rdata": { 41 | "address": "198.51.100.1" 42 | }, 43 | "record_id": 2 44 | }] 45 | }, 46 | "job_id": 371281087, 47 | "msgs": [{ 48 | "INFO": "get_tree: Here is your zone tree", 49 | "SOURCE": "BLL", 50 | "ERR_CD": null, 51 | "LVL": "INFO" 52 | } 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /example-android/README.md: -------------------------------------------------------------------------------- 1 | # Denominator Android Example 2 | 3 | ## Setup 4 | You will need to use either [Android Studio](http://developer.android.com/sdk/installing/studio.html) or the command-line tools to install this. The easiest way to get started is by using homebrew to install the android sdk. 5 | 1. `brew install android` 6 | 2. `android update sdk --no-ui` 7 | 3. `export ANDROID_HOME=/usr/local/opt/android-sdk` 8 | 4. `echo sdk.dir=$ANDROID_HOME >>local.properties` 9 | 10 | ## Build 11 | `gradle clean assemble` to build the android package. 12 | 13 | ## Install on your device 14 | Use adb to install a new copy of the example onto your connected device. 15 | ``` 16 | adb install -r examples/denominator-example-android/build/apk/denominator-example-android-debug-unaligned.apk 17 | ``` 18 | 19 | ## Running 20 | If the configured provider has credentials, you can enter them via the menu button. On refresh, a zone list should be emitted. You can use `adb logcat` to view any stack traces as necessary. 21 | -------------------------------------------------------------------------------- /example-android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:1.3.0' 8 | } 9 | } 10 | 11 | apply plugin: 'android' 12 | 13 | android { 14 | compileSdkVersion 19 15 | buildToolsVersion "19.1" 16 | 17 | // service loader not used as provider is explicitly selected 18 | packagingOptions { 19 | exclude 'META-INF/services/denominator.Provider' 20 | } 21 | 22 | defaultConfig { 23 | minSdkVersion 9 24 | targetSdkVersion 19 25 | } 26 | } 27 | 28 | repositories { mavenLocal() 29 | mavenCentral() } 30 | 31 | dependencies { 32 | compile 'com.android.support:support-v4:19.0.0' 33 | compile 'com.netflix.denominator:denominator-core:4.6.0' 34 | // swap this out to use a different provider 35 | compile 'com.netflix.denominator:denominator-ultradns:4.6.0' 36 | // TODO: find a way to have this in compile classpath, but not in apk 37 | compile 'com.squareup.dagger:dagger-compiler:1.2.2' 38 | compile 'com.squareup:tape:1.2.3' 39 | compile 'com.squareup:otto:1.3.8' 40 | } 41 | -------------------------------------------------------------------------------- /example-android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 18 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /example-android/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/denominator/c565e3b8c6043051252e0947029511f9ac5d306f/example-android/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /example-android/src/main/java/denominator/example/android/ui/PreferencesActivity.java: -------------------------------------------------------------------------------- 1 | package denominator.example.android.ui; 2 | 3 | import android.os.Bundle; 4 | import android.preference.EditTextPreference; 5 | import android.preference.PreferenceActivity; 6 | import android.preference.PreferenceScreen; 7 | 8 | import javax.inject.Inject; 9 | 10 | import denominator.Provider; 11 | import denominator.example.android.DenominatorApplication; 12 | 13 | public class PreferencesActivity extends PreferenceActivity { 14 | 15 | @Inject 16 | Provider provider; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | DenominatorApplication.class.cast(getApplication()).getApplicationGraph().inject(this); 22 | // to support api level 9 23 | this.setPreferenceScreen(createFromProvider()); 24 | } 25 | 26 | private PreferenceScreen createFromProvider() { 27 | PreferenceScreen root = getPreferenceManager().createPreferenceScreen(this); 28 | if (provider.credentialTypeToParameterNames().isEmpty()) { 29 | return root; 30 | } 31 | String credentialType = provider.credentialTypeToParameterNames().keySet().iterator().next(); 32 | root.setTitle(credentialType + " credentials for provider " + provider.name()); 33 | for (String parameter : provider.credentialTypeToParameterNames().get(credentialType)) { 34 | EditTextPreference cred = new EditTextPreference(this); 35 | cred.setKey(parameter); 36 | cred.setTitle(parameter); 37 | cred.setDialogTitle(parameter); 38 | root.addPreference(cred); 39 | } 40 | return root; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example-android/src/main/java/denominator/example/android/ui/ZoneListFragment.java: -------------------------------------------------------------------------------- 1 | package denominator.example.android.ui; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ScrollView; 10 | import android.widget.TableLayout; 11 | import android.widget.TableRow; 12 | import android.widget.TextView; 13 | 14 | import com.squareup.otto.Bus; 15 | import com.squareup.otto.Subscribe; 16 | 17 | import javax.inject.Inject; 18 | 19 | import denominator.example.android.zone.ZoneList; 20 | import denominator.model.Zone; 21 | 22 | import static android.view.Gravity.CENTER; 23 | 24 | public class ZoneListFragment extends Fragment { 25 | 26 | @Inject 27 | Activity activity; 28 | @Inject 29 | Bus bus; 30 | 31 | TableLayout zones; 32 | 33 | @Override 34 | public void onActivityCreated(Bundle savedInstanceState) { 35 | super.onActivityCreated(savedInstanceState); 36 | HomeActivity.class.cast(getActivity()).inject(this); 37 | } 38 | 39 | @Override 40 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 41 | Bundle savedInstanceState) { 42 | zones = new TableLayout(getActivity()); 43 | zones.setGravity(CENTER); 44 | ScrollView view = new ScrollView(getActivity()); 45 | view.addView(zones); 46 | return view; 47 | } 48 | 49 | @Subscribe 50 | public void onZones(ZoneList.SuccessEvent event) { 51 | zones.removeAllViews(); 52 | while (event.zones.hasNext()) { 53 | Zone zone = event.zones.next(); 54 | TableRow row = new TableRow(activity); 55 | TextView name = new TextView(activity); 56 | name.setText(zone.name()); 57 | row.addView(name); 58 | if (zone.id() != null) { 59 | TextView id = new TextView(activity); 60 | id.setText(zone.id()); 61 | row.addView(id); 62 | } 63 | zones.addView(row); 64 | } 65 | } 66 | 67 | @Override 68 | public void onResume() { 69 | super.onResume(); 70 | bus.register(this); 71 | } 72 | 73 | @Override 74 | public void onPause() { 75 | super.onPause(); 76 | bus.unregister(this); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /example-android/src/main/java/denominator/example/android/zone/ZoneList.java: -------------------------------------------------------------------------------- 1 | package denominator.example.android.zone; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import android.util.Log; 6 | 7 | import com.squareup.tape.Task; 8 | 9 | import java.util.Iterator; 10 | 11 | import javax.inject.Inject; 12 | 13 | import denominator.DNSApiManager; 14 | import denominator.model.Zone; 15 | 16 | public class ZoneList implements Task { 17 | 18 | private static final String TAG = "Denominator:ZoneList"; 19 | private static final Handler MAIN_THREAD = new Handler(Looper.getMainLooper()); 20 | private final DNSApiManager mgr; 21 | 22 | @Inject 23 | ZoneList(DNSApiManager mgr) { 24 | this.mgr = mgr; 25 | } 26 | 27 | @Override 28 | public void execute(final ZoneList.Callback callback) { 29 | new Thread(new Runnable() { 30 | @Override 31 | public void run() { 32 | Log.i(TAG, "Listing Zones in " + mgr.provider().name()); 33 | try { 34 | long start = System.currentTimeMillis(); 35 | final Iterator zones = mgr.api().zones().iterator(); 36 | final long duration = System.currentTimeMillis() - start; 37 | Log.i(TAG, "success! " + mgr.provider().name()); 38 | MAIN_THREAD.post(new Runnable() { 39 | @Override 40 | public void run() { 41 | callback.onSuccess(zones, duration); 42 | } 43 | }); 44 | } catch (final RuntimeException e) { 45 | e.printStackTrace(); 46 | MAIN_THREAD.post(new Runnable() { 47 | @Override 48 | public void run() { 49 | callback.onFailure(e); 50 | } 51 | }); 52 | } 53 | } 54 | }).start(); 55 | } 56 | 57 | public interface Callback { 58 | 59 | void onSuccess(Iterator zones, long duration); 60 | 61 | void onFailure(Throwable t); 62 | } 63 | 64 | public static class SuccessEvent { 65 | 66 | public final Iterator zones; 67 | public final long duration; 68 | 69 | SuccessEvent(Iterator zones, long duration) { 70 | this.zones = zones; 71 | this.duration = duration; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /example-android/src/main/java/denominator/example/android/zone/ZoneListModule.java: -------------------------------------------------------------------------------- 1 | package denominator.example.android.zone; 2 | 3 | import com.squareup.otto.Bus; 4 | import com.squareup.tape.InMemoryObjectQueue; 5 | import com.squareup.tape.ObjectQueue; 6 | import com.squareup.tape.TaskQueue; 7 | 8 | import javax.inject.Singleton; 9 | 10 | import dagger.Provides; 11 | 12 | @dagger.Module( 13 | injects = ZoneListTaskService.class, 14 | complete = false // application 15 | ) 16 | public class ZoneListModule { 17 | 18 | @Provides 19 | @Singleton 20 | ObjectQueue objectQueue() { 21 | return new InMemoryObjectQueue(); 22 | } 23 | 24 | @Provides 25 | @Singleton 26 | TaskQueue taskQueue(ZoneListQueue zlq) { 27 | return zlq; 28 | } 29 | 30 | @Provides 31 | @Singleton 32 | Bus bus() { 33 | return new Bus(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example-android/src/main/java/denominator/example/android/zone/ZoneListQueue.java: -------------------------------------------------------------------------------- 1 | package denominator.example.android.zone; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | import com.squareup.otto.Bus; 8 | import com.squareup.tape.ObjectQueue; 9 | 10 | import javax.inject.Inject; 11 | 12 | public class ZoneListQueue extends com.squareup.tape.TaskQueue { 13 | 14 | private final Context context; 15 | private final Bus bus; 16 | 17 | @Inject 18 | ZoneListQueue(ObjectQueue delegate, Application context, Bus bus) { 19 | super(delegate); 20 | this.context = context; 21 | this.bus = bus; 22 | bus.register(this); 23 | 24 | if (size() > 0) { 25 | startService(); 26 | } 27 | } 28 | 29 | private void startService() { 30 | context.startService(new Intent(context, ZoneListTaskService.class)); 31 | } 32 | 33 | @Override 34 | public void add(ZoneList entry) { 35 | super.add(entry); 36 | startService(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example-android/src/main/java/denominator/example/android/zone/ZoneListTaskService.java: -------------------------------------------------------------------------------- 1 | package denominator.example.android.zone; 2 | 3 | import android.app.Service; 4 | import android.content.Intent; 5 | import android.os.IBinder; 6 | import android.util.Log; 7 | 8 | import com.squareup.otto.Bus; 9 | import com.squareup.tape.TaskQueue; 10 | 11 | import java.util.Iterator; 12 | 13 | import javax.inject.Inject; 14 | 15 | import denominator.example.android.DenominatorApplication; 16 | import denominator.example.android.zone.ZoneList.Callback; 17 | import denominator.model.Zone; 18 | 19 | /** 20 | * This service guarantees that zone lists happen in the background and only once at a time. 21 | */ 22 | public class ZoneListTaskService extends Service implements Callback { 23 | 24 | private static final String TAG = "Denominator:ZoneListTaskService"; 25 | 26 | @Inject 27 | TaskQueue taskQueue; 28 | @Inject 29 | Bus bus; 30 | 31 | private boolean running; 32 | 33 | @Override 34 | public void onCreate() { 35 | super.onCreate(); 36 | DenominatorApplication.class.cast(getApplication()).getApplicationGraph().inject(this); 37 | Log.i(TAG, " starting"); 38 | } 39 | 40 | @Override 41 | public int onStartCommand(Intent intent, int flags, int startId) { 42 | executeNext(); 43 | return START_STICKY; 44 | } 45 | 46 | private void executeNext() { 47 | // no duplicates 48 | if (running) { 49 | taskQueue.remove(); 50 | } 51 | ZoneList task = taskQueue.peek(); 52 | if (task != null) { 53 | running = true; 54 | task.execute(this); 55 | } else { 56 | Log.i(TAG, " stopping"); 57 | stopSelf(); 58 | } 59 | } 60 | 61 | @Override 62 | public void onSuccess(Iterator zones, long duration) { 63 | running = false; 64 | taskQueue.remove(); 65 | bus.post(new ZoneList.SuccessEvent(zones, duration)); 66 | executeNext(); 67 | } 68 | 69 | @Override 70 | public void onFailure(Throwable t) { 71 | bus.post(t); 72 | } 73 | 74 | @Override 75 | public IBinder onBind(Intent intent) { 76 | return null; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /example-android/src/main/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/denominator/c565e3b8c6043051252e0947029511f9ac5d306f/example-android/src/main/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /example-android/src/main/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/denominator/c565e3b8c6043051252e0947029511f9ac5d306f/example-android/src/main/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /example-android/src/main/res/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/denominator/c565e3b8c6043051252e0947029511f9ac5d306f/example-android/src/main/res/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /example-android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Denominator 5 | list complete in %sms. 6 | Denominator Example: %s 7 | initialized in %sms 8 | 9 | -------------------------------------------------------------------------------- /example-daemon/src/main/java/denominator/denominatord/DenominatorDispatcher.java: -------------------------------------------------------------------------------- 1 | package denominator.denominatord; 2 | 3 | import com.squareup.okhttp.mockwebserver.Dispatcher; 4 | import com.squareup.okhttp.mockwebserver.MockResponse; 5 | import com.squareup.okhttp.mockwebserver.RecordedRequest; 6 | 7 | import denominator.DNSApiManager; 8 | 9 | import static denominator.denominatord.RecordSetDispatcher.RECORDSET_PATTERN; 10 | 11 | public class DenominatorDispatcher extends Dispatcher { 12 | 13 | private final DNSApiManager mgr; 14 | private final Dispatcher zones; 15 | private final Dispatcher recordSets; 16 | 17 | DenominatorDispatcher(DNSApiManager mgr, JsonCodec codec) { 18 | this.mgr = mgr; 19 | this.zones = new ZoneDispatcher(mgr.api().zones(), codec); 20 | this.recordSets = new RecordSetDispatcher(mgr, codec); 21 | } 22 | 23 | @Override 24 | public MockResponse dispatch(RecordedRequest request) throws InterruptedException { 25 | try { 26 | if ("/healthcheck".equals(request.getPath())) { 27 | if (!request.getMethod().equals("GET")) { 28 | return new MockResponse().setResponseCode(405); 29 | } 30 | return new MockResponse().setResponseCode(mgr.checkConnection() ? 200 : 503); 31 | } else if (RECORDSET_PATTERN.matcher(request.getPath()).matches()) { 32 | return recordSets.dispatch(request); 33 | } else if (request.getPath() != null && request.getPath().startsWith("/zones")) { 34 | return zones.dispatch(request); 35 | } else { 36 | return new MockResponse().setResponseCode(404); 37 | } 38 | } catch (InterruptedException e) { 39 | throw e; 40 | } catch (RuntimeException e) { 41 | return new MockResponse().setResponseCode(e instanceof IllegalArgumentException ? 400 : 500) 42 | .addHeader("Content-Type", "text/plain") 43 | .setBody(e.getMessage() + "\n"); // curl nice 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example-daemon/src/main/java/denominator/denominatord/JsonCodec.java: -------------------------------------------------------------------------------- 1 | package denominator.denominatord; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.stream.JsonWriter; 6 | 7 | import com.squareup.okhttp.mockwebserver.MockResponse; 8 | import com.squareup.okhttp.mockwebserver.RecordedRequest; 9 | 10 | import java.io.IOException; 11 | import java.io.StringWriter; 12 | import java.util.Iterator; 13 | 14 | class JsonCodec { 15 | 16 | private final Gson json; 17 | 18 | JsonCodec() { 19 | this.json = new GsonBuilder().setPrettyPrinting().create(); 20 | } 21 | 22 | T readJson(RecordedRequest request, Class clazz) { 23 | return json.fromJson(request.getUtf8Body(), clazz); 24 | } 25 | 26 | MockResponse toJsonArray(Iterator elements) { 27 | elements.hasNext(); // defensive to make certain error cases eager. 28 | 29 | StringWriter out = new StringWriter(); // MWS cannot do streaming responses. 30 | try { 31 | JsonWriter writer = new JsonWriter(out); 32 | writer.setIndent(" "); 33 | writer.beginArray(); 34 | while (elements.hasNext()) { 35 | Object next = elements.next(); 36 | json.toJson(next, next.getClass(), writer); 37 | } 38 | writer.endArray(); 39 | writer.flush(); 40 | } catch (IOException e) { 41 | throw new RuntimeException(e); 42 | } 43 | 44 | return new MockResponse().setResponseCode(200) 45 | .addHeader("Content-Type", "application/json") 46 | .setBody(out.toString() + "\n"); // curl nice 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /example-daemon/src/main/java/denominator/denominatord/Query.java: -------------------------------------------------------------------------------- 1 | package denominator.denominatord; 2 | 3 | import java.net.URI; 4 | import java.util.List; 5 | 6 | import denominator.common.Util; 7 | import denominator.model.ResourceRecordSet; 8 | 9 | import static denominator.common.Preconditions.checkArgument; 10 | 11 | class Query { 12 | 13 | static Query from(String path) { 14 | String decoded = URI.create(path).getQuery(); 15 | if (decoded == null) { 16 | return new Query(null, null, null); 17 | } 18 | String name = null; 19 | String type = null; 20 | String qualifier = null; 21 | for (String nameValueString : Util.split('&', decoded)) { 22 | List nameValue = Util.split('=', nameValueString); 23 | String queryName = nameValue.get(0); 24 | String queryValue = nameValue.size() > 1 ? nameValue.get(1) : null; 25 | if (queryName.equals("name")) { 26 | name = queryValue; 27 | } else if (queryName.equals("type")) { 28 | type = queryValue; 29 | } else if (queryName.equals("qualifier")) { 30 | qualifier = queryValue; 31 | } 32 | } 33 | return new Query(name, type, qualifier); 34 | } 35 | 36 | static Query from(ResourceRecordSet recordSet) { 37 | return new Query(recordSet.name(), recordSet.type(), recordSet.qualifier()); 38 | } 39 | 40 | final String name; 41 | final String type; 42 | final String qualifier; 43 | 44 | private Query(String name, String type, String qualifier) { 45 | this.name = name; 46 | this.type = type; 47 | this.qualifier = qualifier; 48 | if (qualifier != null) { 49 | checkArgument(type != null && name != null, "name and type query required with qualifier"); 50 | } else if (type != null) { 51 | checkArgument(name != null, "name query required with type"); 52 | } 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return new StringBuilder() 58 | .append("name=").append(name) 59 | .append(", type=").append(type) 60 | .append(", qualifier=").append(qualifier).toString(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /example-daemon/src/main/java/denominator/denominatord/ZoneDispatcher.java: -------------------------------------------------------------------------------- 1 | package denominator.denominatord; 2 | 3 | 4 | import com.squareup.okhttp.mockwebserver.Dispatcher; 5 | import com.squareup.okhttp.mockwebserver.MockResponse; 6 | import com.squareup.okhttp.mockwebserver.RecordedRequest; 7 | 8 | import java.util.logging.Logger; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | import denominator.ZoneApi; 13 | import denominator.model.Zone; 14 | 15 | import static java.lang.String.format; 16 | import static java.lang.System.currentTimeMillis; 17 | 18 | public class ZoneDispatcher extends Dispatcher { 19 | private final Logger log = Logger.getLogger(Dispatcher.class.getName()); 20 | private final ZoneApi api; 21 | private final JsonCodec codec; 22 | 23 | ZoneDispatcher(ZoneApi api, JsonCodec codec) { 24 | this.api = api; 25 | this.codec = codec; 26 | } 27 | 28 | @Override 29 | public MockResponse dispatch(RecordedRequest request) { 30 | if (request.getMethod().equals("GET")) { 31 | Query query = Query.from(request.getPath()); 32 | if (query.name != null) { 33 | return codec.toJsonArray(api.iterateByName(query.name)); 34 | } 35 | return codec.toJsonArray(api.iterator()); 36 | } else if (request.getMethod().equals("PUT")) { 37 | Zone zone = codec.readJson(request, Zone.class); 38 | long s = currentTimeMillis(); 39 | log.info(format("replacing zone %s", zone)); 40 | String id = api.put(zone); 41 | log.info(format("replaced zone %s in %sms", zone, currentTimeMillis() - s)); 42 | return new MockResponse().setResponseCode(201).addHeader("Location", "/zones/" + id); 43 | } else if (request.getMethod().equals("DELETE")) { 44 | String zoneId = request.getPath().replace("/zones/", ""); 45 | long s = currentTimeMillis(); 46 | log.info(format("deleting zone %s ", zoneId)); 47 | api.delete(zoneId); 48 | log.info(format("deleted zone %s in %sms", zoneId, currentTimeMillis() - s)); 49 | return new MockResponse().setResponseCode(204); 50 | } else { 51 | return new MockResponse().setResponseCode(405); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example-daemon/supportedProviders.txt: -------------------------------------------------------------------------------- 1 | # Update this file to add more default providers to the fat jar. 2 | # Note that these providers must be specified as a fatJarProviders dependency 3 | # At build time, this will become META-INF/services/denominator.Provider 4 | # see http://docs.oracle.com/javase/7/docs/api/java/util/ServiceLoader.html 5 | denominator.clouddns.CloudDNSProvider 6 | denominator.dynect.DynECTProvider 7 | denominator.mock.MockProvider 8 | denominator.route53.Route53Provider 9 | denominator.ultradns.UltraDNSProvider 10 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/denominator/c565e3b8c6043051252e0947029511f9ac5d306f/gradle.properties -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Netflix/denominator/c565e3b8c6043051252e0947029511f9ac5d306f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jan 02 13:14:58 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-bin.zip 7 | -------------------------------------------------------------------------------- /installViaTravis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script will build the project. 3 | 4 | if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then 5 | echo -e "Assemble Pull Request #$TRAVIS_PULL_REQUEST => Branch [$TRAVIS_BRANCH]" 6 | ./gradlew assemble 7 | elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ]; then 8 | echo -e 'Assemble Branch with Snapshot => Branch ['$TRAVIS_BRANCH']' 9 | ./gradlew -Prelease.travisci=true assemble 10 | elif [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" != "" ]; then 11 | echo -e 'Assemble Branch for Release => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG']' 12 | ./gradlew -Prelease.travisci=true -Prelease.useLastTag=true assemble 13 | else 14 | echo -e 'WARN: Should not be here => Branch ['$TRAVIS_BRANCH'] Tag ['$TRAVIS_TAG'] Pull Request ['$TRAVIS_PULL_REQUEST']' 15 | ./gradlew assemble 16 | fi 17 | -------------------------------------------------------------------------------- /model/README.md: -------------------------------------------------------------------------------- 1 | # Model of Denominator 2 | 3 | This contains the classes such as `Zone` and `ResourceRecord` used by denominator. This is packaged into a separate jar to allow other projects to share these classes. For example, if `hopper` uses these classes directly, the `Backend` will not need a conversion function at all. 4 | -------------------------------------------------------------------------------- /model/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'nebula-test-jar' 3 | 4 | sourceCompatibility = 1.6 5 | 6 | dependencies { 7 | testCompile 'junit:junit:4.12' 8 | testCompile 'org.assertj:assertj-core:1.7.1' // last version supporting JDK 7 9 | } 10 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/common/Filter.java: -------------------------------------------------------------------------------- 1 | package denominator.common; 2 | 3 | /** 4 | * adapted from guava's {@code com.google.common.base.Predicate}. 5 | */ 6 | public interface Filter { 7 | 8 | /** 9 | * @param in to evaluate, could be null 10 | * @return true if not null and should be retained. 11 | */ 12 | boolean apply(T in); 13 | } 14 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/common/PeekingIterator.java: -------------------------------------------------------------------------------- 1 | package denominator.common; 2 | 3 | import java.util.Iterator; 4 | import java.util.NoSuchElementException; 5 | 6 | /** 7 | * adapted from guava's {@code com.google.common.collect.AbstractIterator}. 8 | */ 9 | public abstract class PeekingIterator implements Iterator { 10 | 11 | private PeekingIterator.State state = State.NOT_READY; 12 | private T next; 13 | 14 | /** 15 | * Constructor for use by subclasses. 16 | */ 17 | protected PeekingIterator() { 18 | } 19 | 20 | protected abstract T computeNext(); 21 | 22 | protected final T endOfData() { 23 | state = State.DONE; 24 | return null; 25 | } 26 | 27 | @Override 28 | public final boolean hasNext() { 29 | switch (state) { 30 | case DONE: 31 | return false; 32 | case READY: 33 | return true; 34 | default: 35 | } 36 | return tryToComputeNext(); 37 | } 38 | 39 | private boolean tryToComputeNext() { 40 | next = computeNext(); 41 | if (state != State.DONE) { 42 | state = State.READY; 43 | return true; 44 | } 45 | return false; 46 | } 47 | 48 | @Override 49 | public final T next() { 50 | if (!hasNext()) { 51 | throw new NoSuchElementException(); 52 | } 53 | state = State.NOT_READY; 54 | return next; 55 | } 56 | 57 | public T peek() { 58 | if (!hasNext()) { 59 | throw new NoSuchElementException(); 60 | } 61 | return next; 62 | } 63 | 64 | @Override 65 | public void remove() { 66 | throw new UnsupportedOperationException(); 67 | } 68 | 69 | private enum State { 70 | /** 71 | * We have computed the next element and haven't returned it yet. 72 | */ 73 | READY, 74 | 75 | /** 76 | * We haven't yet computed or have already returned the element. 77 | */ 78 | NOT_READY, 79 | 80 | /** 81 | * We have reached the end of the data and are finished. 82 | */ 83 | DONE, 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/common/Preconditions.java: -------------------------------------------------------------------------------- 1 | package denominator.common; 2 | 3 | import static java.lang.String.format; 4 | 5 | /** 6 | * cherry-picks from guava {@code com.google.common.base.Preconditions}. 7 | */ 8 | public class Preconditions { 9 | 10 | private Preconditions() { // no instances 11 | } 12 | 13 | public static void checkArgument(boolean expression, String errorMessageTemplate, 14 | Object... errorMessageArgs) { 15 | if (!expression) { 16 | throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs)); 17 | } 18 | } 19 | 20 | public static T checkNotNull(T reference, String errorMessageTemplate, 21 | Object... errorMessageArgs) { 22 | if (reference == null) { 23 | throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs)); 24 | } 25 | return reference; 26 | } 27 | 28 | public static void checkState(boolean expression, String errorMessageTemplate, 29 | Object... errorMessageArgs) { 30 | if (!expression) { 31 | throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/AbstractRecordSetBuilder.java: -------------------------------------------------------------------------------- 1 | package denominator.model; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import denominator.model.profile.Geo; 7 | import denominator.model.profile.Weighted; 8 | 9 | /** 10 | * Capable of building record sets from rdata input types expressed as {@code E} 11 | * 12 | * @param input type of rdata 13 | * @param portable type of the rdata in the {@link ResourceRecordSet} 14 | */ 15 | abstract class AbstractRecordSetBuilder, B extends AbstractRecordSetBuilder> { 16 | 17 | private String name; 18 | private String type; 19 | private String qualifier; 20 | private Integer ttl; 21 | private Geo geo; 22 | private Weighted weighted; 23 | 24 | /** 25 | * @see ResourceRecordSet#name() 26 | */ 27 | @SuppressWarnings("unchecked") 28 | public B name(String name) { 29 | this.name = name; 30 | return (B) this; 31 | } 32 | 33 | /** 34 | * @see ResourceRecordSet#type() 35 | */ 36 | @SuppressWarnings("unchecked") 37 | public B type(String type) { 38 | this.type = type; 39 | return (B) this; 40 | } 41 | 42 | /** 43 | * @see ResourceRecordSet#qualifier() 44 | */ 45 | @SuppressWarnings("unchecked") 46 | public B qualifier(String qualifier) { 47 | this.qualifier = qualifier; 48 | return (B) this; 49 | } 50 | 51 | /** 52 | * @see ResourceRecordSet#ttl() 53 | */ 54 | @SuppressWarnings("unchecked") 55 | public B ttl(Integer ttl) { 56 | this.ttl = ttl; 57 | return (B) this; 58 | } 59 | 60 | /** 61 | * @see ResourceRecordSet#geo() 62 | */ 63 | @SuppressWarnings("unchecked") 64 | public B geo(Geo geo) { 65 | this.geo = geo; 66 | return (B) this; 67 | } 68 | 69 | /** 70 | * @see ResourceRecordSet#weighted() 71 | */ 72 | @SuppressWarnings("unchecked") 73 | public B weighted(Weighted weighted) { 74 | this.weighted = weighted; 75 | return (B) this; 76 | } 77 | 78 | public ResourceRecordSet build() { 79 | return new ResourceRecordSet(name, type, qualifier, ttl, records(), geo, weighted); 80 | } 81 | 82 | /** 83 | * aggregate collected rdata values 84 | */ 85 | protected abstract List records(); 86 | } 87 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/NumbersAreUnsignedIntsLinkedHashMap.java: -------------------------------------------------------------------------------- 1 | package denominator.model; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | /** 6 | * Ensures we don't accidentally serialize whole numbers as floats. 7 | */ 8 | public class NumbersAreUnsignedIntsLinkedHashMap extends LinkedHashMap { 9 | 10 | private static final long serialVersionUID = 1L; 11 | 12 | protected NumbersAreUnsignedIntsLinkedHashMap() { 13 | } 14 | 15 | // only overriding put, as putAll uses this, and we aren't exposing 16 | // a ctor that allows passing a map. 17 | @Override 18 | public Object put(String key, Object val) { 19 | val = val != null && val instanceof Number ? Number.class.cast(val).intValue() : val; 20 | return super.put(key, val); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/StringRecordBuilder.java: -------------------------------------------------------------------------------- 1 | package denominator.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.Collection; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | import denominator.model.rdata.CNAMEData; 10 | 11 | import static denominator.common.Preconditions.checkNotNull; 12 | 13 | /** 14 | * Capable of building record sets where rdata types more easily expressed as Strings, such as 15 | * {@link CNAMEData} 16 | * 17 | * @param portable type of the rdata in the {@link ResourceRecordSet} 18 | */ 19 | abstract class StringRecordBuilder> extends 20 | AbstractRecordSetBuilder> { 21 | 22 | private List records = new ArrayList(); 23 | 24 | /** 25 | * adds a value to the builder. 26 | * 27 | * ex. 28 | * 29 | *
30 |    * builder.add("192.0.2.1");
31 |    * 
32 | */ 33 | public StringRecordBuilder add(String record) { 34 | this.records.add(apply(checkNotNull(record, "record"))); 35 | return this; 36 | } 37 | 38 | /** 39 | * adds values to the builder 40 | * 41 | * ex. 42 | * 43 | *
44 |    * builder.addAll("192.0.2.1", "192.0.2.2");
45 |    * 
46 | */ 47 | public StringRecordBuilder addAll(String... records) { 48 | return addAll(Arrays.asList(checkNotNull(records, "records"))); 49 | } 50 | 51 | /** 52 | * adds a value to the builder. 53 | * 54 | * ex. 55 | * 56 | *
57 |    * builder.addAll("192.0.2.1", "192.0.2.2");
58 |    * 
59 | */ 60 | public StringRecordBuilder addAll(Collection records) { 61 | for (String value : checkNotNull(records, "records")) { 62 | add(value); 63 | } 64 | return this; 65 | } 66 | 67 | @Override 68 | protected List records() { 69 | return records; 70 | } 71 | 72 | /** 73 | * Override to properly convert the input to a string-based RData value; 74 | */ 75 | protected abstract D apply(String in); 76 | } 77 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/Zones.java: -------------------------------------------------------------------------------- 1 | package denominator.model; 2 | 3 | import denominator.common.Filter; 4 | 5 | import static denominator.common.Preconditions.checkNotNull; 6 | 7 | /** 8 | * Static utility methods for {@code Zone} instances. 9 | */ 10 | public final class Zones { 11 | 12 | /** 13 | * Evaluates to true if the input {@link denominator.model.Zone} exists with {@link 14 | * denominator.model.Zone#name() name} corresponding to the {@code name} parameter. 15 | * 16 | * @param name the {@link denominator.model.Zone#name() name} of the desired zone. 17 | */ 18 | public static Filter nameEqualTo(final String name) { 19 | checkNotNull(name, "name"); 20 | return new Filter() { 21 | 22 | @Override 23 | public boolean apply(Zone in) { 24 | return in != null && name.equals(in.name()); 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | return "nameEqualTo(" + name + ")"; 30 | } 31 | }; 32 | } 33 | 34 | private Zones() { 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/profile/Geo.java: -------------------------------------------------------------------------------- 1 | package denominator.model.profile; 2 | 3 | import java.util.Collection; 4 | import java.util.Collections; 5 | import java.util.Map; 6 | 7 | import static denominator.common.Preconditions.checkNotNull; 8 | 9 | /** 10 | * Record sets with this profile are visible to the regions specified. 11 | * 12 | *

Example
13 | * 14 | *
15 |  * Geo profile = Geo.create(Map.of("United States (US)", Collection.of("Maryland")));
16 |  * 
17 | */ 18 | public class Geo { 19 | 20 | private final Map> regions; 21 | 22 | private Geo(Map> regions) { 23 | this.regions = Collections.unmodifiableMap(checkNotNull(regions, "regions")); 24 | } 25 | 26 | /** 27 | * @param regions corresponds to {@link #regions()} 28 | * @since 1.3 29 | */ 30 | public static Geo create(Map> regions) { 31 | return new Geo(regions); 32 | } 33 | 34 | /** 35 | * a filtered view of {@code denominator.profile.GeoResourceRecordSetApi.supportedRegions()} , 36 | * which describes the traffic desired for this profile. 37 | * 38 | * @since 1.3 39 | */ 40 | public Map> regions() { 41 | return regions; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object obj) { 46 | return obj instanceof Geo && regions().equals(((Geo) obj).regions()); 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return regions().hashCode(); 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return new StringBuilder().append("Geo [regions=").append(regions()).append("]").toString(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/profile/Weighted.java: -------------------------------------------------------------------------------- 1 | package denominator.model.profile; 2 | 3 | import static denominator.common.Preconditions.checkArgument; 4 | 5 | /** 6 | * Record sets with this profile are load balanced, differentiated by an integer {@code weight}. 7 | * 8 | *

Example
9 | * 10 | *
11 |  * Weighted profile = Weighted.create(2);
12 |  * 
13 | * 14 | * @since 1.3 15 | */ 16 | public class Weighted { 17 | 18 | private final int weight; 19 | 20 | private Weighted(int weight) { 21 | checkArgument(weight >= 0, "weight must be positive"); 22 | this.weight = weight; 23 | } 24 | 25 | /** 26 | * @param weight corresponds to {@link #weight()} 27 | */ 28 | public static Weighted create(int weight) { 29 | return new Weighted(weight); 30 | } 31 | 32 | /** 33 | * {@code 0} to always serve the record. Otherwise, provider-specific range of positive numbers 34 | * which differentiate the load to send to this record set vs another. 35 | * 36 | *

Note
37 | * 38 | * In some implementation, such as UltraDNS, only even number weights are supported! For highest 39 | * portability, use even numbers between {@code 0-100} 40 | */ 41 | public int weight() { 42 | return weight; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object obj) { 47 | return obj instanceof Weighted && weight() == ((Weighted) obj).weight(); 48 | } 49 | 50 | @Override 51 | public int hashCode() { 52 | return 37 * 17 + weight(); 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return new StringBuilder().append("Weighted [weight=").append(weight()).append("]").toString(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/rdata/AAAAData.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | import static denominator.common.Preconditions.checkArgument; 6 | import static denominator.common.Preconditions.checkNotNull; 7 | 8 | /** 9 | * Corresponds to the binary representation of the {@code AAAA} (Address) RData 10 | * 11 | *

Example
12 | * 13 | *
14 |  * AAAAData rdata = AAAAData.create("1234:ab00:ff00::6b14:abcd");
15 |  * 
16 | * 17 | * See RFC 3596 18 | */ 19 | public final class AAAAData extends LinkedHashMap { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | AAAAData(String address) { 24 | checkNotNull(address, "address"); 25 | checkArgument(address.indexOf(':') != -1, "%s should be a ipv6 address", address); 26 | put("address", address); 27 | } 28 | 29 | /** 30 | * @param ipv6address valid ipv6 address. ex. {@code 1234:ab00:ff00::6b14:abcd} 31 | * @throws IllegalArgumentException if the address is malformed or not ipv6 32 | */ 33 | public static AAAAData create(String ipv6address) throws IllegalArgumentException { 34 | return new AAAAData(ipv6address); 35 | } 36 | 37 | /** 38 | * a 128 bit IPv6 address 39 | * 40 | * @since 1.3 41 | */ 42 | public String address() { 43 | return get("address").toString(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/rdata/AData.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | import static denominator.common.Preconditions.checkArgument; 6 | import static denominator.common.Preconditions.checkNotNull; 7 | 8 | /** 9 | * Corresponds to the binary representation of the {@code A} (Address) RData 10 | * 11 | *

Example
12 | * 13 | *
14 |  * AData rdata = AData.create("192.0.2.1");
15 |  * 
16 | * 17 | * See RFC 1035 18 | */ 19 | public final class AData extends LinkedHashMap { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | AData(String address) { 24 | checkNotNull(address, "address"); 25 | checkArgument(address.indexOf('.') != -1, "%s should be a ipv4 address", address); 26 | put("address", address); 27 | } 28 | 29 | /** 30 | * @param ipv4address valid ipv4 address. ex. {@code 192.0.2.1} 31 | * @throws IllegalArgumentException if the address is malformed or not ipv4 32 | */ 33 | public static AData create(String ipv4address) throws IllegalArgumentException { 34 | return new AData(ipv4address); 35 | } 36 | 37 | /** 38 | * a 32-bit internet address 39 | * 40 | * @since 1.3 41 | */ 42 | public String address() { 43 | return get("address").toString(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/rdata/CNAMEData.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | import static denominator.common.Preconditions.checkNotNull; 6 | 7 | /** 8 | * Corresponds to the binary representation of the {@code CNAME} (Canonical Name) RData 9 | * 10 | *

Example
11 | * 12 | *
13 |  * CNAMEData rdata = CNAMEData.create("cname.foo.com.");
14 |  * 
15 | * 16 | * See RFC 1035 17 | */ 18 | public final class CNAMEData extends LinkedHashMap { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | CNAMEData(String cname) { 23 | put("cname", checkNotNull(cname, "cname")); 24 | } 25 | 26 | public static CNAMEData create(String cname) { 27 | return new CNAMEData(cname); 28 | } 29 | 30 | /** 31 | * domain-name which specifies the canonical or primary name for the owner. The owner name is an 32 | * alias. 33 | * 34 | * @since 1.3 35 | */ 36 | public String cname() { 37 | return get("cname").toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/rdata/MXData.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import denominator.model.NumbersAreUnsignedIntsLinkedHashMap; 4 | 5 | import static denominator.common.Preconditions.checkArgument; 6 | import static denominator.common.Preconditions.checkNotNull; 7 | 8 | /** 9 | * Corresponds to the binary representation of the {@code MX} (Mail Exchange) RData 10 | * 11 | *

Example
12 | * 13 | *
14 |  * MXData rdata = MXData.create(1, "mail.jclouds.org");
15 |  * 
16 | * 17 | * See RFC 1035 18 | */ 19 | public final class MXData extends NumbersAreUnsignedIntsLinkedHashMap { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | MXData(int preference, String exchange) { 24 | checkArgument(preference <= 0xFFFF, "preference must be 65535 or less"); 25 | checkNotNull(exchange, "exchange"); 26 | put("preference", preference); 27 | put("exchange", exchange); 28 | } 29 | 30 | public static MXData create(int preference, String exchange) { 31 | return new MXData(preference, exchange); 32 | } 33 | 34 | /** 35 | * specifies the preference given to this RR among others at the same owner. Lower values are 36 | * preferred. 37 | * 38 | * @since 1.3 39 | */ 40 | public int preference() { 41 | return Integer.class.cast(get("preference")); 42 | } 43 | 44 | /** 45 | * domain-name which specifies a host willing to act as a mail exchange for the owner name. 46 | * 47 | * @since 1.3 48 | */ 49 | public String exchange() { 50 | return get("exchange").toString(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/rdata/NSData.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | import static denominator.common.Preconditions.checkNotNull; 6 | 7 | /** 8 | * Corresponds to the binary representation of the {@code NS} (Name Server) RData 9 | * 10 | *

Example
11 | * 12 | *
13 |  * NSData rdata = NSData.create("ns.foo.com.");
14 |  * 
15 | * 16 | * See RFC 1035 17 | */ 18 | public final class NSData extends LinkedHashMap { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | NSData(String nsdname) { 23 | put("nsdname", checkNotNull(nsdname, "nsdname")); 24 | } 25 | 26 | public static NSData create(String nsdname) { 27 | return new NSData(nsdname); 28 | } 29 | 30 | /** 31 | * domain-name which specifies a host which should be authoritative for the specified class and 32 | * domain. 33 | * 34 | * @since 1.3 35 | */ 36 | public String nsdname() { 37 | return get("nsdname").toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/rdata/PTRData.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | import static denominator.common.Preconditions.checkNotNull; 6 | 7 | /** 8 | * Corresponds to the binary representation of the {@code PTR} (Pointer) RData 9 | * 10 | *

Example
11 | * 12 | *
13 |  * PTRData rdata = PTRData.create("ptr.foo.com.");
14 |  * 
15 | * 16 | * See RFC 1035 17 | */ 18 | public final class PTRData extends LinkedHashMap { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | PTRData(String ptrdname) { 23 | put("ptrdname", checkNotNull(ptrdname, "ptrdname")); 24 | } 25 | 26 | public static PTRData create(String ptrdname) { 27 | return new PTRData(ptrdname); 28 | } 29 | 30 | /** 31 | * domain-name which points to some location in the domain name space. 32 | * 33 | * @since 1.3 34 | */ 35 | public String ptrdname() { 36 | return get("ptrdname").toString(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/rdata/SPFData.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | import static denominator.common.Preconditions.checkArgument; 6 | import static denominator.common.Preconditions.checkNotNull; 7 | 8 | /** 9 | * Corresponds to the binary representation of the {@code SPF} (Sender Policy Framework) RData 10 | * 11 | *

Example
12 | * 13 | *
14 |  * import static denominator.model.rdata.SPFData.spf;
15 |  * ...
16 |  * SPFData rdata = spf("v=spf1 +mx a:colo.example.com/28 -all");
17 |  * 
18 | * 19 | * See RFC 4408 20 | */ 21 | public final class SPFData extends LinkedHashMap { 22 | 23 | private static final long serialVersionUID = 1L; 24 | 25 | SPFData(String txtdata) { 26 | checkArgument(checkNotNull(txtdata, "txtdata").length() <= 65535, 27 | "txt data is limited to 65535"); 28 | put("txtdata", txtdata); 29 | } 30 | 31 | public static SPFData create(String txtdata) { 32 | return new SPFData(txtdata); 33 | } 34 | 35 | /** 36 | * One or more character-strings. 37 | * 38 | * @since 1.3 39 | */ 40 | public String txtdata() { 41 | return get("txtdata").toString(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/rdata/TXTData.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | import static denominator.common.Preconditions.checkArgument; 6 | import static denominator.common.Preconditions.checkNotNull; 7 | 8 | /** 9 | * Corresponds to the binary representation of the {@code TXT} (Text) RData 10 | * 11 | *

Example
12 | * 13 | *
14 |  * TXTData rdata = TXTData.create("=spf1 ip4:192.0.2.1/24 ip4:198.51.100.1/24 -all");
15 |  * 
16 | * 17 | * See RFC 1035 18 | */ 19 | public final class TXTData extends LinkedHashMap { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | TXTData(String txtdata) { 24 | checkArgument(checkNotNull(txtdata, "txtdata").length() <= 65535, 25 | "txt data is limited to 65535"); 26 | put("txtdata", txtdata); 27 | } 28 | 29 | public static TXTData create(String txtdata) { 30 | return new TXTData(txtdata); 31 | } 32 | 33 | /** 34 | * One or more character-strings. 35 | * 36 | * @since 1.3 37 | */ 38 | public String txtdata() { 39 | return get("txtdata").toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /model/src/main/java/denominator/model/rdata/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * DNS record data ({@code rdata}) in Master File Format are represented as {@code Map}, preferring values to be unsigned integers or {@code String}. 3 | */ 4 | package denominator.model.rdata; 5 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/ResourceTypeToValueTest.java: -------------------------------------------------------------------------------- 1 | package denominator; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import java.util.Arrays; 8 | 9 | import denominator.ResourceTypeToValue.ResourceTypes; 10 | 11 | import static org.assertj.core.api.Assertions.assertThat; 12 | 13 | public class ResourceTypeToValueTest { 14 | 15 | @Rule 16 | public final ExpectedException thrown = ExpectedException.none(); 17 | 18 | @Test 19 | public void testNiceExceptionOnNotFound() { 20 | thrown.expect(IllegalArgumentException.class); 21 | thrown.expectMessage( 22 | "ResourceTypes do not include RRRR; types: " + Arrays.asList(ResourceTypes.values())); 23 | 24 | ResourceTypeToValue.lookup("RRRR"); 25 | } 26 | 27 | @Test 28 | public void testNiceExceptionOnNull() { 29 | thrown.expect(NullPointerException.class); 30 | thrown.expectMessage("resource type was null"); 31 | 32 | ResourceTypeToValue.lookup((String) null); 33 | } 34 | 35 | @Test 36 | public void testBasicCase() { 37 | 38 | assertThat(ResourceTypeToValue.lookup("AAAA")).isEqualTo(28); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/assertj/ModelAssertions.java: -------------------------------------------------------------------------------- 1 | package denominator.assertj; 2 | 3 | import org.assertj.core.api.Assertions; 4 | 5 | import denominator.model.ResourceRecordSet; 6 | import denominator.model.Zone; 7 | 8 | public class ModelAssertions extends Assertions { 9 | 10 | public static ResourceRecordSetAssert assertThat(ResourceRecordSet actual) { 11 | return new ResourceRecordSetAssert(actual); 12 | } 13 | 14 | public static ZoneAssert assertThat(Zone actual) { 15 | return new ZoneAssert(actual); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/assertj/ZoneAssert.java: -------------------------------------------------------------------------------- 1 | package denominator.assertj; 2 | 3 | import org.assertj.core.api.AbstractAssert; 4 | import org.assertj.core.internal.Objects; 5 | import org.assertj.core.internal.Strings; 6 | 7 | import denominator.model.Zone; 8 | 9 | public class ZoneAssert extends AbstractAssert { 10 | 11 | Objects objects = Objects.instance(); 12 | Strings strings = Strings.instance(); 13 | 14 | public ZoneAssert(Zone actual) { 15 | super(actual, ZoneAssert.class); 16 | } 17 | 18 | public ZoneAssert hasName(String expected) { 19 | isNotNull(); 20 | objects.assertEqual(info, actual.name(), expected); 21 | return this; 22 | } 23 | 24 | public ZoneAssert hasId(String expected) { 25 | isNotNull(); 26 | objects.assertEqual(info, actual.id(), expected); 27 | return this; 28 | } 29 | 30 | /** 31 | * Tolerates differences when the actual email ends with a trailing dot or when the first 32 | * {@literal @} with a dot. 33 | */ 34 | public ZoneAssert hasEmail(String expected) { 35 | isNotNull(); 36 | String actualEmail = actual.email(); 37 | if (actualEmail.endsWith(".")) { 38 | actualEmail = actualEmail.substring(0, actualEmail.length() - 1); 39 | } 40 | if (actualEmail.indexOf('@') == -1) { 41 | actualEmail = actualEmail.replaceFirst("\\.", "@"); 42 | } 43 | strings.assertStartsWith(info, actualEmail, expected); 44 | return this; 45 | } 46 | 47 | public ZoneAssert hasTtl(Integer expected) { 48 | isNotNull(); 49 | objects.assertEqual(info, actual.ttl(), expected); 50 | return this; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/common/PeekingIteratorTest.java: -------------------------------------------------------------------------------- 1 | package denominator.common; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import java.util.NoSuchElementException; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.junit.Assert.assertTrue; 11 | 12 | public class PeekingIteratorTest { 13 | 14 | @Rule 15 | public final ExpectedException thrown = ExpectedException.none(); 16 | 17 | @Test 18 | public void unmodifiable() { 19 | thrown.expect(UnsupportedOperationException.class); 20 | 21 | PeekingIterator it = TrueThenDone.INSTANCE.iterator(); 22 | assertThat(it).containsExactly(true); 23 | it.remove(); 24 | } 25 | 26 | @Test 27 | public void next() { 28 | thrown.expect(NoSuchElementException.class); 29 | 30 | PeekingIterator it = TrueThenDone.INSTANCE.iterator(); 31 | assertThat(it).containsExactly(true); 32 | it.next(); 33 | } 34 | 35 | @Test 36 | public void peek() { 37 | thrown.expect(NoSuchElementException.class); 38 | 39 | PeekingIterator it = TrueThenDone.INSTANCE.iterator(); 40 | assertTrue(it.peek()); 41 | assertThat(it).containsExactly(true); 42 | it.peek(); 43 | } 44 | 45 | enum TrueThenDone implements Iterable { 46 | INSTANCE; 47 | 48 | @Override 49 | public PeekingIterator iterator() { 50 | return new PeekingIterator() { 51 | boolean val = true; 52 | 53 | @Override 54 | public Boolean computeNext() { 55 | if (val) { 56 | val = false; 57 | return true; 58 | } 59 | return endOfData(); 60 | } 61 | }; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/common/PreconditionsTest.java: -------------------------------------------------------------------------------- 1 | package denominator.common; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import static denominator.common.Preconditions.checkArgument; 8 | import static denominator.common.Preconditions.checkNotNull; 9 | import static denominator.common.Preconditions.checkState; 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | public class PreconditionsTest { 13 | 14 | @Rule 15 | public final ExpectedException thrown = ExpectedException.none(); 16 | 17 | @Test 18 | public void checkArgumentFormatted() { 19 | thrown.expect(IllegalArgumentException.class); 20 | thrown.expectMessage("should be foo"); 21 | 22 | checkArgument(false, "should be %s", "foo"); 23 | } 24 | 25 | @Test 26 | public void checkArgumentPass() { 27 | checkArgument(true, "should be %s", "foo"); 28 | } 29 | 30 | @Test 31 | public void checkStateFormatted() { 32 | thrown.expect(IllegalStateException.class); 33 | thrown.expectMessage("should be foo"); 34 | 35 | checkState(false, "should be %s", "foo"); 36 | } 37 | 38 | @Test 39 | public void checkStatePass() { 40 | checkState(true, "should be %s", "foo"); 41 | } 42 | 43 | @Test 44 | public void checkNotNullFormatted() { 45 | thrown.expect(NullPointerException.class); 46 | thrown.expectMessage("should be foo"); 47 | 48 | checkNotNull(null, "should be %s", "foo"); 49 | } 50 | 51 | @Test 52 | public void checkNotNullPass() { 53 | assertThat(checkNotNull("foo", "should be %s", "foo")).isEqualTo("foo"); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/ResourceRecordSetTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import denominator.model.rdata.AData; 8 | 9 | import static denominator.assertj.ModelAssertions.assertThat; 10 | 11 | public class ResourceRecordSetTest { 12 | 13 | @Rule 14 | public final ExpectedException thrown = ExpectedException.none(); 15 | 16 | @Test 17 | public void canBuildARecordSetInLongForm() { 18 | ResourceRecordSet record = ResourceRecordSet.builder() 19 | .name("www.denominator.io.") 20 | .type("A") 21 | .ttl(3600) 22 | .add(AData.create("192.0.2.1")).build(); 23 | 24 | assertThat(record) 25 | .hasName("www.denominator.io.") 26 | .hasType("A") 27 | .hasTtl(3600) 28 | .containsExactlyRecords(AData.create("192.0.2.1")); 29 | } 30 | 31 | @Test 32 | public void testNullRdataNPE() { 33 | thrown.expect(NullPointerException.class); 34 | thrown.expectMessage("record"); 35 | 36 | ResourceRecordSet.builder().add(null); 37 | } 38 | 39 | @Test 40 | 41 | public void testInvalidTTL() { 42 | thrown.expect(IllegalArgumentException.class); 43 | thrown.expectMessage("Invalid ttl"); 44 | 45 | ResourceRecordSet.builder()// 46 | .name("www.denominator.io.")// 47 | .type("A")// 48 | .ttl(0xFFFFFFFF)// 49 | .add(AData.create("192.0.2.1")).build(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/ZonesTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model; 2 | 3 | import org.junit.Test; 4 | 5 | import static denominator.model.Zones.nameEqualTo; 6 | import static org.junit.Assert.assertFalse; 7 | import static org.junit.Assert.assertTrue; 8 | 9 | public class ZonesTest { 10 | 11 | Zone zone = Zone.create("denominator.io."); 12 | 13 | @Test 14 | public void nameEqualToReturnsFalseOnNull() { 15 | assertFalse(nameEqualTo(zone.name()).apply(null)); 16 | } 17 | 18 | @Test 19 | public void nameEqualToReturnsFalseOnDifferentName() { 20 | assertFalse(nameEqualTo("denominator.io").apply(zone)); 21 | } 22 | 23 | @Test 24 | public void nameEqualToReturnsTrueOnSameName() { 25 | assertTrue(nameEqualTo(zone.name()).apply(zone)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/profile/GeoTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.profile; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | 12 | public class GeoTest { 13 | 14 | @Rule 15 | public final ExpectedException thrown = ExpectedException.none(); 16 | 17 | @Test 18 | public void immutableRegions() { 19 | thrown.expect(UnsupportedOperationException.class); 20 | 21 | Map> regions = new LinkedHashMap>(); 22 | regions.put("US", Arrays.asList("US-VA", "US-CA")); 23 | 24 | Geo geo = Geo.create(regions); 25 | 26 | geo.regions().remove("US"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/profile/WeightedTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.profile; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | public class WeightedTest { 8 | 9 | @Rule 10 | public final ExpectedException thrown = ExpectedException.none(); 11 | 12 | @Test 13 | public void testInvalidWeight() { 14 | thrown.expect(IllegalArgumentException.class); 15 | thrown.expectMessage("weight must be positive"); 16 | 17 | Weighted.create(-1); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/rdata/AAAADataTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import static denominator.model.ResourceRecordSets.aaaa; 8 | 9 | public class AAAADataTest { 10 | 11 | @Rule 12 | public final ExpectedException thrown = ExpectedException.none(); 13 | 14 | @Test 15 | public void testBadIPv4() { 16 | thrown.expect(IllegalArgumentException.class); 17 | thrown.expectMessage("should be a ipv6 address"); 18 | 19 | aaaa("www.denominator.io.", "192.0.2.1"); 20 | } 21 | 22 | @Test 23 | public void testNoIP() { 24 | thrown.expect(IllegalArgumentException.class); 25 | thrown.expectMessage("should be a ipv6 address"); 26 | 27 | aaaa("www.denominator.io.", ""); 28 | } 29 | 30 | @Test 31 | public void testGoodIPv6() { 32 | aaaa("www.denominator.io.", "2001:db8:1cfe:face:b00c::3"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/rdata/ADataTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.junit.rules.ExpectedException; 7 | 8 | import static denominator.model.ResourceRecordSets.a; 9 | 10 | public class ADataTest { 11 | 12 | @Rule 13 | public final ExpectedException thrown = ExpectedException.none(); 14 | 15 | @Test 16 | public void testBadIPv6() { 17 | thrown.expect(IllegalArgumentException.class); 18 | thrown.expectMessage("should be a ipv4 address"); 19 | 20 | a("www.denominator.io.", "2001:db8:1cfe:face:b00c::3"); 21 | } 22 | 23 | @Test 24 | public void testNoIP() { 25 | thrown.expect(IllegalArgumentException.class); 26 | thrown.expectMessage("should be a ipv4 address"); 27 | 28 | a("www.denominator.io.", ""); 29 | } 30 | 31 | @Test 32 | public void testGoodIPv4() { 33 | a("www.denominator.io.", "192.0.2.1"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/rdata/CERTDataTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import static denominator.model.ResourceRecordSets.cert; 8 | 9 | public class CERTDataTest { 10 | 11 | @Rule 12 | public final ExpectedException thrown = ExpectedException.none(); 13 | 14 | @Test 15 | public void testGoodRecord() { 16 | cert("www.denominator.io.", "12345 1 1 B33F"); 17 | } 18 | 19 | @Test 20 | public void testMissingParts() { 21 | thrown.expect(IllegalArgumentException.class); 22 | thrown.expectMessage("record must have exactly four parts"); 23 | 24 | cert("www.denominator.io.", "12345 1 1"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/rdata/CNAMEDataTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import static denominator.model.ResourceRecordSets.cname; 8 | 9 | public class CNAMEDataTest { 10 | 11 | @Rule 12 | public final ExpectedException thrown = ExpectedException.none(); 13 | 14 | @Test 15 | public void testNullTarget() { 16 | thrown.expect(NullPointerException.class); 17 | thrown.expectMessage("record"); 18 | 19 | cname("www.denominator.io.", (String) null); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/rdata/MXDataTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import static denominator.model.ResourceRecordSets.mx; 8 | 9 | public class MXDataTest { 10 | 11 | @Rule 12 | public final ExpectedException thrown = ExpectedException.none(); 13 | 14 | @Test 15 | public void testGoodRecord() { 16 | mx("www.denominator.io.", "1 mx1.denominator.io."); 17 | } 18 | 19 | @Test 20 | public void testMissingParts() { 21 | thrown.expect(IllegalArgumentException.class); 22 | thrown.expectMessage("record must have exactly two parts"); 23 | 24 | mx("www.denominator.io.", "1"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/rdata/NAPTRDataTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import static denominator.model.ResourceRecordSets.naptr; 8 | 9 | public class NAPTRDataTest { 10 | 11 | @Rule 12 | public final ExpectedException thrown = ExpectedException.none(); 13 | 14 | @Test 15 | public void testGoodRecord() { 16 | naptr("www.denominator.io.", "1 1 U E2U+sip !^.*$!sip:customer-service@example.com! ."); 17 | } 18 | 19 | @Test 20 | public void testMissingParts() { 21 | thrown.expect(IllegalArgumentException.class); 22 | thrown.expectMessage("record must have exactly six parts"); 23 | 24 | naptr("www.denominator.io.", "1 1 U E2U+sip"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/rdata/NSDataTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import static denominator.model.ResourceRecordSets.ns; 8 | 9 | public class NSDataTest { 10 | 11 | @Rule 12 | public final ExpectedException thrown = ExpectedException.none(); 13 | 14 | @Test 15 | public void testNullTargetNS() { 16 | thrown.expect(NullPointerException.class); 17 | thrown.expectMessage("record"); 18 | 19 | ns("www.denominator.io.", (String) null); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/rdata/SPFDataTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import static denominator.model.ResourceRecordSets.spf; 8 | 9 | public class SPFDataTest { 10 | 11 | @Rule 12 | public final ExpectedException thrown = ExpectedException.none(); 13 | 14 | @Test 15 | public void testSinglePart() { 16 | spf("www.denominator.io.", "v=spf1 a mx -all"); 17 | } 18 | 19 | @Test 20 | public void testMultiPart() { 21 | spf("www.denominator.io.", "\"v=spf1 a mx -all\" \"v=spf1 aaaa mx -all\""); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/rdata/SRVDataTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import static denominator.model.ResourceRecordSets.srv; 8 | 9 | public class SRVDataTest { 10 | 11 | @Rule 12 | public final ExpectedException thrown = ExpectedException.none(); 13 | 14 | @Test 15 | public void testGoodRecord() { 16 | srv("www.denominator.io.", "0 1 80 www.foo.com."); 17 | } 18 | 19 | @Test 20 | public void testMissingParts() { 21 | thrown.expect(IllegalArgumentException.class); 22 | thrown.expectMessage("record must have exactly four parts"); 23 | 24 | srv("www.denominator.io.", "0 1 80"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/rdata/SSHFPDataTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import static denominator.model.ResourceRecordSets.sshfp; 8 | 9 | public class SSHFPDataTest { 10 | 11 | @Rule 12 | public final ExpectedException thrown = ExpectedException.none(); 13 | 14 | @Test 15 | public void testGoodRecord() { 16 | sshfp("www.denominator.io.", "1 1 B33F"); 17 | } 18 | 19 | @Test 20 | public void testMissingParts() { 21 | thrown.expect(IllegalArgumentException.class); 22 | thrown.expectMessage("record must have exactly three parts"); 23 | 24 | sshfp("www.denominator.io.", "1"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /model/src/test/java/denominator/model/rdata/TXTDataTest.java: -------------------------------------------------------------------------------- 1 | package denominator.model.rdata; 2 | 3 | import org.junit.Test; 4 | 5 | import static denominator.model.ResourceRecordSets.txt; 6 | 7 | public class TXTDataTest { 8 | 9 | @Test 10 | public void testSinglePart() { 11 | txt("www.denominator.io.", "foo"); 12 | } 13 | 14 | @Test 15 | public void testMultiPart() { 16 | txt("www.denominator.io.", "\"foo bar\""); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /route53/README.md: -------------------------------------------------------------------------------- 1 | ## Notable Behaviors 2 | The following are notable when compared to different providers. 3 | * `Zone.id()` is opaque and multiple zones can exist with the same `Zone.name()`. 4 | * Zone lists are 1 + N requests in order to zip with the SOA's ttl and rname. 5 | * The default ttl for record sets is hard-coded to 300. 6 | * The zone's NS record set can be altered, but not removed. 7 | * `SPF` and `TXT` rdata are quoted when accessed from the Route53 api directly. Denominator unquotes them. 8 | -------------------------------------------------------------------------------- /route53/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | sourceCompatibility = 1.6 4 | 5 | test { 6 | systemProperty 'route53.url', System.getProperty('route53.url', '') 7 | systemProperty 'route53.accessKey', System.getProperty('route53.accessKey', '') 8 | systemProperty 'route53.secretKey', System.getProperty('route53.secretKey', '') 9 | systemProperty 'route53.zone', System.getProperty('route53.zone', '') 10 | } 11 | 12 | dependencies { 13 | compile project(':denominator-core') 14 | compile 'com.netflix.feign:feign-core:8.10.0' 15 | compile 'com.netflix.feign:feign-sax:8.10.0' 16 | testCompile project(':denominator-model').sourceSets.test.output 17 | testCompile project(':denominator-core').sourceSets.test.output 18 | testCompile 'junit:junit:4.12' 19 | testCompile 'org.assertj:assertj-core:1.7.1' // last version supporting JDK 7 20 | testCompile 'com.squareup.okhttp:mockwebserver:2.5.0' 21 | } 22 | -------------------------------------------------------------------------------- /route53/src/main/java/denominator/route53/AliasTarget.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | import denominator.model.ResourceRecordSet; 6 | 7 | import static denominator.common.Preconditions.checkNotNull; 8 | 9 | /** 10 | * Reference to the addresses in a CloudFront distribution, Amazon S3 bucket, Elastic Load Balancing 11 | * load balancer, or Route 53 hosted zone.
Only valid when {@link ResourceRecordSet#type()} is 12 | * {@code A} or {@code AAAA} . 13 | * 14 | *
Example
15 | * 16 | *
17 |  * AliasTarget rdata = AliasTarget.create("Z3DZXE0Q79N41H",
18 |  * "nccp-cbp-frontend-12345678.us-west-2.elb.amazonaws.com.");
19 |  * 
20 | * 21 | * See API to create aliases 23 | * 24 | * @since 4.2 25 | */ 26 | public final class AliasTarget extends LinkedHashMap { 27 | 28 | private static final long serialVersionUID = 1L; 29 | 30 | AliasTarget(String hostedZoneId, String dnsName) { 31 | put("HostedZoneId", checkNotNull(hostedZoneId, "HostedZoneId")); 32 | put("DNSName", checkNotNull(dnsName, "DNSName")); 33 | } 34 | 35 | public static AliasTarget create(String hostedZoneId, String dnsName) { 36 | return new AliasTarget(hostedZoneId, dnsName); 37 | } 38 | 39 | /** 40 | * Hosted zone ID for your CloudFront distribution, Amazon S3 bucket, Elastic Load Balancing load 41 | * balancer, or Route 53 hosted zone. 42 | */ 43 | public String hostedZoneId() { 44 | return get("HostedZoneId").toString(); 45 | } 46 | 47 | /** 48 | * DNS domain name for your CloudFront distribution, Amazon S3 bucket, Elastic Load Balancing load 49 | * balancer, or another resource record set in this hosted zone. 50 | */ 51 | public String dnsName() { 52 | return get("DNSName").toString(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /route53/src/main/java/denominator/route53/GetHostedZoneResponseHandler.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import org.xml.sax.helpers.DefaultHandler; 4 | 5 | import denominator.route53.Route53.NameAndCount; 6 | import feign.sax.SAXDecoder.ContentHandlerWithResult; 7 | 8 | /** 9 | * See docs 11 | */ 12 | class GetHostedZoneResponseHandler extends DefaultHandler 13 | implements ContentHandlerWithResult { 14 | 15 | private final StringBuilder currentText = new StringBuilder(); 16 | private NameAndCount zone = new NameAndCount(); 17 | 18 | @Override 19 | public NameAndCount result() { 20 | return zone; 21 | } 22 | 23 | @Override 24 | public void endElement(String uri, String name, String qName) { 25 | if (qName.equals("Name")) { 26 | zone.name = currentText.toString().trim(); 27 | } else if (qName.equals("ResourceRecordSetCount")) { 28 | zone.resourceRecordSetCount = Integer.parseInt(currentText.toString().trim()); 29 | } 30 | currentText.setLength(0); 31 | } 32 | 33 | @Override 34 | public void characters(char ch[], int start, int length) { 35 | currentText.append(ch, start, length); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /route53/src/main/java/denominator/route53/HostedZonesReadable.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import javax.inject.Inject; 4 | 5 | import denominator.CheckConnection; 6 | 7 | class HostedZonesReadable implements CheckConnection { 8 | 9 | private final Route53 api; 10 | 11 | @Inject 12 | HostedZonesReadable(Route53 api) { 13 | this.api = api; 14 | } 15 | 16 | @Override 17 | public boolean ok() { 18 | try { 19 | api.listHostedZones(); 20 | return true; 21 | } catch (RuntimeException e) { 22 | return false; 23 | } 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "HostedZonesReadable"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /route53/src/main/java/denominator/route53/InvalidChangeBatchException.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import java.util.List; 4 | 5 | import feign.FeignException; 6 | 7 | import static java.lang.String.format; 8 | 9 | /** 10 | * See docs 12 | */ 13 | public class InvalidChangeBatchException extends FeignException { 14 | 15 | private static final long serialVersionUID = 1L; 16 | private final List messages; 17 | 18 | InvalidChangeBatchException(String methodKey, List messages) { 19 | super(format("%s failed with errors %s", methodKey, messages)); 20 | this.messages = messages; 21 | } 22 | 23 | /** 24 | * Messages corresponding to the changes. 25 | */ 26 | public List messages() { 27 | return messages; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /route53/src/main/java/denominator/route53/ListHostedZonesResponseHandler.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import org.xml.sax.Attributes; 4 | import org.xml.sax.helpers.DefaultHandler; 5 | 6 | import denominator.route53.Route53.HostedZone; 7 | import denominator.route53.Route53.HostedZoneList; 8 | import feign.sax.SAXDecoder.ContentHandlerWithResult; 9 | 10 | /** 11 | * See docs 13 | */ 14 | class ListHostedZonesResponseHandler extends DefaultHandler 15 | implements ContentHandlerWithResult { 16 | 17 | private final StringBuilder currentText = new StringBuilder(); 18 | private final HostedZoneList zones = new HostedZoneList(); 19 | private HostedZone zone = new HostedZone(); 20 | 21 | @Override 22 | public HostedZoneList result() { 23 | return zones; 24 | } 25 | 26 | @Override 27 | public void endElement(String uri, String name, String qName) { 28 | if (qName.equals("Name")) { 29 | zone.name = currentText.toString().trim(); 30 | } else if (qName.equals("Id")) { 31 | zone.id = currentText.toString().trim().replace("/hostedzone/", ""); 32 | } else if (qName.equals("HostedZone")) { 33 | zones.add(zone); 34 | zone = new HostedZone(); 35 | } else if (qName.equals("NextMarker")) { 36 | zones.next = currentText.toString().trim(); 37 | } 38 | currentText.setLength(0); 39 | } 40 | 41 | @Override 42 | public void characters(char ch[], int start, int length) { 43 | currentText.append(ch, start, length); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /route53/src/main/java/denominator/route53/ListResourceRecordSetsResponseHandler.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import org.xml.sax.Attributes; 4 | import org.xml.sax.helpers.DefaultHandler; 5 | 6 | import denominator.route53.Route53.ResourceRecordSetList; 7 | import denominator.route53.Route53.ResourceRecordSetList.NextRecord; 8 | import feign.sax.SAXDecoder.ContentHandlerWithResult; 9 | 10 | /** 11 | * See 13 | */ 14 | class ListResourceRecordSetsResponseHandler extends DefaultHandler implements 15 | ContentHandlerWithResult { 16 | 17 | private final StringBuilder currentText = new StringBuilder(); 18 | private final ResourceRecordSetList rrsets = new ResourceRecordSetList(); 19 | private ResourceRecordSetHandler resourceRecordSetHandler = new ResourceRecordSetHandler(); 20 | private NextRecord next = null; 21 | private boolean inResourceRecordSets; 22 | 23 | @Override 24 | public ResourceRecordSetList result() { 25 | rrsets.next = next; 26 | return rrsets; 27 | } 28 | 29 | @Override 30 | public void startElement(String url, String name, String qName, Attributes attributes) { 31 | if ("ResourceRecordSets".equals(qName)) { 32 | inResourceRecordSets = true; 33 | } 34 | } 35 | 36 | @Override 37 | public void endElement(String uri, String name, String qName) { 38 | if (inResourceRecordSets) { 39 | if ("ResourceRecordSets".equals(qName)) { 40 | inResourceRecordSets = false; 41 | } else if (qName.equals("ResourceRecordSet")) { 42 | rrsets.add(resourceRecordSetHandler.result()); 43 | resourceRecordSetHandler = new ResourceRecordSetHandler(); 44 | } else { 45 | resourceRecordSetHandler.endElement(uri, name, qName); 46 | } 47 | } else if (qName.equals("NextRecordName")) { 48 | next = new NextRecord(currentText.toString().trim()); 49 | } else if (qName.equals("NextRecordType")) { 50 | next.type = currentText.toString().trim(); 51 | } else if (qName.equals("NextRecordIdentifier")) { 52 | next.identifier = currentText.toString().trim(); 53 | } 54 | currentText.setLength(0); 55 | } 56 | 57 | @Override 58 | public void characters(char ch[], int start, int length) { 59 | if (inResourceRecordSets) { 60 | resourceRecordSetHandler.characters(ch, start, length); 61 | } else { 62 | currentText.append(ch, start, length); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /route53/src/main/java/denominator/route53/Route53Exception.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import feign.FeignException; 4 | 5 | class Route53Exception extends FeignException { 6 | 7 | private static final long serialVersionUID = 1L; 8 | private final String code; 9 | 10 | Route53Exception(String message, String code) { 11 | super(message); 12 | this.code = code; 13 | } 14 | 15 | /** 16 | * The error code. ex {@code InvalidInput} 17 | */ 18 | public String code() { 19 | return code; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /route53/src/main/java/denominator/route53/Route53ResourceRecordSetApi.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import java.util.Arrays; 4 | import java.util.Iterator; 5 | 6 | import javax.inject.Inject; 7 | 8 | import denominator.ResourceRecordSetApi; 9 | import denominator.model.ResourceRecordSet; 10 | 11 | import static denominator.common.Util.filter; 12 | import static denominator.model.ResourceRecordSets.alwaysVisible; 13 | import static denominator.route53.Route53.ActionOnResourceRecordSet.delete; 14 | 15 | public final class Route53ResourceRecordSetApi implements ResourceRecordSetApi { 16 | 17 | private final Route53AllProfileResourceRecordSetApi allApi; 18 | private final Route53 api; 19 | private final String zoneId; 20 | 21 | Route53ResourceRecordSetApi(Route53AllProfileResourceRecordSetApi allProfileResourceRecordSetApi, 22 | Route53 api, 23 | String zoneId) { 24 | this.allApi = allProfileResourceRecordSetApi; 25 | this.api = api; 26 | this.zoneId = zoneId; 27 | } 28 | 29 | @Override 30 | public Iterator> iterator() { 31 | return filter(allApi.iterator(), alwaysVisible()); 32 | } 33 | 34 | @Override 35 | public Iterator> iterateByName(String name) { 36 | return filter(allApi.iterateByName(name), alwaysVisible()); 37 | } 38 | 39 | @Override 40 | public ResourceRecordSet getByNameAndType(String name, String type) { 41 | ResourceRecordSet rrset = allApi.getByNameAndType(name, type); 42 | return alwaysVisible().apply(rrset) ? rrset : null; 43 | } 44 | 45 | @Override 46 | public void put(ResourceRecordSet rrset) { 47 | allApi.put(rrset); 48 | } 49 | 50 | @Override 51 | public void deleteByNameAndType(String name, String type) { 52 | ResourceRecordSet oldRRS = getByNameAndType(name, type); 53 | if (oldRRS == null) { 54 | return; 55 | } 56 | api.changeResourceRecordSets(zoneId, Arrays.asList(delete(oldRRS))); 57 | } 58 | 59 | static final class Factory implements denominator.ResourceRecordSetApi.Factory { 60 | 61 | private final Route53AllProfileResourceRecordSetApi.Factory allApi; 62 | private final Route53 api; 63 | 64 | @Inject 65 | Factory(Route53AllProfileResourceRecordSetApi.Factory allApi, Route53 api) { 66 | this.allApi = allApi; 67 | this.api = api; 68 | } 69 | 70 | @Override 71 | public ResourceRecordSetApi create(String id) { 72 | return new Route53ResourceRecordSetApi(allApi.create(id), api, id); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /route53/src/main/java/denominator/route53/Route53Target.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import java.util.Map.Entry; 4 | 5 | import javax.inject.Inject; 6 | 7 | import denominator.Provider; 8 | import feign.Request; 9 | import feign.RequestTemplate; 10 | import feign.Target; 11 | 12 | class Route53Target implements Target { 13 | 14 | private final Provider provider; 15 | private final InvalidatableAuthenticationHeadersProvider lazyAuthHeaders; 16 | 17 | @Inject 18 | Route53Target(Provider provider, InvalidatableAuthenticationHeadersProvider lazyAuthHeaders) { 19 | this.provider = provider; 20 | this.lazyAuthHeaders = lazyAuthHeaders; 21 | } 22 | 23 | @Override 24 | public Class type() { 25 | return Route53.class; 26 | } 27 | 28 | @Override 29 | public String name() { 30 | return provider.name(); 31 | } 32 | 33 | @Override 34 | public String url() { 35 | return provider.url(); 36 | } 37 | 38 | @Override 39 | public Request apply(RequestTemplate input) { 40 | if (input.url().indexOf("http") != 0) { 41 | input.insert(0, url()); 42 | } 43 | for (Entry entry : lazyAuthHeaders.get().entrySet()) { 44 | input.header(entry.getKey(), entry.getValue()); 45 | } 46 | return input.request(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /route53/src/main/resources/META-INF/services/denominator.Provider: -------------------------------------------------------------------------------- 1 | denominator.route53.Route53Provider 2 | -------------------------------------------------------------------------------- /route53/src/test/java/denominator/route53/HostedZonesReadableMockTest.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import com.squareup.okhttp.mockwebserver.MockResponse; 4 | 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | 8 | import denominator.DNSApiManager; 9 | 10 | import static org.junit.Assert.assertFalse; 11 | import static org.junit.Assert.assertTrue; 12 | 13 | public class HostedZonesReadableMockTest { 14 | 15 | @Rule 16 | public MockRoute53Server server = new MockRoute53Server(); 17 | 18 | @Test 19 | public void singleRequestOnSuccess() throws Exception { 20 | server.enqueue(new MockResponse().setBody("")); 21 | 22 | DNSApiManager api = server.connect(); 23 | assertTrue(api.checkConnection()); 24 | 25 | server.assertRequest() 26 | .hasMethod("GET") 27 | .hasPath("/2012-12-12/hostedzone"); 28 | } 29 | 30 | @Test 31 | public void singleRequestOnFailure() throws Exception { 32 | server.enqueue(new MockResponse().setResponseCode(403).setBody( 33 | "\n" 34 | + " \n" 35 | + " Sender\n" 36 | + " InvalidClientTokenId\n" 37 | + " The security token included in the request is invalid\n" 38 | + " \n" 39 | + " d3801bc8-f70d-11e2-8a6e-435ba83aa63f\n" 40 | + "")); 41 | 42 | DNSApiManager api = server.connect(); 43 | assertFalse(api.checkConnection()); 44 | 45 | server.assertRequest() 46 | .hasMethod("GET") 47 | .hasPath("/2012-12-12/hostedzone"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /route53/src/test/java/denominator/route53/MockRoute53Server.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import com.squareup.okhttp.mockwebserver.MockResponse; 4 | import com.squareup.okhttp.mockwebserver.MockWebServer; 5 | 6 | import org.junit.rules.TestRule; 7 | import org.junit.runner.Description; 8 | import org.junit.runners.model.Statement; 9 | 10 | import java.io.IOException; 11 | 12 | import denominator.Credentials; 13 | import denominator.CredentialsConfiguration; 14 | import denominator.DNSApiManager; 15 | import denominator.Denominator; 16 | import denominator.assertj.RecordedRequestAssert; 17 | 18 | import static denominator.Credentials.ListCredentials; 19 | import static denominator.assertj.MockWebServerAssertions.assertThat; 20 | 21 | final class MockRoute53Server extends Route53Provider implements TestRule { 22 | 23 | private final MockWebServer delegate = new MockWebServer(); 24 | private String accessKey = "accessKey"; 25 | private String secretKey = "secretKey"; 26 | private String token = null; 27 | 28 | MockRoute53Server() { 29 | credentials(accessKey, secretKey, token); 30 | } 31 | 32 | @Override 33 | public String url() { 34 | return "http://localhost:" + delegate.getPort(); 35 | } 36 | 37 | DNSApiManager connect() { 38 | return Denominator.create(this, CredentialsConfiguration.credentials(credentials())); 39 | } 40 | 41 | Credentials credentials() { 42 | return token == null ? ListCredentials.from(accessKey, secretKey) 43 | : ListCredentials.from(accessKey, secretKey, token); 44 | } 45 | 46 | MockRoute53Server credentials(String accessKey, String secretKey, String token) { 47 | this.accessKey = accessKey; 48 | this.secretKey = secretKey; 49 | this.token = token; 50 | return this; 51 | } 52 | 53 | void enqueue(MockResponse mockResponse) { 54 | delegate.enqueue(mockResponse); 55 | } 56 | 57 | RecordedRequestAssert assertRequest() throws InterruptedException { 58 | return assertThat(delegate.takeRequest()); 59 | } 60 | 61 | void shutdown() throws IOException { 62 | delegate.shutdown(); 63 | } 64 | 65 | @Override 66 | public Statement apply(Statement base, Description description) { 67 | return delegate.apply(base, description); 68 | } 69 | 70 | @dagger.Module(injects = DNSApiManager.class, complete = false, includes = 71 | Route53Provider.Module.class) 72 | static final class Module { 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /route53/src/test/java/denominator/route53/Route53CheckConnectionLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import denominator.CheckConnectionLiveTest; 4 | import denominator.Live.UseTestGraph; 5 | 6 | @UseTestGraph(Route53TestGraph.class) 7 | public class Route53CheckConnectionLiveTest extends CheckConnectionLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /route53/src/test/java/denominator/route53/Route53ReadOnlyLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.ReadOnlyLiveTest; 5 | 6 | @UseTestGraph(Route53TestGraph.class) 7 | public class Route53ReadOnlyLiveTest extends ReadOnlyLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /route53/src/test/java/denominator/route53/Route53RoundRobinWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.RoundRobinWriteCommandsLiveTest; 5 | 6 | @UseTestGraph(Route53TestGraph.class) 7 | public class Route53RoundRobinWriteCommandsLiveTest extends RoundRobinWriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /route53/src/test/java/denominator/route53/Route53TestGraph.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import denominator.DNSApiManagerFactory; 4 | import denominator.TestGraph; 5 | 6 | import static feign.Util.emptyToNull; 7 | import static java.lang.System.getProperty; 8 | 9 | public class Route53TestGraph extends TestGraph { 10 | 11 | private static final String url = emptyToNull(getProperty("route53.url")); 12 | private static final String zone = emptyToNull(getProperty("route53.zone")); 13 | 14 | public Route53TestGraph() { 15 | super(DNSApiManagerFactory.create(new Route53Provider(url)), zone); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /route53/src/test/java/denominator/route53/Route53WeightedReadOnlyLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.profile.WeightedReadOnlyLiveTest; 5 | 6 | @UseTestGraph(Route53TestGraph.class) 7 | public class Route53WeightedReadOnlyLiveTest extends WeightedReadOnlyLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /route53/src/test/java/denominator/route53/Route53WeightedWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.profile.WeightedWriteCommandsLiveTest; 5 | 6 | @UseTestGraph(Route53TestGraph.class) 7 | public class Route53WeightedWriteCommandsLiveTest extends WeightedWriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /route53/src/test/java/denominator/route53/Route53WriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.WriteCommandsLiveTest; 5 | 6 | @UseTestGraph(Route53TestGraph.class) 7 | public class Route53WriteCommandsLiveTest extends WriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /route53/src/test/java/denominator/route53/Route53ZoneWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.route53; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.ZoneWriteCommandsLiveTest; 5 | 6 | @UseTestGraph(Route53TestGraph.class) 7 | public class Route53ZoneWriteCommandsLiveTest extends ZoneWriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='denominator' 2 | 3 | include 'model', 'core', 'route53', 'ultradns', 'dynect', 'clouddns', 'designate', 'cli' 4 | 5 | rootProject.children.each { childProject -> 6 | childProject.name = 'denominator-' + childProject.name 7 | } 8 | -------------------------------------------------------------------------------- /ultradns/README.md: -------------------------------------------------------------------------------- 1 | ## Notable Behaviors 2 | The following are notable when compared to different providers. 3 | * `Zone.id()` is the `Zone.name()` 4 | * Zone lists are 1 + N requests in order to zip with the SOA's ttl and rname. 5 | * The default ttl for record sets is hard-coded to 300. 6 | * The zone's NS record set must contain at least 2 nsdnames. 7 | -------------------------------------------------------------------------------- /ultradns/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | sourceCompatibility = 1.6 4 | 5 | test { 6 | systemProperty 'ultradns.url', System.getProperty('ultradns.url', '') 7 | systemProperty 'ultradns.username', System.getProperty('ultradns.username', '') 8 | systemProperty 'ultradns.password', System.getProperty('ultradns.password', '') 9 | systemProperty 'ultradns.zone', System.getProperty('ultradns.zone', '') 10 | } 11 | 12 | dependencies { 13 | compile project(':denominator-core') 14 | compile 'com.netflix.feign:feign-core:8.10.0' 15 | compile 'com.netflix.feign:feign-sax:8.10.0' 16 | testCompile project(':denominator-model').sourceSets.test.output 17 | testCompile project(':denominator-core').sourceSets.test.output 18 | testCompile 'junit:junit:4.12' 19 | testCompile 'org.assertj:assertj-core:1.7.1' // last version supporting JDK 7 20 | testCompile 'com.squareup.okhttp:mockwebserver:2.5.0' 21 | } 22 | -------------------------------------------------------------------------------- /ultradns/src/main/java/denominator/ultradns/GroupByRecordNameAndTypeIterator.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import java.util.Iterator; 4 | import java.util.Map; 5 | 6 | import denominator.ResourceTypeToValue; 7 | import denominator.common.PeekingIterator; 8 | import denominator.model.ResourceRecordSet; 9 | import denominator.model.ResourceRecordSet.Builder; 10 | import denominator.ultradns.UltraDNS.Record; 11 | 12 | import static denominator.common.Util.peekingIterator; 13 | import static denominator.common.Util.toMap; 14 | 15 | class GroupByRecordNameAndTypeIterator implements Iterator> { 16 | 17 | private final PeekingIterator peekingIterator; 18 | 19 | public GroupByRecordNameAndTypeIterator(Iterator sortedIterator) { 20 | this.peekingIterator = peekingIterator(sortedIterator); 21 | } 22 | 23 | static boolean fqdnAndTypeEquals(Record actual, Record expected) { 24 | return actual.name.equals(expected.name) && actual.typeCode == expected.typeCode; 25 | } 26 | 27 | @Override 28 | public boolean hasNext() { 29 | return peekingIterator.hasNext(); 30 | } 31 | 32 | @Override 33 | public ResourceRecordSet next() { 34 | Record record = peekingIterator.next(); 35 | String type = ResourceTypeToValue.lookup(record.typeCode); 36 | Builder> builder = ResourceRecordSet.builder() 37 | .name(record.name) 38 | .type(type) 39 | .ttl(record.ttl); 40 | 41 | builder.add(toMap(type, record.rdata)); 42 | 43 | while (hasNext()) { 44 | Record next = peekingIterator.peek(); 45 | if (fqdnAndTypeEquals(next, record)) { 46 | peekingIterator.next(); 47 | builder.add(toMap(type, next.rdata)); 48 | } else { 49 | break; 50 | } 51 | } 52 | return builder.build(); 53 | } 54 | 55 | @Override 56 | public void remove() { 57 | throw new UnsupportedOperationException(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ultradns/src/main/java/denominator/ultradns/InvalidatableAccountIdSupplier.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Provider; 5 | import javax.inject.Singleton; 6 | 7 | import denominator.Credentials; 8 | 9 | /** 10 | * gets the last account id, expiring if the url or credentials changed 11 | */ 12 | // similar to guava MemoizingSupplier 13 | @Singleton 14 | class InvalidatableAccountIdSupplier { 15 | 16 | private final denominator.Provider provider; 17 | private final UltraDNS api; 18 | private final Provider credentials; 19 | transient volatile String lastUrl; 20 | transient volatile int lastCredentialsHashCode; 21 | transient volatile boolean initialized; 22 | transient String value; // "value" does not need to be volatile; visibility piggy-backs on above 23 | 24 | @Inject 25 | InvalidatableAccountIdSupplier(denominator.Provider provider, UltraDNS api, 26 | javax.inject.Provider credentials) { 27 | this.provider = provider; 28 | this.api = api; 29 | this.credentials = credentials; 30 | this.lastUrl = provider.url(); // for toString 31 | } 32 | 33 | public void invalidate() { 34 | initialized = false; 35 | } 36 | 37 | public String get() { 38 | String currentUrl = provider.url(); 39 | Credentials currentCreds = credentials.get(); 40 | 41 | if (needsRefresh(currentUrl, currentCreds)) { 42 | synchronized (this) { 43 | if (needsRefresh(currentUrl, currentCreds)) { 44 | lastCredentialsHashCode = currentCreds.hashCode(); 45 | lastUrl = currentUrl; 46 | String accountId = api.getAccountsListOfUser(); 47 | value = accountId; 48 | initialized = true; 49 | return accountId; 50 | } 51 | } 52 | } 53 | return value; 54 | } 55 | 56 | private boolean needsRefresh(String currentUrl, Credentials currentCreds) { 57 | return !initialized || currentCreds.hashCode() != lastCredentialsHashCode || !currentUrl 58 | .equals(lastUrl); 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return "InvalidatableAccountIdSupplier(" + lastUrl + ")"; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ultradns/src/main/java/denominator/ultradns/NetworkStatusReadable.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import javax.inject.Inject; 4 | 5 | import denominator.CheckConnection; 6 | import denominator.ultradns.UltraDNS.NetworkStatus; 7 | 8 | class NetworkStatusReadable implements CheckConnection { 9 | 10 | private final UltraDNS api; 11 | 12 | @Inject 13 | NetworkStatusReadable(UltraDNS api) { 14 | this.api = api; 15 | } 16 | 17 | @Override 18 | public boolean ok() { 19 | try { 20 | return NetworkStatus.GOOD == api.getNeustarNetworkStatus(); 21 | } catch (RuntimeException e) { 22 | return false; 23 | } 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "NetworkStatusReadable"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ultradns/src/main/java/denominator/ultradns/UltraDNSException.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import feign.FeignException; 4 | 5 | class UltraDNSException extends FeignException { 6 | 7 | /** 8 | * System Error 9 | */ 10 | static final int SYSTEM_ERROR = 9999; 11 | /** 12 | * Zone does not exist in the system. 13 | */ 14 | static final int ZONE_NOT_FOUND = 1801; 15 | /** 16 | * Zone already exists in the system. 17 | */ 18 | static final int ZONE_ALREADY_EXISTS = 1802; 19 | /** 20 | * No resource record with GUID found in the system. 21 | */ 22 | static final int RESOURCE_RECORD_NOT_FOUND = 2103; 23 | /** 24 | * Resource record exists with the same name and type. 25 | */ 26 | static final int RESOURCE_RECORD_ALREADY_EXISTS = 2111; 27 | 28 | // there are 51002 potential codes. These are the ones we are handling. 29 | /** 30 | * No Pool or Multiple pools of same type exists for the PoolName 31 | */ 32 | static final int DIRECTIONALPOOL_NOT_FOUND = 2142; 33 | /** 34 | * Invalid zone name 35 | */ 36 | static final int INVALID_ZONE_NAME = 2507; 37 | /** 38 | * Directional Pool Record does not exist in the system 39 | */ 40 | static final int DIRECTIONALPOOL_RECORD_NOT_FOUND = 2705; 41 | /** 42 | * Pool does not exist in the system. 43 | */ 44 | static final int POOL_NOT_FOUND = 2911; 45 | /** 46 | * Pool already created for the given rrGUID. 47 | */ 48 | static final int POOL_ALREADY_EXISTS = 2912; 49 | /** 50 | * Group does not exist. 51 | */ 52 | static final int GROUP_NOT_FOUND = 4003; 53 | /** 54 | * Directional feature not Enabled or Directional migration is not done. 55 | */ 56 | static final int DIRECTIONAL_NOT_ENABLED = 4006; 57 | /** 58 | * Resource Record already exists. 59 | */ 60 | static final int POOL_RECORD_ALREADY_EXISTS = 4009; 61 | private static final long serialVersionUID = 1L; 62 | private final int code; 63 | 64 | UltraDNSException(String message, int code) { 65 | super(message); 66 | this.code = code; 67 | } 68 | 69 | /** 70 | * The error code. ex {@code 1801} 71 | */ 72 | public int code() { 73 | return code; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /ultradns/src/main/java/denominator/ultradns/UltraDNSFilters.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import java.io.Serializable; 4 | 5 | import denominator.common.Filter; 6 | import denominator.ultradns.UltraDNS.Record; 7 | 8 | import static denominator.common.Preconditions.checkNotNull; 9 | 10 | final class UltraDNSFilters { 11 | 12 | private UltraDNSFilters() { 13 | } 14 | 15 | public static Filter resourceTypeEqualTo(int typeValue) { 16 | return new ResourceTypeEqualToPredicate(typeValue); 17 | } 18 | 19 | public static Filter recordIdEqualTo(String id) { 20 | return new RecordIdEqualToPredicate(id); 21 | } 22 | 23 | /** 24 | * @see UltraDNSFilters#resourceTypeEqualTo(int) 25 | */ 26 | private static class ResourceTypeEqualToPredicate implements Filter, Serializable { 27 | 28 | private static final long serialVersionUID = 0; 29 | private final int typeValue; 30 | 31 | private ResourceTypeEqualToPredicate(int typeValue) { 32 | this.typeValue = checkNotNull(typeValue, "typeValue"); 33 | } 34 | 35 | @Override 36 | public boolean apply(Record in) { 37 | return typeValue == in.typeCode; 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "ResourceTypeEqualTo(" + typeValue + ")"; 43 | } 44 | } 45 | 46 | private static class RecordIdEqualToPredicate implements Filter, Serializable { 47 | 48 | private static final long serialVersionUID = 0; 49 | private final String id; 50 | 51 | private RecordIdEqualToPredicate(String id) { 52 | this.id = checkNotNull(id, "id"); 53 | } 54 | 55 | @Override 56 | public boolean apply(Record in) { 57 | return id.equals(in.id); 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "RecordIdEqualTo(" + id + ")"; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ultradns/src/main/java/denominator/ultradns/UltraDNSGeoSupport.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | 6 | import javax.inject.Named; 7 | 8 | import dagger.Module; 9 | import dagger.Provides; 10 | 11 | @Module(injects = UltraDNSGeoResourceRecordSetApi.Factory.class, complete = false) 12 | public class UltraDNSGeoSupport { 13 | 14 | @Provides 15 | @Named("geo") 16 | Map> regions(UltraDNS api) { 17 | return api.getAvailableRegions(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ultradns/src/main/java/denominator/ultradns/UltraDNSRoundRobinPoolApi.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import denominator.ultradns.UltraDNS.NameAndType; 7 | 8 | import static denominator.ResourceTypeToValue.lookup; 9 | import static denominator.common.Preconditions.checkNotNull; 10 | import static denominator.common.Preconditions.checkState; 11 | 12 | class UltraDNSRoundRobinPoolApi { 13 | 14 | private final UltraDNS api; 15 | private final String zoneName; 16 | 17 | UltraDNSRoundRobinPoolApi(UltraDNS api, String zoneName) { 18 | this.api = api; 19 | this.zoneName = zoneName; 20 | } 21 | 22 | boolean isPoolType(String type) { 23 | return type.equals("A") || type.equals("AAAA"); 24 | } 25 | 26 | void add(String name, String type, int ttl, List> rdatas) { 27 | checkState(isPoolType(type), "%s not A or AAAA type", type); 28 | String poolId = reuseOrCreatePoolForNameAndType(name, type); 29 | for (Map rdata : rdatas) { 30 | String address = rdata.get("address").toString(); 31 | int typeCode = lookup(type); 32 | api.addRecordToRRPool(typeCode, ttl, address, poolId, zoneName); 33 | } 34 | } 35 | 36 | private String reuseOrCreatePoolForNameAndType(String name, String type) { 37 | try { 38 | return api.addRRLBPool(zoneName, name, lookup(type)); 39 | } catch (UltraDNSException e) { 40 | if (e.code() != UltraDNSException.POOL_ALREADY_EXISTS) { 41 | throw e; 42 | } 43 | return getPoolByNameAndType(name, type); 44 | } 45 | } 46 | 47 | String getPoolByNameAndType(String name, String type) { 48 | NameAndType nameAndType = new NameAndType(); 49 | nameAndType.name = name; 50 | nameAndType.type = type; 51 | return api.getLoadBalancingPoolsByZone(zoneName).get(nameAndType); 52 | } 53 | 54 | void deletePool(String name, String type) { 55 | NameAndType nameAndType = new NameAndType(); 56 | nameAndType.name = checkNotNull(name, "pool name was null"); 57 | nameAndType.type = checkNotNull(type, "pool record type was null"); 58 | String poolId = api.getLoadBalancingPoolsByZone(zoneName).get(nameAndType); 59 | if (poolId != null) { 60 | if (api.getRRPoolRecords(poolId).isEmpty()) { 61 | try { 62 | api.deleteLBPool(poolId); 63 | } catch (UltraDNSException e) { 64 | switch (e.code()) { 65 | // lost race 66 | case UltraDNSException.POOL_NOT_FOUND: 67 | case UltraDNSException.RESOURCE_RECORD_NOT_FOUND: 68 | return; 69 | } 70 | throw e; 71 | } 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ultradns/src/main/resources/META-INF/services/denominator.Provider: -------------------------------------------------------------------------------- 1 | denominator.ultradns.UltraDNSProvider 2 | -------------------------------------------------------------------------------- /ultradns/src/test/java/denominator/ultradns/NetworkStatusReadableMockTest.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import com.squareup.okhttp.mockwebserver.MockResponse; 4 | 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | 8 | import denominator.DNSApiManager; 9 | 10 | import static denominator.ultradns.UltraDNSTest.getNeustarNetworkStatus; 11 | import static denominator.ultradns.UltraDNSTest.getNeustarNetworkStatusResponse; 12 | import static org.junit.Assert.assertFalse; 13 | import static org.junit.Assert.assertTrue; 14 | 15 | public class NetworkStatusReadableMockTest { 16 | 17 | @Rule 18 | public final MockUltraDNSServer server = new MockUltraDNSServer(); 19 | 20 | @Test 21 | public void singleRequestOnSuccess() throws Exception { 22 | server.enqueue(new MockResponse().setBody(getNeustarNetworkStatusResponse)); 23 | 24 | DNSApiManager api = server.connect(); 25 | assertTrue(api.checkConnection()); 26 | 27 | server.assertSoapBody(getNeustarNetworkStatus); 28 | } 29 | 30 | @Test 31 | public void singleRequestOnFailure() throws Exception { 32 | server.enqueue(new MockResponse().setResponseCode(500)); 33 | 34 | DNSApiManager api = server.connect(); 35 | assertFalse(api.checkConnection()); 36 | 37 | server.assertSoapBody(getNeustarNetworkStatus); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ultradns/src/test/java/denominator/ultradns/UltraDNSCheckConnectionLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import denominator.CheckConnectionLiveTest; 4 | import denominator.Live.UseTestGraph; 5 | 6 | @UseTestGraph(UltraDNSTestGraph.class) 7 | public class UltraDNSCheckConnectionLiveTest extends CheckConnectionLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /ultradns/src/test/java/denominator/ultradns/UltraDNSConnection.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import denominator.DNSApiManager; 8 | import denominator.Denominator; 9 | import feign.Logger; 10 | 11 | import static denominator.CredentialsConfiguration.credentials; 12 | import static feign.Util.emptyToNull; 13 | import static java.lang.System.getProperty; 14 | 15 | public class UltraDNSConnection { 16 | 17 | final DNSApiManager manager; 18 | final String mutableZone; 19 | 20 | UltraDNSConnection() { 21 | String username = emptyToNull(getProperty("ultradns.username")); 22 | String password = emptyToNull(getProperty("ultradns.password")); 23 | if (username != null && password != null) { 24 | manager = create(username, password); 25 | } else { 26 | manager = null; 27 | } 28 | mutableZone = emptyToNull(getProperty("ultradns.zone")); 29 | } 30 | 31 | static DNSApiManager create(String username, String password) { 32 | UltraDNSProvider provider = new UltraDNSProvider(emptyToNull(getProperty("ultradns.url"))); 33 | return Denominator.create(provider, credentials(username, password), new Overrides()); 34 | } 35 | 36 | @Module(overrides = true, library = true) 37 | static class Overrides { 38 | 39 | @Provides 40 | @Singleton 41 | Logger.Level provideLevel() { 42 | return Logger.Level.FULL; 43 | } 44 | 45 | @Provides 46 | @Singleton 47 | Logger provideLogger() { 48 | return new Logger.JavaLogger().appendToFile("build/http-wire.log"); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ultradns/src/test/java/denominator/ultradns/UltraDNSErrorDecoderTest.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import java.util.Collection; 8 | import java.util.Collections; 9 | 10 | import feign.FeignException; 11 | import feign.Response; 12 | import feign.RetryableException; 13 | import feign.codec.ErrorDecoder; 14 | 15 | import static denominator.ultradns.MockUltraDNSServer.FAULT_TEMPLATE; 16 | import static denominator.ultradns.UltraDNSException.SYSTEM_ERROR; 17 | import static feign.Util.UTF_8; 18 | import static java.lang.String.format; 19 | 20 | /** 21 | * Error decode tests not implicitly tested in {@linkplain denominator.ultradns.UltraDNSTest}. 22 | */ 23 | public class UltraDNSErrorDecoderTest { 24 | 25 | @Rule 26 | public final ExpectedException thrown = ExpectedException.none(); 27 | 28 | ErrorDecoder errors = new UltraDNSErrorDecoder(UltraDNSProvider.FeignModule.decoder()); 29 | 30 | static Response errorResponse(String body) { 31 | return Response 32 | .create(500, "Server Error", Collections.>emptyMap(), body, 33 | UTF_8); 34 | } 35 | 36 | @Test 37 | public void noBody() throws Exception { 38 | thrown.expect(FeignException.class); 39 | thrown.expectMessage("status 500 reading UltraDNS#accountId()"); 40 | 41 | throw errors.decode("UltraDNS#accountId()", errorResponse(null)); 42 | } 43 | 44 | @Test 45 | public void systemError() throws Exception { 46 | thrown.expect(RetryableException.class); 47 | thrown.expectMessage("UltraDNS#networkStatus() failed with error 9999: System Error"); 48 | throw errors.decode("UltraDNS#networkStatus()", 49 | errorResponse(format(FAULT_TEMPLATE, SYSTEM_ERROR, "System Error"))); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ultradns/src/test/java/denominator/ultradns/UltraDNSGeoReadOnlyLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.profile.GeoReadOnlyLiveTest; 5 | 6 | @UseTestGraph(UltraDNSTestGraph.class) 7 | public class UltraDNSGeoReadOnlyLiveTest extends GeoReadOnlyLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /ultradns/src/test/java/denominator/ultradns/UltraDNSGeoWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.profile.GeoWriteCommandsLiveTest; 5 | 6 | @UseTestGraph(UltraDNSTestGraph.class) 7 | public class UltraDNSGeoWriteCommandsLiveTest extends GeoWriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /ultradns/src/test/java/denominator/ultradns/UltraDNSPredicatesTest.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import org.junit.Test; 4 | 5 | import denominator.ultradns.UltraDNS.Record; 6 | 7 | import static org.junit.Assert.assertFalse; 8 | import static org.junit.Assert.assertTrue; 9 | 10 | public class UltraDNSPredicatesTest { 11 | 12 | Record a; 13 | 14 | public UltraDNSPredicatesTest() { 15 | a = new Record(); 16 | a.id = "AAAAAAAAAAAA"; 17 | a.name = "foo.com."; 18 | a.typeCode = 1; 19 | a.ttl = 3600; 20 | a.rdata.add("192.0.2.1"); 21 | } 22 | 23 | @Test 24 | public void resourceTypeEqualToFalseOnDifferentType() { 25 | assertFalse(UltraDNSFilters.resourceTypeEqualTo(28).apply(a)); 26 | } 27 | 28 | @Test 29 | public void resourceTypeEqualToTrueOnSameType() { 30 | assertTrue(UltraDNSFilters.resourceTypeEqualTo(a.typeCode).apply(a)); 31 | } 32 | 33 | @Test 34 | public void recordIdEqualToFalseOnDifferentId() { 35 | assertFalse(UltraDNSFilters.recordIdEqualTo("BBBBBBBBBBBB").apply(a)); 36 | } 37 | 38 | @Test 39 | public void recordIdEqualToTrueOnSameId() { 40 | assertTrue(UltraDNSFilters.recordIdEqualTo(a.id).apply(a)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ultradns/src/test/java/denominator/ultradns/UltraDNSReadOnlyLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.ReadOnlyLiveTest; 5 | 6 | @UseTestGraph(UltraDNSTestGraph.class) 7 | public class UltraDNSReadOnlyLiveTest extends ReadOnlyLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /ultradns/src/test/java/denominator/ultradns/UltraDNSRoundRobinWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.RoundRobinWriteCommandsLiveTest; 5 | 6 | @UseTestGraph(UltraDNSTestGraph.class) 7 | public class UltraDNSRoundRobinWriteCommandsLiveTest extends RoundRobinWriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /ultradns/src/test/java/denominator/ultradns/UltraDNSTestGraph.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import denominator.DNSApiManagerFactory; 4 | 5 | import static feign.Util.emptyToNull; 6 | import static java.lang.System.getProperty; 7 | 8 | public class UltraDNSTestGraph extends denominator.TestGraph { 9 | 10 | private static final String url = emptyToNull(getProperty("ultradns.url")); 11 | private static final String zone = emptyToNull(getProperty("ultradns.zone")); 12 | 13 | public UltraDNSTestGraph() { 14 | super(DNSApiManagerFactory.create(new UltraDNSProvider(url)), zone); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ultradns/src/test/java/denominator/ultradns/UltraDNSWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.WriteCommandsLiveTest; 5 | 6 | @UseTestGraph(UltraDNSTestGraph.class) 7 | public class UltraDNSWriteCommandsLiveTest extends WriteCommandsLiveTest { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /ultradns/src/test/java/denominator/ultradns/UltraDNSZoneWriteCommandsLiveTest.java: -------------------------------------------------------------------------------- 1 | package denominator.ultradns; 2 | 3 | import denominator.Live.UseTestGraph; 4 | import denominator.ZoneWriteCommandsLiveTest; 5 | 6 | @UseTestGraph(UltraDNSTestGraph.class) 7 | public class UltraDNSZoneWriteCommandsLiveTest extends ZoneWriteCommandsLiveTest { 8 | 9 | } 10 | --------------------------------------------------------------------------------