Remove the ofy().load() inside of HostResource.cloneProjectedAtTime

In fact, completely eviscerate cloneProjectedAtTime (to be removed in
a followup CL) in favor of doing the projection of transfers and the
loading of values from the superordinate domain at call sites. This
is one of the issues that blocked the memcache audit work, since the
load inside of cloneProjectedAtTime could not be controlled by the
caller.

Note: fixed a minor bug where a subordinate host created after its superordinate domain was last transferred should have lastTransferTime==null but was previously reporting the domain's lastTransferTime.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=149769125
This commit is contained in:
cgoldfeder 2017-03-10 10:05:26 -08:00 committed by Ben McIlwain
parent 1f000b94e6
commit 9174855a47
67 changed files with 970 additions and 471 deletions

View file

@ -276,9 +276,18 @@ public class DeleteContactsAndHostsAction implements Runnable {
if (!doesResourceStateAllowDeletion(resource, now)) {
return DeletionResult.create(Type.ERRORED, "");
}
// Contacts and external hosts have a direct client id. For subordinate hosts it needs to be
// read off of the superordinate domain.
String resourceClientId = resource.getPersistedCurrentSponsorClientId();
if (resource instanceof HostResource && ((HostResource) resource).isSubordinate()) {
resourceClientId =
ofy().load().key(((HostResource) resource).getSuperordinateDomain()).now()
.cloneProjectedAtTime(now)
.getCurrentSponsorClientId();
}
boolean requestedByCurrentOwner =
resource.getCurrentSponsorClientId().equals(deletionRequest.requestingClientId());
resourceClientId.equals(deletionRequest.requestingClientId());
boolean deleteAllowed =
hasNoActiveReferences && (requestedByCurrentOwner || deletionRequest.isSuperuser());

View file

@ -139,7 +139,7 @@ public final class ResourceFlowUtils {
/** Check that the given clientId corresponds to the owner of given resource. */
public static void verifyResourceOwnership(String myClientId, EppResource resource)
throws EppException {
if (!myClientId.equals(resource.getCurrentSponsorClientId())) {
if (!myClientId.equals(resource.getPersistedCurrentSponsorClientId())) {
throw new ResourceNotOwnedException();
}
}
@ -264,7 +264,7 @@ public final class ResourceFlowUtils {
B builder = ResourceFlowUtils.<R, B>resolvePendingTransfer(resource, transferStatus, now);
return builder
.setLastTransferTime(now)
.setCurrentSponsorClientId(resource.getTransferData().getGainingClientId())
.setPersistedCurrentSponsorClientId(resource.getTransferData().getGainingClientId())
.build();
}

View file

@ -72,7 +72,7 @@ public final class ContactCreateFlow implements TransactionalFlow {
.setContactId(targetId)
.setAuthInfo(command.getAuthInfo())
.setCreationClientId(clientId)
.setCurrentSponsorClientId(clientId)
.setPersistedCurrentSponsorClientId(clientId)
.setRepoId(createRepoId(ObjectifyService.allocateId(), roidSuffix))
.setFaxNumber(command.getFax())
.setVoiceNumber(command.getVoice())

View file

@ -155,7 +155,7 @@ public class DomainAllocateFlow implements TransactionalFlow {
DateTime registrationExpirationTime = leapSafeAddYears(now, years);
DomainResource newDomain = new DomainResource.Builder()
.setCreationClientId(clientId)
.setCurrentSponsorClientId(clientId)
.setPersistedCurrentSponsorClientId(clientId)
.setRepoId(repoId)
.setIdnTableName(validateDomainNameWithIdnTables(domainName))
.setRegistrationExpirationTime(registrationExpirationTime)

View file

@ -234,7 +234,7 @@ public final class DomainApplicationCreateFlow implements TransactionalFlow {
DomainApplication newApplication = new DomainApplication.Builder()
.setCreationTrid(trid)
.setCreationClientId(clientId)
.setCurrentSponsorClientId(clientId)
.setPersistedCurrentSponsorClientId(clientId)
.setRepoId(createDomainRepoId(ObjectifyService.allocateId(), tld))
.setLaunchNotice(launchCreate == null ? null : launchCreate.getNotice())
.setIdnTableName(idnTableName)

View file

@ -266,7 +266,7 @@ public class DomainCreateFlow implements TransactionalFlow {
}
DomainResource newDomain = new DomainResource.Builder()
.setCreationClientId(clientId)
.setCurrentSponsorClientId(clientId)
.setPersistedCurrentSponsorClientId(clientId)
.setRepoId(repoId)
.setIdnTableName(validateDomainNameWithIdnTables(domainName))
.setRegistrationExpirationTime(registrationExpirationTime)

View file

@ -18,7 +18,7 @@ import static google.registry.flows.FlowUtils.validateClientIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist;
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.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
import static google.registry.model.EppResourceUtils.createRepoId;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
@ -98,7 +98,7 @@ public final class HostCreateFlow implements TransactionalFlow {
// we can detect error conditions earlier.
Optional<DomainResource> superordinateDomain =
lookupSuperordinateDomain(validateHostName(targetId), now);
verifyDomainIsSameRegistrar(superordinateDomain.orNull(), clientId);
verifySuperordinateDomainOwnership(clientId, superordinateDomain.orNull());
boolean willBeSubordinate = superordinateDomain.isPresent();
boolean hasIpAddresses = !isNullOrEmpty(command.getInetAddresses());
if (willBeSubordinate != hasIpAddresses) {
@ -109,7 +109,7 @@ public final class HostCreateFlow implements TransactionalFlow {
}
HostResource newHost = new Builder()
.setCreationClientId(clientId)
.setCurrentSponsorClientId(clientId)
.setPersistedCurrentSponsorClientId(clientId)
.setFullyQualifiedHostName(targetId)
.setInetAddresses(command.getInetAddresses())
.setRepoId(createRepoId(ObjectifyService.allocateId(), roidSuffix))

View file

@ -33,6 +33,7 @@ import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.async.AsyncFlowEnqueuer;
import google.registry.model.EppResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.eppcommon.StatusValue;
@ -93,7 +94,14 @@ public final class HostDeleteFlow implements TransactionalFlow {
HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now);
verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES);
if (!isSuperuser) {
verifyResourceOwnership(clientId, existingHost);
// Hosts transfer with their superordinate domains, so for hosts with a superordinate domain,
// the client id, needs to be read off of it.
EppResource owningResource =
existingHost.isSubordinate()
? ofy().load().key(existingHost.getSuperordinateDomain()).now()
.cloneProjectedAtTime(now)
: existingHost;
verifyResourceOwnership(clientId, owningResource);
}
asyncFlowEnqueuer.enqueueAsyncDelete(existingHost, clientId, isSuperuser);
HostResource newHost =

View file

@ -111,9 +111,9 @@ public class HostFlowUtils {
}
/** Ensure that the superordinate domain is sponsored by the provided clientId. */
static void verifyDomainIsSameRegistrar(
DomainResource superordinateDomain,
String clientId) throws EppException {
static void verifySuperordinateDomainOwnership(
String clientId,
DomainResource superordinateDomain) throws EppException {
if (superordinateDomain != null
&& !clientId.equals(superordinateDomain.getCurrentSponsorClientId())) {
throw new HostDomainNotOwnedException();

View file

@ -18,6 +18,7 @@ import static google.registry.flows.FlowUtils.validateClientIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
import static google.registry.flows.host.HostFlowUtils.validateHostName;
import static google.registry.model.EppResourceUtils.isLinked;
import static google.registry.model.ofy.ObjectifyService.ofy;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
@ -26,9 +27,11 @@ import google.registry.flows.ExtensionManager;
import google.registry.flows.Flow;
import google.registry.flows.FlowModule.ClientId;
import google.registry.flows.FlowModule.TargetId;
import google.registry.model.domain.DomainResource;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.host.HostInfoData;
import google.registry.model.host.HostInfoData.Builder;
import google.registry.model.host.HostResource;
import google.registry.util.Clock;
import javax.inject.Inject;
@ -66,18 +69,34 @@ public final class HostInfoFlow implements Flow {
if (isLinked(Key.create(host), now)) {
statusValues.add(StatusValue.LINKED);
}
Builder hostInfoDataBuilder = HostInfoData.newBuilder();
// Hosts transfer with their superordinate domains, so for hosts with a superordinate domain,
// the client id, last transfer time, and pending transfer status need to be read off of it. If
// there is no superordinate domain, the host's own values for these fields will be correct.
if (host.isSubordinate()) {
DomainResource superordinateDomain =
ofy().load().key(host.getSuperordinateDomain()).now().cloneProjectedAtTime(now);
hostInfoDataBuilder
.setCurrentSponsorClientId(superordinateDomain.getCurrentSponsorClientId())
.setLastTransferTime(host.computeLastTransferTime(superordinateDomain));
if (superordinateDomain.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) {
statusValues.add(StatusValue.PENDING_TRANSFER);
}
} else {
hostInfoDataBuilder
.setCurrentSponsorClientId(host.getPersistedCurrentSponsorClientId())
.setLastTransferTime(host.getLastTransferTime());
}
return responseBuilder
.setResData(HostInfoData.newBuilder()
.setResData(hostInfoDataBuilder
.setFullyQualifiedHostName(host.getFullyQualifiedHostName())
.setRepoId(host.getRepoId())
.setStatusValues(statusValues.build())
.setInetAddresses(host.getInetAddresses())
.setCurrentSponsorClientId(host.getCurrentSponsorClientId())
.setCreationClientId(host.getCreationClientId())
.setCreationTime(host.getCreationTime())
.setLastEppUpdateClientId(host.getLastEppUpdateClientId())
.setLastEppUpdateTime(host.getLastEppUpdateTime())
.setLastTransferTime(host.getLastTransferTime())
.build())
.build();
}

View file

@ -24,7 +24,7 @@ import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses
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.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
@ -45,6 +45,7 @@ import google.registry.flows.FlowModule.TargetId;
import google.registry.flows.TransactionalFlow;
import google.registry.flows.async.AsyncFlowEnqueuer;
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.DomainResource;
import google.registry.model.domain.metadata.MetadataExtension;
@ -80,11 +81,13 @@ import org.joda.time.DateTime;
* @error {@link google.registry.flows.ResourceFlowUtils.StatusNotClientSettableException}
* @error {@link google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException}
* @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException}
* @error {@link HostFlowUtils.HostNameTooShallowException}
* @error {@link HostFlowUtils.InvalidHostNameException}
* @error {@link HostFlowUtils.HostDomainNotOwnedException}
* @error {@link HostFlowUtils.HostNameNotLowerCaseException}
* @error {@link HostFlowUtils.HostNameNotNormalizedException}
* @error {@link HostFlowUtils.HostNameNotPunyCodedException}
* @error {@link HostFlowUtils.HostNameTooLongException}
* @error {@link HostFlowUtils.HostNameTooShallowException}
* @error {@link HostFlowUtils.InvalidHostNameException}
* @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException}
* @error {@link CannotAddIpToExternalHostException}
* @error {@link CannotRemoveSubordinateHostLastIpException}
@ -128,10 +131,14 @@ public final class HostUpdateFlow implements TransactionalFlow {
boolean isHostRename = suppliedNewHostName != null;
String oldHostName = targetId;
String newHostName = firstNonNull(suppliedNewHostName, oldHostName);
DomainResource oldSuperordinateDomain = existingHost.isSubordinate()
? ofy().load().key(existingHost.getSuperordinateDomain()).now().cloneProjectedAtTime(now)
: null;
// Note that lookupSuperordinateDomain calls cloneProjectedAtTime on the domain for us.
Optional<DomainResource> newSuperordinateDomain =
lookupSuperordinateDomain(validateHostName(newHostName), now);
verifyUpdateAllowed(command, existingHost, newSuperordinateDomain.orNull());
EppResource owningResource = firstNonNull(oldSuperordinateDomain, existingHost);
verifyUpdateAllowed(command, existingHost, newSuperordinateDomain.orNull(), owningResource);
if (isHostRename && loadAndGetKey(HostResource.class, newHostName, now) != null) {
throw new HostAlreadyExistsException(newHostName);
}
@ -139,13 +146,27 @@ public final class HostUpdateFlow implements TransactionalFlow {
AddRemove remove = command.getInnerRemove();
checkSameValuesNotAddedAndRemoved(add.getStatusValues(), remove.getStatusValues());
checkSameValuesNotAddedAndRemoved(add.getInetAddresses(), remove.getInetAddresses());
Key<DomainResource> newSuperordinateDomainKey =
newSuperordinateDomain.isPresent() ? Key.create(newSuperordinateDomain.get()) : null;
Key<DomainResource> newSuperordinateDomainKey = newSuperordinateDomain.isPresent()
? Key.create(newSuperordinateDomain.get())
: null;
// If the superordinateDomain field is changing, set the lastSuperordinateChange to now.
DateTime lastSuperordinateChange =
Objects.equals(newSuperordinateDomainKey, existingHost.getSuperordinateDomain())
? existingHost.getLastSuperordinateChange()
: now;
// Compute afresh the last transfer time to handle any superordinate domain transfer that may
// have just completed. This is only critical for updates that rename a host away from its
// current superordinate domain, where we must "freeze" the last transfer time, but it's easiest
// to just update it unconditionally.
DateTime lastTransferTime = existingHost.computeLastTransferTime(oldSuperordinateDomain);
// Copy the clientId onto the host. This is only really needed when the host will be external,
// since external hosts store their own clientId. For subordinate hosts the canonical clientId
// comes from the superordinate domain, but we might as well update the persisted value. For
// non-superusers this is the flow clientId, but for superusers it might not be, so compute it.
String newPersistedClientId =
newSuperordinateDomain.isPresent()
? newSuperordinateDomain.get().getCurrentSponsorClientId()
: owningResource.getPersistedCurrentSponsorClientId();
HostResource newHost = existingHost.asBuilder()
.setFullyQualifiedHostName(newHostName)
.addStatusValues(add.getStatusValues())
@ -156,9 +177,9 @@ public final class HostUpdateFlow implements TransactionalFlow {
.setLastEppUpdateClientId(clientId)
.setSuperordinateDomain(newSuperordinateDomainKey)
.setLastSuperordinateChange(lastSuperordinateChange)
.build()
// Rely on the host's cloneProjectedAtTime() method to handle setting of transfer data.
.cloneProjectedAtTime(now);
.setLastTransferTime(lastTransferTime)
.setPersistedCurrentSponsorClientId(newPersistedClientId)
.build();
verifyHasIpsIffIsExternal(command, existingHost, newHost);
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
entitiesToSave.add(newHost);
@ -181,32 +202,37 @@ public final class HostUpdateFlow implements TransactionalFlow {
}
private void verifyUpdateAllowed(
Update command, HostResource existingResource, DomainResource superordinateDomain)
throws EppException {
Update command,
HostResource existingHost,
DomainResource newSuperordinateDomain,
EppResource owningResource)
throws EppException {
if (!isSuperuser) {
verifyResourceOwnership(clientId, existingResource);
// Verify that the host belongs to this registrar, either directly or because it is currently
// subordinate to a domain owned by this registrar.
verifyResourceOwnership(clientId, owningResource);
// Verify that the new superordinate domain belongs to this registrar.
verifySuperordinateDomainOwnership(clientId, newSuperordinateDomain);
ImmutableSet<StatusValue> statusesToAdd = command.getInnerAdd().getStatusValues();
ImmutableSet<StatusValue> statusesToRemove = command.getInnerRemove().getStatusValues();
// 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)
// status, then the update must be disallowed.
if (existingHost.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
&& !statusesToRemove.contains(StatusValue.CLIENT_UPDATE_PROHIBITED)) {
throw new ResourceHasClientUpdateProhibitedException();
}
verifyAllStatusesAreClientSettable(union(statusesToAdd, statusesToRemove));
}
verifyDomainIsSameRegistrar(superordinateDomain, clientId);
verifyNoDisallowedStatuses(existingResource, DISALLOWED_STATUSES);
verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES);
}
private void verifyHasIpsIffIsExternal(
Update command, HostResource existingResource, HostResource newResource) throws EppException {
boolean wasSubordinate = existingResource.isSubordinate();
Update command, HostResource existingHost, HostResource newHost) throws EppException {
boolean wasSubordinate = existingHost.isSubordinate();
boolean wasExternal = !wasSubordinate;
boolean willBeSubordinate = newResource.isSubordinate();
boolean willBeSubordinate = newHost.isSubordinate();
boolean willBeExternal = !willBeSubordinate;
boolean newResourceHasIps = !isNullOrEmpty(newResource.getInetAddresses());
boolean newHostHasIps = !isNullOrEmpty(newHost.getInetAddresses());
boolean commandAddsIps = !isNullOrEmpty(command.getInnerAdd().getInetAddresses());
// These checks are order-dependent. For example a subordinate-to-external rename that adds new
// ips should hit the first exception, whereas one that only fails to remove the existing ips
@ -214,61 +240,61 @@ public final class HostUpdateFlow implements TransactionalFlow {
if (willBeExternal && commandAddsIps) {
throw new CannotAddIpToExternalHostException();
}
if (wasSubordinate && willBeExternal && newResourceHasIps) {
if (wasSubordinate && willBeExternal && newHostHasIps) {
throw new RenameHostToExternalRemoveIpException();
}
if (wasExternal && willBeSubordinate && !commandAddsIps) {
throw new RenameHostToSubordinateRequiresIpException();
}
if (willBeSubordinate && !newResourceHasIps) {
if (willBeSubordinate && !newHostHasIps) {
throw new CannotRemoveSubordinateHostLastIpException();
}
}
private void enqueueTasks(HostResource existingResource, HostResource newResource) {
private void enqueueTasks(HostResource existingHost, HostResource newHost) {
// 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.isSubordinate()) {
dnsQueue.addHostRefreshTask(existingResource.getFullyQualifiedHostName());
if (existingHost.isSubordinate()) {
dnsQueue.addHostRefreshTask(existingHost.getFullyQualifiedHostName());
}
// In case of a rename, there are many updates we need to queue up.
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.isSubordinate()) {
dnsQueue.addHostRefreshTask(newResource.getFullyQualifiedHostName());
if (newHost.isSubordinate()) {
dnsQueue.addHostRefreshTask(newHost.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.
asyncFlowEnqueuer.enqueueAsyncDnsRefresh(existingResource);
asyncFlowEnqueuer.enqueueAsyncDnsRefresh(existingHost);
}
}
private void updateSuperordinateDomains(HostResource existingResource, HostResource newResource) {
if (existingResource.isSubordinate()
&& newResource.isSubordinate()
private static void updateSuperordinateDomains(HostResource existingHost, HostResource newHost) {
if (existingHost.isSubordinate()
&& newHost.isSubordinate()
&& Objects.equals(
existingResource.getSuperordinateDomain(),
newResource.getSuperordinateDomain())) {
existingHost.getSuperordinateDomain(),
newHost.getSuperordinateDomain())) {
ofy().save().entity(
ofy().load().key(existingResource.getSuperordinateDomain()).now().asBuilder()
.removeSubordinateHost(existingResource.getFullyQualifiedHostName())
.addSubordinateHost(newResource.getFullyQualifiedHostName())
ofy().load().key(existingHost.getSuperordinateDomain()).now().asBuilder()
.removeSubordinateHost(existingHost.getFullyQualifiedHostName())
.addSubordinateHost(newHost.getFullyQualifiedHostName())
.build());
return;
}
if (existingResource.isSubordinate()) {
if (existingHost.isSubordinate()) {
ofy().save().entity(
ofy().load().key(existingResource.getSuperordinateDomain()).now()
ofy().load().key(existingHost.getSuperordinateDomain()).now()
.asBuilder()
.removeSubordinateHost(existingResource.getFullyQualifiedHostName())
.removeSubordinateHost(existingHost.getFullyQualifiedHostName())
.build());
}
if (newResource.isSubordinate()) {
if (newHost.isSubordinate()) {
ofy().save().entity(
ofy().load().key(newResource.getSuperordinateDomain()).now()
ofy().load().key(newHost.getSuperordinateDomain()).now()
.asBuilder()
.addSubordinateHost(newResource.getFullyQualifiedHostName())
.addSubordinateHost(newHost.getFullyQualifiedHostName())
.build());
}
}

View file

@ -130,7 +130,13 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
return lastEppUpdateClientId;
}
public final String getCurrentSponsorClientId() {
/**
* Get the stored value of {@link #currentSponsorClientId}.
*
* <p>For subordinate hosts, this value may not represent the actual current client id, which is
* the client id of the superordinate host. For all other resources this is the true client id.
*/
public final String getPersistedCurrentSponsorClientId() {
return currentSponsorClientId;
}
@ -218,7 +224,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
}
/** Set the current sponsoring registrar. */
public B setCurrentSponsorClientId(String currentSponsorClientId) {
public B setPersistedCurrentSponsorClientId(String currentSponsorClientId) {
getInstance().currentSponsorClientId = currentSponsorClientId;
return thisCastToDerived();
}

View file

@ -207,7 +207,7 @@ public final class EppResourceUtils {
.setServerApproveAutorenewPollMessage(null)
.build())
.setLastTransferTime(transferData.getPendingTransferExpirationTime())
.setCurrentSponsorClientId(transferData.getGainingClientId());
.setPersistedCurrentSponsorClientId(transferData.getGainingClientId());
}
/**

View file

@ -47,8 +47,8 @@ import org.joda.time.DateTime;
@ReportedOn
@Entity
@ExternalMessagingName("contact")
public class ContactResource extends EppResource
implements ForeignKeyedEppResource, ResourceWithTransferData {
public class ContactResource extends EppResource implements
ForeignKeyedEppResource, ResourceWithTransferData {
/**
* Unique identifier for this contact.
@ -144,6 +144,10 @@ public class ContactResource extends EppResource
return disclose;
}
public final String getCurrentSponsorClientId() {
return getPersistedCurrentSponsorClientId();
}
@Override
public final TransferData getTransferData() {
return Optional.fromNullable(transferData).or(TransferData.EMPTY);

View file

@ -128,6 +128,10 @@ public abstract class DomainBase extends EppResource {
return nullToEmptyImmutableCopy(nsHosts);
}
public final String getCurrentSponsorClientId() {
return getPersistedCurrentSponsorClientId();
}
/** Loads and returns the fully qualified host names of all linked nameservers. */
public ImmutableSortedSet<String> loadNameserverFullyQualifiedHostNames() {
return FluentIterable.from(ofy().load().keys(getNameservers()).values())

View file

@ -17,7 +17,6 @@ package google.registry.model.host;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.union;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.ofy.Ofy.RECOMMENDED_MEMCACHE_EXPIRATION;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@ -36,11 +35,10 @@ import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.domain.DomainResource;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus;
import java.net.InetAddress;
import java.util.Set;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
/**
@ -122,37 +120,41 @@ public class HostResource extends EppResource implements ForeignKeyedEppResource
return fullyQualifiedHostName;
}
@Deprecated
@Override
public HostResource cloneProjectedAtTime(DateTime now) {
Builder builder = this.asBuilder();
return this;
}
if (superordinateDomain == null) {
// If this was a subordinate host to a domain that was being transferred, there might be a
// pending transfer still extant, so remove it.
builder.removeStatusValue(StatusValue.PENDING_TRANSFER);
} else {
// For hosts with superordinate domains, the client id, last transfer time, and transfer
// status value need to be read off the domain projected to the correct time.
DomainResource domainAtTime = ofy().load().key(superordinateDomain).now()
.cloneProjectedAtTime(now);
builder.setCurrentSponsorClientId(domainAtTime.getCurrentSponsorClientId());
// If the superordinate domain's last transfer time is what is relevant, because the host's
// superordinate domain was last changed less recently than the domain's last transfer, then
// use the last transfer time on the domain.
if (Optional.fromNullable(lastSuperordinateChange).or(START_OF_TIME)
.isBefore(Optional.fromNullable(domainAtTime.getLastTransferTime()).or(START_OF_TIME))) {
builder.setLastTransferTime(domainAtTime.getLastTransferTime());
}
// Copy the transfer status from the superordinate domain onto the host, because the host's
// doesn't matter and the superordinate domain always has the canonical data.
TransferStatus domainTransferStatus = domainAtTime.getTransferData().getTransferStatus();
if (TransferStatus.PENDING.equals(domainTransferStatus)) {
builder.addStatusValue(StatusValue.PENDING_TRANSFER);
} else {
builder.removeStatusValue(StatusValue.PENDING_TRANSFER);
}
/**
* Compute the correct last transfer time for this host given its loaded superordinate domain.
*
* <p>Hosts can move between superordinate domains, so to know which lastTransferTime is correct
* we need to know if the host was attached to this superordinate the last time that the
* superordinate was transferred. If the last superordinate change was before this time, then the
* host was attached to this superordinate domain during that transfer.
*
* <p>If the host is not subordinate the domain can be null and we just return last transfer time.
*
* @param superordinateDomain the loaded superordinate domain, which must match the key in
* the {@link #superordinateDomain} field. Passing it as a parameter allows the caller to
* control the degree of consistency used to load it.
*/
public DateTime computeLastTransferTime(@Nullable DomainResource superordinateDomain) {
if (!isSubordinate()) {
checkArgument(superordinateDomain == null);
return getLastTransferTime();
}
return builder.build();
checkArgument(
superordinateDomain != null
&& Key.create(superordinateDomain).equals(getSuperordinateDomain()));
DateTime lastSuperordinateChange =
Optional.fromNullable(getLastSuperordinateChange()).or(getCreationTime());
DateTime lastTransferOfCurrentSuperordinate =
Optional.fromNullable(superordinateDomain.getLastTransferTime()).or(START_OF_TIME);
return (lastSuperordinateChange.isBefore(lastTransferOfCurrentSuperordinate))
? superordinateDomain.getLastTransferTime()
: getLastTransferTime();
}
@Override

View file

@ -553,10 +553,19 @@ public class RdapJsonFormatter {
if (hasUnicodeComponents(hostResource.getFullyQualifiedHostName())) {
jsonBuilder.put("unicodeName", Idn.toUnicode(hostResource.getFullyQualifiedHostName()));
}
jsonBuilder.put("status", makeStatusValueList(
isLinked(Key.create(hostResource), now)
? union(hostResource.getStatusValues(), StatusValue.LINKED)
: hostResource.getStatusValues()));
ImmutableSet.Builder<StatusValue> statuses = new ImmutableSet.Builder<>();
statuses.addAll(hostResource.getStatusValues());
if (isLinked(Key.create(hostResource), now)) {
statuses.add(StatusValue.LINKED);
}
if (hostResource.isSubordinate()
&& ofy().load().key(hostResource.getSuperordinateDomain()).now().cloneProjectedAtTime(now)
.getStatusValues()
.contains(StatusValue.PENDING_TRANSFER)) {
statuses.add(StatusValue.PENDING_TRANSFER);
}
jsonBuilder.put("status", makeStatusValueList(statuses.build()));
jsonBuilder.put("links", ImmutableList.of(
makeLink("nameserver", hostResource.getFullyQualifiedHostName(), linkBase)));
List<ImmutableMap<String, Object>> remarks;

View file

@ -14,7 +14,11 @@
package google.registry.rde;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.net.InetAddresses;
import com.googlecode.objectify.Key;
import google.registry.model.domain.DomainResource;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource;
import google.registry.xjc.host.XjcHostAddrType;
@ -25,28 +29,62 @@ import google.registry.xjc.rdehost.XjcRdeHost;
import google.registry.xjc.rdehost.XjcRdeHostElement;
import java.net.Inet6Address;
import java.net.InetAddress;
import org.joda.time.DateTime;
/** Utility class that turns {@link HostResource} as {@link XjcRdeHostElement}. */
final class HostResourceToXjcConverter {
/** Converts {@link HostResource} to {@link XjcRdeHostElement}. */
static XjcRdeHostElement convert(HostResource host) {
return new XjcRdeHostElement(convertHost(host));
/** Converts a subordinate {@link HostResource} to {@link XjcRdeHostElement}. */
static XjcRdeHostElement convertSubordinate(
HostResource host, DomainResource superordinateDomain) {
checkArgument(Key.create(superordinateDomain).equals(host.getSuperordinateDomain()));
return new XjcRdeHostElement(convertSubordinateHost(host, superordinateDomain));
}
/** Converts an external {@link HostResource} to {@link XjcRdeHostElement}. */
static XjcRdeHostElement convertExternal(HostResource host) {
checkArgument(!host.isSubordinate());
return new XjcRdeHostElement(convertExternalHost(host));
}
/** Converts {@link HostResource} to {@link XjcRdeHost}. */
static XjcRdeHost convertHost(HostResource model) {
static XjcRdeHost convertSubordinateHost(HostResource model, DomainResource superordinateDomain) {
XjcRdeHost bean = convertHostCommon(
model,
superordinateDomain.getCurrentSponsorClientId(),
model.computeLastTransferTime(superordinateDomain));
if (superordinateDomain.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) {
bean.getStatuses().add(convertStatusValue(StatusValue.PENDING_TRANSFER));
}
return bean;
}
/** Converts {@link HostResource} to {@link XjcRdeHost}. */
static XjcRdeHost convertExternalHost(HostResource model) {
return convertHostCommon(
model,
model.getPersistedCurrentSponsorClientId(),
model.getLastTransferTime());
}
private static XjcRdeHost convertHostCommon(
HostResource model, String clientId, DateTime lastTransferTime) {
XjcRdeHost bean = new XjcRdeHost();
bean.setName(model.getFullyQualifiedHostName());
bean.setRoid(model.getRepoId());
bean.setClID(model.getCurrentSponsorClientId());
bean.setTrDate(model.getLastTransferTime());
bean.setCrDate(model.getCreationTime());
bean.setUpDate(model.getLastEppUpdateTime());
bean.setCrRr(RdeAdapter.convertRr(model.getCreationClientId(), null));
bean.setUpRr(RdeAdapter.convertRr(model.getLastEppUpdateClientId(), null));
bean.setCrRr(RdeAdapter.convertRr(model.getCreationClientId(), null));
bean.setClID(clientId);
bean.setTrDate(lastTransferTime);
for (StatusValue status : model.getStatusValues()) {
// TODO(b/34844887): Remove when PENDING_TRANSFER is not persisted on host resources.
if (status.equals(StatusValue.PENDING_TRANSFER)) {
continue;
}
// TODO(cgoldfeder): Add in LINKED status if applicable.
bean.getStatuses().add(convertStatusValue(status));
}
for (InetAddress addr : model.getInetAddresses()) {

View file

@ -117,9 +117,16 @@ public final class RdeMarshaller implements Serializable {
}
/** Turns {@link HostResource} object into an XML fragment. */
public DepositFragment marshalHost(HostResource host) {
public DepositFragment marshalSubordinateHost(
HostResource host, DomainResource superordinateDomain) {
return marshalResource(RdeResourceType.HOST, host,
HostResourceToXjcConverter.convert(host));
HostResourceToXjcConverter.convertSubordinate(host, superordinateDomain));
}
/** Turns {@link HostResource} object into an XML fragment. */
public DepositFragment marshalExternalHost(HostResource host) {
return marshalResource(RdeResourceType.HOST, host,
HostResourceToXjcConverter.convertExternal(host));
}
/** Turns {@link Registrar} object into an XML fragment. */

View file

@ -15,6 +15,7 @@
package google.registry.rde;
import static com.google.common.base.Strings.nullToEmpty;
import static google.registry.model.EppResourceUtils.loadAtPointInTime;
import static google.registry.model.ofy.ObjectifyService.ofy;
import com.google.appengine.tools.mapreduce.Mapper;
@ -28,7 +29,6 @@ import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Maps;
import com.googlecode.objectify.Result;
import google.registry.model.EppResource;
import google.registry.model.EppResourceUtils;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainResource;
import google.registry.model.host.HostResource;
@ -77,7 +77,7 @@ public final class RdeStagingMapper extends Mapper<EppResource, PendingDeposit,
// Skip prober data.
if (nullToEmpty(resource.getCreationClientId()).startsWith("prober-")
|| nullToEmpty(resource.getCurrentSponsorClientId()).startsWith("prober-")
|| nullToEmpty(resource.getPersistedCurrentSponsorClientId()).startsWith("prober-")
|| nullToEmpty(resource.getLastEppUpdateClientId()).startsWith("prober-")) {
return;
}
@ -110,7 +110,7 @@ public final class RdeStagingMapper extends Mapper<EppResource, PendingDeposit,
new Function<DateTime, Result<EppResource>>() {
@Override
public Result<EppResource> apply(DateTime input) {
return EppResourceUtils.loadAtPointInTime(resource, input);
return loadAtPointInTime(resource, input);
}}));
// Convert resource to an XML fragment for each watermark/mode pair lazily and cache the result.
@ -167,7 +167,14 @@ public final class RdeStagingMapper extends Mapper<EppResource, PendingDeposit,
cache.put(WatermarkModePair.create(watermark, RdeMode.THIN), result);
return result;
} else if (resource instanceof HostResource) {
result = Optional.of(marshaller.marshalHost((HostResource) resource));
HostResource host = (HostResource) resource;
result = Optional.of(host.isSubordinate()
? marshaller.marshalSubordinateHost(
host,
// Note that loadAtPointInTime() does cloneProjectedAtTime(watermark) for us.
loadAtPointInTime(
ofy().load().key(host.getSuperordinateDomain()).now(), watermark).now())
: marshaller.marshalExternalHost(host));
cache.put(WatermarkModePair.create(watermark, RdeMode.FULL), result);
cache.put(WatermarkModePair.create(watermark, RdeMode.THIN), result);
return result;

View file

@ -98,7 +98,7 @@ final class XjcToContactResourceConverter extends XjcToEppResourceConverter {
.setInternationalizedPostalInfo(
getPostalInfoOfType(contact.getPostalInfos(), XjcContactPostalInfoEnumType.INT))
.setContactId(contact.getId())
.setCurrentSponsorClientId(contact.getClID())
.setPersistedCurrentSponsorClientId(contact.getClID())
.setCreationClientId(contact.getCrRr() == null ? null : contact.getCrRr().getValue())
.setLastEppUpdateClientId(contact.getUpRr() == null ? null : contact.getUpRr().getValue())
.setCreationTime(contact.getCrDate())

View file

@ -189,7 +189,7 @@ final class XjcToDomainResourceConverter extends XjcToEppResourceConverter {
.setFullyQualifiedDomainName(canonicalizeDomainName(domain.getName()))
.setRepoId(domain.getRoid())
.setIdnTableName(domain.getIdnTableId())
.setCurrentSponsorClientId(domain.getClID())
.setPersistedCurrentSponsorClientId(domain.getClID())
.setCreationClientId(domain.getCrRr().getValue())
.setCreationTime(domain.getCrDate())
.setAutorenewPollMessage(Key.create(autoRenewPollMessage))

View file

@ -14,7 +14,7 @@
package google.registry.rde.imports;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.in;
import static com.google.common.base.Predicates.not;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.rde.imports.RdeImportUtils.generateTridForImport;
@ -56,6 +56,9 @@ public class XjcToHostResourceConverter extends XjcToEppResourceConverter {
};
static HostResource convert(XjcRdeHost host) {
// TODO(b/35384052): Handle subordinate hosts correctly by setting superordinateDomaina and
// lastSuperordinateChange fields.
// First create and save history entry
ofy().save().entity(
new HistoryEntry.Builder()
@ -72,7 +75,7 @@ public class XjcToHostResourceConverter extends XjcToEppResourceConverter {
return new HostResource.Builder()
.setFullyQualifiedHostName(canonicalizeDomainName(host.getName()))
.setRepoId(host.getRoid())
.setCurrentSponsorClientId(host.getClID())
.setPersistedCurrentSponsorClientId(host.getClID())
.setLastTransferTime(host.getTrDate())
.setCreationTime(host.getCrDate())
.setLastEppUpdateTime(host.getUpDate())
@ -82,7 +85,8 @@ public class XjcToHostResourceConverter extends XjcToEppResourceConverter {
FluentIterable.from(host.getStatuses())
.transform(STATUS_VALUE_CONVERTER)
// LINKED is implicit and should not be imported onto the new host.
.filter(not(equalTo(StatusValue.LINKED)))
// PENDING_TRANSFER is a property of the superordinate host.
.filter(not(in(ImmutableSet.of(StatusValue.LINKED, StatusValue.PENDING_TRANSFER))))
.toSet())
.setInetAddresses(ImmutableSet.copyOf(Lists.transform(host.getAddrs(), ADDR_CONVERTER)))
.build();

View file

@ -14,6 +14,7 @@
package google.registry.tools;
import static google.registry.model.EppResourceUtils.loadAtPointInTime;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.registry.Registries.assertTldExists;
import static java.nio.charset.StandardCharsets.UTF_8;
@ -132,7 +133,14 @@ final class GenerateEscrowDepositCommand implements RemoteApiCommand {
}
frag = marshaller.marshalDomain(domain, mode);
} else if (resource instanceof HostResource) {
frag = marshaller.marshalHost((HostResource) resource);
HostResource host = (HostResource) resource;
frag = host.isSubordinate()
? marshaller.marshalSubordinateHost(
host,
// Note that loadAtPointInTime() does cloneProjectedAtTime(watermark) for us.
loadAtPointInTime(
ofy().load().key(host.getSuperordinateDomain()).now(), watermark).now())
: marshaller.marshalExternalHost(host);
} else {
continue; // Surprise polymorphic entities, e.g. DomainApplication.
}

View file

@ -15,6 +15,7 @@
package google.registry.whois;
import static com.google.common.base.Preconditions.checkNotNull;
import static google.registry.model.ofy.ObjectifyService.ofy;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
@ -46,7 +47,13 @@ final class NameserverWhoisResponse extends WhoisResponseImpl {
BasicEmitter emitter = new BasicEmitter();
for (int i = 0; i < hosts.size(); i++) {
HostResource host = hosts.get(i);
Registrar registrar = getRegistrar(host.getCurrentSponsorClientId());
String clientId =
host.isSubordinate()
? ofy().load().key(host.getSuperordinateDomain()).now()
.cloneProjectedAtTime(getTimestamp())
.getCurrentSponsorClientId()
: host.getPersistedCurrentSponsorClientId();
Registrar registrar = getRegistrar(clientId);
emitter
.emitField("Server Name", maybeFormatHostname(
host.getFullyQualifiedHostName(), preferUnicode))