mirror of
https://github.com/google/nomulus.git
synced 2025-05-19 18:59:35 +02:00
Make DnsWriter truly atomic
Right now - if there's an error during DnsWriter.publish*, all the publish from before that error will be committed, while all the publish after that error will not. More than that - in some writers partial publishes can be committed, depending on implementation. This defines a new contract that publish* are only committed when .commit is called. That way any error will simply mean no publish is committed. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=165708063
This commit is contained in:
parent
fcb554947c
commit
d5ac03aae4
8 changed files with 204 additions and 90 deletions
|
@ -14,6 +14,7 @@
|
|||
|
||||
package google.registry.dns.writer.dnsupdate;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static com.google.common.collect.Sets.intersection;
|
||||
import static com.google.common.collect.Sets.union;
|
||||
|
@ -26,6 +27,7 @@ import com.google.common.collect.ImmutableSet;
|
|||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.dns.writer.DnsWriter;
|
||||
import google.registry.dns.writer.DnsWriterZone;
|
||||
import google.registry.model.domain.DomainResource;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.host.HostResource;
|
||||
|
@ -54,9 +56,11 @@ import org.xbill.DNS.Update;
|
|||
* A DnsWriter that implements the DNS UPDATE protocol as specified in
|
||||
* <a href="https://tools.ietf.org/html/rfc2136">RFC 2136</a>. Publishes changes in the
|
||||
* domain-registry to a (capable) external DNS server, sometimes called a "hidden master". DNS
|
||||
* UPDATE messages are sent via a supplied "transport" class. For each publish call, a single
|
||||
* UPDATE message is created containing the records required to "synchronize" the DNS with the
|
||||
* current (at the time of processing) state of the registry, for the supplied domain/host.
|
||||
* UPDATE messages are sent via a supplied "transport" class.
|
||||
*
|
||||
* On call to {@link #commit()}, a single UPDATE message is created containing the records required
|
||||
* to "synchronize" the DNS with the current (at the time of processing) state of the registry, for
|
||||
* the supplied domain/host.
|
||||
*
|
||||
* <p>The general strategy of the publish methods is to delete <em>all</em> resource records of any
|
||||
* <em>type</em> that match the exact domain/host name supplied. And then for create/update cases,
|
||||
|
@ -67,11 +71,10 @@ import org.xbill.DNS.Update;
|
|||
* <p>Only NS, DS, A, and AAAA records are published, and in particular no DNSSEC signing is done
|
||||
* assuming that this will be done by a third party DNS provider.
|
||||
*
|
||||
* <p>Each publish call is treated as an atomic update to the DNS. If an update fails an exception
|
||||
* is thrown, expecting the caller to retry the update later. The SOA record serial number is
|
||||
* implicitly incremented by the server on each UPDATE message, as required by RFC 2136. Care must
|
||||
* be taken to make sure the SOA serial number does not go backwards if the entire TLD (zone) is
|
||||
* "reset" to empty and republished.
|
||||
* <p>Each commit call is treated as an atomic update to the DNS. If a commit fails an exception
|
||||
* is thrown. The SOA record serial number is implicitly incremented by the server on each UPDATE
|
||||
* message, as required by RFC 2136. Care must be taken to make sure the SOA serial number does not
|
||||
* go backwards if the entire TLD (zone) is "reset" to empty and republished.
|
||||
*/
|
||||
public class DnsUpdateWriter implements DnsWriter {
|
||||
|
||||
|
@ -86,6 +89,10 @@ public class DnsUpdateWriter implements DnsWriter {
|
|||
private final Duration dnsDefaultDsTtl;
|
||||
private final DnsMessageTransport transport;
|
||||
private final Clock clock;
|
||||
private final Update update;
|
||||
private final String zoneName;
|
||||
|
||||
private boolean committed = false;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
|
@ -96,11 +103,14 @@ public class DnsUpdateWriter implements DnsWriter {
|
|||
*/
|
||||
@Inject
|
||||
public DnsUpdateWriter(
|
||||
@DnsWriterZone String zoneName,
|
||||
@Config("dnsDefaultATtl") Duration dnsDefaultATtl,
|
||||
@Config("dnsDefaultNsTtl") Duration dnsDefaultNsTtl,
|
||||
@Config("dnsDefaultDsTtl") Duration dnsDefaultDsTtl,
|
||||
DnsMessageTransport transport,
|
||||
Clock clock) {
|
||||
this.zoneName = zoneName;
|
||||
this.update = new Update(toAbsoluteName(zoneName));
|
||||
this.dnsDefaultATtl = dnsDefaultATtl;
|
||||
this.dnsDefaultNsTtl = dnsDefaultNsTtl;
|
||||
this.dnsDefaultDsTtl = dnsDefaultDsTtl;
|
||||
|
@ -118,26 +128,15 @@ public class DnsUpdateWriter implements DnsWriter {
|
|||
*/
|
||||
private void publishDomain(String domainName, String requestingHostName) {
|
||||
DomainResource domain = loadByForeignKey(DomainResource.class, domainName, clock.nowUtc());
|
||||
try {
|
||||
Update update = new Update(toAbsoluteName(findTldFromName(domainName)));
|
||||
update.delete(toAbsoluteName(domainName), Type.ANY);
|
||||
if (domain != null) {
|
||||
// As long as the domain exists, orphan glues should be cleaned.
|
||||
deleteSubordinateHostAddressSet(domain, requestingHostName, update);
|
||||
if (domain.shouldPublishToDns()) {
|
||||
addInBailiwickNameServerSet(domain, update);
|
||||
update.add(makeNameServerSet(domain));
|
||||
update.add(makeDelegationSignerSet(domain));
|
||||
}
|
||||
update.delete(toAbsoluteName(domainName), Type.ANY);
|
||||
if (domain != null) {
|
||||
// As long as the domain exists, orphan glues should be cleaned.
|
||||
deleteSubordinateHostAddressSet(domain, requestingHostName, update);
|
||||
if (domain.shouldPublishToDns()) {
|
||||
addInBailiwickNameServerSet(domain, update);
|
||||
update.add(makeNameServerSet(domain));
|
||||
update.add(makeDelegationSignerSet(domain));
|
||||
}
|
||||
Message response = transport.send(update);
|
||||
verify(
|
||||
response.getRcode() == Rcode.NOERROR,
|
||||
"DNS server failed domain update for '%s' rcode: %s",
|
||||
domainName,
|
||||
Rcode.string(response.getRcode()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("publishDomain failed: " + domainName, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,13 +167,24 @@ public class DnsUpdateWriter implements DnsWriter {
|
|||
publishDomain(domain, hostName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing. Publish calls are synchronous and atomic.
|
||||
*/
|
||||
@Override
|
||||
public void close() {}
|
||||
public void commit() {
|
||||
checkState(!committed, "commit() has already been called");
|
||||
committed = true;
|
||||
|
||||
private RRset makeDelegationSignerSet(DomainResource domain) throws TextParseException {
|
||||
try {
|
||||
Message response = transport.send(update);
|
||||
verify(
|
||||
response.getRcode() == Rcode.NOERROR,
|
||||
"DNS server failed domain update for '%s' rcode: %s",
|
||||
zoneName,
|
||||
Rcode.string(response.getRcode()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("publishDomain failed for zone: " + zoneName, e);
|
||||
}
|
||||
}
|
||||
|
||||
private RRset makeDelegationSignerSet(DomainResource domain) {
|
||||
RRset signerSet = new RRset();
|
||||
for (DelegationSignerData signerData : domain.getDsData()) {
|
||||
DSRecord dsRecord =
|
||||
|
@ -192,7 +202,7 @@ public class DnsUpdateWriter implements DnsWriter {
|
|||
}
|
||||
|
||||
private void deleteSubordinateHostAddressSet(
|
||||
DomainResource domain, String additionalHost, Update update) throws TextParseException {
|
||||
DomainResource domain, String additionalHost, Update update) {
|
||||
for (String hostName :
|
||||
union(
|
||||
domain.getSubordinateHosts(),
|
||||
|
@ -203,8 +213,7 @@ public class DnsUpdateWriter implements DnsWriter {
|
|||
}
|
||||
}
|
||||
|
||||
private void addInBailiwickNameServerSet(DomainResource domain, Update update)
|
||||
throws TextParseException {
|
||||
private void addInBailiwickNameServerSet(DomainResource domain, Update update) {
|
||||
for (String hostName :
|
||||
intersection(
|
||||
domain.loadNameserverFullyQualifiedHostNames(), domain.getSubordinateHosts())) {
|
||||
|
@ -214,7 +223,7 @@ public class DnsUpdateWriter implements DnsWriter {
|
|||
}
|
||||
}
|
||||
|
||||
private RRset makeNameServerSet(DomainResource domain) throws TextParseException {
|
||||
private RRset makeNameServerSet(DomainResource domain) {
|
||||
RRset nameServerSet = new RRset();
|
||||
for (String hostName : domain.loadNameserverFullyQualifiedHostNames()) {
|
||||
NSRecord record =
|
||||
|
@ -228,7 +237,7 @@ public class DnsUpdateWriter implements DnsWriter {
|
|||
return nameServerSet;
|
||||
}
|
||||
|
||||
private RRset makeAddressSet(HostResource host) throws TextParseException {
|
||||
private RRset makeAddressSet(HostResource host) {
|
||||
RRset addressSet = new RRset();
|
||||
for (InetAddress address : host.getInetAddresses()) {
|
||||
if (address instanceof Inet4Address) {
|
||||
|
@ -244,7 +253,7 @@ public class DnsUpdateWriter implements DnsWriter {
|
|||
return addressSet;
|
||||
}
|
||||
|
||||
private RRset makeV6AddressSet(HostResource host) throws TextParseException {
|
||||
private RRset makeV6AddressSet(HostResource host) {
|
||||
RRset addressSet = new RRset();
|
||||
for (InetAddress address : host.getInetAddresses()) {
|
||||
if (address instanceof Inet6Address) {
|
||||
|
@ -260,11 +269,12 @@ public class DnsUpdateWriter implements DnsWriter {
|
|||
return addressSet;
|
||||
}
|
||||
|
||||
private String findTldFromName(String name) {
|
||||
return Registries.findTldForNameOrThrow(InternetDomainName.from(name)).toString();
|
||||
}
|
||||
|
||||
private Name toAbsoluteName(String name) throws TextParseException {
|
||||
return Name.fromString(name, Name.root);
|
||||
private Name toAbsoluteName(String name) {
|
||||
try {
|
||||
return Name.fromString(name, Name.root);
|
||||
} catch (TextParseException e) {
|
||||
throw new RuntimeException(
|
||||
String.format("toAbsoluteName failed for name: %s in zone: %s", name, zoneName), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue