Flatten the hosts flows

There's so little meat here that there's not much
reason to break this cl up any further

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=133171754
This commit is contained in:
cgoldfeder 2016-07-10 14:10:42 -04:00 committed by Ben McIlwain
parent 00ea99960a
commit 516b5663a5
12 changed files with 336 additions and 210 deletions

View file

@ -15,14 +15,22 @@
package google.registry.flows.host;
import static com.google.common.base.MoreObjects.firstNonNull;
import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses;
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource;
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
import static google.registry.flows.host.HostFlowUtils.lookupSuperordinateDomain;
import static google.registry.flows.host.HostFlowUtils.validateHostName;
import static google.registry.flows.host.HostFlowUtils.verifyDomainIsSameRegistrar;
import static google.registry.model.EppResourceUtils.loadByUniqueId;
import static google.registry.model.eppoutput.Result.Code.Success;
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.googlecode.objectify.Key;
import google.registry.dns.DnsQueue;
import google.registry.flows.EppException;
@ -30,10 +38,23 @@ import google.registry.flows.EppException.ObjectAlreadyExistsException;
import google.registry.flows.EppException.ParameterValueRangeErrorException;
import google.registry.flows.EppException.RequiredParameterMissingException;
import google.registry.flows.EppException.StatusProhibitsOperationException;
import google.registry.flows.ResourceUpdateFlow;
import google.registry.flows.FlowModule.ClientId;
import google.registry.flows.LoggedInFlow;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.async.AsyncFlowUtils;
import google.registry.flows.async.DnsRefreshForHostRenameAction;
import google.registry.flows.exceptions.AddRemoveSameValueEppException;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.flows.exceptions.StatusNotClientSettableException;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppinput.ResourceCommand.AddRemoveSameValueException;
import google.registry.model.eppoutput.EppOutput;
import google.registry.model.host.HostCommand.Update;
import google.registry.model.host.HostResource;
import google.registry.model.host.HostResource.Builder;
@ -47,10 +68,10 @@ import org.joda.time.Duration;
* An EPP flow that updates a host resource.
*
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
* @error {@link google.registry.flows.ResourceUpdateFlow.ResourceHasClientUpdateProhibitedException}
* @error {@link google.registry.flows.ResourceUpdateFlow.StatusNotClientSettableException}
* @error {@link google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException}
* @error {@link google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException}
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
* @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
* @error {@link google.registry.flows.exceptions.StatusNotClientSettableException}
* @error {@link HostFlowUtils.HostNameTooShallowException}
* @error {@link HostFlowUtils.InvalidHostNameException}
* @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException}
@ -60,40 +81,115 @@ import org.joda.time.Duration;
* @error {@link RenameHostToExternalRemoveIpException}
* @error {@link RenameHostToSubordinateRequiresIpException}
*/
public class HostUpdateFlow extends ResourceUpdateFlow<HostResource, Builder, Update> {
public class HostUpdateFlow extends LoggedInFlow implements TransactionalFlow {
private Key<DomainResource> superordinateDomain;
private String oldHostName;
private String newHostName;
private boolean isHostRename;
/**
* Note that CLIENT_UPDATE_PROHIBITED is intentionally not in this list. This is because it
* requires special checking, since you must be able to clear the status off the object with an
* update.
*/
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
StatusValue.PENDING_DELETE,
StatusValue.SERVER_UPDATE_PROHIBITED);
@Inject ResourceCommand resourceCommand;
@Inject Optional<AuthInfo> authInfo;
@Inject @ClientId String clientId;
@Inject HistoryEntry.Builder historyBuilder;
@Inject HostUpdateFlow() {}
@Override
protected void initResourceCreateOrMutateFlow() throws EppException {
String suppliedNewHostName = command.getInnerChange().getFullyQualifiedHostName();
isHostRename = suppliedNewHostName != null;
oldHostName = targetId;
newHostName = firstNonNull(suppliedNewHostName, oldHostName);
superordinateDomain =
lookupSuperordinateDomain(validateHostName(newHostName), now);
protected final void initLoggedInFlow() throws EppException {
registerExtensions(MetadataExtension.class);
}
@Override
protected void verifyUpdateIsAllowed() throws EppException {
verifyDomainIsSameRegistrar(superordinateDomain, getClientId());
if (isHostRename
&& loadAndGetKey(HostResource.class, newHostName, now) != null) {
public final EppOutput run() throws EppException {
Update command = (Update) resourceCommand;
String suppliedNewHostName = command.getInnerChange().getFullyQualifiedHostName();
String targetId = command.getTargetId();
HostResource existingResource = loadByUniqueId(HostResource.class, targetId, now);
if (existingResource == null) {
throw new ResourceToMutateDoesNotExistException(HostResource.class, targetId);
}
boolean isHostRename = suppliedNewHostName != null;
String oldHostName = targetId;
String newHostName = firstNonNull(suppliedNewHostName, oldHostName);
Optional<DomainResource> superordinateDomain =
Optional.fromNullable(lookupSuperordinateDomain(validateHostName(newHostName), now));
verifyUpdateAllowed(command, existingResource, superordinateDomain.orNull());
if (isHostRename && loadAndGetKey(HostResource.class, newHostName, now) != null) {
throw new HostAlreadyExistsException(newHostName);
}
Builder builder = existingResource.asBuilder();
try {
command.applyTo(builder);
} catch (AddRemoveSameValueException e) {
throw new AddRemoveSameValueEppException();
}
builder
.setLastEppUpdateTime(now)
.setLastEppUpdateClientId(clientId)
// The superordinateDomain can be missing if the new name is external.
// Note that the value of superordinateDomain is projected to the current time inside of
// the lookupSuperordinateDomain(...) call above, so that it will never be stale.
.setSuperordinateDomain(
superordinateDomain.isPresent() ? Key.create(superordinateDomain.get()) : null)
.setLastSuperordinateChange(superordinateDomain == null ? null : now);
// Rely on the host's cloneProjectedAtTime() method to handle setting of transfer data.
HostResource newResource = builder.build().cloneProjectedAtTime(now);
verifyHasIpsIffIsExternal(command, existingResource, newResource);
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
entitiesToSave.add(newResource);
// Keep the {@link ForeignKeyIndex} for this host up to date.
if (isHostRename) {
// Update the foreign key for the old host name and save one for the new host name.
entitiesToSave.add(
ForeignKeyIndex.create(existingResource, now),
ForeignKeyIndex.create(newResource, newResource.getDeletionTime()));
updateSuperordinateDomains(existingResource, newResource);
}
enqueueTasks(existingResource, newResource);
entitiesToSave.add(historyBuilder
.setType(HistoryEntry.Type.HOST_UPDATE)
.setModificationTime(now)
.setParent(Key.create(existingResource))
.build());
ofy().save().entities(entitiesToSave.build());
return createOutput(Success);
}
@Override
protected void verifyNewUpdatedStateIsAllowed() throws EppException {
private void verifyUpdateAllowed(
Update command, HostResource existingResource, DomainResource superordinateDomain)
throws EppException {
verifyOptionalAuthInfoForResource(authInfo, existingResource);
if (!isSuperuser) {
verifyResourceOwnership(clientId, existingResource);
// If the resource is marked with clientUpdateProhibited, and this update does not clear that
// status, then the update must be disallowed (unless a superuser is requesting the change).
if (!isSuperuser
&& existingResource.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
&& !command.getInnerRemove().getStatusValues()
.contains(StatusValue.CLIENT_UPDATE_PROHIBITED)) {
throw new ResourceHasClientUpdateProhibitedException();
}
}
for (StatusValue statusValue : Sets.union(
command.getInnerAdd().getStatusValues(),
command.getInnerRemove().getStatusValues())) {
if (!isSuperuser && !statusValue.isClientSettable()) { // The superuser can set any status.
throw new StatusNotClientSettableException(statusValue.getXmlName());
}
}
verifyDomainIsSameRegistrar(superordinateDomain, clientId);
verifyNoDisallowedStatuses(existingResource, DISALLOWED_STATUSES);
}
void verifyHasIpsIffIsExternal(
Update command, HostResource existingResource, HostResource newResource) throws EppException {
boolean wasExternal = existingResource.getSuperordinateDomain() == null;
boolean wasSubordinate = !wasExternal;
boolean willBeExternal = superordinateDomain == null;
boolean willBeExternal = newResource.getSuperordinateDomain() == null;
boolean willBeSubordinate = !willBeExternal;
boolean newResourceHasIps = !isNullOrEmpty(newResource.getInetAddresses());
boolean commandAddsIps = !isNullOrEmpty(command.getInnerAdd().getInetAddresses());
@ -114,43 +210,18 @@ public class HostUpdateFlow extends ResourceUpdateFlow<HostResource, Builder, Up
}
}
@Override
protected Builder setUpdateProperties(Builder builder) {
// The superordinateDomain can be null if the new name is external.
// Note that the value of superordinateDomain is projected to the current time inside of
// the lookupSuperordinateDomain(...) call above, so that it will never be stale.
builder.setSuperordinateDomain(superordinateDomain);
builder.setLastSuperordinateChange(superordinateDomain == null ? null : now);
// Rely on the host's cloneProjectedAtTime() method to handle setting of transfer data.
return builder.build().cloneProjectedAtTime(now).asBuilder();
}
/** Keep the {@link ForeignKeyIndex} for this host up to date. */
@Override
protected void modifyRelatedResources() {
if (isHostRename) {
// Update the foreign key for the old host name.
ofy().save().entity(ForeignKeyIndex.create(existingResource, now));
// Save the foreign key for the new host name.
ofy().save().entity(ForeignKeyIndex.create(newResource, newResource.getDeletionTime()));
updateSuperordinateDomains();
}
}
@Override
public void enqueueTasks() throws EppException {
DnsQueue dnsQueue = DnsQueue.create();
public void enqueueTasks(HostResource existingResource, HostResource newResource) {
// Only update DNS for subordinate hosts. External hosts have no glue to write, so they
// are only written as NS records from the referencing domain.
if (existingResource.getSuperordinateDomain() != null) {
dnsQueue.addHostRefreshTask(oldHostName);
DnsQueue.create().addHostRefreshTask(existingResource.getFullyQualifiedHostName());
}
// In case of a rename, there are many updates we need to queue up.
if (isHostRename) {
if (((Update) resourceCommand).getInnerChange().getFullyQualifiedHostName() != null) {
// If the renamed host is also subordinate, then we must enqueue an update to write the new
// glue.
if (newResource.getSuperordinateDomain() != null) {
dnsQueue.addHostRefreshTask(newHostName);
DnsQueue.create().addHostRefreshTask(newResource.getFullyQualifiedHostName());
}
// We must also enqueue updates for all domains that use this host as their nameserver so
// that their NS records can be updated to point at the new name.
@ -163,33 +234,29 @@ public class HostUpdateFlow extends ResourceUpdateFlow<HostResource, Builder, Up
}
}
@Override
protected final HistoryEntry.Type getHistoryEntryType() {
return HistoryEntry.Type.HOST_UPDATE;
}
private void updateSuperordinateDomains() {
private void updateSuperordinateDomains(HostResource existingResource, HostResource newResource) {
Key<DomainResource> oldSuperordinateDomain = existingResource.getSuperordinateDomain();
if (oldSuperordinateDomain != null || superordinateDomain != null) {
if (Objects.equals(oldSuperordinateDomain, superordinateDomain)) {
Key<DomainResource> newSuperordinateDomain = newResource.getSuperordinateDomain();
if (oldSuperordinateDomain != null || newSuperordinateDomain != null) {
if (Objects.equals(oldSuperordinateDomain, newSuperordinateDomain)) {
ofy().save().entity(
ofy().load().key(oldSuperordinateDomain).now().asBuilder()
.removeSubordinateHost(oldHostName)
.addSubordinateHost(newHostName)
.removeSubordinateHost(existingResource.getFullyQualifiedHostName())
.addSubordinateHost(newResource.getFullyQualifiedHostName())
.build());
} else {
if (oldSuperordinateDomain != null) {
ofy().save().entity(
ofy().load().key(oldSuperordinateDomain).now()
.asBuilder()
.removeSubordinateHost(oldHostName)
.removeSubordinateHost(existingResource.getFullyQualifiedHostName())
.build());
}
if (superordinateDomain != null) {
if (newSuperordinateDomain != null) {
ofy().save().entity(
ofy().load().key(superordinateDomain).now()
ofy().load().key(newSuperordinateDomain).now()
.asBuilder()
.addSubordinateHost(newHostName)
.addSubordinateHost(newResource.getFullyQualifiedHostName())
.build());
}
}