mirror of
https://github.com/google/nomulus.git
synced 2025-08-06 01:35:17 +02:00
Record domain transaction for domain deletes
This is the third of many cls adding explicit logging in all our domain mutation flows to facilitate transaction reporting. We add a +1 counter for either grace or nograce deletes, based on the grace period status of the domain. We then search back in time for DOMAIN_CREATE, DOMAIN_RENEW and DOMAIN_AUTORENEW HistoryEntries off the same resource that happened in their corresponding grace periods (5, 5 and 45 days respectively). All transaction records for these events are then given -1 counters to properly account for cancellations in the NET_CREATE and NET_RENEW fields. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=166506010
This commit is contained in:
parent
d011b8e073
commit
7ee8bc9070
4 changed files with 350 additions and 60 deletions
|
@ -24,15 +24,20 @@ import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses
|
|||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.createCancellingRecords;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPredelegation;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.model.reporting.DomainTransactionRecord.TransactionReportField.isAddsField;
|
||||
import static google.registry.model.reporting.DomainTransactionRecord.TransactionReportField.isRenewsField;
|
||||
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static google.registry.util.CollectionUtils.union;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
|
@ -77,14 +82,18 @@ import google.registry.model.poll.PendingActionNotificationResponse.DomainPendin
|
|||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.poll.PollMessage.OneTime;
|
||||
import google.registry.model.registry.Registry;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/**
|
||||
* An EPP flow that deletes a domain.
|
||||
|
@ -133,24 +142,30 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
|||
customLogic.afterValidation(
|
||||
AfterValidationParameters.newBuilder().setExistingDomain(existingDomain).build());
|
||||
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
|
||||
HistoryEntry historyEntry = buildHistoryEntry(existingDomain, now);
|
||||
Builder builder = existingDomain.getStatusValues().contains(StatusValue.PENDING_TRANSFER)
|
||||
? ResourceFlowUtils.<DomainResource, DomainResource.Builder>resolvePendingTransfer(
|
||||
existingDomain, TransferStatus.SERVER_CANCELLED, now)
|
||||
: existingDomain.asBuilder();
|
||||
builder.setDeletionTime(now).setStatusValues(null);
|
||||
// If the domain is in the Add Grace Period, we delete it immediately, which is already
|
||||
// reflected in the builder we just prepared. Otherwise we give it a PENDING_DELETE status.
|
||||
if (!existingDomain.getGracePeriodStatuses().contains(GracePeriodStatus.ADD)) {
|
||||
// By default, this should be 30 days of grace, and 5 days of pending delete.
|
||||
DateTime deletionTime = now
|
||||
.plus(registry.getRedemptionGracePeriodLength())
|
||||
.plus(registry.getPendingDeleteLength());
|
||||
boolean inAddGracePeriod =
|
||||
existingDomain.getGracePeriodStatuses().contains(GracePeriodStatus.ADD);
|
||||
// If the domain is in the Add Grace Period, we delete it immediately.
|
||||
// Otherwise, we give it a PENDING_DELETE status.
|
||||
Duration durationUntilDelete =
|
||||
inAddGracePeriod
|
||||
? Duration.ZERO
|
||||
// By default, this should be 30 days of grace, and 5 days of pending delete.
|
||||
: registry.getRedemptionGracePeriodLength().plus(registry.getPendingDeleteLength());
|
||||
HistoryEntry historyEntry = buildHistoryEntry(
|
||||
existingDomain, registry, now, durationUntilDelete, inAddGracePeriod);
|
||||
if (inAddGracePeriod) {
|
||||
builder.setDeletionTime(now).setStatusValues(null);
|
||||
} else {
|
||||
DateTime deletionTime = now.plus(durationUntilDelete);
|
||||
PollMessage.OneTime deletePollMessage =
|
||||
createDeletePollMessage(existingDomain, historyEntry, deletionTime);
|
||||
entitiesToSave.add(deletePollMessage);
|
||||
builder.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
.setDeletionTime(deletionTime)
|
||||
builder.setDeletionTime(deletionTime)
|
||||
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
// Clear out all old grace periods and add REDEMPTION, which does not include a key to a
|
||||
// billing event because there isn't one for a domain delete.
|
||||
.setGracePeriods(ImmutableSet.of(GracePeriod.createWithoutBillingEvent(
|
||||
|
@ -218,11 +233,43 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
|||
}
|
||||
}
|
||||
|
||||
private HistoryEntry buildHistoryEntry(DomainResource existingResource, DateTime now) {
|
||||
private HistoryEntry buildHistoryEntry(
|
||||
DomainResource existingResource,
|
||||
Registry registry,
|
||||
DateTime now,
|
||||
Duration durationUntilDelete,
|
||||
boolean inAddGracePeriod) {
|
||||
Duration maxGracePeriod = Collections.max(
|
||||
ImmutableSet.of(
|
||||
registry.getAddGracePeriodLength(),
|
||||
registry.getAutoRenewGracePeriodLength(),
|
||||
registry.getRenewGracePeriodLength()));
|
||||
ImmutableSet<DomainTransactionRecord> cancelledRecords =
|
||||
createCancellingRecords(
|
||||
existingResource,
|
||||
now,
|
||||
maxGracePeriod,
|
||||
new Predicate<DomainTransactionRecord>() {
|
||||
@Override
|
||||
public boolean apply(@Nullable DomainTransactionRecord domainTransactionRecord) {
|
||||
return isAddsField(domainTransactionRecord.getReportField())
|
||||
|| isRenewsField(domainTransactionRecord.getReportField());
|
||||
}
|
||||
});
|
||||
return historyBuilder
|
||||
.setType(HistoryEntry.Type.DOMAIN_DELETE)
|
||||
.setModificationTime(now)
|
||||
.setParent(Key.create(existingResource))
|
||||
.setDomainTransactionRecords(
|
||||
union(
|
||||
cancelledRecords,
|
||||
DomainTransactionRecord.create(
|
||||
existingResource.getTld(),
|
||||
now.plus(durationUntilDelete),
|
||||
inAddGracePeriod
|
||||
? TransactionReportField.DELETED_DOMAINS_GRACE
|
||||
: TransactionReportField.DELETED_DOMAINS_NOGRACE,
|
||||
1)))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,9 @@ import static google.registry.util.FormattingLogger.getLoggerForCallerClass;
|
|||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
@ -105,6 +107,7 @@ import google.registry.model.registry.Registry;
|
|||
import google.registry.model.registry.Registry.TldState;
|
||||
import google.registry.model.registry.label.ReservationType;
|
||||
import google.registry.model.registry.label.ReservedList;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tmch.ClaimsListShard;
|
||||
import google.registry.util.FormattingLogger;
|
||||
|
@ -118,6 +121,7 @@ import javax.annotation.Nullable;
|
|||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/** Static utility functions for domain flows. */
|
||||
public class DomainFlowUtils {
|
||||
|
@ -909,6 +913,56 @@ public class DomainFlowUtils {
|
|||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of DomainTransactionRecords which negate the most recent HistoryEntry's records.
|
||||
*
|
||||
* <p>Domain deletes and transfers use this function to account for previous records negated by
|
||||
* their flow. For example, if a grace period delete occurs, we must add -1 counters for the
|
||||
* associated NET_ADDS_#_YRS field, if it exists.
|
||||
*
|
||||
* <p>The steps are as follows: 1. Find all HistoryEntries under the domain modified in the past,
|
||||
* up to the maxSearchPeriod. 2. Only keep HistoryEntries with a DomainTransactionRecord that a)
|
||||
* hasn't been reported yet and b) matches the predicate 3. Return the transactionRecords under
|
||||
* the most recent HistoryEntry that fits the above criteria, with negated reportAmounts.
|
||||
*/
|
||||
static ImmutableSet<DomainTransactionRecord> createCancellingRecords(
|
||||
DomainResource domainResource,
|
||||
final DateTime now,
|
||||
Duration maxSearchPeriod,
|
||||
final Predicate<DomainTransactionRecord> isCancellable) {
|
||||
|
||||
List<HistoryEntry> recentHistoryEntries = ofy().load()
|
||||
.type(HistoryEntry.class)
|
||||
.ancestor(domainResource)
|
||||
.filter("modificationTime >=", now.minus(maxSearchPeriod))
|
||||
.order("modificationTime")
|
||||
.list();
|
||||
Optional<HistoryEntry> entryToCancel = FluentIterable.from(recentHistoryEntries)
|
||||
.filter(
|
||||
new Predicate<HistoryEntry>() {
|
||||
@Override
|
||||
public boolean apply(HistoryEntry historyEntry) {
|
||||
// Look for add and renew transaction records that have yet to be reported
|
||||
for (DomainTransactionRecord record : historyEntry.getDomainTransactionRecords()) {
|
||||
if (isCancellable.apply(record) && record.getReportingTime().isAfter(now)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
})
|
||||
// We only want to cancel out the most recent add or renewal
|
||||
.last();
|
||||
ImmutableSet.Builder<DomainTransactionRecord> recordsBuilder = new ImmutableSet.Builder<>();
|
||||
if (entryToCancel.isPresent()) {
|
||||
for (DomainTransactionRecord record : entryToCancel.get().getDomainTransactionRecords()) {
|
||||
int cancelledAmount = -1 * record.getReportAmount();
|
||||
recordsBuilder.add(record.asBuilder().setReportAmount(cancelledAmount).build());
|
||||
}
|
||||
}
|
||||
return recordsBuilder.build();
|
||||
}
|
||||
|
||||
/** Resource linked to this domain does not exist. */
|
||||
static class LinkedResourcesDoNotExistException extends ObjectDoesNotExistException {
|
||||
public LinkedResourcesDoNotExistException(Class<?> type, ImmutableSet<String> resourceIds) {
|
||||
|
|
|
@ -17,6 +17,7 @@ package google.registry.model.reporting;
|
|||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
|
@ -114,6 +115,42 @@ public class DomainTransactionRecord extends ImmutableObject implements Buildabl
|
|||
return nameToField("NET_RENEWS_%d_YR", years);
|
||||
}
|
||||
|
||||
private static final ImmutableSet<TransactionReportField> ADD_FIELDS =
|
||||
ImmutableSet.of(
|
||||
NET_ADDS_1_YR,
|
||||
NET_ADDS_2_YR,
|
||||
NET_ADDS_3_YR,
|
||||
NET_ADDS_4_YR,
|
||||
NET_ADDS_5_YR,
|
||||
NET_ADDS_6_YR,
|
||||
NET_ADDS_7_YR,
|
||||
NET_ADDS_8_YR,
|
||||
NET_ADDS_9_YR,
|
||||
NET_ADDS_10_YR);
|
||||
|
||||
private static final ImmutableSet<TransactionReportField> RENEW_FIELDS =
|
||||
ImmutableSet.of(
|
||||
NET_RENEWS_1_YR,
|
||||
NET_RENEWS_2_YR,
|
||||
NET_RENEWS_3_YR,
|
||||
NET_RENEWS_4_YR,
|
||||
NET_RENEWS_5_YR,
|
||||
NET_RENEWS_6_YR,
|
||||
NET_RENEWS_7_YR,
|
||||
NET_RENEWS_8_YR,
|
||||
NET_RENEWS_9_YR,
|
||||
NET_RENEWS_10_YR);
|
||||
|
||||
|
||||
/** Boilerplate to determine if a field is one of the NET_ADDS fields. */
|
||||
public static boolean isAddsField(TransactionReportField field) {
|
||||
return ADD_FIELDS.contains(field);
|
||||
}
|
||||
/** Boilerplate to determine if a field is one of the NET_ADDS fields. */
|
||||
public static boolean isRenewsField(TransactionReportField field) {
|
||||
return RENEW_FIELDS.contains(field);
|
||||
}
|
||||
|
||||
private static TransactionReportField nameToField(String enumTemplate, int years) {
|
||||
checkArgument(
|
||||
years >= 1 && years <= 10, "domain add and renew years must be between 1 and 10");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue