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

@ -64,6 +64,13 @@ public class ContactDeleteFlow extends LoggedInFlow implements TransactionalFlow
StatusValue.PENDING_DELETE, StatusValue.PENDING_DELETE,
StatusValue.SERVER_DELETE_PROHIBITED); StatusValue.SERVER_DELETE_PROHIBITED);
private static final Function<DomainBase, ImmutableSet<?>> GET_REFERENCED_CONTACTS =
new Function<DomainBase, ImmutableSet<?>>() {
@Override
public ImmutableSet<?> apply(DomainBase domain) {
return domain.getReferencedContacts();
}};
@Inject AsyncFlowEnqueuer asyncFlowEnqueuer; @Inject AsyncFlowEnqueuer asyncFlowEnqueuer;
@Inject @ClientId String clientId; @Inject @ClientId String clientId;
@Inject @TargetId String targetId; @Inject @TargetId String targetId;
@ -79,15 +86,7 @@ public class ContactDeleteFlow extends LoggedInFlow implements TransactionalFlow
@Override @Override
public final EppOutput run() throws EppException { public final EppOutput run() throws EppException {
failfastForAsyncDelete( failfastForAsyncDelete(targetId, now, ContactResource.class, GET_REFERENCED_CONTACTS);
targetId,
now,
ContactResource.class,
new Function<DomainBase, ImmutableSet<?>>() {
@Override
public ImmutableSet<?> apply(DomainBase domain) {
return domain.getReferencedContacts();
}});
ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now); ContactResource existingResource = loadByUniqueId(ContactResource.class, targetId, now);
if (existingResource == null) { if (existingResource == null) {
throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId); throw new ResourceToMutateDoesNotExistException(ContactResource.class, targetId);

View file

@ -15,34 +15,46 @@
package google.registry.flows.host; package google.registry.flows.host;
import static google.registry.model.EppResourceUtils.checkResourcesExist; import static google.registry.model.EppResourceUtils.checkResourcesExist;
import static google.registry.model.eppoutput.Result.Code.Success;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import google.registry.flows.ResourceCheckFlow; import google.registry.config.ConfigModule.Config;
import google.registry.model.eppoutput.CheckData; import google.registry.flows.EppException;
import google.registry.flows.LoggedInFlow;
import google.registry.flows.exceptions.TooManyResourceChecksException;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.CheckData.HostCheck; import google.registry.model.eppoutput.CheckData.HostCheck;
import google.registry.model.eppoutput.CheckData.HostCheckData; import google.registry.model.eppoutput.CheckData.HostCheckData;
import google.registry.model.eppoutput.EppOutput;
import google.registry.model.host.HostCommand.Check; import google.registry.model.host.HostCommand.Check;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import java.util.List;
import java.util.Set; import java.util.Set;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
* An EPP flow that checks whether a host can be provisioned. * An EPP flow that checks whether a host can be provisioned.
* *
* @error {@link google.registry.flows.ResourceCheckFlow.TooManyResourceChecksException} * @error {@link google.registry.flows.exceptions.TooManyResourceChecksException}
*/ */
public class HostCheckFlow extends ResourceCheckFlow<HostResource, Check> { public class HostCheckFlow extends LoggedInFlow {
@Inject ResourceCommand resourceCommand;
@Inject @Config("maxChecks") int maxChecks;
@Inject HostCheckFlow() {} @Inject HostCheckFlow() {}
@Override @Override
protected CheckData getCheckData() { protected final EppOutput run() throws EppException {
Set<String> existingIds = checkResourcesExist(resourceClass, targetIds, now); List<String> targetIds = ((Check) resourceCommand).getTargetIds();
if (targetIds.size() > maxChecks) {
throw new TooManyResourceChecksException(maxChecks);
}
Set<String> existingIds = checkResourcesExist(HostResource.class, targetIds, now);
ImmutableList.Builder<HostCheck> checks = new ImmutableList.Builder<>(); ImmutableList.Builder<HostCheck> checks = new ImmutableList.Builder<>();
for (String id : targetIds) { for (String id : targetIds) {
boolean unused = !existingIds.contains(id); boolean unused = !existingIds.contains(id);
checks.add(HostCheck.create(unused, id, unused ? null : "In use")); checks.add(HostCheck.create(unused, id, unused ? null : "In use"));
} }
return HostCheckData.create(checks.build()); return createOutput(Success, HostCheckData.create(checks.build()));
} }
} }

View file

@ -18,23 +18,34 @@ import static google.registry.flows.host.HostFlowUtils.lookupSuperordinateDomain
import static google.registry.flows.host.HostFlowUtils.validateHostName; import static google.registry.flows.host.HostFlowUtils.validateHostName;
import static google.registry.flows.host.HostFlowUtils.verifyDomainIsSameRegistrar; import static google.registry.flows.host.HostFlowUtils.verifyDomainIsSameRegistrar;
import static google.registry.model.EppResourceUtils.createContactHostRoid; import static google.registry.model.EppResourceUtils.createContactHostRoid;
import static google.registry.model.EppResourceUtils.loadByUniqueId;
import static google.registry.model.eppoutput.Result.Code.Success; import static google.registry.model.eppoutput.Result.Code.Success;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.CollectionUtils.isNullOrEmpty; import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.union;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.dns.DnsQueue; import google.registry.dns.DnsQueue;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.flows.EppException.ParameterValueRangeErrorException; import google.registry.flows.EppException.ParameterValueRangeErrorException;
import google.registry.flows.EppException.RequiredParameterMissingException; import google.registry.flows.EppException.RequiredParameterMissingException;
import google.registry.flows.ResourceCreateFlow; import google.registry.flows.FlowModule.ClientId;
import google.registry.flows.LoggedInFlow;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.exceptions.ResourceAlreadyExistsException;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.DomainResource; import google.registry.model.domain.DomainResource;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.CreateData.HostCreateData; import google.registry.model.eppoutput.CreateData.HostCreateData;
import google.registry.model.eppoutput.EppOutput; import google.registry.model.eppoutput.EppOutput;
import google.registry.model.host.HostCommand.Create; import google.registry.model.host.HostCommand.Create;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.model.host.HostResource.Builder; import google.registry.model.host.HostResource.Builder;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.ofy.ObjectifyService; import google.registry.model.ofy.ObjectifyService;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import javax.inject.Inject; import javax.inject.Inject;
@ -43,7 +54,7 @@ import javax.inject.Inject;
* An EPP flow that creates a new host resource. * An EPP flow that creates a new host resource.
* *
* @error {@link google.registry.flows.EppXmlTransformer.IpAddressVersionMismatchException} * @error {@link google.registry.flows.EppXmlTransformer.IpAddressVersionMismatchException}
* @error {@link google.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException} * @error {@link google.registry.flows.exceptions.ResourceAlreadyExistsException}
* @error {@link HostFlowUtils.HostNameTooLongException} * @error {@link HostFlowUtils.HostNameTooLongException}
* @error {@link HostFlowUtils.HostNameTooShallowException} * @error {@link HostFlowUtils.HostNameTooShallowException}
* @error {@link HostFlowUtils.InvalidHostNameException} * @error {@link HostFlowUtils.InvalidHostNameException}
@ -51,37 +62,33 @@ import javax.inject.Inject;
* @error {@link SubordinateHostMustHaveIpException} * @error {@link SubordinateHostMustHaveIpException}
* @error {@link UnexpectedExternalHostIpException} * @error {@link UnexpectedExternalHostIpException}
*/ */
public class HostCreateFlow extends ResourceCreateFlow<HostResource, Builder, Create> { public class HostCreateFlow extends LoggedInFlow implements TransactionalFlow {
/**
* The superordinate domain of the host object if creating an in-bailiwick host, or null if
* creating an external host. This is looked up before we actually create the Host object so that
* we can detect error conditions earlier. By the time {@link #setCreateProperties} is called
* (where this reference is actually used), we no longer have the ability to return an
* {@link EppException}.
*
* <p>The general model of these classes is to do validation of parameters up front before we get
* to the actual object creation, which is why this class looks up and stores the superordinate
* domain ahead of time.
*/
private Optional<Key<DomainResource>> superordinateDomain;
@Inject ResourceCommand resourceCommand;
@Inject @ClientId String clientId;
@Inject HistoryEntry.Builder historyBuilder;
@Inject HostCreateFlow() {} @Inject HostCreateFlow() {}
@Override @Override
protected void initResourceCreateOrMutateFlow() throws EppException { @SuppressWarnings("unchecked")
superordinateDomain = Optional.fromNullable(lookupSuperordinateDomain( protected final void initLoggedInFlow() throws EppException {
validateHostName(command.getFullyQualifiedHostName()), now)); registerExtensions(MetadataExtension.class);
} }
@Override @Override
protected String createFlowRepoId() { protected final EppOutput run() throws EppException {
return createContactHostRoid(ObjectifyService.allocateId()); Create command = (Create) resourceCommand;
String targetId = command.getTargetId();
HostResource existingResource = loadByUniqueId(HostResource.class, targetId, now);
if (existingResource != null) {
throw new ResourceAlreadyExistsException(targetId);
} }
// The superordinate domain of the host object if creating an in-bailiwick host, or null if
@Override // creating an external host. This is looked up before we actually create the Host object so
protected void verifyCreateIsAllowed() throws EppException { // we can detect error conditions earlier.
verifyDomainIsSameRegistrar(superordinateDomain.orNull(), getClientId()); Optional<DomainResource> superordinateDomain = Optional.fromNullable(
lookupSuperordinateDomain(validateHostName(command.getFullyQualifiedHostName()), now));
verifyDomainIsSameRegistrar(superordinateDomain.orNull(), clientId);
boolean willBeSubordinate = superordinateDomain.isPresent(); boolean willBeSubordinate = superordinateDomain.isPresent();
boolean hasIpAddresses = !isNullOrEmpty(command.getInetAddresses()); boolean hasIpAddresses = !isNullOrEmpty(command.getInetAddresses());
if (willBeSubordinate != hasIpAddresses) { if (willBeSubordinate != hasIpAddresses) {
@ -90,43 +97,36 @@ public class HostCreateFlow extends ResourceCreateFlow<HostResource, Builder, Cr
? new SubordinateHostMustHaveIpException() ? new SubordinateHostMustHaveIpException()
: new UnexpectedExternalHostIpException(); : new UnexpectedExternalHostIpException();
} }
} Builder builder = new Builder();
command.applyTo(builder);
@Override HostResource newResource = builder
protected void setCreateProperties(Builder builder) { .setCreationClientId(clientId)
.setCurrentSponsorClientId(clientId)
.setRepoId(createContactHostRoid(ObjectifyService.allocateId()))
.setSuperordinateDomain(
superordinateDomain.isPresent() ? Key.create(superordinateDomain.get()) : null)
.build();
historyBuilder
.setType(HistoryEntry.Type.HOST_CREATE)
.setModificationTime(now)
.setParent(Key.create(newResource));
ImmutableSet<ImmutableObject> entitiesToSave = ImmutableSet.of(
newResource,
historyBuilder.build(),
ForeignKeyIndex.create(newResource, newResource.getDeletionTime()),
EppResourceIndex.create(Key.create(newResource)));
if (superordinateDomain.isPresent()) { if (superordinateDomain.isPresent()) {
builder.setSuperordinateDomain(superordinateDomain.get()); entitiesToSave = union(
} entitiesToSave,
} superordinateDomain.get().asBuilder()
/** Modify any other resources that need to be informed of this create. */
@Override
protected void modifyCreateRelatedResources() {
if (superordinateDomain.isPresent()) {
ofy().save().entity(ofy().load().key(superordinateDomain.get()).now().asBuilder()
.addSubordinateHost(command.getFullyQualifiedHostName()) .addSubordinateHost(command.getFullyQualifiedHostName())
.build()); .build());
// Only update DNS if this is a subordinate host. External hosts have no glue to write, so
// they are only written as NS records from the referencing domain.
DnsQueue.create().addHostRefreshTask(targetId);
} }
} ofy().save().entities(entitiesToSave);
return createOutput(Success, HostCreateData.create(targetId, now));
@Override
protected void enqueueTasks() {
// Only update DNS if this is a subordinate host. External hosts have no glue to write, so they
// are only written as NS records from the referencing domain.
if (superordinateDomain.isPresent()) {
DnsQueue.create().addHostRefreshTask(newResource.getFullyQualifiedHostName());
}
}
@Override
protected final HistoryEntry.Type getHistoryEntryType() {
return HistoryEntry.Type.HOST_CREATE;
}
@Override
protected EppOutput getOutput() {
return createOutput(Success,
HostCreateData.create(newResource.getFullyQualifiedHostName(), now));
} }
/** Subordinate hosts must have an ip address. */ /** Subordinate hosts must have an ip address. */

View file

@ -14,79 +14,106 @@
package google.registry.flows.host; package google.registry.flows.host;
import static google.registry.model.EppResourceUtils.queryDomainsUsingResource; import static google.registry.flows.ResourceFlowUtils.failfastForAsyncDelete;
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.model.EppResourceUtils.loadByUniqueId;
import static google.registry.model.eppoutput.Result.Code.SuccessWithActionPending;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
import com.google.common.base.Predicate; import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables; import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.config.RegistryEnvironment; import google.registry.config.ConfigModule.Config;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.flows.ResourceAsyncDeleteFlow; import google.registry.flows.FlowModule.ClientId;
import google.registry.flows.async.AsyncFlowEnqueuer; import google.registry.flows.LoggedInFlow;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.async.AsyncFlowUtils; import google.registry.flows.async.AsyncFlowUtils;
import google.registry.flows.async.DeleteEppResourceAction; import google.registry.flows.async.DeleteEppResourceAction;
import google.registry.flows.async.DeleteHostResourceAction; import google.registry.flows.async.DeleteHostResourceAction;
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainBase;
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.eppoutput.EppOutput;
import google.registry.model.host.HostCommand.Delete; import google.registry.model.host.HostCommand.Delete;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.model.host.HostResource.Builder;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import javax.inject.Inject; import javax.inject.Inject;
import org.joda.time.Duration;
/** /**
* An EPP flow that deletes a host resource. * An EPP flow that deletes a host resource.
* *
* @error {@link google.registry.flows.ResourceAsyncDeleteFlow.ResourceToDeleteIsReferencedException}
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException} * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
* @error {@link google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException} * @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
* @error {@link google.registry.flows.exceptions.ResourceToDeleteIsReferencedException}
*/ */
public class HostDeleteFlow extends ResourceAsyncDeleteFlow<HostResource, Builder, Delete> { public class HostDeleteFlow extends LoggedInFlow implements TransactionalFlow {
/** In {@link #isLinkedForFailfast}, check this (arbitrary) number of resources from the query. */ private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
private static final int FAILFAST_CHECK_COUNT = 5; StatusValue.LINKED,
StatusValue.CLIENT_DELETE_PROHIBITED,
StatusValue.PENDING_DELETE,
StatusValue.SERVER_DELETE_PROHIBITED);
@Inject AsyncFlowEnqueuer asyncFlowEnqueuer; private static final Function<DomainBase, ImmutableSet<?>> GET_NAMESERVERS =
new Function<DomainBase, ImmutableSet<?>>() {
@Override
public ImmutableSet<?> apply(DomainBase domain) {
return domain.getNameservers();
}};
@Inject ResourceCommand resourceCommand;
@Inject Optional<AuthInfo> authInfo;
@Inject @ClientId String clientId;
@Inject @Config("asyncDeleteFlowMapreduceDelay") Duration mapreduceDelay;
@Inject HistoryEntry.Builder historyBuilder;
@Inject HostDeleteFlow() {} @Inject HostDeleteFlow() {}
@Override @Override
protected boolean isLinkedForFailfast(final Key<HostResource> key) { protected final void initLoggedInFlow() throws EppException {
// Query for the first few linked domains, and if found, actually load them. The query is registerExtensions(MetadataExtension.class);
// eventually consistent and so might be very stale, but the direct load will not be stale,
// just non-transactional. If we find at least one actual reference then we can reliably
// fail. If we don't find any, we can't trust the query and need to do the full mapreduce.
return Iterables.any(
ofy().load().keys(
queryDomainsUsingResource(
HostResource.class, key, now, FAILFAST_CHECK_COUNT)).values(),
new Predicate<DomainBase>() {
@Override
public boolean apply(DomainBase domain) {
return domain.getNameservers().contains(key);
}});
} }
/** Enqueues an asynchronous host resource deletion. */
@Override @Override
protected final void enqueueTasks() throws EppException { public final EppOutput run() throws EppException {
Delete command = (Delete) resourceCommand;
String targetId = command.getTargetId();
failfastForAsyncDelete(targetId, now, HostResource.class, GET_NAMESERVERS);
HostResource existingResource = loadByUniqueId(HostResource.class, targetId, now);
if (existingResource == null) {
throw new ResourceToMutateDoesNotExistException(HostResource.class, targetId);
}
verifyNoDisallowedStatuses(existingResource, DISALLOWED_STATUSES);
verifyOptionalAuthInfoForResource(authInfo, existingResource);
if (!isSuperuser) {
verifyResourceOwnership(clientId, existingResource);
}
AsyncFlowUtils.enqueueMapreduceAction( AsyncFlowUtils.enqueueMapreduceAction(
DeleteHostResourceAction.class, DeleteHostResourceAction.class,
ImmutableMap.of( ImmutableMap.of(
DeleteEppResourceAction.PARAM_RESOURCE_KEY, DeleteEppResourceAction.PARAM_RESOURCE_KEY,
Key.create(existingResource).getString(), Key.create(existingResource).getString(),
DeleteEppResourceAction.PARAM_REQUESTING_CLIENT_ID, DeleteEppResourceAction.PARAM_REQUESTING_CLIENT_ID,
getClientId(), clientId,
DeleteEppResourceAction.PARAM_IS_SUPERUSER, DeleteEppResourceAction.PARAM_IS_SUPERUSER,
Boolean.toString(isSuperuser)), Boolean.toString(isSuperuser)),
RegistryEnvironment.get().config().getAsyncDeleteFlowMapreduceDelay()); mapreduceDelay);
// TODO(b/26140521): Switch over to batch async operations as follows: HostResource newResource =
// asyncFlowEnqueuer.enqueueAsyncDelete(existingResource, getClientId(), isSuperuser); existingResource.asBuilder().addStatusValue(StatusValue.PENDING_DELETE).build();
} historyBuilder
.setType(HistoryEntry.Type.HOST_PENDING_DELETE)
@Override .setModificationTime(now)
protected final HistoryEntry.Type getHistoryEntryType() { .setParent(Key.create(existingResource));
return HistoryEntry.Type.HOST_PENDING_DELETE; ofy().save().<Object>entities(newResource, historyBuilder.build());
return createOutput(SuccessWithActionPending);
} }
} }

View file

@ -16,14 +16,12 @@ package google.registry.flows.host;
import static google.registry.model.EppResourceUtils.isActive; import static google.registry.model.EppResourceUtils.isActive;
import static google.registry.model.EppResourceUtils.loadByUniqueId; import static google.registry.model.EppResourceUtils.loadByUniqueId;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.registry.Registries.findTldForName; import static google.registry.model.registry.Registries.findTldForName;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.net.InternetDomainName; import com.google.common.net.InternetDomainName;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException; import google.registry.flows.EppException;
import google.registry.flows.EppException.AuthorizationErrorException; import google.registry.flows.EppException.AuthorizationErrorException;
import google.registry.flows.EppException.ObjectDoesNotExistException; import google.registry.flows.EppException.ObjectDoesNotExistException;
@ -76,23 +74,21 @@ public class HostFlowUtils {
} }
/** Return the {@link DomainResource} this host is subordinate to, or null for external hosts. */ /** Return the {@link DomainResource} this host is subordinate to, or null for external hosts. */
static Key<DomainResource> lookupSuperordinateDomain( static DomainResource lookupSuperordinateDomain(
InternetDomainName hostName, DateTime now) throws EppException { InternetDomainName hostName, DateTime now) throws EppException {
Optional<InternetDomainName> tldParsed = findTldForName(hostName); Optional<InternetDomainName> tld = findTldForName(hostName);
if (!tldParsed.isPresent()) { if (!tld.isPresent()) {
// This is an host on a TLD we don't run, therefore obviously external, so we are done. // This is an host on a TLD we don't run, therefore obviously external, so we are done.
return null; return null;
} }
// This is a subordinate host // This is a subordinate host
@SuppressWarnings("deprecation")
String domainName = Joiner.on('.').join(Iterables.skip( String domainName = Joiner.on('.').join(Iterables.skip(
hostName.parts(), hostName.parts().size() - (tldParsed.get().parts().size() + 1))); hostName.parts(), hostName.parts().size() - (tld.get().parts().size() + 1)));
DomainResource superordinateDomain = loadByUniqueId(DomainResource.class, domainName, now); DomainResource superordinateDomain = loadByUniqueId(DomainResource.class, domainName, now);
if (superordinateDomain == null || !isActive(superordinateDomain, now)) { if (superordinateDomain == null || !isActive(superordinateDomain, now)) {
throw new SuperordinateDomainDoesNotExistException(domainName); throw new SuperordinateDomainDoesNotExistException(domainName);
} }
return Key.create(superordinateDomain); return superordinateDomain;
} }
/** Superordinate domain for this hostname does not exist. */ /** Superordinate domain for this hostname does not exist. */
@ -104,11 +100,10 @@ public class HostFlowUtils {
/** Ensure that the superordinate domain is sponsored by the provided clientId. */ /** Ensure that the superordinate domain is sponsored by the provided clientId. */
static void verifyDomainIsSameRegistrar( static void verifyDomainIsSameRegistrar(
Key<DomainResource> superordinateDomain, DomainResource superordinateDomain,
String clientId) throws EppException { String clientId) throws EppException {
if (superordinateDomain != null if (superordinateDomain != null
&& !clientId.equals( && !clientId.equals(superordinateDomain.getCurrentSponsorClientId())) {
ofy().load().key(superordinateDomain).now().getCurrentSponsorClientId())) {
throw new HostDomainNotOwnedException(); throw new HostDomainNotOwnedException();
} }
} }

View file

@ -14,16 +14,42 @@
package google.registry.flows.host; package google.registry.flows.host;
import google.registry.flows.ResourceInfoFlow; import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfoForResource;
import google.registry.model.host.HostCommand; import static google.registry.model.EppResourceUtils.cloneResourceWithLinkedStatus;
import static google.registry.model.EppResourceUtils.loadByUniqueId;
import static google.registry.model.eppoutput.Result.Code.Success;
import com.google.common.base.Optional;
import google.registry.flows.EppException;
import google.registry.flows.LoggedInFlow;
import google.registry.flows.exceptions.ResourceToQueryDoesNotExistException;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.EppOutput;
import google.registry.model.host.HostCommand.Info;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
* An EPP flow that reads a host. * An EPP flow that reads a host.
* *
* @error {@link google.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException} * @error {@link google.registry.flows.exceptions.ResourceToQueryDoesNotExistException}
*/ */
public class HostInfoFlow extends ResourceInfoFlow<HostResource, HostCommand.Info> { public class HostInfoFlow extends LoggedInFlow {
@Inject ResourceCommand resourceCommand;
@Inject Optional<AuthInfo> authInfo;
@Inject HostInfoFlow() {} @Inject HostInfoFlow() {}
@Override
public EppOutput run() throws EppException {
Info command = (Info) resourceCommand;
String targetId = command.getTargetId();
HostResource existingResource = loadByUniqueId(HostResource.class, targetId, now);
if (existingResource == null) {
throw new ResourceToQueryDoesNotExistException(HostResource.class, targetId);
}
verifyOptionalAuthInfoForResource(authInfo, existingResource);
return createOutput(Success, cloneResourceWithLinkedStatus(existingResource, now));
}
} }

View file

@ -15,14 +15,22 @@
package google.registry.flows.host; package google.registry.flows.host;
import static com.google.common.base.MoreObjects.firstNonNull; 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.lookupSuperordinateDomain;
import static google.registry.flows.host.HostFlowUtils.validateHostName; import static google.registry.flows.host.HostFlowUtils.validateHostName;
import static google.registry.flows.host.HostFlowUtils.verifyDomainIsSameRegistrar; 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.index.ForeignKeyIndex.loadAndGetKey;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.CollectionUtils.isNullOrEmpty; import static google.registry.util.CollectionUtils.isNullOrEmpty;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.dns.DnsQueue; import google.registry.dns.DnsQueue;
import google.registry.flows.EppException; 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.ParameterValueRangeErrorException;
import google.registry.flows.EppException.RequiredParameterMissingException; import google.registry.flows.EppException.RequiredParameterMissingException;
import google.registry.flows.EppException.StatusProhibitsOperationException; 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.AsyncFlowUtils;
import google.registry.flows.async.DnsRefreshForHostRenameAction; 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.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.HostCommand.Update;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.model.host.HostResource.Builder; import google.registry.model.host.HostResource.Builder;
@ -47,10 +68,10 @@ import org.joda.time.Duration;
* An EPP flow that updates a host resource. * An EPP flow that updates a host resource.
* *
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
* @error {@link google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException} * @error {@link google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException}
* @error {@link google.registry.flows.ResourceUpdateFlow.ResourceHasClientUpdateProhibitedException} * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
* @error {@link google.registry.flows.ResourceUpdateFlow.StatusNotClientSettableException} * @error {@link google.registry.flows.exceptions.ResourceToMutateDoesNotExistException}
* @error {@link google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException} * @error {@link google.registry.flows.exceptions.StatusNotClientSettableException}
* @error {@link HostFlowUtils.HostNameTooShallowException} * @error {@link HostFlowUtils.HostNameTooShallowException}
* @error {@link HostFlowUtils.InvalidHostNameException} * @error {@link HostFlowUtils.InvalidHostNameException}
* @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException} * @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException}
@ -60,40 +81,115 @@ import org.joda.time.Duration;
* @error {@link RenameHostToExternalRemoveIpException} * @error {@link RenameHostToExternalRemoveIpException}
* @error {@link RenameHostToSubordinateRequiresIpException} * @error {@link RenameHostToSubordinateRequiresIpException}
*/ */
public class HostUpdateFlow extends ResourceUpdateFlow<HostResource, Builder, Update> { public class HostUpdateFlow extends LoggedInFlow implements TransactionalFlow {
private Key<DomainResource> superordinateDomain; /**
* Note that CLIENT_UPDATE_PROHIBITED is intentionally not in this list. This is because it
private String oldHostName; * requires special checking, since you must be able to clear the status off the object with an
private String newHostName; * update.
private boolean isHostRename; */
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() {} @Inject HostUpdateFlow() {}
@Override @Override
protected void initResourceCreateOrMutateFlow() throws EppException { protected final void initLoggedInFlow() throws EppException {
String suppliedNewHostName = command.getInnerChange().getFullyQualifiedHostName(); registerExtensions(MetadataExtension.class);
isHostRename = suppliedNewHostName != null;
oldHostName = targetId;
newHostName = firstNonNull(suppliedNewHostName, oldHostName);
superordinateDomain =
lookupSuperordinateDomain(validateHostName(newHostName), now);
} }
@Override @Override
protected void verifyUpdateIsAllowed() throws EppException { public final EppOutput run() throws EppException {
verifyDomainIsSameRegistrar(superordinateDomain, getClientId()); Update command = (Update) resourceCommand;
if (isHostRename String suppliedNewHostName = command.getInnerChange().getFullyQualifiedHostName();
&& loadAndGetKey(HostResource.class, newHostName, now) != null) { 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); 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 private void verifyUpdateAllowed(
protected void verifyNewUpdatedStateIsAllowed() throws EppException { 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 wasExternal = existingResource.getSuperordinateDomain() == null;
boolean wasSubordinate = !wasExternal; boolean wasSubordinate = !wasExternal;
boolean willBeExternal = superordinateDomain == null; boolean willBeExternal = newResource.getSuperordinateDomain() == null;
boolean willBeSubordinate = !willBeExternal; boolean willBeSubordinate = !willBeExternal;
boolean newResourceHasIps = !isNullOrEmpty(newResource.getInetAddresses()); boolean newResourceHasIps = !isNullOrEmpty(newResource.getInetAddresses());
boolean commandAddsIps = !isNullOrEmpty(command.getInnerAdd().getInetAddresses()); boolean commandAddsIps = !isNullOrEmpty(command.getInnerAdd().getInetAddresses());
@ -114,43 +210,18 @@ public class HostUpdateFlow extends ResourceUpdateFlow<HostResource, Builder, Up
} }
} }
@Override public void enqueueTasks(HostResource existingResource, HostResource newResource) {
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();
// Only update DNS for subordinate hosts. External hosts have no glue to write, so they // 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. // are only written as NS records from the referencing domain.
if (existingResource.getSuperordinateDomain() != null) { 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. // 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 // If the renamed host is also subordinate, then we must enqueue an update to write the new
// glue. // glue.
if (newResource.getSuperordinateDomain() != null) { 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 // 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. // 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 private void updateSuperordinateDomains(HostResource existingResource, HostResource newResource) {
protected final HistoryEntry.Type getHistoryEntryType() {
return HistoryEntry.Type.HOST_UPDATE;
}
private void updateSuperordinateDomains() {
Key<DomainResource> oldSuperordinateDomain = existingResource.getSuperordinateDomain(); Key<DomainResource> oldSuperordinateDomain = existingResource.getSuperordinateDomain();
if (oldSuperordinateDomain != null || superordinateDomain != null) { Key<DomainResource> newSuperordinateDomain = newResource.getSuperordinateDomain();
if (Objects.equals(oldSuperordinateDomain, superordinateDomain)) { if (oldSuperordinateDomain != null || newSuperordinateDomain != null) {
if (Objects.equals(oldSuperordinateDomain, newSuperordinateDomain)) {
ofy().save().entity( ofy().save().entity(
ofy().load().key(oldSuperordinateDomain).now().asBuilder() ofy().load().key(oldSuperordinateDomain).now().asBuilder()
.removeSubordinateHost(oldHostName) .removeSubordinateHost(existingResource.getFullyQualifiedHostName())
.addSubordinateHost(newHostName) .addSubordinateHost(newResource.getFullyQualifiedHostName())
.build()); .build());
} else { } else {
if (oldSuperordinateDomain != null) { if (oldSuperordinateDomain != null) {
ofy().save().entity( ofy().save().entity(
ofy().load().key(oldSuperordinateDomain).now() ofy().load().key(oldSuperordinateDomain).now()
.asBuilder() .asBuilder()
.removeSubordinateHost(oldHostName) .removeSubordinateHost(existingResource.getFullyQualifiedHostName())
.build()); .build());
} }
if (superordinateDomain != null) { if (newSuperordinateDomain != null) {
ofy().save().entity( ofy().save().entity(
ofy().load().key(superordinateDomain).now() ofy().load().key(newSuperordinateDomain).now()
.asBuilder() .asBuilder()
.addSubordinateHost(newHostName) .addSubordinateHost(newResource.getFullyQualifiedHostName())
.build()); .build());
} }
} }

View file

@ -18,8 +18,8 @@ import static google.registry.model.eppoutput.CheckData.HostCheck.create;
import static google.registry.testing.DatastoreHelper.persistActiveHost; import static google.registry.testing.DatastoreHelper.persistActiveHost;
import static google.registry.testing.DatastoreHelper.persistDeletedHost; import static google.registry.testing.DatastoreHelper.persistDeletedHost;
import google.registry.flows.ResourceCheckFlow.TooManyResourceChecksException;
import google.registry.flows.ResourceCheckFlowTestCase; import google.registry.flows.ResourceCheckFlowTestCase;
import google.registry.flows.exceptions.TooManyResourceChecksException;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import org.junit.Test; import org.junit.Test;

View file

@ -28,8 +28,8 @@ import static google.registry.testing.TaskQueueHelper.assertNoDnsTasksEnqueued;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import google.registry.flows.EppXmlTransformer.IpAddressVersionMismatchException; import google.registry.flows.EppXmlTransformer.IpAddressVersionMismatchException;
import google.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException;
import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.exceptions.ResourceAlreadyExistsException;
import google.registry.flows.host.HostCreateFlow.SubordinateHostMustHaveIpException; import google.registry.flows.host.HostCreateFlow.SubordinateHostMustHaveIpException;
import google.registry.flows.host.HostCreateFlow.UnexpectedExternalHostIpException; import google.registry.flows.host.HostCreateFlow.UnexpectedExternalHostIpException;
import google.registry.flows.host.HostFlowUtils.HostNameTooLongException; import google.registry.flows.host.HostFlowUtils.HostNameTooLongException;

View file

@ -30,13 +30,13 @@ import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.flows.ResourceAsyncDeleteFlow.ResourceToDeleteIsReferencedException;
import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException; import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
import google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException;
import google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException;
import google.registry.flows.async.DeleteEppResourceAction; import google.registry.flows.async.DeleteEppResourceAction;
import google.registry.flows.async.DeleteHostResourceAction; import google.registry.flows.async.DeleteHostResourceAction;
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
import google.registry.flows.exceptions.ResourceToDeleteIsReferencedException;
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;

View file

@ -25,7 +25,7 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses; import com.google.common.net.InetAddresses;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException; import google.registry.flows.exceptions.ResourceToQueryDoesNotExistException;
import google.registry.model.domain.DomainResource; import google.registry.model.domain.DomainResource;
import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;

View file

@ -43,11 +43,11 @@ import com.googlecode.objectify.Key;
import google.registry.flows.EppRequestSource; import google.registry.flows.EppRequestSource;
import google.registry.flows.ResourceFlowTestCase; import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException; import google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException;
import google.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException;
import google.registry.flows.ResourceUpdateFlow.ResourceHasClientUpdateProhibitedException;
import google.registry.flows.ResourceUpdateFlow.StatusNotClientSettableException;
import google.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException;
import google.registry.flows.async.DnsRefreshForHostRenameAction; import google.registry.flows.async.DnsRefreshForHostRenameAction;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.flows.exceptions.ResourceStatusProhibitsOperationException;
import google.registry.flows.exceptions.ResourceToMutateDoesNotExistException;
import google.registry.flows.exceptions.StatusNotClientSettableException;
import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException; import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException;
import google.registry.flows.host.HostFlowUtils.InvalidHostNameException; import google.registry.flows.host.HostFlowUtils.InvalidHostNameException;
import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException; import google.registry.flows.host.HostFlowUtils.SuperordinateDomainDoesNotExistException;