1
0
mirror of https://github.com/google/nomulus synced 2025-12-23 14:25:44 +00:00

Add batching to DeleteProberDataAction (#2322)

* Add batching to DeleteProberDataAction

* Only get time once

* Add separate query for dry run

* Update querries to actually properly delete all the data

* Fix merge conflicts

* Add test for foreign key constraints

* Make transaction repeatable read

* Make queries to subtables native

* Add native query for GracePeriodHistory

* Kill job after 20 hours

* remove extra time check from read query
This commit is contained in:
sarahcaseybot
2024-03-29 16:51:19 -04:00
committed by GitHub
parent fa344776a1
commit dff2d90325
7 changed files with 145 additions and 65 deletions

View File

@@ -16,15 +16,19 @@ package google.registry.batch;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.dns.DnsUtils.requestDomainDnsRefresh; import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_DELETE; import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_DELETE;
import static google.registry.model.tld.Tlds.getTldsOfType; import static google.registry.model.tld.Tlds.getTldsOfType;
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST; import static google.registry.request.Action.Method.POST;
import static google.registry.request.RequestParameters.PARAM_BATCH_SIZE;
import static google.registry.request.RequestParameters.PARAM_DRY_RUN; import static google.registry.request.RequestParameters.PARAM_DRY_RUN;
import static google.registry.request.RequestParameters.PARAM_TLDS; import static google.registry.request.RequestParameters.PARAM_TLDS;
import static google.registry.util.RegistryEnvironment.PRODUCTION; import static google.registry.util.RegistryEnvironment.PRODUCTION;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@@ -41,12 +45,11 @@ import google.registry.request.Action;
import google.registry.request.Parameter; import google.registry.request.Parameter;
import google.registry.request.auth.Auth; import google.registry.request.auth.Auth;
import google.registry.util.RegistryEnvironment; import google.registry.util.RegistryEnvironment;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject; import javax.inject.Inject;
import org.hibernate.CacheMode; import javax.persistence.TypedQuery;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.query.Query;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.Duration; import org.joda.time.Duration;
@@ -61,6 +64,7 @@ import org.joda.time.Duration;
auth = Auth.AUTH_API_ADMIN) auth = Auth.AUTH_API_ADMIN)
public class DeleteProberDataAction implements Runnable { public class DeleteProberDataAction implements Runnable {
// TODO(b/323026070): Add email alert on failure of this action
private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** /**
@@ -90,31 +94,38 @@ public class DeleteProberDataAction implements Runnable {
// Note: creationTime must be compared to a Java object (CreateAutoTimestamp) but deletionTime can // Note: creationTime must be compared to a Java object (CreateAutoTimestamp) but deletionTime can
// be compared directly to the SQL timestamp (it's a DateTime) // be compared directly to the SQL timestamp (it's a DateTime)
private static final String DOMAIN_QUERY_STRING = private static final String DOMAIN_QUERY_STRING =
"FROM Domain d WHERE d.tld IN :tlds AND d.domainName NOT LIKE 'nic.%' AND" "FROM Domain d WHERE d.tld IN :tlds AND d.domainName NOT LIKE 'nic.%%' AND"
+ " (d.subordinateHosts IS EMPTY OR d.subordinateHosts IS NULL) AND d.creationTime <" + " (d.subordinateHosts IS EMPTY OR d.subordinateHosts IS NULL) AND d.creationTime <"
+ " :creationTimeCutoff AND ((d.creationTime <= :nowAutoTimestamp AND d.deletionTime >" + " :creationTimeCutoff AND (d.deletionTime > :now OR d.deletionTime <"
+ " current_timestamp()) OR d.deletionTime < :nowMinusSoftDeleteDelay) ORDER BY d.repoId"; + " :nowMinusSoftDeleteDelay)";
/** Number of domains to retrieve and delete per SQL transaction. */ /** Number of domains to retrieve and delete per SQL transaction. */
private static final int BATCH_SIZE = 1000; private static final int DEFAULT_BATCH_SIZE = 1000;
@Inject
@Parameter(PARAM_DRY_RUN)
boolean isDryRun; boolean isDryRun;
/** List of TLDs to work on. If empty - will work on all TLDs that end with .test. */ /** List of TLDs to work on. If empty - will work on all TLDs that end with .test. */
@Inject
@Parameter(PARAM_TLDS)
ImmutableSet<String> tlds; ImmutableSet<String> tlds;
@Inject int batchSize;
@Config("registryAdminClientId")
String registryAdminRegistrarId; String registryAdminRegistrarId;
@Inject @Inject
DeleteProberDataAction() {} DeleteProberDataAction(
@Parameter(PARAM_DRY_RUN) boolean isDryRun,
@Parameter(PARAM_TLDS) ImmutableSet<String> tlds,
@Parameter(PARAM_BATCH_SIZE) Optional<Integer> batchSize,
@Config("registryAdminClientId") String registryAdminRegistrarId) {
this.isDryRun = isDryRun;
this.tlds = tlds;
this.batchSize = batchSize.orElse(DEFAULT_BATCH_SIZE);
this.registryAdminRegistrarId = registryAdminRegistrarId;
}
@Override @Override
public void run() { public void run() {
checkArgument(batchSize > 0, "The batch size must be greater than 0");
checkState( checkState(
!Strings.isNullOrEmpty(registryAdminRegistrarId), !Strings.isNullOrEmpty(registryAdminRegistrarId),
"Registry admin client ID must be configured for prober data deletion to work"); "Registry admin client ID must be configured for prober data deletion to work");
@@ -131,13 +142,30 @@ public class DeleteProberDataAction implements Runnable {
"If tlds are given, they must all exist and be TEST tlds. Given: %s, not found: %s", "If tlds are given, they must all exist and be TEST tlds. Given: %s, not found: %s",
tlds, tlds,
Sets.difference(tlds, deletableTlds)); Sets.difference(tlds, deletableTlds));
runSqlJob(deletableTlds);
}
private void runSqlJob(ImmutableSet<String> deletableTlds) {
AtomicInteger softDeletedDomains = new AtomicInteger(); AtomicInteger softDeletedDomains = new AtomicInteger();
AtomicInteger hardDeletedDomains = new AtomicInteger(); AtomicInteger hardDeletedDomains = new AtomicInteger();
tm().transact(() -> processDomains(deletableTlds, softDeletedDomains, hardDeletedDomains)); AtomicReference<ImmutableList<Domain>> domainsBatch = new AtomicReference<>();
DateTime startTime = DateTime.now(UTC);
do {
tm().transact(
TRANSACTION_REPEATABLE_READ,
() ->
domainsBatch.set(
processDomains(
deletableTlds, softDeletedDomains, hardDeletedDomains, startTime)));
// Only process one batch in dryrun mode
if (isDryRun) {
break;
}
logger.atInfo().log(
"deleteProberData hard deleted %d names with %d batchSize",
hardDeletedDomains.get(), batchSize);
// Automatically kill the job if it is running for over 20 hours
} while (DateTime.now(UTC).isBefore(startTime.plusHours(20))
&& domainsBatch.get().size() == batchSize);
logger.atInfo().log( logger.atInfo().log(
"%s %d domains.", "%s %d domains.",
isDryRun ? "Would have soft-deleted" : "Soft-deleted", softDeletedDomains.get()); isDryRun ? "Would have soft-deleted" : "Soft-deleted", softDeletedDomains.get());
@@ -146,46 +174,32 @@ public class DeleteProberDataAction implements Runnable {
isDryRun ? "Would have hard-deleted" : "Hard-deleted", hardDeletedDomains.get()); isDryRun ? "Would have hard-deleted" : "Hard-deleted", hardDeletedDomains.get());
} }
private void processDomains( private ImmutableList<Domain> processDomains(
ImmutableSet<String> deletableTlds, ImmutableSet<String> deletableTlds,
AtomicInteger softDeletedDomains, AtomicInteger softDeletedDomains,
AtomicInteger hardDeletedDomains) { AtomicInteger hardDeletedDomains,
DateTime now = tm().getTransactionTime(); DateTime now) {
// Scroll through domains, soft-deleting as necessary (very few will be soft-deleted) and TypedQuery<Domain> query =
// keeping track of which domains to hard-delete (there can be many, so we batch them up)
try (ScrollableResults scrollableResult =
tm().query(DOMAIN_QUERY_STRING, Domain.class) tm().query(DOMAIN_QUERY_STRING, Domain.class)
.setParameter("tlds", deletableTlds) .setParameter("tlds", deletableTlds)
.setParameter( .setParameter(
"creationTimeCutoff", CreateAutoTimestamp.create(now.minus(DOMAIN_USED_DURATION))) "creationTimeCutoff", CreateAutoTimestamp.create(now.minus(DOMAIN_USED_DURATION)))
.setParameter("nowMinusSoftDeleteDelay", now.minus(SOFT_DELETE_DELAY)) .setParameter("nowMinusSoftDeleteDelay", now.minus(SOFT_DELETE_DELAY))
.setParameter("nowAutoTimestamp", CreateAutoTimestamp.create(now)) .setParameter("now", now);
.unwrap(Query.class) ImmutableList<Domain> domainList =
.setCacheMode(CacheMode.IGNORE) query.setMaxResults(batchSize).getResultStream().collect(toImmutableList());
.scroll(ScrollMode.FORWARD_ONLY)) {
ImmutableList.Builder<String> domainRepoIdsToHardDelete = new ImmutableList.Builder<>(); ImmutableList.Builder<String> domainRepoIdsToHardDelete = new ImmutableList.Builder<>();
ImmutableList.Builder<String> hostNamesToHardDelete = new ImmutableList.Builder<>(); ImmutableList.Builder<String> hostNamesToHardDelete = new ImmutableList.Builder<>();
for (int i = 1; scrollableResult.next(); i = (i + 1) % BATCH_SIZE) { for (Domain domain : domainList) {
Domain domain = (Domain) scrollableResult.get(0); processDomain(
processDomain( domain,
domain, domainRepoIdsToHardDelete,
domainRepoIdsToHardDelete, hostNamesToHardDelete,
hostNamesToHardDelete, softDeletedDomains,
softDeletedDomains, hardDeletedDomains);
hardDeletedDomains);
// Batch the deletion and DB flush + session clearing, so we don't OOM
if (i == 0) {
hardDeleteDomainsAndHosts(
domainRepoIdsToHardDelete.build(), hostNamesToHardDelete.build());
domainRepoIdsToHardDelete = new ImmutableList.Builder<>();
hostNamesToHardDelete = new ImmutableList.Builder<>();
tm().getEntityManager().flush();
tm().getEntityManager().clear();
}
}
// process the remainder
hardDeleteDomainsAndHosts(domainRepoIdsToHardDelete.build(), hostNamesToHardDelete.build());
} }
hardDeleteDomainsAndHosts(domainRepoIdsToHardDelete.build(), hostNamesToHardDelete.build());
return domainList;
} }
private void processDomain( private void processDomain(
@@ -225,6 +239,10 @@ public class DeleteProberDataAction implements Runnable {
tm().query("DELETE FROM Host WHERE hostName IN :hostNames") tm().query("DELETE FROM Host WHERE hostName IN :hostNames")
.setParameter("hostNames", hostNames) .setParameter("hostNames", hostNames)
.executeUpdate(); .executeUpdate();
tm().getEntityManager()
.createNativeQuery("DELETE FROM \"GracePeriod\" WHERE domain_repo_id IN :repoIds")
.setParameter("repoIds", domainRepoIds)
.executeUpdate();
tm().query("DELETE FROM BillingEvent WHERE domainRepoId IN :repoIds") tm().query("DELETE FROM BillingEvent WHERE domainRepoId IN :repoIds")
.setParameter("repoIds", domainRepoIds) .setParameter("repoIds", domainRepoIds)
.executeUpdate(); .executeUpdate();
@@ -234,12 +252,45 @@ public class DeleteProberDataAction implements Runnable {
tm().query("DELETE FROM BillingCancellation WHERE domainRepoId IN :repoIds") tm().query("DELETE FROM BillingCancellation WHERE domainRepoId IN :repoIds")
.setParameter("repoIds", domainRepoIds) .setParameter("repoIds", domainRepoIds)
.executeUpdate(); .executeUpdate();
tm().query("DELETE FROM DomainHistory WHERE repoId IN :repoIds") tm().getEntityManager()
.createNativeQuery("DELETE FROM \"GracePeriodHistory\" WHERE domain_repo_id IN :repoIds")
.setParameter("repoIds", domainRepoIds) .setParameter("repoIds", domainRepoIds)
.executeUpdate(); .executeUpdate();
tm().query("DELETE FROM PollMessage WHERE domainRepoId IN :repoIds") tm().query("DELETE FROM PollMessage WHERE domainRepoId IN :repoIds")
.setParameter("repoIds", domainRepoIds) .setParameter("repoIds", domainRepoIds)
.executeUpdate(); .executeUpdate();
tm().getEntityManager()
.createNativeQuery("DELETE FROM \"DomainHost\" WHERE domain_repo_id IN :repoIds")
.setParameter("repoIds", domainRepoIds)
.executeUpdate();
tm().getEntityManager()
.createNativeQuery(
"DELETE FROM \"DomainHistoryHost\" WHERE domain_history_domain_repo_id IN :repoIds")
.setParameter("repoIds", domainRepoIds)
.executeUpdate();
tm().getEntityManager()
.createNativeQuery("DELETE FROM \"HostHistory\" WHERE host_name IN :hostNames")
.setParameter("hostNames", hostNames)
.executeUpdate();
tm().getEntityManager()
.createNativeQuery("DELETE FROM \"DelegationSignerData\" WHERE domain_repo_id IN :repoIds")
.setParameter("repoIds", domainRepoIds)
.executeUpdate();
tm().getEntityManager()
.createNativeQuery(
"DELETE FROM \"DomainTransactionRecord\" WHERE domain_repo_id IN :repoIds")
.setParameter("repoIds", domainRepoIds)
.executeUpdate();
tm().getEntityManager()
.createNativeQuery("DELETE FROM \"DomainDsDataHistory\" WHERE domain_repo_id IN :repoIds")
.setParameter("repoIds", domainRepoIds)
.executeUpdate();
tm().getEntityManager()
.createNativeQuery("DELETE FROM \"DomainHistory\" WHERE domain_repo_id IN :repoIds")
.setParameter("repoIds", domainRepoIds)
.executeUpdate();
// Delete from domain table is done last so that a trainsient failure in the middle of an action
// run will not prevent the domain from being deleted by this action in a future run
tm().query("DELETE FROM Domain WHERE repoId IN :repoIds") tm().query("DELETE FROM Domain WHERE repoId IN :repoIds")
.setParameter("repoIds", domainRepoIds) .setParameter("repoIds", domainRepoIds)
.executeUpdate(); .executeUpdate();

View File

@@ -36,7 +36,7 @@ import javax.xml.bind.annotation.XmlType;
* @see <a href="http://tools.ietf.org/html/rfc4034">RFC 4034</a> * @see <a href="http://tools.ietf.org/html/rfc4034">RFC 4034</a>
*/ */
@XmlType(name = "dsData") @XmlType(name = "dsData")
@Entity @Entity(name = "DelegationSignerData")
@IdClass(DomainDsDataId.class) @IdClass(DomainDsDataId.class)
@Table(name = "DelegationSignerData", indexes = @Index(columnList = "domainRepoId")) @Table(name = "DelegationSignerData", indexes = @Index(columnList = "domainRepoId"))
public class DomainDsData extends DomainDsDataBase { public class DomainDsData extends DomainDsDataBase {

View File

@@ -18,8 +18,10 @@ import static com.google.common.net.MediaType.JSON_UTF_8;
import static google.registry.dns.PublishDnsUpdatesAction.CLOUD_TASKS_RETRY_HEADER; import static google.registry.dns.PublishDnsUpdatesAction.CLOUD_TASKS_RETRY_HEADER;
import static google.registry.model.tld.Tlds.assertTldExists; import static google.registry.model.tld.Tlds.assertTldExists;
import static google.registry.model.tld.Tlds.assertTldsExist; import static google.registry.model.tld.Tlds.assertTldsExist;
import static google.registry.request.RequestParameters.PARAM_BATCH_SIZE;
import static google.registry.request.RequestParameters.PARAM_DRY_RUN; import static google.registry.request.RequestParameters.PARAM_DRY_RUN;
import static google.registry.request.RequestParameters.extractBooleanParameter; import static google.registry.request.RequestParameters.extractBooleanParameter;
import static google.registry.request.RequestParameters.extractOptionalIntParameter;
import static google.registry.request.RequestParameters.extractRequiredHeader; import static google.registry.request.RequestParameters.extractRequiredHeader;
import static google.registry.request.RequestParameters.extractRequiredParameter; import static google.registry.request.RequestParameters.extractRequiredParameter;
import static google.registry.request.RequestParameters.extractSetOfParameters; import static google.registry.request.RequestParameters.extractSetOfParameters;
@@ -99,6 +101,12 @@ public final class RequestModule {
return extractBooleanParameter(req, PARAM_DRY_RUN); return extractBooleanParameter(req, PARAM_DRY_RUN);
} }
@Provides
@Parameter(PARAM_BATCH_SIZE)
static Optional<Integer> provideBatchSize(HttpServletRequest req) {
return extractOptionalIntParameter(req, PARAM_BATCH_SIZE);
}
@Provides @Provides
static Response provideResponse(ResponseImpl response) { static Response provideResponse(ResponseImpl response) {
return response; return response;

View File

@@ -41,6 +41,11 @@ public final class RequestParameters {
/** The standardized request parameter name used by any action in dry run mode. */ /** The standardized request parameter name used by any action in dry run mode. */
public static final String PARAM_DRY_RUN = "dryRun"; public static final String PARAM_DRY_RUN = "dryRun";
/**
* The standardized request parameter name used by any action taking a configurable batch size.
*/
public static final String PARAM_BATCH_SIZE = "batchSize";
/** /**
* Returns first GET or POST parameter associated with {@code name}. * Returns first GET or POST parameter associated with {@code name}.
* *

View File

@@ -21,6 +21,7 @@ import static google.registry.dns.DnsUtils.requestDomainDnsRefresh;
import static google.registry.model.tld.Tlds.assertTldsExist; import static google.registry.model.tld.Tlds.assertTldsExist;
import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ; import static google.registry.persistence.PersistenceModule.TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.RequestParameters.PARAM_BATCH_SIZE;
import static google.registry.request.RequestParameters.PARAM_TLDS; import static google.registry.request.RequestParameters.PARAM_TLDS;
import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.END_OF_TIME;
@@ -83,7 +84,7 @@ public class RefreshDnsForAllDomainsAction implements Runnable {
RefreshDnsForAllDomainsAction( RefreshDnsForAllDomainsAction(
Response response, Response response,
@Parameter(PARAM_TLDS) ImmutableSet<String> tlds, @Parameter(PARAM_TLDS) ImmutableSet<String> tlds,
@Parameter("batchSize") Optional<Integer> batchSize, @Parameter(PARAM_BATCH_SIZE) Optional<Integer> batchSize,
@Parameter("refreshQps") Optional<Integer> refreshQps, @Parameter("refreshQps") Optional<Integer> refreshQps,
Random random) { Random random) {
this.response = response; this.response = response;

View File

@@ -70,12 +70,6 @@ public class ToolsServerModule {
return extractRequiredParameter(req, "rawKeys"); return extractRequiredParameter(req, "rawKeys");
} }
@Provides
@Parameter("batchSize")
static Optional<Integer> provideBatchSize(HttpServletRequest req) {
return extractOptionalIntParameter(req, "batchSize");
}
@Provides @Provides
@Parameter("refreshQps") @Parameter("refreshQps")
static Optional<Integer> provideRefreshQps(HttpServletRequest req) { static Optional<Integer> provideRefreshQps(HttpServletRequest req) {

View File

@@ -17,6 +17,7 @@ package google.registry.batch;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth.assertWithMessage;
import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.domain.rgp.GracePeriodStatus.ADD;
import static google.registry.testing.DatabaseHelper.assertDomainDnsRequests; import static google.registry.testing.DatabaseHelper.assertDomainDnsRequests;
import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadByEntitiesIfPresent; import static google.registry.testing.DatabaseHelper.loadByEntitiesIfPresent;
@@ -37,6 +38,7 @@ import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent;
import google.registry.model.domain.Domain; import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory; import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.GracePeriod;
import google.registry.model.poll.PollMessage; import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Tld; import google.registry.model.tld.Tld;
@@ -92,11 +94,7 @@ class DeleteProberDataActionTest {
} }
private void resetAction() { private void resetAction() {
action = new DeleteProberDataAction(); action = new DeleteProberDataAction(false, ImmutableSet.of(), Optional.empty(), "TheRegistrar");
action.isDryRun = false;
action.tlds = ImmutableSet.of();
action.registryAdminRegistrarId = "TheRegistrar";
// RegistryEnvironment.SANDBOX.setup(systemPropertyExtension);
} }
@AfterEach @AfterEach
@@ -119,6 +117,19 @@ class DeleteProberDataActionTest {
assertAllAbsent(oaEntities); assertAllAbsent(oaEntities);
} }
@Test
void test_deletesAllInBatches() throws Exception {
// Persist 40 domains
Set<ImmutableObject> ibEntities = persistLotsOfDomains("ib-any.test");
Set<ImmutableObject> oaEntities = persistLotsOfDomains("oa-canary.test");
// Create action with batch size of 3
DeleteProberDataAction batchedAction =
new DeleteProberDataAction(false, ImmutableSet.of(), Optional.of(3), "TheRegistrar");
batchedAction.run();
assertAllAbsent(ibEntities);
assertAllAbsent(oaEntities);
}
@Test @Test
void testSuccess_deletesAllAndOnlyGivenTlds() throws Exception { void testSuccess_deletesAllAndOnlyGivenTlds() throws Exception {
Set<ImmutableObject> tldEntities = persistLotsOfDomains("tld"); Set<ImmutableObject> tldEntities = persistLotsOfDomains("tld");
@@ -303,12 +314,22 @@ class DeleteProberDataActionTest {
.setRegistrarId("TheRegistrar") .setRegistrarId("TheRegistrar")
.setMsg("Domain registered") .setMsg("Domain registered")
.build()); .build());
GracePeriod gracePeriod =
persistSimpleResource(
GracePeriod.create(
ADD,
domain.getRepoId(),
DELETION_TIME.plusDays(5),
"TheRegistrar",
billingEvent.createVKey()));
domain = persistResource(domain.asBuilder().addGracePeriod(gracePeriod).build());
ImmutableSet.Builder<ImmutableObject> builder = ImmutableSet.Builder<ImmutableObject> builder =
new ImmutableSet.Builder<ImmutableObject>() new ImmutableSet.Builder<ImmutableObject>()
.add(domain) .add(domain)
.add(historyEntry) .add(historyEntry)
.add(billingEvent) .add(billingEvent)
.add(pollMessage); .add(pollMessage)
.add(gracePeriod);
return builder.build(); return builder.build();
} }