diff --git a/gcloud-java-dns/pom.xml b/gcloud-java-dns/pom.xml
new file mode 100644
index 000000000000..5f04f261d500
--- /dev/null
+++ b/gcloud-java-dns/pom.xml
@@ -0,0 +1,56 @@
+
+
+ 4.0.0
+ com.google.gcloud
+ gcloud-java-dns
+ jar
+ GCloud Java DNS
+
+ Java idiomatic client for Google Cloud DNS.
+
+
+ com.google.gcloud
+ gcloud-java-pom
+ 0.1.3-SNAPSHOT
+
+
+ gcloud-java-dns
+
+
+
+ ${project.groupId}
+ gcloud-java-core
+ ${project.version}
+
+
+ com.google.apis
+ google-api-services-dns
+ v1-rev7-1.21.0
+ compile
+
+
+ com.google.guava
+ guava-jdk5
+
+
+ com.google.api-client
+ google-api-client
+
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ org.easymock
+ easymock
+ 3.3
+ test
+
+
+
diff --git a/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsRecord.java b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsRecord.java
new file mode 100644
index 000000000000..c41e72a77400
--- /dev/null
+++ b/gcloud-java-dns/src/main/java/com/google/gcloud/dns/DnsRecord.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.gcloud.dns;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A class that represents a Google Cloud DNS record set.
+ *
+ *
A {@code DnsRecord} is the unit of data that will be returned by the DNS servers upon a DNS
+ * request for a specific domain. The {@code DnsRecord} holds the current state of the DNS records
+ * that make up a managed zone. You can read the records but you cannot modify them directly.
+ * Rather, you edit the records in a managed zone by creating a ChangeRequest.
+ *
+ * @see Google Cloud DNS
+ * documentation
+ */
+public class DnsRecord implements Serializable {
+
+ private static final long serialVersionUID = 2016011914302204L;
+ private final String name;
+ private final List rrdatas;
+ private final Integer ttl;
+ private final Type type;
+
+ /**
+ * Enum for the DNS record types supported by Cloud DNS.
+ *
+ * Google Cloud DNS currently supports records of type A, AAAA, CNAME, MX NAPTR, NS, PTR, SOA,
+ * SPF, SRV, TXT.
+ *
+ * @see Cloud DNS
+ * supported record types
+ */
+ public enum Type {
+ /**
+ * Address record, which is used to map host names to their IPv4 address.
+ */
+ A,
+ /**
+ * IPv6 Address record, which is used to map host names to their IPv6 address.
+ */
+ AAAA,
+ /**
+ * Canonical name record, which is used to alias names.
+ */
+ CNAME,
+ /**
+ * Mail exchange record, which is used in routing requests to mail servers.
+ */
+ MX,
+ /**
+ * Naming authority pointer record, defined by RFC3403.
+ */
+ NAPTR,
+ /**
+ * Name server record, which delegates a DNS zone to an authoritative server.
+ */
+ NS,
+ /**
+ * Pointer record, which is often used for reverse DNS lookups.
+ */
+ PTR,
+ /**
+ * Start of authority record, which specifies authoritative information about a DNS zone.
+ */
+ SOA,
+ /**
+ * Sender policy framework record, which is used in email validation systems.
+ */
+ SPF,
+ /**
+ * Service locator record, which is used by some voice over IP, instant messaging protocols and
+ * other applications.
+ */
+ SRV,
+ /**
+ * Text record, which can contain arbitrary text and can also be used to define machine readable
+ * data such as security or abuse prevention information.
+ */
+ TXT
+ }
+
+ /**
+ * A builder of {@link DnsRecord}.
+ */
+ public static class Builder {
+
+ private List rrdatas = new LinkedList<>();
+ private String name;
+ private Integer ttl;
+ private Type type;
+
+ private Builder(String name, Type type) {
+ this.name = checkNotNull(name);
+ this.type = checkNotNull(type);
+ }
+
+ /**
+ * Creates a builder and pre-populates attributes with the values from the provided {@code
+ * DnsRecord} instance.
+ */
+ private Builder(DnsRecord record) {
+ this.name = record.name;
+ this.ttl = record.ttl;
+ this.type = record.type;
+ this.rrdatas.addAll(record.rrdatas);
+ }
+
+ /**
+ * Adds a record to the record set. The records should be as defined in RFC 1035 (section 5) and
+ * RFC 1034 (section 3.6.1). Examples of records are available in Google DNS documentation.
+ *
+ * @see Google
+ * DNS documentation .
+ */
+ public Builder addRecord(String record) {
+ this.rrdatas.add(checkNotNull(record));
+ return this;
+ }
+
+ /**
+ * Removes a record from the set. An exact match is required.
+ */
+ public Builder removeRecord(String record) {
+ this.rrdatas.remove(checkNotNull(record));
+ return this;
+ }
+
+ /**
+ * Removes all the records.
+ */
+ public Builder clearRecords() {
+ this.rrdatas.clear();
+ return this;
+ }
+
+ /**
+ * Replaces the current records with the provided list of records.
+ */
+ public Builder records(List records) {
+ this.rrdatas = Lists.newLinkedList(checkNotNull(records));
+ return this;
+ }
+
+ /**
+ * Sets name for this DNS record set. For example, www.example.com.
+ */
+ public Builder name(String name) {
+ this.name = checkNotNull(name);
+ return this;
+ }
+
+ /**
+ * Sets the number of seconds that this record can be cached by resolvers. This number must be
+ * non-negative.
+ *
+ * @param ttl A non-negative number of seconds
+ */
+ public Builder ttl(int ttl) {
+ checkArgument(ttl >= 0, "TTL cannot be negative. The supplied value was %s.", ttl);
+ this.ttl = ttl;
+ return this;
+ }
+
+ /**
+ * The identifier of a supported record type, for example, A, AAAA, MX, TXT, and so on.
+ */
+ public Builder type(Type type) {
+ this.type = checkNotNull(type);
+ return this;
+ }
+
+ /**
+ * Builds the DNS record.
+ */
+ public DnsRecord build() {
+ return new DnsRecord(this);
+ }
+ }
+
+ private DnsRecord(Builder builder) {
+ this.name = builder.name;
+ this.rrdatas = ImmutableList.copyOf(builder.rrdatas);
+ this.ttl = builder.ttl;
+ this.type = builder.type;
+ }
+
+ /**
+ * Creates a builder pre-populated with the attribute values of this instance.
+ */
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
+ /**
+ * Creates a {@code DnsRecord} builder for the given {@code name} and {@code type}.
+ */
+ public static Builder builder(String name, Type type) {
+ return new Builder(name, type);
+ }
+
+ /**
+ * Returns the user-assigned name of this DNS record.
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * Returns a list of DNS record stored in this record set.
+ */
+ public List records() {
+ return rrdatas;
+ }
+
+ /**
+ * Returns the number of seconds that this DnsResource can be cached by resolvers.
+ */
+ public Integer ttl() {
+ return ttl;
+ }
+
+ /**
+ * Returns the type of this DNS record.
+ */
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, rrdatas, ttl, type);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof DnsRecord) && Objects.equals(this.toPb(), ((DnsRecord) obj).toPb());
+ }
+
+ com.google.api.services.dns.model.ResourceRecordSet toPb() {
+ com.google.api.services.dns.model.ResourceRecordSet pb =
+ new com.google.api.services.dns.model.ResourceRecordSet();
+ pb.setName(this.name());
+ pb.setRrdatas(this.records());
+ pb.setTtl(this.ttl());
+ pb.setType(this.type().name());
+ return pb;
+ }
+
+ static DnsRecord fromPb(com.google.api.services.dns.model.ResourceRecordSet pb) {
+ Builder b = builder(pb.getName(), Type.valueOf(pb.getType()));
+ if (pb.getRrdatas() != null) {
+ b.records(pb.getRrdatas());
+ }
+ if (pb.getTtl() != null) {
+ b.ttl(pb.getTtl());
+ }
+ return b.build();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("name", name())
+ .add("rrdatas", records())
+ .add("ttl", ttl())
+ .add("type", type())
+ .toString();
+ }
+}
diff --git a/gcloud-java-dns/src/test/java/com/google/gcloud/dns/DnsRecordTest.java b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/DnsRecordTest.java
new file mode 100644
index 000000000000..43ced20cf207
--- /dev/null
+++ b/gcloud-java-dns/src/test/java/com/google/gcloud/dns/DnsRecordTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.gcloud.dns;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertNotEquals;
+
+import org.junit.Test;
+
+public class DnsRecordTest {
+
+ private static final String NAME = "example.com.";
+ private static final Integer TTL = 3600;
+ private static final DnsRecord.Type TYPE = DnsRecord.Type.AAAA;
+ private static final DnsRecord record = DnsRecord.builder(NAME, TYPE)
+ .ttl(TTL)
+ .build();
+
+ @Test
+ public void testDefaultDnsRecord() {
+ DnsRecord record = DnsRecord.builder(NAME, TYPE).build();
+ assertEquals(0, record.records().size());
+ assertEquals(TYPE, record.type());
+ assertEquals(NAME, record.name());
+ }
+
+ @Test
+ public void testBuilder() {
+ assertEquals(NAME, record.name());
+ assertEquals(TTL, record.ttl());
+ assertEquals(TYPE, record.type());
+ assertEquals(0, record.records().size());
+ // verify that one can add records to the record set
+ String testingRecord = "Testing record";
+ String anotherTestingRecord = "Another record 123";
+ DnsRecord anotherRecord = record.toBuilder()
+ .addRecord(testingRecord)
+ .addRecord(anotherTestingRecord)
+ .build();
+ assertEquals(2, anotherRecord.records().size());
+ assertTrue(anotherRecord.records().contains(testingRecord));
+ assertTrue(anotherRecord.records().contains(anotherTestingRecord));
+ }
+
+ @Test
+ public void testValidTtl() {
+ try {
+ DnsRecord.builder(NAME, TYPE).ttl(-1);
+ fail("A negative value is not acceptable for ttl.");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ DnsRecord.builder(NAME, TYPE).ttl(0);
+ DnsRecord.builder(NAME, TYPE).ttl(Integer.MAX_VALUE);
+ }
+
+ @Test
+ public void testEqualsAndNotEquals() {
+ DnsRecord clone = record.toBuilder().build();
+ assertEquals(clone, record);
+ clone = record.toBuilder().addRecord("another record").build();
+ assertNotEquals(clone, record);
+ String differentName = "totally different name";
+ clone = record.toBuilder().name(differentName).build();
+ assertNotEquals(clone, record);
+ clone = record.toBuilder().ttl(record.ttl() + 1).build();
+ assertNotEquals(clone, record);
+ clone = record.toBuilder().type(DnsRecord.Type.TXT).build();
+ assertNotEquals(clone, record);
+ }
+
+ @Test
+ public void testSameHashCodeOnEquals() {
+ int hash = record.hashCode();
+ DnsRecord clone = record.toBuilder().build();
+ assertEquals(clone.hashCode(), hash);
+ }
+
+ @Test
+ public void testToAndFromPb() {
+ assertEquals(record, DnsRecord.fromPb(record.toPb()));
+ DnsRecord partial = DnsRecord.builder(NAME, TYPE).build();
+ assertEquals(partial, DnsRecord.fromPb(partial.toPb()));
+ partial = DnsRecord.builder(NAME, TYPE).addRecord("test").build();
+ assertEquals(partial, DnsRecord.fromPb(partial.toPb()));
+ partial = DnsRecord.builder(NAME, TYPE).ttl(15).build();
+ assertEquals(partial, DnsRecord.fromPb(partial.toPb()));
+ }
+
+ @Test
+ public void testToBuilder() {
+ assertEquals(record, record.toBuilder().build());
+ DnsRecord partial = DnsRecord.builder(NAME, TYPE).build();
+ assertEquals(partial, partial.toBuilder().build());
+ partial = DnsRecord.builder(NAME, TYPE).addRecord("test").build();
+ assertEquals(partial, partial.toBuilder().build());
+ partial = DnsRecord.builder(NAME, TYPE).ttl(15).build();
+ assertEquals(partial, partial.toBuilder().build());
+ }
+
+ @Test
+ public void clearRecordSet() {
+ // make sure that we are starting not empty
+ DnsRecord clone = record.toBuilder().addRecord("record").addRecord("another").build();
+ assertNotEquals(0, clone.records().size());
+ clone = clone.toBuilder().clearRecords().build();
+ assertEquals(0, clone.records().size());
+ clone.toPb(); // verify that pb allows it
+ }
+
+ @Test
+ public void removeFromRecordSet() {
+ String recordString = "record";
+ // make sure that we are starting not empty
+ DnsRecord clone = record.toBuilder().addRecord(recordString).build();
+ assertNotEquals(0, clone.records().size());
+ clone = clone.toBuilder().removeRecord(recordString).build();
+ assertEquals(0, clone.records().size());
+ }
+}
diff --git a/pom.xml b/pom.xml
index cd5c19720cd8..7a435cc1dbae 100644
--- a/pom.xml
+++ b/pom.xml
@@ -70,6 +70,7 @@
gcloud-java-bigquery
gcloud-java-core
gcloud-java-datastore
+ gcloud-java-dns
gcloud-java-examples
gcloud-java-resourcemanager
gcloud-java-storage