1
0
mirror of https://github.com/google/nomulus synced 2025-12-23 06:15:42 +00:00

Skip poll messages on deletions for configured registrars (#2613)

See b/379331882 for more details
This commit is contained in:
gbrodman
2024-11-21 17:16:26 -05:00
committed by GitHub
parent 304f0002b4
commit 0c0b0df36e
9 changed files with 87 additions and 30 deletions

View File

@@ -1743,6 +1743,17 @@ public final class RegistryConfig {
CONFIG_SETTINGS.get().registryPolicy.tieredPricingPromotionRegistrarIds);
}
/**
* Set of registrars for which we do not send poll messages on standard domain deletion.
*
* <p>For these registrars we won't send a poll message in order to avoid database contention. See
* b/379331882 for more details.
*/
public static ImmutableSet<String> getNoPollMessageOnDeletionRegistrarIds() {
return ImmutableSet.copyOf(
CONFIG_SETTINGS.get().registryPolicy.noPollMessageOnDeletionRegistrarIds);
}
/**
* Memoizes loading of the {@link RegistryConfigSettings} POJO.
*

View File

@@ -115,6 +115,7 @@ public class RegistryConfigSettings {
public boolean requireSslCertificates;
public double sunriseDomainCreateDiscount;
public Set<String> tieredPricingPromotionRegistrarIds;
public Set<String> noPollMessageOnDeletionRegistrarIds;
}
/** Configuration for Hibernate. */

View File

@@ -220,6 +220,9 @@ registryPolicy:
# In addition, we will return the non-promotional (i.e. incorrect) price on
# domain create requests.
tieredPricingPromotionRegistrarIds: []
# List of registrars for which we won't send poll message on standard domain
# deletions.
noPollMessageOnDeletionRegistrarIds: []
hibernate:
# If set to false, calls to tm().transact() cannot be nested. If set to true,

View File

@@ -11,6 +11,8 @@ registryPolicy:
Line 2 is this 1.
tieredPricingPromotionRegistrarIds:
- NewRegistrar
noPollMessageOnDeletionRegistrarIds:
- NewRegistrar
caching:
singletonCacheRefreshSeconds: 0

View File

@@ -44,7 +44,9 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import google.registry.batch.AsyncTaskEnqueuer;
import google.registry.config.RegistryConfig;
import google.registry.flows.EppException;
import google.registry.flows.EppException.AssociationProhibitsOperationException;
import google.registry.flows.ExtensionManager;
@@ -117,6 +119,8 @@ import org.joda.time.Duration;
@ReportingSpec(ActivityReportField.DOMAIN_DELETE)
public final class DomainDeleteFlow implements MutatingFlow {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final ImmutableSet<StatusValue> DISALLOWED_STATUSES = ImmutableSet.of(
StatusValue.CLIENT_DELETE_PROHIBITED,
StatusValue.PENDING_DELETE,
@@ -212,10 +216,17 @@ public final class DomainDeleteFlow implements MutatingFlow {
// superuser (i.e. the registrar didn't request this delete and thus should be notified even if
// it is synchronous).
if (durationUntilDelete.isLongerThan(Duration.ZERO) || isSuperuser) {
PollMessage.OneTime deletePollMessage =
createDeletePollMessage(existingDomain, domainHistoryId, deletionTime);
entitiesToSave.add(deletePollMessage);
builder.setDeletePollMessage(deletePollMessage.createVKey());
if (RegistryConfig.getNoPollMessageOnDeletionRegistrarIds()
.contains(existingDomain.getCurrentSponsorRegistrarId())) {
logger.atInfo().log(
"Skipping poll message on domain deletion for registrar %s due to configuration",
existingDomain.getCurrentSponsorRegistrarId());
} else {
PollMessage.OneTime deletePollMessage =
createDeletePollMessage(existingDomain, domainHistoryId, deletionTime);
entitiesToSave.add(deletePollMessage);
builder.setDeletePollMessage(deletePollMessage.createVKey());
}
}
// Send a second poll message immediately if the domain is being deleted asynchronously by a

View File

@@ -183,7 +183,9 @@ public final class DomainRestoreRequestFlow implements MutatingFlow {
DomainHistory domainHistory = buildDomainHistory(newDomain, now);
entitiesToSave.add(newDomain, domainHistory, autorenewEvent, autorenewPollMessage);
tm().putAll(entitiesToSave.build());
tm().delete(existingDomain.getDeletePollMessage());
if (existingDomain.getDeletePollMessage() != null) {
tm().delete(existingDomain.getDeletePollMessage());
}
requestDomainDnsRefresh(existingDomain.getDomainName());
return responseBuilder
.setExtensions(createResponseExtensions(feesAndCredits, feeUpdate, isExpired))

View File

@@ -1247,4 +1247,15 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
clock.advanceOneMilli();
runFlowAssertResponse(loadFile("domain_delete_response_fee_free_grace.xml"));
}
@Test
void testSuccess_skipsPollMessage_whenConfigured() throws Exception {
setUpSuccessfulTest();
domain =
persistResource(
domain.asBuilder().setPersistedCurrentSponsorRegistrarId("NewRegistrar").build());
setRegistrarIdForFlow("NewRegistrar");
runFlowAssertResponse(loadFile("domain_delete_response_pending.xml"));
assertPollMessages();
}
}

View File

@@ -74,6 +74,7 @@ import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Tld;
import google.registry.persistence.VKey;
import google.registry.testing.DatabaseHelper;
import java.util.Map;
import java.util.Optional;
@@ -103,12 +104,12 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
setEppInput("domain_update_restore_request.xml", ImmutableMap.of("DOMAIN", "example.tld"));
}
void persistPendingDeleteDomain() throws Exception {
Domain persistPendingDeleteDomain() throws Exception {
// The domain is now past what had been its expiration date at the time of deletion.
persistPendingDeleteDomain(clock.nowUtc().minusDays(5));
return persistPendingDeleteDomain(clock.nowUtc().minusDays(5));
}
void persistPendingDeleteDomain(DateTime expirationTime) throws Exception {
Domain persistPendingDeleteDomain(DateTime expirationTime) throws Exception {
Domain domain = persistResource(DatabaseHelper.newDomain(getUniqueIdFromCommand()));
HistoryEntry historyEntry =
persistResource(
@@ -118,29 +119,31 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
.setRegistrarId(domain.getCurrentSponsorRegistrarId())
.setDomain(domain)
.build());
persistResource(
domain
.asBuilder()
.setRegistrationExpirationTime(expirationTime)
.setDeletionTime(clock.nowUtc().plusDays(35))
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.REDEMPTION,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"TheRegistrar",
null))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.setDeletePollMessage(
persistResource(
new PollMessage.OneTime.Builder()
.setRegistrarId("TheRegistrar")
.setEventTime(clock.nowUtc().plusDays(5))
.setHistoryEntry(historyEntry)
.build())
.createVKey())
.build());
domain =
persistResource(
domain
.asBuilder()
.setRegistrationExpirationTime(expirationTime)
.setDeletionTime(clock.nowUtc().plusDays(35))
.addGracePeriod(
GracePeriod.create(
GracePeriodStatus.REDEMPTION,
domain.getRepoId(),
clock.nowUtc().plusDays(1),
"TheRegistrar",
null))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.setDeletePollMessage(
persistResource(
new PollMessage.OneTime.Builder()
.setRegistrarId("TheRegistrar")
.setEventTime(clock.nowUtc().plusDays(5))
.setHistoryEntry(historyEntry)
.build())
.createVKey())
.build());
clock.advanceOneMilli();
return domain;
}
@Test
@@ -491,6 +494,15 @@ class DomainRestoreRequestFlowTest extends ResourceFlowTestCase<DomainRestoreReq
loadFile("domain_update_restore_request_response_premium.xml"));
}
@Test
void testSuccess_worksWithoutPollMessage() throws Exception {
Domain domain = persistPendingDeleteDomain();
VKey<PollMessage.OneTime> deletePollMessage = domain.getDeletePollMessage();
persistResource(domain.asBuilder().setDeletePollMessage(null).build());
DatabaseHelper.deleteByKey(deletePollMessage);
runFlowAssertResponse(loadFile("generic_success_response.xml"));
}
@Test
void testFailure_doesNotExist() throws Exception {
ResourceDoesNotExistException thrown =

View File

@@ -1168,6 +1168,10 @@ public final class DatabaseHelper {
tm().transact(() -> tm().delete(resource));
}
public static void deleteByKey(VKey<?> key) {
tm().transact(() -> tm().delete(key));
}
/** Force the create and update timestamps to get written into the resource. */
public static <R> R cloneAndSetAutoTimestamps(final R resource) {
// We have to separate the read and write operation into different transactions otherwise JPA