mirror of
https://github.com/google/nomulus.git
synced 2025-07-21 02:06:00 +02:00
Notify registrars of async contact/host deletions
We now send PendingActionNotificationResponses in our poll messages upon completion of an asynchronous contact or host deletion. This is part 1 of 2, which begins logging Trid in all enqueued Host/Contact deletion flows for use in batch deletions, and optionally consuming the resultant Trid info to emit a Host/ContactPendingActionNotifcationResponse. Part 2 will make this response emission non-optional, which will happen once the queue is cleared of all non-Trid containing tasks. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=153084197
This commit is contained in:
parent
478c7576c6
commit
5047d568de
12 changed files with 240 additions and 66 deletions
|
@ -23,9 +23,11 @@ import static com.googlecode.objectify.Key.getKind;
|
|||
import static google.registry.flows.ResourceFlowUtils.createResolvedTransferData;
|
||||
import static google.registry.flows.ResourceFlowUtils.handlePendingTransferOnDelete;
|
||||
import static google.registry.flows.ResourceFlowUtils.updateForeignKeyIndexDeletionTime;
|
||||
import static google.registry.flows.async.AsyncFlowEnqueuer.PARAM_CLIENT_TRANSACTION_ID;
|
||||
import static google.registry.flows.async.AsyncFlowEnqueuer.PARAM_IS_SUPERUSER;
|
||||
import static google.registry.flows.async.AsyncFlowEnqueuer.PARAM_REQUESTING_CLIENT_ID;
|
||||
import static google.registry.flows.async.AsyncFlowEnqueuer.PARAM_RESOURCE_KEY;
|
||||
import static google.registry.flows.async.AsyncFlowEnqueuer.PARAM_SERVER_TRANSACTION_ID;
|
||||
import static google.registry.flows.async.AsyncFlowEnqueuer.QUEUE_ASYNC_DELETE;
|
||||
import static google.registry.model.EppResourceUtils.isActive;
|
||||
import static google.registry.model.EppResourceUtils.isDeleted;
|
||||
|
@ -68,7 +70,11 @@ import google.registry.model.annotations.ExternalMessagingName;
|
|||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseData;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.HostPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
|
@ -318,6 +324,8 @@ public class DeleteContactsAndHostsAction implements Runnable {
|
|||
.setMsg(pollMessageText)
|
||||
.setParent(historyEntry)
|
||||
.setEventTime(now)
|
||||
.setResponseData(
|
||||
getPollMessageResponseData(deletionRequest, resource, deleteAllowed, now))
|
||||
.build();
|
||||
|
||||
EppResource resourceToSave;
|
||||
|
@ -348,6 +356,35 @@ public class DeleteContactsAndHostsAction implements Runnable {
|
|||
deleteAllowed ? Type.DELETED : Type.NOT_DELETED, pollMessageText);
|
||||
}
|
||||
|
||||
private static ImmutableList<? extends ResponseData> getPollMessageResponseData(
|
||||
DeletionRequest deletionRequest,
|
||||
EppResource resource,
|
||||
boolean deleteAllowed,
|
||||
DateTime now) {
|
||||
Optional<String> clientTransactionId = deletionRequest.clientTransactionId();
|
||||
Optional<String> serverTransactionId = deletionRequest.serverTransactionId();
|
||||
// TODO(b/36402020): Make this unconditional, once older tasks enqueued without Trid data
|
||||
// have been processed out of the queue.
|
||||
checkState(
|
||||
clientTransactionId.isPresent() == serverTransactionId.isPresent(),
|
||||
"Found one part of TRID without the other!");
|
||||
if (clientTransactionId.isPresent() && serverTransactionId.isPresent()) {
|
||||
Trid trid = Trid.create(clientTransactionId.get(), serverTransactionId.get());
|
||||
if (resource instanceof HostResource) {
|
||||
return ImmutableList.of(
|
||||
HostPendingActionNotificationResponse.create(
|
||||
((HostResource) resource).getFullyQualifiedHostName(), deleteAllowed, trid, now));
|
||||
} else if (resource instanceof ContactResource) {
|
||||
return ImmutableList.of(
|
||||
ContactPendingActionNotificationResponse.create(
|
||||
((ContactResource) resource).getContactId(), deleteAllowed, trid, now));
|
||||
} else {
|
||||
throw new IllegalStateException("EPP resource of unknown type " + Key.create(resource));
|
||||
}
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the proper history entry type for the delete operation, as a function of
|
||||
* whether or not the delete was successful.
|
||||
|
@ -403,6 +440,13 @@ public class DeleteContactsAndHostsAction implements Runnable {
|
|||
* the actual current owner of the resource).
|
||||
*/
|
||||
abstract String requestingClientId();
|
||||
|
||||
/** First half of TRID for the original request, split for serializability. */
|
||||
abstract Optional<String> clientTransactionId();
|
||||
|
||||
/** Second half of TRID for the original request, split for serializability. */
|
||||
abstract Optional<String> serverTransactionId();
|
||||
|
||||
abstract boolean isSuperuser();
|
||||
abstract TaskHandle task();
|
||||
|
||||
|
@ -411,6 +455,8 @@ public class DeleteContactsAndHostsAction implements Runnable {
|
|||
abstract Builder setKey(Key<? extends EppResource> key);
|
||||
abstract Builder setLastUpdateTime(DateTime lastUpdateTime);
|
||||
abstract Builder setRequestingClientId(String requestingClientId);
|
||||
abstract Builder setClientTransactionId(Optional<String> clientTransactionId);
|
||||
abstract Builder setServerTransactionId(Optional<String> serverTransactionId);
|
||||
abstract Builder setIsSuperuser(boolean isSuperuser);
|
||||
abstract Builder setTask(TaskHandle task);
|
||||
abstract DeletionRequest build();
|
||||
|
@ -435,10 +481,18 @@ public class DeleteContactsAndHostsAction implements Runnable {
|
|||
new AutoValue_DeleteContactsAndHostsAction_DeletionRequest.Builder()
|
||||
.setKey(resourceKey)
|
||||
.setLastUpdateTime(resource.getUpdateAutoTimestamp().getTimestamp())
|
||||
.setRequestingClientId(checkNotNull(
|
||||
params.get(PARAM_REQUESTING_CLIENT_ID), "Requesting client id not specified"))
|
||||
.setIsSuperuser(Boolean.valueOf(
|
||||
checkNotNull(params.get(PARAM_IS_SUPERUSER), "Is superuser not specified")))
|
||||
.setRequestingClientId(
|
||||
checkNotNull(
|
||||
params.get(PARAM_REQUESTING_CLIENT_ID), "Requesting client id not specified"))
|
||||
.setClientTransactionId(
|
||||
Optional.fromNullable(
|
||||
params.get(PARAM_CLIENT_TRANSACTION_ID)))
|
||||
.setServerTransactionId(
|
||||
Optional.fromNullable(
|
||||
params.get(PARAM_SERVER_TRANSACTION_ID)))
|
||||
.setIsSuperuser(
|
||||
Boolean.valueOf(
|
||||
checkNotNull(params.get(PARAM_IS_SUPERUSER), "Is superuser not specified")))
|
||||
.setTask(task)
|
||||
.build());
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import com.google.common.annotations.VisibleForTesting;
|
|||
import com.googlecode.objectify.Key;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.util.FormattingLogger;
|
||||
import google.registry.util.Retrier;
|
||||
|
@ -36,6 +37,8 @@ public final class AsyncFlowEnqueuer {
|
|||
/** The HTTP parameter names used by async flows. */
|
||||
public static final String PARAM_RESOURCE_KEY = "resourceKey";
|
||||
public static final String PARAM_REQUESTING_CLIENT_ID = "requestingClientId";
|
||||
public static final String PARAM_CLIENT_TRANSACTION_ID = "clientTransactionId";
|
||||
public static final String PARAM_SERVER_TRANSACTION_ID = "serverTransactionId";
|
||||
public static final String PARAM_IS_SUPERUSER = "isSuperuser";
|
||||
public static final String PARAM_HOST_KEY = "hostKey";
|
||||
|
||||
|
@ -70,17 +73,18 @@ public final class AsyncFlowEnqueuer {
|
|||
|
||||
/** Enqueues a task to asynchronously delete a contact or host, by key. */
|
||||
public void enqueueAsyncDelete(
|
||||
EppResource resourceToDelete, String requestingClientId, boolean isSuperuser) {
|
||||
EppResource resourceToDelete, String requestingClientId, Trid trid, boolean isSuperuser) {
|
||||
Key<EppResource> resourceKey = Key.create(resourceToDelete);
|
||||
logger.infofmt(
|
||||
"Enqueuing async deletion of %s on behalf of registrar %s.",
|
||||
resourceKey, requestingClientId);
|
||||
TaskOptions task =
|
||||
TaskOptions.Builder
|
||||
.withMethod(Method.PULL)
|
||||
TaskOptions.Builder.withMethod(Method.PULL)
|
||||
.countdownMillis(asyncDeleteDelay.getMillis())
|
||||
.param(PARAM_RESOURCE_KEY, resourceKey.getString())
|
||||
.param(PARAM_REQUESTING_CLIENT_ID, requestingClientId)
|
||||
.param(PARAM_CLIENT_TRANSACTION_ID, trid.getClientTransactionId())
|
||||
.param(PARAM_SERVER_TRANSACTION_ID, trid.getServerTransactionId())
|
||||
.param(PARAM_IS_SUPERUSER, Boolean.toString(isSuperuser));
|
||||
addTaskToQueueWithRetry(asyncDeletePullQueue, task);
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ 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.eppcommon.Trid;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
|
@ -78,6 +79,7 @@ public final class ContactDeleteFlow implements TransactionalFlow {
|
|||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @ClientId String clientId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject Trid trid;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject Optional<AuthInfo> authInfo;
|
||||
@Inject HistoryEntry.Builder historyBuilder;
|
||||
|
@ -98,7 +100,7 @@ public final class ContactDeleteFlow implements TransactionalFlow {
|
|||
if (!isSuperuser) {
|
||||
verifyResourceOwnership(clientId, existingContact);
|
||||
}
|
||||
asyncFlowEnqueuer.enqueueAsyncDelete(existingContact, clientId, isSuperuser);
|
||||
asyncFlowEnqueuer.enqueueAsyncDelete(existingContact, clientId, trid, isSuperuser);
|
||||
ContactResource newContact =
|
||||
existingContact.asBuilder().addStatusValue(StatusValue.PENDING_DELETE).build();
|
||||
historyBuilder
|
||||
|
|
|
@ -38,6 +38,7 @@ import google.registry.model.EppResource;
|
|||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
|
@ -80,6 +81,7 @@ public final class HostDeleteFlow implements TransactionalFlow {
|
|||
@Inject ExtensionManager extensionManager;
|
||||
@Inject @ClientId String clientId;
|
||||
@Inject @TargetId String targetId;
|
||||
@Inject Trid trid;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject HistoryEntry.Builder historyBuilder;
|
||||
@Inject AsyncFlowEnqueuer asyncFlowEnqueuer;
|
||||
|
@ -106,7 +108,7 @@ public final class HostDeleteFlow implements TransactionalFlow {
|
|||
: existingHost;
|
||||
verifyResourceOwnership(clientId, owningResource);
|
||||
}
|
||||
asyncFlowEnqueuer.enqueueAsyncDelete(existingHost, clientId, isSuperuser);
|
||||
asyncFlowEnqueuer.enqueueAsyncDelete(existingHost, clientId, trid, isSuperuser);
|
||||
HostResource newHost =
|
||||
existingHost.asBuilder().addStatusValue(StatusValue.PENDING_DELETE).build();
|
||||
historyBuilder
|
||||
|
|
|
@ -60,6 +60,7 @@ import google.registry.model.host.HostInfoData;
|
|||
import google.registry.model.poll.MessageQueueInfo;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.HostPendingActionNotificationResponse;
|
||||
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
|
||||
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -98,20 +99,22 @@ public class EppResponse extends ImmutableObject implements ResponseOrGreeting {
|
|||
|
||||
/** Zero or more response "resData" results. */
|
||||
@XmlElementRefs({
|
||||
@XmlElementRef(type = ContactCheckData.class),
|
||||
@XmlElementRef(type = ContactCreateData.class),
|
||||
@XmlElementRef(type = ContactInfoData.class),
|
||||
@XmlElementRef(type = ContactPendingActionNotificationResponse.class),
|
||||
@XmlElementRef(type = ContactTransferResponse.class),
|
||||
@XmlElementRef(type = DomainCheckData.class),
|
||||
@XmlElementRef(type = DomainCreateData.class),
|
||||
@XmlElementRef(type = DomainInfoData.class),
|
||||
@XmlElementRef(type = DomainPendingActionNotificationResponse.class),
|
||||
@XmlElementRef(type = DomainRenewData.class),
|
||||
@XmlElementRef(type = DomainTransferResponse.class),
|
||||
@XmlElementRef(type = HostCheckData.class),
|
||||
@XmlElementRef(type = HostCreateData.class),
|
||||
@XmlElementRef(type = HostInfoData.class)})
|
||||
@XmlElementRef(type = ContactCheckData.class),
|
||||
@XmlElementRef(type = ContactCreateData.class),
|
||||
@XmlElementRef(type = ContactInfoData.class),
|
||||
@XmlElementRef(type = ContactPendingActionNotificationResponse.class),
|
||||
@XmlElementRef(type = ContactTransferResponse.class),
|
||||
@XmlElementRef(type = DomainCheckData.class),
|
||||
@XmlElementRef(type = DomainCreateData.class),
|
||||
@XmlElementRef(type = DomainInfoData.class),
|
||||
@XmlElementRef(type = DomainPendingActionNotificationResponse.class),
|
||||
@XmlElementRef(type = DomainRenewData.class),
|
||||
@XmlElementRef(type = DomainTransferResponse.class),
|
||||
@XmlElementRef(type = HostCheckData.class),
|
||||
@XmlElementRef(type = HostCreateData.class),
|
||||
@XmlElementRef(type = HostInfoData.class),
|
||||
@XmlElementRef(type = HostPendingActionNotificationResponse.class)
|
||||
})
|
||||
@XmlElementWrapper
|
||||
ImmutableList<? extends ResponseData> resData;
|
||||
|
||||
|
|
|
@ -125,4 +125,30 @@ public abstract class PendingActionNotificationResponse
|
|||
processedDate);
|
||||
}
|
||||
}
|
||||
|
||||
/** An adapter to output the XML in response to resolving a pending command on a host. */
|
||||
@Embed
|
||||
@XmlRootElement(name = "panData", namespace = "urn:ietf:params:xml:ns:domain-1.0")
|
||||
@XmlType(
|
||||
propOrder = {"name", "trid", "processedDate"},
|
||||
namespace = "urn:ietf:params:xml:ns:domain-1.0"
|
||||
)
|
||||
public static class HostPendingActionNotificationResponse
|
||||
extends PendingActionNotificationResponse {
|
||||
|
||||
@XmlElement
|
||||
NameOrId getName() {
|
||||
return nameOrId;
|
||||
}
|
||||
|
||||
public static HostPendingActionNotificationResponse create(
|
||||
String fullyQualifiedHostName, boolean actionResult, Trid trid, DateTime processedDate) {
|
||||
return init(
|
||||
new HostPendingActionNotificationResponse(),
|
||||
fullyQualifiedHostName,
|
||||
actionResult,
|
||||
trid,
|
||||
processedDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ import google.registry.model.eppoutput.EppResponse.ResponseData;
|
|||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.HostPendingActionNotificationResponse;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
|
||||
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
|
||||
|
@ -189,6 +190,7 @@ public abstract class PollMessage extends ImmutableObject
|
|||
List<ContactTransferResponse> contactTransferResponses;
|
||||
List<DomainPendingActionNotificationResponse> domainPendingActionNotificationResponses;
|
||||
List<DomainTransferResponse> domainTransferResponses;
|
||||
List<HostPendingActionNotificationResponse> hostPendingActionNotificationResponses;
|
||||
|
||||
// Extensions. Objectify cannot persist a base class type, so we must have a separate field
|
||||
// to hold every possible derived type of ResponseExtensions that we might store.
|
||||
|
@ -211,6 +213,7 @@ public abstract class PollMessage extends ImmutableObject
|
|||
.addAll(nullToEmpty(contactTransferResponses))
|
||||
.addAll(nullToEmpty(domainPendingActionNotificationResponses))
|
||||
.addAll(nullToEmpty(domainTransferResponses))
|
||||
.addAll(nullToEmpty(hostPendingActionNotificationResponses))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -240,6 +243,8 @@ public abstract class PollMessage extends ImmutableObject
|
|||
iterable.filter(DomainPendingActionNotificationResponse.class).toList());
|
||||
getInstance().domainTransferResponses = forceEmptyToNull(
|
||||
iterable.filter(DomainTransferResponse.class).toList());
|
||||
getInstance().hostPendingActionNotificationResponses = forceEmptyToNull(
|
||||
iterable.filter(HostPendingActionNotificationResponse.class).toList());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue