From 4c61df65b2023308ee994e5d06ab20fd864e90ff Mon Sep 17 00:00:00 2001 From: gbrodman Date: Tue, 7 Jun 2022 11:43:33 -0400 Subject: [PATCH] Remove Ofy code from various flow-related classes (#1653) This included removing ofy-specific code from various tests. Also, some of the other tests (e.g. RdapDomainActionTest) had to be configured to use only SQL -- otherwise, as it currently stands, they were trying to use ofy. We also delete the CreateSyntheticHistoryEntriesAction and pipeline because they're no longer relevant, and impossible to test (the goal of the actions were to create objects in ofy which doesn't happen any more). --- core/build.gradle | 5 - .../batch/DeleteContactsAndHostsAction.java | 621 ------------ .../env/common/backend/WEB-INF/web.xml | 15 - .../registry/flows/ResourceFlowUtils.java | 59 +- .../flows/contact/ContactDeleteFlow.java | 42 +- .../flows/domain/DomainFlowUtils.java | 27 +- .../registry/flows/domain/DomainInfoFlow.java | 5 - .../registry/flows/host/HostDeleteFlow.java | 40 +- .../registry/flows/poll/PollAckFlow.java | 8 +- .../registry/flows/poll/PollFlowUtils.java | 5 +- .../model/reporting/HistoryEntryDao.java | 78 +- .../backend/BackendRequestComponent.java | 6 - .../CreateSyntheticHistoryEntriesAction.java | 198 ---- ...CreateSyntheticHistoryEntriesPipeline.java | 135 --- .../DeleteContactsAndHostsActionTest.java | 950 ------------------ .../flows/EppLifecycleContactTest.java | 17 +- .../registry/flows/EppLifecycleHostTest.java | 20 +- .../registry/flows/EppPointInTimeTest.java | 19 +- .../registry/flows/ResourceFlowTestCase.java | 69 +- .../flows/contact/ContactCreateFlowTest.java | 4 - .../flows/contact/ContactDeleteFlowTest.java | 58 +- .../flows/domain/DomainCreateFlowTest.java | 1 - .../flows/host/HostCreateFlowTest.java | 4 - .../flows/host/HostDeleteFlowTest.java | 75 +- .../flows/host/HostUpdateFlowTest.java | 11 - .../registry/rdap/RdapDomainActionTest.java | 46 +- .../registry/tools/EppLifecycleToolsTest.java | 12 +- ...eateSyntheticHistoryEntriesActionTest.java | 152 --- ...teSyntheticHistoryEntriesPipelineTest.java | 115 --- .../tools/server/VerifyOteActionTest.java | 12 +- .../server/registrar/OteStatusActionTest.java | 18 +- .../RegistryLockVerifyActionTest.java | 36 +- .../webdriver/TestServerExtension.java | 15 +- .../module/backend/backend_routing.txt | 2 - 34 files changed, 200 insertions(+), 2680 deletions(-) delete mode 100644 core/src/main/java/google/registry/batch/DeleteContactsAndHostsAction.java delete mode 100644 core/src/main/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesAction.java delete mode 100644 core/src/main/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesPipeline.java delete mode 100644 core/src/test/java/google/registry/batch/DeleteContactsAndHostsActionTest.java delete mode 100644 core/src/test/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesActionTest.java delete mode 100644 core/src/test/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesPipelineTest.java diff --git a/core/build.gradle b/core/build.gradle index 60ffbba89..53161af4f 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -37,7 +37,6 @@ def jsDir = "${project.projectDir}/src/main/javascript" // TODO(weiminyu): identify cause and fix offending tests. def outcastTestPatterns = [ // Problem seems to lie with AppEngine TaskQueue for test. - "google/registry/batch/DeleteContactsAndHostsActionTest.*", "google/registry/batch/RefreshDnsOnHostRenameActionTest.*", "google/registry/flows/CheckApiActionTest.*", "google/registry/flows/EppLifecycleHostTest.*", @@ -714,10 +713,6 @@ createToolTask( createToolTask( 'jpaDemoPipeline', 'google.registry.beam.common.JpaDemoPipeline') -createToolTask( - 'createSyntheticHistoryEntries', - 'google.registry.tools.javascrap.CreateSyntheticHistoryEntriesPipeline') - // Caller must provide projectId, GCP region, runner, and the kinds to delete // (comma-separated kind names or '*' for all). E.g.: // nom_build :core:bulkDeleteDatastore --args="--project=domain-registry-crash \ diff --git a/core/src/main/java/google/registry/batch/DeleteContactsAndHostsAction.java b/core/src/main/java/google/registry/batch/DeleteContactsAndHostsAction.java deleted file mode 100644 index 1496b29c2..000000000 --- a/core/src/main/java/google/registry/batch/DeleteContactsAndHostsAction.java +++ /dev/null @@ -1,621 +0,0 @@ -// Copyright 2017 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.batch; - -import static com.google.appengine.api.taskqueue.QueueConstants.maxLeaseCount; -import static com.google.appengine.api.taskqueue.QueueFactory.getQueue; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.math.IntMath.divide; -import static com.googlecode.objectify.Key.getKind; -import static google.registry.batch.AsyncTaskEnqueuer.PARAM_CLIENT_TRANSACTION_ID; -import static google.registry.batch.AsyncTaskEnqueuer.PARAM_IS_SUPERUSER; -import static google.registry.batch.AsyncTaskEnqueuer.PARAM_REQUESTED_TIME; -import static google.registry.batch.AsyncTaskEnqueuer.PARAM_REQUESTING_CLIENT_ID; -import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY; -import static google.registry.batch.AsyncTaskEnqueuer.PARAM_SERVER_TRANSACTION_ID; -import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_DELETE; -import static google.registry.model.EppResourceUtils.isActive; -import static google.registry.model.EppResourceUtils.isDeleted; -import static google.registry.model.ResourceTransferUtils.denyPendingTransfer; -import static google.registry.model.ResourceTransferUtils.handlePendingTransferOnDelete; -import static google.registry.model.ResourceTransferUtils.updateForeignKeyIndexDeletionTime; -import static google.registry.model.eppcommon.StatusValue.PENDING_DELETE; -import static google.registry.model.ofy.ObjectifyService.auditedOfy; -import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_DELETE; -import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_DELETE_FAILURE; -import static google.registry.model.reporting.HistoryEntry.Type.HOST_DELETE; -import static google.registry.model.reporting.HistoryEntry.Type.HOST_DELETE_FAILURE; -import static google.registry.model.transfer.TransferStatus.SERVER_CANCELLED; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static java.math.RoundingMode.CEILING; -import static java.util.concurrent.TimeUnit.DAYS; -import static java.util.concurrent.TimeUnit.SECONDS; -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.SEVERE; -import static org.joda.time.Duration.standardHours; - -import com.google.appengine.api.taskqueue.LeaseOptions; -import com.google.appengine.api.taskqueue.Queue; -import com.google.appengine.api.taskqueue.TaskHandle; -import com.google.appengine.api.taskqueue.TransientFailureException; -import com.google.appengine.tools.mapreduce.Mapper; -import com.google.appengine.tools.mapreduce.Reducer; -import com.google.appengine.tools.mapreduce.ReducerInput; -import com.google.auto.value.AutoValue; -import com.google.common.collect.HashMultiset; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterators; -import com.google.common.collect.Multiset; -import com.google.common.flogger.FluentLogger; -import com.googlecode.objectify.Key; -import google.registry.batch.AsyncTaskMetrics.OperationResult; -import google.registry.batch.AsyncTaskMetrics.OperationType; -import google.registry.batch.DeleteContactsAndHostsAction.DeletionResult.Type; -import google.registry.dns.DnsQueue; -import google.registry.mapreduce.MapreduceRunner; -import google.registry.mapreduce.UnlockerOutput; -import google.registry.mapreduce.inputs.EppResourceInputs; -import google.registry.mapreduce.inputs.NullInput; -import google.registry.model.EppResource; -import google.registry.model.ImmutableObject; -import google.registry.model.annotations.DeleteAfterMigration; -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.server.Lock; -import google.registry.persistence.VKey; -import google.registry.request.Action; -import google.registry.request.Response; -import google.registry.request.auth.Auth; -import google.registry.util.Clock; -import google.registry.util.NonFinalForTesting; -import google.registry.util.RequestStatusChecker; -import google.registry.util.Retrier; -import google.registry.util.SystemClock; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.logging.Level; -import javax.annotation.Nullable; -import javax.inject.Inject; -import javax.inject.Named; -import org.joda.time.DateTime; -import org.joda.time.Duration; - -/** - * A mapreduce that processes batch asynchronous deletions of contact and host resources by mapping - * over all domains and checking for any references to the contacts/hosts in pending deletion. - */ -@Deprecated -@Action( - service = Action.Service.BACKEND, - path = "/_dr/task/deleteContactsAndHosts", - auth = Auth.AUTH_INTERNAL_OR_ADMIN) -@DeleteAfterMigration -public class DeleteContactsAndHostsAction implements Runnable { - - static final String KIND_CONTACT = getKind(ContactResource.class); - static final String KIND_HOST = getKind(HostResource.class); - - private static final Duration LEASE_LENGTH = standardHours(4); - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - private static final int MAX_REDUCE_SHARDS = 50; - private static final int DELETES_PER_SHARD = 5; - - @Inject AsyncTaskMetrics asyncTaskMetrics; - @Inject Clock clock; - @Inject MapreduceRunner mrRunner; - @Inject @Named(QUEUE_ASYNC_DELETE) Queue queue; - @Inject RequestStatusChecker requestStatusChecker; - @Inject Response response; - @Inject Retrier retrier; - @Inject DeleteContactsAndHostsAction() {} - - @Override - public void run() { - // Check if the lock can be acquired, and if not, a previous run of this mapreduce is still - // executing, so return early. - Optional lock = - Lock.acquire( - DeleteContactsAndHostsAction.class.getSimpleName(), - null, - LEASE_LENGTH, - requestStatusChecker, - false); - if (!lock.isPresent()) { - logRespondAndUnlock(INFO, "Can't acquire lock; aborting.", lock); - return; - } - - // Lease the async tasks to process. - LeaseOptions options = - LeaseOptions.Builder.withCountLimit(maxLeaseCount()) - .leasePeriod(LEASE_LENGTH.getStandardSeconds(), SECONDS); - List tasks = queue.leaseTasks(options); - asyncTaskMetrics.recordContactHostDeletionBatchSize(tasks.size()); - - // Check if there are no tasks to process, and if so, return early. - if (tasks.isEmpty()) { - logRespondAndUnlock(INFO, "No contact/host deletion tasks in pull queue; finishing.", lock); - return; - } - - Multiset kindCounts = HashMultiset.create(2); - ImmutableList.Builder builder = new ImmutableList.Builder<>(); - ImmutableList.Builder> resourceKeys = new ImmutableList.Builder<>(); - final List requestsToDelete = new ArrayList<>(); - for (TaskHandle task : tasks) { - try { - DeletionRequest deletionRequest = DeletionRequest.createFromTask(task, clock.nowUtc()); - if (deletionRequest.isDeletionAllowed()) { - builder.add(deletionRequest); - resourceKeys.add(deletionRequest.key()); - kindCounts.add(deletionRequest.key().getKind()); - } else { - requestsToDelete.add(deletionRequest); - } - } catch (Exception e) { - logger.atSevere().withCause(e).log( - "Could not parse async deletion request, delaying task for a day: %s", task); - // Grab the lease for a whole day, so that it won't continue throwing errors every five - // minutes. - queue.modifyTaskLease(task, 1L, DAYS); - } - } - deleteStaleTasksWithRetry(requestsToDelete); - ImmutableList deletionRequests = builder.build(); - if (deletionRequests.isEmpty()) { - logRespondAndUnlock( - INFO, "No async deletions to process because all were already handled.", lock); - } else { - logger.atInfo().log( - "Processing asynchronous deletion of %d contacts and %d hosts: %s", - kindCounts.count(KIND_CONTACT), kindCounts.count(KIND_HOST), resourceKeys.build()); - runMapreduce(deletionRequests, lock); - } - } - - /** - * Deletes a list of tasks associated with deletion requests from the async delete queue using a - * retrier. - */ - private void deleteStaleTasksWithRetry(final List deletionRequests) { - if (deletionRequests.isEmpty()) { - return; - } - final List tasks = - deletionRequests.stream().map(DeletionRequest::task).collect(toImmutableList()); - retrier.callWithRetry(() -> queue.deleteTask(tasks), TransientFailureException.class); - deletionRequests.forEach( - deletionRequest -> - asyncTaskMetrics.recordAsyncFlowResult( - deletionRequest.getMetricOperationType(), - OperationResult.STALE, - deletionRequest.requestedTime())); - } - - private void runMapreduce(ImmutableList deletionRequests, Optional lock) { - try { - int numReducers = - Math.min(MAX_REDUCE_SHARDS, divide(deletionRequests.size(), DELETES_PER_SHARD, CEILING)); - mrRunner - .setJobName("Check for EPP resource references and then delete") - .setModuleName("backend") - .setDefaultReduceShards(numReducers) - .runMapreduce( - new DeleteContactsAndHostsMapper(deletionRequests), - new DeleteEppResourceReducer(), - ImmutableList.of( - // Add an extra shard that maps over a null domain. See the mapper code for why. - new NullInput<>(), EppResourceInputs.createEntityInput(DomainBase.class)), - new UnlockerOutput(lock.get())) - .sendLinkToMapreduceConsole(response); - } catch (Throwable t) { - logRespondAndUnlock(SEVERE, "Error starting mapreduce to delete contacts/hosts.", lock); - } - } - - private void logRespondAndUnlock(Level level, String message, Optional lock) { - logger.at(level).log(message); - response.setPayload(message); - lock.ifPresent(Lock::release); - } - - /** - * A mapper that iterates over all {@link DomainBase} entities. - * - *

It emits the target key and {@code true} for domains referencing the target resource. For - * the special input of {@code null} it emits the target key and {@code false}. - */ - public static class DeleteContactsAndHostsMapper - extends Mapper { - - private static final long serialVersionUID = -253652818502690537L; - - private final ImmutableList deletionRequests; - - DeleteContactsAndHostsMapper(ImmutableList resourcesToDelete) { - this.deletionRequests = resourcesToDelete; - } - - @Override - public void map(DomainBase domain) { - for (DeletionRequest deletionRequest : deletionRequests) { - if (domain == null) { - // The reducer only runs if at least one value is emitted. We add a null input to the - // mapreduce and emit one 'false' for each deletion request so that the reducer always - // runs for each requested deletion (so that it can finish up tasks if nothing else). - emit(deletionRequest, false); - } else if (isActive(domain, deletionRequest.lastUpdateTime()) - && isLinked(domain, deletionRequest.key())) { - emit(deletionRequest, true); - getContext() - .incrementCounter( - String.format("active Domain-%s links found", deletionRequest.key().getKind())); - } - } - if (domain != null) { - getContext().incrementCounter("domains processed"); - } - } - - /** Determine whether the target resource is a linked resource on the domain. */ - private boolean isLinked(DomainBase domain, Key resourceKey) { - if (resourceKey.getKind().equals(KIND_CONTACT)) { - return domain - .getReferencedContacts() - .contains(VKey.from((Key) resourceKey)); - } else if (resourceKey.getKind().equals(KIND_HOST)) { - return domain.getNameservers().contains(VKey.from((Key) resourceKey)); - } else { - throw new IllegalStateException("EPP resource key of unknown type: " + resourceKey); - } - } - } - - /** - * A reducer that checks if the EPP resource to be deleted is referenced anywhere, and then - * deletes it if not and unmarks it for deletion if so. - */ - public static class DeleteEppResourceReducer - extends Reducer { - - private static final long serialVersionUID = 6569363449285506326L; - private static final DnsQueue dnsQueue = DnsQueue.create(); - - @NonFinalForTesting - private static AsyncTaskMetrics asyncTaskMetrics = new AsyncTaskMetrics(new SystemClock()); - - @Override - public void reduce(final DeletionRequest deletionRequest, ReducerInput values) { - final boolean hasNoActiveReferences = !Iterators.contains(values, true); - logger.atInfo().log("Processing async deletion request for %s.", deletionRequest.key()); - DeletionResult result = - tm() - .transactNew( - () -> { - DeletionResult deletionResult = - attemptToDeleteResource(deletionRequest, hasNoActiveReferences); - getQueue(QUEUE_ASYNC_DELETE).deleteTask(deletionRequest.task()); - return deletionResult; - }); - asyncTaskMetrics.recordAsyncFlowResult( - deletionRequest.getMetricOperationType(), - result.getMetricOperationResult(), - deletionRequest.requestedTime()); - String resourceNamePlural = deletionRequest.key().getKind() + "s"; - getContext().incrementCounter(result.type().renderCounterText(resourceNamePlural)); - logger.atInfo().log( - "Result of async deletion for resource %s: %s", - deletionRequest.key(), result.pollMessageText()); - } - - private DeletionResult attemptToDeleteResource( - DeletionRequest deletionRequest, boolean hasNoActiveReferences) { - DateTime now = tm().getTransactionTime(); - EppResource resource = - auditedOfy().load().key(deletionRequest.key()).now().cloneProjectedAtTime(now); - // Double-check transactionally that the resource is still active and in PENDING_DELETE. - 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 resourceRegistrarId = resource.getPersistedCurrentSponsorRegistrarId(); - if (resource instanceof HostResource && ((HostResource) resource).isSubordinate()) { - resourceRegistrarId = - tm().loadByKey(((HostResource) resource).getSuperordinateDomain()) - .cloneProjectedAtTime(now) - .getCurrentSponsorRegistrarId(); - } - boolean requestedByCurrentOwner = - resourceRegistrarId.equals(deletionRequest.requestingClientId()); - - boolean deleteAllowed = - hasNoActiveReferences && (requestedByCurrentOwner || deletionRequest.isSuperuser()); - - String resourceTypeName = - resource.getClass().getAnnotation(ExternalMessagingName.class).value(); - String pollMessageText = - deleteAllowed - ? String.format("Deleted %s %s.", resourceTypeName, resource.getForeignKey()) - : String.format( - "Can't delete %s %s because %s.", - resourceTypeName, - resource.getForeignKey(), - requestedByCurrentOwner - ? "it is referenced by a domain" - : "it was transferred prior to deletion"); - - HistoryEntry historyEntry = - HistoryEntry.createBuilderForResource(resource) - .setRegistrarId(deletionRequest.requestingClientId()) - .setModificationTime(now) - .setType(getHistoryEntryType(resource, deleteAllowed)) - .build(); - - PollMessage.OneTime pollMessage = - new PollMessage.OneTime.Builder() - .setRegistrarId(deletionRequest.requestingClientId()) - .setMsg(pollMessageText) - .setParent(historyEntry) - .setEventTime(now) - .setResponseData( - getPollMessageResponseData(deletionRequest, resource, deleteAllowed, now)) - .build(); - - EppResource resourceToSave; - if (deleteAllowed) { - EppResource.Builder resourceToSaveBuilder; - if (resource instanceof ContactResource) { - ContactResource contact = (ContactResource) resource; - // Handle pending transfers on contact deletion. - if (contact.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) { - contact = - denyPendingTransfer( - contact, SERVER_CANCELLED, now, deletionRequest.requestingClientId()); - } - // Wipe out PII on contact deletion. - resourceToSaveBuilder = contact.asBuilder().wipeOut(); - } else { - resourceToSaveBuilder = resource.asBuilder(); - } - resourceToSave = resourceToSaveBuilder.setDeletionTime(now).setStatusValues(null).build(); - performDeleteTasks(resource, resourceToSave, now, historyEntry); - updateForeignKeyIndexDeletionTime(resourceToSave); - } else { - resourceToSave = resource.asBuilder().removeStatusValue(PENDING_DELETE).build(); - } - auditedOfy() - .save() - .entities(resourceToSave, historyEntry.asHistoryEntry(), pollMessage); - return DeletionResult.create( - deleteAllowed ? Type.DELETED : Type.NOT_DELETED, pollMessageText); - } - - private static ImmutableList getPollMessageResponseData( - DeletionRequest deletionRequest, - EppResource resource, - boolean deleteAllowed, - DateTime now) { - @Nullable String clientTransactionId = deletionRequest.clientTransactionId(); - String serverTransactionId = deletionRequest.serverTransactionId(); - Trid trid = Trid.create(clientTransactionId, serverTransactionId); - if (resource instanceof HostResource) { - return ImmutableList.of( - HostPendingActionNotificationResponse.create( - ((HostResource) resource).getHostName(), 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)); - } - } - - /** - * Determine the proper history entry type for the delete operation, as a function of - * whether or not the delete was successful. - */ - private HistoryEntry.Type getHistoryEntryType(EppResource resource, boolean successfulDelete) { - if (resource instanceof ContactResource) { - return successfulDelete ? CONTACT_DELETE : CONTACT_DELETE_FAILURE; - } else if (resource instanceof HostResource) { - return successfulDelete ? HOST_DELETE : HOST_DELETE_FAILURE; - } else { - throw new IllegalStateException("EPP resource of unknown type: " + Key.create(resource)); - } - } - - /** Perform any type-specific tasks on the resource to be deleted (and/or its dependencies). */ - private void performDeleteTasks( - EppResource existingResource, - EppResource deletedResource, - DateTime deletionTime, - HistoryEntry historyEntryForDelete) { - if (existingResource instanceof ContactResource) { - handlePendingTransferOnDelete( - (ContactResource) existingResource, - (ContactResource) deletedResource, - deletionTime, - historyEntryForDelete); - } else if (existingResource instanceof HostResource) { - HostResource host = (HostResource) existingResource; - if (host.isSubordinate()) { - dnsQueue.addHostRefreshTask(host.getHostName()); - tm().put( - tm().loadByKey(host.getSuperordinateDomain()) - .asBuilder() - .removeSubordinateHost(host.getHostName()) - .build()); - } - } else { - throw new IllegalStateException( - "EPP resource of unknown type: " + Key.create(existingResource)); - } - } - } - - /** A class that encapsulates the values of a request to delete a contact or host. */ - @AutoValue - abstract static class DeletionRequest implements Serializable { - - private static final long serialVersionUID = -4612618525760839240L; - - abstract Key key(); - abstract DateTime lastUpdateTime(); - - /** - * The client id of the registrar that requested this deletion (which might NOT be the same as - * the actual current owner of the resource). - */ - abstract String requestingClientId(); - - /** First half of TRID for the original request, split for serializability. */ - @Nullable - abstract String clientTransactionId(); - - /** Second half of TRID for the original request, split for serializability. */ - abstract String serverTransactionId(); - - abstract boolean isSuperuser(); - abstract DateTime requestedTime(); - abstract boolean isDeletionAllowed(); - abstract TaskHandle task(); - - @AutoValue.Builder - abstract static class Builder { - abstract Builder setKey(Key key); - abstract Builder setLastUpdateTime(DateTime lastUpdateTime); - abstract Builder setRequestingClientId(String requestingClientId); - abstract Builder setClientTransactionId(@Nullable String clientTransactionId); - abstract Builder setServerTransactionId(String serverTransactionId); - abstract Builder setIsSuperuser(boolean isSuperuser); - abstract Builder setRequestedTime(DateTime requestedTime); - abstract Builder setIsDeletionAllowed(boolean isDeletionAllowed); - abstract Builder setTask(TaskHandle task); - abstract DeletionRequest build(); - } - - static DeletionRequest createFromTask(TaskHandle task, DateTime now) - throws Exception { - ImmutableMap params = ImmutableMap.copyOf(task.extractParams()); - VKey resourceKey = - VKey.create( - checkNotNull(params.get(PARAM_RESOURCE_KEY), "Resource to delete not specified")); - EppResource resource = - checkNotNull( - auditedOfy().load().key(resourceKey.getOfyKey()).now(), - "Resource to delete doesn't exist"); - checkState( - resource instanceof ContactResource || resource instanceof HostResource, - "Cannot delete a %s via this action", - resource.getClass().getSimpleName()); - return new AutoValue_DeleteContactsAndHostsAction_DeletionRequest.Builder() - .setKey(resourceKey.getOfyKey()) - .setLastUpdateTime(resource.getUpdateTimestamp().getTimestamp()) - .setRequestingClientId( - checkNotNull( - params.get(PARAM_REQUESTING_CLIENT_ID), "Requesting client id not specified")) - // Note that client transaction ID is optional, in which case this sets it to null. - .setClientTransactionId(params.get(PARAM_CLIENT_TRANSACTION_ID)) - .setServerTransactionId( - checkNotNull( - params.get(PARAM_SERVER_TRANSACTION_ID), "Server transaction id not specified")) - .setIsSuperuser( - Boolean.parseBoolean( - checkNotNull(params.get(PARAM_IS_SUPERUSER), "Is superuser not specified"))) - .setRequestedTime( - DateTime.parse( - checkNotNull(params.get(PARAM_REQUESTED_TIME), "Requested time not specified"))) - .setIsDeletionAllowed(doesResourceStateAllowDeletion(resource, now)) - .setTask(task) - .build(); - } - - OperationType getMetricOperationType() { - if (key().getKind().equals(KIND_CONTACT)) { - return OperationType.CONTACT_DELETE; - } else if (key().getKind().equals(KIND_HOST)) { - return OperationType.HOST_DELETE; - } else { - throw new IllegalStateException( - String.format( - "Cannot determine async operation type for metric for resource %s", key())); - } - } - } - - /** A class that encapsulates the values resulting from attempted contact/host deletion. */ - @AutoValue - abstract static class DeletionResult { - - enum Type { - DELETED("%s deleted", OperationResult.SUCCESS), - NOT_DELETED("%s not deleted", OperationResult.FAILURE), - ERRORED("%s errored out during deletion", OperationResult.ERROR); - - private final String counterFormat; - private final OperationResult operationResult; - - Type(String counterFormat, OperationResult operationResult) { - this.counterFormat = counterFormat; - this.operationResult = operationResult; - } - - String renderCounterText(String resourceName) { - return String.format(counterFormat, resourceName); - } - } - - abstract Type type(); - abstract String pollMessageText(); - - static DeletionResult create(Type type, String pollMessageText) { - return new AutoValue_DeleteContactsAndHostsAction_DeletionResult(type, pollMessageText); - } - - OperationResult getMetricOperationResult() { - return type().operationResult; - } - } - - static boolean doesResourceStateAllowDeletion(EppResource resource, DateTime now) { - Key key = Key.create(resource); - if (isDeleted(resource, now)) { - logger.atWarning().log("Cannot asynchronously delete %s because it is already deleted.", key); - return false; - } - if (!resource.getStatusValues().contains(PENDING_DELETE)) { - logger.atWarning().log( - "Cannot asynchronously delete %s because it is not in PENDING_DELETE.", key); - return false; - } - return true; - } -} diff --git a/core/src/main/java/google/registry/env/common/backend/WEB-INF/web.xml b/core/src/main/java/google/registry/env/common/backend/WEB-INF/web.xml index a381b00ec..2c125b6ee 100644 --- a/core/src/main/java/google/registry/env/common/backend/WEB-INF/web.xml +++ b/core/src/main/java/google/registry/env/common/backend/WEB-INF/web.xml @@ -334,15 +334,6 @@ /_dr/task/resaveEntity - - - backend-servlet - /_dr/task/deleteContactsAndHosts - - backend-servlet @@ -422,12 +413,6 @@ have been in the database for a certain period of time. --> /_dr/task/wipeOutDatastore - - - backend-servlet - /_dr/task/createSyntheticHistoryEntries - - backend-servlet diff --git a/core/src/main/java/google/registry/flows/ResourceFlowUtils.java b/core/src/main/java/google/registry/flows/ResourceFlowUtils.java index 6364df7dc..4d24d21b3 100644 --- a/core/src/main/java/google/registry/flows/ResourceFlowUtils.java +++ b/core/src/main/java/google/registry/flows/ResourceFlowUtils.java @@ -15,7 +15,6 @@ package google.registry.flows; import static com.google.common.collect.Sets.intersection; -import static google.registry.model.EppResourceUtils.getLinkedDomainKeys; import static google.registry.model.EppResourceUtils.isLinked; import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey; @@ -54,8 +53,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.function.Function; -import java.util.function.Predicate; import org.joda.time.DateTime; /** Static utility functions for resource flows. */ @@ -63,12 +60,6 @@ public final class ResourceFlowUtils { private ResourceFlowUtils() {} - /** - * In {@link #checkLinkedDomains(String, DateTime, Class, Function)}, check this (arbitrary) - * number of query results. - */ - private static final int FAILFAST_CHECK_COUNT = 5; - /** Check that the given registrarId corresponds to the owner of given resource. */ public static void verifyResourceOwnership(String myRegistrarId, EppResource resource) throws EppException { @@ -85,46 +76,18 @@ public final class ResourceFlowUtils { * consistent, so we only check a few domains to fail fast. */ public static void checkLinkedDomains( - final String targetId, - final DateTime now, - final Class resourceClass, - final Function> getPotentialReferences) - throws EppException { + final String targetId, final DateTime now, final Class resourceClass) throws EppException { EppException failfastException = - tm().isOfy() - ? tm().doTransactionless( - () -> { - final ForeignKeyIndex fki = - ForeignKeyIndex.load(resourceClass, targetId, now); - if (fki == null) { - return new ResourceDoesNotExistException(resourceClass, targetId); - } - // Query for the first few linked domains, and if found, actually load them. - // The query is eventually consistent and so might be very stale, but the - // direct load will not be stale, just non-transactional. If we find at least - // one actual reference then we can reliably fail. If we don't find any, - // we can't trust the query and need to do the full mapreduce. - Iterable> keys = - getLinkedDomainKeys(fki.getResourceKey(), now, FAILFAST_CHECK_COUNT); - - VKey resourceVKey = fki.getResourceKey(); - Predicate predicate = - domain -> getPotentialReferences.apply(domain).contains(resourceVKey); - return tm().loadByKeys(keys).values().stream().anyMatch(predicate) - ? new ResourceToDeleteIsReferencedException() - : null; - }) - : tm().transact( - () -> { - final ForeignKeyIndex fki = - ForeignKeyIndex.load(resourceClass, targetId, now); - if (fki == null) { - return new ResourceDoesNotExistException(resourceClass, targetId); - } - return isLinked(fki.getResourceKey(), now) - ? new ResourceToDeleteIsReferencedException() - : null; - }); + tm().transact( + () -> { + final ForeignKeyIndex fki = ForeignKeyIndex.load(resourceClass, targetId, now); + if (fki == null) { + return new ResourceDoesNotExistException(resourceClass, targetId); + } + return isLinked(fki.getResourceKey(), now) + ? new ResourceToDeleteIsReferencedException() + : null; + }); if (failfastException != null) { throw failfastException; } diff --git a/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java b/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java index f79146f08..f8f636277 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java @@ -23,7 +23,6 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership; import static google.registry.model.ResourceTransferUtils.denyPendingTransfer; import static google.registry.model.ResourceTransferUtils.handlePendingTransferOnDelete; 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.transfer.TransferStatus.SERVER_CANCELLED; import static google.registry.persistence.transaction.TransactionManagerFactory.assertAsyncActionsAreAllowed; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; @@ -39,13 +38,11 @@ import google.registry.flows.TransactionalFlow; import google.registry.flows.annotations.ReportingSpec; import google.registry.model.contact.ContactHistory; import google.registry.model.contact.ContactResource; -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.eppoutput.Result.Code; import google.registry.model.reporting.HistoryEntry.Type; import google.registry.model.reporting.IcannReportingTypes.ActivityReportField; import java.util.Optional; @@ -97,41 +94,26 @@ public final class ContactDeleteFlow implements TransactionalFlow { extensionManager.validate(); assertAsyncActionsAreAllowed(); DateTime now = tm().getTransactionTime(); - checkLinkedDomains(targetId, now, ContactResource.class, DomainBase::getReferencedContacts); + checkLinkedDomains(targetId, now, ContactResource.class); ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now); verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES); verifyOptionalAuthInfo(authInfo, existingContact); if (!isSuperuser) { verifyResourceOwnership(registrarId, existingContact); } - Type historyEntryType; - Code resultCode; - ContactResource newContact; - if (tm().isOfy()) { - asyncTaskEnqueuer.enqueueAsyncDelete( - existingContact, tm().getTransactionTime(), registrarId, trid, isSuperuser); - newContact = existingContact.asBuilder().addStatusValue(StatusValue.PENDING_DELETE).build(); - historyEntryType = Type.CONTACT_PENDING_DELETE; - resultCode = SUCCESS_WITH_ACTION_PENDING; - } else { - // Handle pending transfers on contact deletion. - newContact = - existingContact.getStatusValues().contains(StatusValue.PENDING_TRANSFER) - ? denyPendingTransfer(existingContact, SERVER_CANCELLED, now, registrarId) - : existingContact; - // Wipe out PII on contact deletion. - newContact = - newContact.asBuilder().wipeOut().setStatusValues(null).setDeletionTime(now).build(); - historyEntryType = Type.CONTACT_DELETE; - resultCode = SUCCESS; - } + // Handle pending transfers on contact deletion. + ContactResource newContact = + existingContact.getStatusValues().contains(StatusValue.PENDING_TRANSFER) + ? denyPendingTransfer(existingContact, SERVER_CANCELLED, now, registrarId) + : existingContact; + // Wipe out PII on contact deletion. + newContact = + newContact.asBuilder().wipeOut().setStatusValues(null).setDeletionTime(now).build(); ContactHistory contactHistory = - historyBuilder.setType(historyEntryType).setContact(newContact).build(); - if (!tm().isOfy()) { - handlePendingTransferOnDelete(existingContact, newContact, now, contactHistory); - } + historyBuilder.setType(Type.CONTACT_DELETE).setContact(newContact).build(); + handlePendingTransferOnDelete(existingContact, newContact, now, contactHistory); tm().insert(contactHistory); tm().update(newContact); - return responseBuilder.setResultFromCode(resultCode).build(); + return responseBuilder.setResultFromCode(SUCCESS).build(); } } diff --git a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java index 1e298adfd..f24ce5e0f 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java @@ -26,7 +26,6 @@ import static com.google.common.collect.Sets.difference; import static com.google.common.collect.Sets.intersection; import static com.google.common.collect.Sets.union; import static google.registry.model.domain.DomainBase.MAX_REGISTRATION_YEARS; -import static google.registry.model.ofy.ObjectifyService.auditedOfy; import static google.registry.model.tld.Registries.findTldForName; import static google.registry.model.tld.Registries.getTlds; import static google.registry.model.tld.Registry.TldState.GENERAL_AVAILABILITY; @@ -1159,24 +1158,14 @@ public class DomainFlowUtils { private static List findRecentHistoryEntries( DomainBase domainBase, DateTime now, Duration maxSearchPeriod) { - if (tm().isOfy()) { - return auditedOfy() - .load() - .type(HistoryEntry.class) - .ancestor(domainBase) - .filter("modificationTime >=", now.minus(maxSearchPeriod)) - .order("modificationTime") - .list(); - } else { - return jpaTm() - .query( - "FROM DomainHistory WHERE modificationTime >= :beginning AND domainRepoId = " - + ":repoId ORDER BY modificationTime ASC", - DomainHistory.class) - .setParameter("beginning", now.minus(maxSearchPeriod)) - .setParameter("repoId", domainBase.getRepoId()) - .getResultList(); - } + return jpaTm() + .query( + "FROM DomainHistory WHERE modificationTime >= :beginning AND domainRepoId = " + + ":repoId ORDER BY modificationTime ASC", + DomainHistory.class) + .setParameter("beginning", now.minus(maxSearchPeriod)) + .setParameter("repoId", domainBase.getRepoId()) + .getResultList(); } /** Resource linked to this domain does not exist. */ diff --git a/core/src/main/java/google/registry/flows/domain/DomainInfoFlow.java b/core/src/main/java/google/registry/flows/domain/DomainInfoFlow.java index 0d4d61489..0ff20743c 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainInfoFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainInfoFlow.java @@ -102,11 +102,6 @@ public final class DomainInfoFlow implements Flow { verifyOptionalAuthInfo(authInfo, domain); flowCustomLogic.afterValidation( AfterValidationParameters.newBuilder().setDomain(domain).build()); - // In ofy, refetch all referenced resources. - if (tm().isOfy()) { - tm().loadByKeys(domain.getNameservers()); - tm().loadByKeys(domain.getReferencedContacts()); - } // Registrars can only see a few fields on unauthorized domains. // This is a policy decision that is left up to us by the rfcs. DomainInfoData.Builder infoBuilder = diff --git a/core/src/main/java/google/registry/flows/host/HostDeleteFlow.java b/core/src/main/java/google/registry/flows/host/HostDeleteFlow.java index d00125cea..babdc068b 100644 --- a/core/src/main/java/google/registry/flows/host/HostDeleteFlow.java +++ b/core/src/main/java/google/registry/flows/host/HostDeleteFlow.java @@ -21,7 +21,6 @@ import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership; import static google.registry.flows.host.HostFlowUtils.validateHostName; 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.persistence.transaction.TransactionManagerFactory.assertAsyncActionsAreAllowed; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; @@ -36,15 +35,12 @@ import google.registry.flows.FlowModule.TargetId; import google.registry.flows.TransactionalFlow; import google.registry.flows.annotations.ReportingSpec; 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.eppoutput.Result; import google.registry.model.host.HostHistory; import google.registry.model.host.HostResource; -import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry.Type; import google.registry.model.reporting.IcannReportingTypes.ActivityReportField; import javax.inject.Inject; @@ -100,7 +96,7 @@ public final class HostDeleteFlow implements TransactionalFlow { assertAsyncActionsAreAllowed(); DateTime now = tm().getTransactionTime(); validateHostName(targetId); - checkLinkedDomains(targetId, now, HostResource.class, DomainBase::getNameservers); + checkLinkedDomains(targetId, now, HostResource.class); HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now); verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES); if (!isSuperuser) { @@ -112,31 +108,19 @@ public final class HostDeleteFlow implements TransactionalFlow { : existingHost; verifyResourceOwnership(registrarId, owningResource); } - HistoryEntry.Type historyEntryType; - Result.Code resultCode; - HostResource newHost; - if (tm().isOfy()) { - asyncTaskEnqueuer.enqueueAsyncDelete( - existingHost, tm().getTransactionTime(), registrarId, trid, isSuperuser); - newHost = existingHost.asBuilder().addStatusValue(StatusValue.PENDING_DELETE).build(); - historyEntryType = Type.HOST_PENDING_DELETE; - resultCode = SUCCESS_WITH_ACTION_PENDING; - } else { - newHost = existingHost.asBuilder().setStatusValues(null).setDeletionTime(now).build(); - if (existingHost.isSubordinate()) { - dnsQueue.addHostRefreshTask(existingHost.getHostName()); - tm().update( - tm().loadByKey(existingHost.getSuperordinateDomain()) - .asBuilder() - .removeSubordinateHost(existingHost.getHostName()) - .build()); - } - historyEntryType = Type.HOST_DELETE; - resultCode = SUCCESS; + HostResource newHost = + existingHost.asBuilder().setStatusValues(null).setDeletionTime(now).build(); + if (existingHost.isSubordinate()) { + dnsQueue.addHostRefreshTask(existingHost.getHostName()); + tm().update( + tm().loadByKey(existingHost.getSuperordinateDomain()) + .asBuilder() + .removeSubordinateHost(existingHost.getHostName()) + .build()); } - historyBuilder.setType(historyEntryType).setHost(newHost); + historyBuilder.setType(Type.HOST_DELETE).setHost(newHost); tm().insert(historyBuilder.build()); tm().update(newHost); - return responseBuilder.setResultFromCode(resultCode).build(); + return responseBuilder.setResultFromCode(SUCCESS).build(); } } diff --git a/core/src/main/java/google/registry/flows/poll/PollAckFlow.java b/core/src/main/java/google/registry/flows/poll/PollAckFlow.java index b797bdb70..6a2b15d65 100644 --- a/core/src/main/java/google/registry/flows/poll/PollAckFlow.java +++ b/core/src/main/java/google/registry/flows/poll/PollAckFlow.java @@ -102,19 +102,13 @@ public final class PollAckFlow implements TransactionalFlow { // This keeps track of whether we should include the current acked message in the updated // message count that's returned to the user. The only case where we do so is if an autorenew // poll message is acked, but its next event is already ready to be delivered. - boolean includeAckedMessageInCount = ackPollMessage(pollMessage); + ackPollMessage(pollMessage); // We need to return the new queue length. If this was the last message in the queue being // acked, then we return a special status code indicating that. Note that the query will // include the message being acked. int messageCount = tm().doTransactionless(() -> getPollMessageCount(registrarId, now)); - // Within the same transaction, Datastore will not reflect the updated count (potentially - // reduced by one thanks to the acked poll message). SQL will, however, so we shouldn't reduce - // the count in the SQL case. - if (!includeAckedMessageInCount && tm().isOfy()) { - messageCount--; - } if (messageCount <= 0) { return responseBuilder.setResultFromCode(SUCCESS_WITH_NO_MESSAGES).build(); } diff --git a/core/src/main/java/google/registry/flows/poll/PollFlowUtils.java b/core/src/main/java/google/registry/flows/poll/PollFlowUtils.java index 16a19a573..6ba489966 100644 --- a/core/src/main/java/google/registry/flows/poll/PollFlowUtils.java +++ b/core/src/main/java/google/registry/flows/poll/PollFlowUtils.java @@ -47,13 +47,12 @@ public final class PollFlowUtils { *

The only case where we do so is if an autorenew poll message is acked, but its next event is * already ready to be delivered. */ - public static boolean ackPollMessage(PollMessage pollMessage) { + public static void ackPollMessage(PollMessage pollMessage) { checkArgument( isBeforeOrAt(pollMessage.getEventTime(), tm().getTransactionTime()), "Cannot ACK poll message with ID %s because its event time is in the future: %s", pollMessage.getId(), pollMessage.getEventTime()); - boolean includeAckedMessageInCount = false; if (pollMessage instanceof PollMessage.OneTime) { // One-time poll messages are deleted once acked. tm().delete(pollMessage.createVKey()); @@ -68,14 +67,12 @@ public final class PollFlowUtils { // autorenew poll message has no more events to deliver and should be deleted. if (nextEventTime.isBefore(autorenewPollMessage.getAutorenewEndTime())) { tm().put(autorenewPollMessage.asBuilder().setEventTime(nextEventTime).build()); - includeAckedMessageInCount = isBeforeOrAt(nextEventTime, tm().getTransactionTime()); } else { tm().delete(autorenewPollMessage.createVKey()); } } else { throw new IllegalArgumentException("Unknown poll message type: " + pollMessage.getClass()); } - return includeAckedMessageInCount; } /** diff --git a/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java b/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java index d61f3ac78..1fcdebffc 100644 --- a/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java +++ b/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java @@ -16,9 +16,7 @@ package google.registry.model.reporting; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; -import static google.registry.model.ofy.ObjectifyService.auditedOfy; import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME; @@ -72,29 +70,16 @@ public class HistoryEntryDao { /** Loads all history objects in the times specified, including all types. */ public static ImmutableList loadAllHistoryObjects( DateTime afterTime, DateTime beforeTime) { - if (tm().isOfy()) { - return Streams.stream( - auditedOfy() - .load() - .type(HistoryEntry.class) - .order("modificationTime") - .filter("modificationTime >=", afterTime) - .filter("modificationTime <=", beforeTime)) - .map(HistoryEntry::toChildHistoryEntity) - .collect(toImmutableList()); - } else { - return jpaTm() - .transact( - () -> - new ImmutableList.Builder() - .addAll( - loadAllHistoryObjectsFromSql(ContactHistory.class, afterTime, beforeTime)) - .addAll( - loadAllHistoryObjectsFromSql(DomainHistory.class, afterTime, beforeTime)) - .addAll( - loadAllHistoryObjectsFromSql(HostHistory.class, afterTime, beforeTime)) - .build()); - } + return jpaTm() + .transact( + () -> + new ImmutableList.Builder() + .addAll( + loadAllHistoryObjectsFromSql(ContactHistory.class, afterTime, beforeTime)) + .addAll( + loadAllHistoryObjectsFromSql(DomainHistory.class, afterTime, beforeTime)) + .addAll(loadAllHistoryObjectsFromSql(HostHistory.class, afterTime, beforeTime)) + .build()); } /** Loads all history objects corresponding to the given {@link EppResource}. */ @@ -115,21 +100,8 @@ public class HistoryEntryDao { /** Loads all history objects in the time period specified for the given {@link EppResource}. */ public static ImmutableList loadHistoryObjectsForResource( VKey parentKey, DateTime afterTime, DateTime beforeTime) { - if (tm().isOfy()) { - return Streams.stream( - auditedOfy() - .load() - .type(HistoryEntry.class) - .ancestor(parentKey.getOfyKey()) - .order("modificationTime") - .filter("modificationTime >=", afterTime) - .filter("modificationTime <=", beforeTime)) - .map(HistoryEntry::toChildHistoryEntity) - .collect(toImmutableList()); - } else { - return jpaTm() - .transact(() -> loadHistoryObjectsForResourceFromSql(parentKey, afterTime, beforeTime)); - } + return jpaTm() + .transact(() -> loadHistoryObjectsForResourceFromSql(parentKey, afterTime, beforeTime)); } /** @@ -162,23 +134,15 @@ public class HistoryEntryDao { /** Loads all history objects from all time from the given registrars. */ public static Iterable loadHistoryObjectsByRegistrars( ImmutableCollection registrarIds) { - if (tm().isOfy()) { - return auditedOfy() - .load() - .type(HistoryEntry.class) - .filter("clientId in", registrarIds) - .order("modificationTime"); - } else { - return jpaTm() - .transact( - () -> - Streams.concat( - loadHistoryObjectFromSqlByRegistrars(ContactHistory.class, registrarIds), - loadHistoryObjectFromSqlByRegistrars(DomainHistory.class, registrarIds), - loadHistoryObjectFromSqlByRegistrars(HostHistory.class, registrarIds)) - .sorted(Comparator.comparing(HistoryEntry::getModificationTime)) - .collect(toImmutableList())); - } + return jpaTm() + .transact( + () -> + Streams.concat( + loadHistoryObjectFromSqlByRegistrars(ContactHistory.class, registrarIds), + loadHistoryObjectFromSqlByRegistrars(DomainHistory.class, registrarIds), + loadHistoryObjectFromSqlByRegistrars(HostHistory.class, registrarIds)) + .sorted(Comparator.comparing(HistoryEntry::getModificationTime)) + .collect(toImmutableList())); } private static Stream loadHistoryObjectFromSqlByRegistrars( diff --git a/core/src/main/java/google/registry/module/backend/BackendRequestComponent.java b/core/src/main/java/google/registry/module/backend/BackendRequestComponent.java index fb06d0360..5fa160d14 100644 --- a/core/src/main/java/google/registry/module/backend/BackendRequestComponent.java +++ b/core/src/main/java/google/registry/module/backend/BackendRequestComponent.java @@ -23,7 +23,6 @@ import google.registry.backup.ExportCommitLogDiffAction; import google.registry.backup.ReplayCommitLogsToSqlAction; import google.registry.backup.SyncDatastoreToSqlSnapshotAction; import google.registry.batch.BatchModule; -import google.registry.batch.DeleteContactsAndHostsAction; import google.registry.batch.DeleteExpiredDomainsAction; import google.registry.batch.DeleteLoadTestDataAction; import google.registry.batch.DeleteProberDataAction; @@ -93,7 +92,6 @@ import google.registry.tmch.TmchCrlAction; import google.registry.tmch.TmchDnlAction; import google.registry.tmch.TmchModule; import google.registry.tmch.TmchSmdrlAction; -import google.registry.tools.javascrap.CreateSyntheticHistoryEntriesAction; /** Dagger component with per-request lifetime for "backend" App Engine module. */ @RequestScope @@ -137,10 +135,6 @@ interface BackendRequestComponent { CopyDetailReportsAction copyDetailReportAction(); - CreateSyntheticHistoryEntriesAction createSyntheticHistoryEntriesAction(); - - DeleteContactsAndHostsAction deleteContactsAndHostsAction(); - DeleteExpiredDomainsAction deleteExpiredDomainsAction(); DeleteLoadTestDataAction deleteLoadTestDataAction(); diff --git a/core/src/main/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesAction.java b/core/src/main/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesAction.java deleted file mode 100644 index 2941a5e0c..000000000 --- a/core/src/main/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesAction.java +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2021 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.tools.javascrap; - -import static google.registry.model.ofy.ObjectifyService.auditedOfy; -import static google.registry.model.reporting.HistoryEntryDao.REPO_ID_FIELD_NAMES; -import static google.registry.model.reporting.HistoryEntryDao.RESOURCE_TYPES_TO_HISTORY_TYPES; -import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; -import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm; - -import com.google.appengine.tools.mapreduce.Mapper; -import com.google.common.base.CaseFormat; -import com.google.common.collect.ImmutableList; -import com.googlecode.objectify.Key; -import google.registry.config.RegistryConfig.Config; -import google.registry.mapreduce.MapreduceRunner; -import google.registry.mapreduce.inputs.EppResourceInputs; -import google.registry.model.EppResource; -import google.registry.model.domain.DomainHistory; -import google.registry.model.reporting.HistoryEntry; -import google.registry.rde.RdeStagingAction; -import google.registry.request.Action; -import google.registry.request.Response; -import google.registry.request.auth.Auth; -import google.registry.tools.server.GenerateZoneFilesAction; -import javax.inject.Inject; - -/** - * A mapreduce that creates synthetic history objects in SQL for all {@link EppResource} objects. - * - *

Certain operations, e.g. {@link RdeStagingAction} or {@link GenerateZoneFilesAction}, require - * that we are able to answer the question of "What did this EPP resource look like at a point in - * time?" In the Datastore world, we are able to answer this question using the commit logs, however - * this is no longer possible in the SQL world. Instead, we will use the history objects, e.g. - * {@link DomainHistory} to see what a particular resource looked like at that point in time, since - * history objects store a snapshot of that resource. - * - *

This command creates a synthetic history object at the current point in time for every single - * EPP resource to guarantee that later on, when examining in-the-past resources, we have some - * history object for which the EppResource field is filled. This synthetic history object contains - * basically nothing and its only purpose is to create a populated history object in SQL through - * asynchronous replication. - * - *

NB: This class operates entirely in Datastore, which may be counterintuitive at first glance. - * However, since this is meant to be run during the Datastore-primary, SQL-secondary stage of the - * migration, we want to make sure that we are using the most up-to-date version of the data. The - * resource field of the history objects will be populated during asynchronous migration, e.g. in - * {@link DomainHistory#beforeSqlSaveOnReplay}. - */ -@Action( - service = Action.Service.BACKEND, - path = "/_dr/task/createSyntheticHistoryEntries", - auth = Auth.AUTH_INTERNAL_OR_ADMIN) -public class CreateSyntheticHistoryEntriesAction implements Runnable { - - private static final String HISTORY_REASON = - "Backfill EppResource history objects during Cloud SQL migration"; - - private final MapreduceRunner mrRunner; - private final Response response; - private final String registryAdminRegistrarId; - - @Inject - CreateSyntheticHistoryEntriesAction( - MapreduceRunner mrRunner, - Response response, - @Config("registryAdminClientId") String registryAdminRegistrarId) { - this.mrRunner = mrRunner; - this.response = response; - this.registryAdminRegistrarId = registryAdminRegistrarId; - } - - /** - * The default number of shards to run the map-only mapreduce on. - * - *

This is much lower than the default of 100 because we can afford it being slow and we want - * to avoid overloading SQL. - */ - private static final int NUM_SHARDS = 10; - - @Override - public void run() { - mrRunner - .setJobName("Create a synthetic HistoryEntry for each EPP resource") - .setModuleName("backend") - .setDefaultMapShards(NUM_SHARDS) - .runMapOnly( - new CreateSyntheticHistoryEntriesMapper(registryAdminRegistrarId), - ImmutableList.of(EppResourceInputs.createKeyInput(EppResource.class))) - .sendLinkToMapreduceConsole(response); - } - - // Returns true iff any of the *History objects in SQL contain a representation of this resource - // at the point in time that the *History object was created. - private static boolean hasHistoryContainingResource(EppResource resource) { - return jpaTm() - .transact( - () -> { - // Use READ COMMITTED isolation level so that any long-living queries don't cause - // collection of predicate locks to spiral out of control (as would happen with a - // SERIALIZABLE isolation level) - // - // NB: setting the isolation level inside the transaction only works for Postgres and - // will be reverted to the default once the transaction is committed. - jpaTm() - .getEntityManager() - .createNativeQuery("SET TRANSACTION ISOLATION LEVEL READ COMMITTED") - .executeUpdate(); - // The class we're searching from is based on which parent type (e.g. Domain) we have - Class historyClass = - getHistoryClassFromParent(resource.getClass()); - // The field representing repo ID unfortunately varies by history class - String repoIdFieldName = - CaseFormat.LOWER_CAMEL.to( - CaseFormat.LOWER_UNDERSCORE, - getRepoIdFieldNameFromHistoryClass(historyClass)); - // The "history" fields in the *History objects are all prefixed with "history_". If - // any of the non-"history_" fields are non-null, that means that that row contains - // a representation of that EppResource at that point in time. We use creation_time as - // a marker since it's the simplest field and all EppResources will have it. - return (boolean) - jpaTm() - .getEntityManager() - .createNativeQuery( - String.format( - "SELECT EXISTS (SELECT 1 FROM \"%s\" WHERE %s = :repoId AND" - + " creation_time IS NOT NULL)", - historyClass.getSimpleName(), repoIdFieldName)) - .setParameter("repoId", resource.getRepoId()) - .getSingleResult(); - }); - } - - // Lifted from HistoryEntryDao - private static Class getHistoryClassFromParent( - Class parent) { - if (!RESOURCE_TYPES_TO_HISTORY_TYPES.containsKey(parent)) { - throw new IllegalArgumentException( - String.format("Unknown history type for parent %s", parent.getName())); - } - return RESOURCE_TYPES_TO_HISTORY_TYPES.get(parent); - } - - // Lifted from HistoryEntryDao - private static String getRepoIdFieldNameFromHistoryClass( - Class historyClass) { - if (!REPO_ID_FIELD_NAMES.containsKey(historyClass)) { - throw new IllegalArgumentException( - String.format("Unknown history type %s", historyClass.getName())); - } - return REPO_ID_FIELD_NAMES.get(historyClass); - } - - /** Mapper to re-save all EPP resources. */ - public static class CreateSyntheticHistoryEntriesMapper - extends Mapper, Void, Void> { - - private final String registryAdminRegistrarId; - - public CreateSyntheticHistoryEntriesMapper(String registryAdminRegistrarId) { - this.registryAdminRegistrarId = registryAdminRegistrarId; - } - - @Override - public final void map(final Key resourceKey) { - EppResource eppResource = auditedOfy().load().key(resourceKey).now(); - // Only save new history entries if the most recent history for this object in SQL does not - // have the resource at that point in time already - if (!hasHistoryContainingResource(eppResource)) { - ofyTm() - .transact( - () -> - ofyTm() - .put( - HistoryEntry.createBuilderForResource(eppResource) - .setRegistrarId(registryAdminRegistrarId) - .setBySuperuser(true) - .setRequestedByRegistrar(false) - .setModificationTime(ofyTm().getTransactionTime()) - .setReason(HISTORY_REASON) - .setType(HistoryEntry.Type.SYNTHETIC) - .build())); - } - } - } -} diff --git a/core/src/main/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesPipeline.java b/core/src/main/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesPipeline.java deleted file mode 100644 index 19319a13b..000000000 --- a/core/src/main/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesPipeline.java +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2021 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.tools.javascrap; - -import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; - -import com.google.common.collect.ImmutableList; -import dagger.Component; -import google.registry.beam.common.RegistryJpaIO; -import google.registry.beam.common.RegistryPipelineOptions; -import google.registry.config.RegistryConfig.Config; -import google.registry.config.RegistryConfig.ConfigModule; -import google.registry.model.EppResource; -import google.registry.model.UpdateAutoTimestamp; -import google.registry.model.UpdateAutoTimestamp.DisableAutoUpdateResource; -import google.registry.model.contact.ContactResource; -import google.registry.model.domain.DomainBase; -import google.registry.model.host.HostResource; -import google.registry.model.reporting.HistoryEntry; -import google.registry.persistence.PersistenceModule.TransactionIsolationLevel; -import google.registry.persistence.VKey; -import java.io.Serializable; -import javax.inject.Singleton; -import javax.persistence.Entity; -import org.apache.beam.sdk.Pipeline; -import org.apache.beam.sdk.options.PipelineOptionsFactory; -import org.apache.beam.sdk.transforms.MapElements; -import org.apache.beam.sdk.values.TypeDescriptor; - -/** - * Pipeline that creates a synthetic history entry for every {@link EppResource} in SQL at the - * current time. - * - *

The history entries in Datastore does not have the EPP resource embedded in them. Therefore - * after {@link google.registry.beam.initsql.InitSqlPipeline} runs, these fields will all be empty. - * This pipeline loads all EPP resources and for each of them creates a synthetic history entry that - * contains the resource and saves them back to SQL, so that they can be used in the RDE pipeline. - * - *

Note that this pipeline should only be run in a test environment right after the init SQL - * pipeline finishes, and no EPP update is being made to the system, otherwise there is no garuantee - * that the latest history entry for a given EPP resource does not already have the resource - * embedded within it. - * - *

To run the pipeline: - * - *

- * $ ./nom_build :core:cSHE --args="--region=us-central1 - * --runner=DataflowRunner - * --registryEnvironment=CRASH - * --project={project-id} - * --workerMachineType=n2-standard-4" - * - * - * @see google.registry.tools.javascrap.CreateSyntheticHistoryEntriesAction - */ -public class CreateSyntheticHistoryEntriesPipeline implements Serializable { - - private static final ImmutableList> EPP_RESOURCE_CLASSES = - ImmutableList.of(DomainBase.class, ContactResource.class, HostResource.class); - - private static final String HISTORY_REASON = - "Backfill EppResource history objects after initial backup to SQL"; - - static void setup(Pipeline pipeline, String registryAdminRegistrarId) { - for (Class clazz : EPP_RESOURCE_CLASSES) { - pipeline - .apply( - String.format("Read all %s", clazz.getSimpleName()), - RegistryJpaIO.read( - "SELECT id FROM %entity%" - .replace("%entity%", clazz.getAnnotation(Entity.class).name()), - String.class, - repoId -> VKey.createSql(clazz, repoId))) - .apply( - String.format("Save a synthetic HistoryEntry for each %s", clazz), - MapElements.into(TypeDescriptor.of(Void.class)) - .via( - (VKey key) -> { - jpaTm() - .transact( - () -> { - EppResource eppResource = jpaTm().loadByKey(key); - try (DisableAutoUpdateResource disable = - UpdateAutoTimestamp.disableAutoUpdate()) { - jpaTm() - .put( - HistoryEntry.createBuilderForResource(eppResource) - .setRegistrarId(registryAdminRegistrarId) - .setBySuperuser(true) - .setRequestedByRegistrar(false) - .setModificationTime(jpaTm().getTransactionTime()) - .setReason(HISTORY_REASON) - .setType(HistoryEntry.Type.SYNTHETIC) - .build()); - } - }); - return null; - })); - } - } - - public static void main(String[] args) { - RegistryPipelineOptions options = - PipelineOptionsFactory.fromArgs(args).withValidation().as(RegistryPipelineOptions.class); - RegistryPipelineOptions.validateRegistryPipelineOptions(options); - options.setIsolationOverride(TransactionIsolationLevel.TRANSACTION_READ_COMMITTED); - String registryAdminRegistrarId = - DaggerCreateSyntheticHistoryEntriesPipeline_ConfigComponent.create() - .getRegistryAdminRegistrarId(); - - Pipeline pipeline = Pipeline.create(options); - setup(pipeline, registryAdminRegistrarId); - pipeline.run(); - } - - @Singleton - @Component(modules = ConfigModule.class) - interface ConfigComponent { - - @Config("registryAdminClientId") - String getRegistryAdminRegistrarId(); - } -} diff --git a/core/src/test/java/google/registry/batch/DeleteContactsAndHostsActionTest.java b/core/src/test/java/google/registry/batch/DeleteContactsAndHostsActionTest.java deleted file mode 100644 index f2170b83b..000000000 --- a/core/src/test/java/google/registry/batch/DeleteContactsAndHostsActionTest.java +++ /dev/null @@ -1,950 +0,0 @@ -// Copyright 2017 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.batch; - -import static com.google.appengine.api.taskqueue.QueueFactory.getQueue; -import static com.google.common.collect.MoreCollectors.onlyElement; -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth8.assertThat; -import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY; -import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_DELETE; -import static google.registry.batch.AsyncTaskMetrics.OperationResult.STALE; -import static google.registry.model.EppResourceUtils.loadByForeignKey; -import static google.registry.model.eppcommon.StatusValue.PENDING_DELETE; -import static google.registry.model.ofy.ObjectifyService.auditedOfy; -import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_DELETE; -import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_DELETE_FAILURE; -import static google.registry.model.reporting.HistoryEntry.Type.CONTACT_TRANSFER_REQUEST; -import static google.registry.model.reporting.HistoryEntry.Type.HOST_DELETE; -import static google.registry.model.reporting.HistoryEntry.Type.HOST_DELETE_FAILURE; -import static google.registry.testing.ContactResourceSubject.assertAboutContacts; -import static google.registry.testing.DatabaseHelper.assertNoBillingEvents; -import static google.registry.testing.DatabaseHelper.createTld; -import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType; -import static google.registry.testing.DatabaseHelper.getOnlyPollMessageForHistoryEntry; -import static google.registry.testing.DatabaseHelper.getPollMessages; -import static google.registry.testing.DatabaseHelper.newContactResource; -import static google.registry.testing.DatabaseHelper.newDomainBase; -import static google.registry.testing.DatabaseHelper.newHostResource; -import static google.registry.testing.DatabaseHelper.persistActiveContact; -import static google.registry.testing.DatabaseHelper.persistActiveHost; -import static google.registry.testing.DatabaseHelper.persistContactWithPendingTransfer; -import static google.registry.testing.DatabaseHelper.persistDeletedContact; -import static google.registry.testing.DatabaseHelper.persistDeletedHost; -import static google.registry.testing.DatabaseHelper.persistResource; -import static google.registry.testing.HostResourceSubject.assertAboutHosts; -import static google.registry.testing.TaskQueueHelper.assertDnsTasksEnqueued; -import static google.registry.testing.TaskQueueHelper.assertNoTasksEnqueued; -import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued; -import static google.registry.util.DateTimeUtils.END_OF_TIME; -import static org.joda.time.DateTimeZone.UTC; -import static org.joda.time.Duration.millis; -import static org.joda.time.Duration.standardDays; -import static org.joda.time.Duration.standardHours; -import static org.joda.time.Duration.standardSeconds; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import com.google.appengine.api.taskqueue.TaskOptions; -import com.google.appengine.api.taskqueue.TaskOptions.Method; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import google.registry.batch.AsyncTaskMetrics.OperationResult; -import google.registry.batch.AsyncTaskMetrics.OperationType; -import google.registry.batch.DeleteContactsAndHostsAction.DeleteEppResourceReducer; -import google.registry.model.EppResource; -import google.registry.model.contact.ContactAddress; -import google.registry.model.contact.ContactPhoneNumber; -import google.registry.model.contact.ContactResource; -import google.registry.model.contact.PostalInfo; -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.ofy.Ofy; -import google.registry.model.poll.PendingActionNotificationResponse; -import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse; -import google.registry.model.poll.PendingActionNotificationResponse.HostPendingActionNotificationResponse; -import google.registry.model.poll.PollMessage; -import google.registry.model.poll.PollMessage.OneTime; -import google.registry.model.reporting.HistoryEntry; -import google.registry.model.server.Lock; -import google.registry.model.tld.Registry; -import google.registry.model.transfer.ContactTransferData; -import google.registry.model.transfer.TransferData; -import google.registry.model.transfer.TransferResponse; -import google.registry.model.transfer.TransferStatus; -import google.registry.testing.CloudTasksHelper; -import google.registry.testing.FakeClock; -import google.registry.testing.FakeResponse; -import google.registry.testing.FakeSleeper; -import google.registry.testing.InjectExtension; -import google.registry.testing.TaskQueueHelper.TaskMatcher; -import google.registry.testing.mapreduce.MapreduceTestCase; -import google.registry.util.RequestStatusChecker; -import google.registry.util.Retrier; -import google.registry.util.Sleeper; -import google.registry.util.SystemSleeper; -import java.util.Optional; -import org.joda.time.DateTime; -import org.joda.time.Duration; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockito.Mock; - -/** Unit tests for {@link DeleteContactsAndHostsAction}. */ -public class DeleteContactsAndHostsActionTest - extends MapreduceTestCase { - - @RegisterExtension public final InjectExtension inject = new InjectExtension(); - - private AsyncTaskEnqueuer enqueuer; - private final FakeClock clock = new FakeClock(DateTime.parse("2015-01-15T11:22:33Z")); - private final FakeResponse fakeResponse = new FakeResponse(); - @Mock private RequestStatusChecker requestStatusChecker; - - private void runMapreduce() throws Exception { - clock.advanceBy(standardSeconds(5)); - // Apologies for the hard sleeps. Without them, the tests can be flaky because the tasks aren't - // quite fully enqueued by the time the tests attempt to lease from the queue. - Sleeper sleeper = new SystemSleeper(); - sleeper.sleep(millis(50)); - action.run(); - sleeper.sleep(millis(50)); - executeTasksUntilEmpty("mapreduce", clock); - sleeper.sleep(millis(50)); - clock.advanceBy(standardSeconds(5)); - auditedOfy().clearSessionCache(); - } - - /** Kicks off, but does not run, the mapreduce tasks. Useful for testing validation/setup. */ - private void enqueueMapreduceOnly() { - clock.advanceBy(standardSeconds(5)); - action.run(); - clock.advanceBy(standardSeconds(5)); - auditedOfy().clearSessionCache(); - } - - @BeforeEach - void beforeEach() { - inject.setStaticField(Ofy.class, "clock", clock); - enqueuer = - AsyncTaskEnqueuerTest.createForTesting( - new CloudTasksHelper(clock).getTestCloudTasksUtils(), clock, Duration.ZERO); - AsyncTaskMetrics asyncTaskMetricsMock = mock(AsyncTaskMetrics.class); - action = new DeleteContactsAndHostsAction(); - action.asyncTaskMetrics = asyncTaskMetricsMock; - inject.setStaticField(DeleteEppResourceReducer.class, "asyncTaskMetrics", asyncTaskMetricsMock); - action.clock = clock; - action.mrRunner = makeDefaultRunner(); - action.requestStatusChecker = requestStatusChecker; - action.response = fakeResponse; - action.retrier = new Retrier(new FakeSleeper(clock), 1); - action.queue = getQueue(QUEUE_ASYNC_DELETE); - when(requestStatusChecker.getLogId()).thenReturn("requestId"); - when(requestStatusChecker.isRunning(anyString())) - .thenThrow(new AssertionError("Should not be called")); - - createTld("tld"); - clock.advanceOneMilli(); - } - - @Test - void testSuccess_contact_referencedByActiveDomain_doesNotGetDeleted() throws Exception { - ContactResource contact = persistContactPendingDelete("blah8221"); - persistResource(newDomainBase("example.tld", contact)); - DateTime timeEnqueued = clock.nowUtc(); - enqueuer.enqueueAsyncDelete( - contact, - timeEnqueued, - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - runMapreduce(); - ContactResource contactUpdated = - loadByForeignKey(ContactResource.class, "blah8221", clock.nowUtc()).get(); - assertAboutContacts() - .that(contactUpdated) - .doesNotHaveStatusValue(PENDING_DELETE) - .and() - .hasDeletionTime(END_OF_TIME); - DomainBase domainReloaded = - loadByForeignKey(DomainBase.class, "example.tld", clock.nowUtc()).get(); - // We have to check for the objectify key here, specifically, as the SQL side of the key does - // not get persisted. - assertThat(domainReloaded.getReferencedContacts()).contains(contactUpdated.createVKey()); - HistoryEntry historyEntry = - getOnlyHistoryEntryOfType(contactUpdated, HistoryEntry.Type.CONTACT_DELETE_FAILURE); - assertPollMessageFor( - historyEntry, - "TheRegistrar", - "Can't delete contact blah8221 because it is referenced by a domain.", - false, - contact, - Optional.of("fakeClientTrid")); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - verify(action.asyncTaskMetrics).recordContactHostDeletionBatchSize(1L); - verify(action.asyncTaskMetrics) - .recordAsyncFlowResult(OperationType.CONTACT_DELETE, OperationResult.FAILURE, timeEnqueued); - verifyNoMoreInteractions(action.asyncTaskMetrics); - } - - @Test - void testSuccess_contact_notReferenced_getsDeleted_andPiiWipedOut() throws Exception { - runSuccessfulContactDeletionTest(Optional.of("fakeClientTrid")); - } - - @Test - void testSuccess_contact_andNoClientTrid_deletesSuccessfully() throws Exception { - runSuccessfulContactDeletionTest(Optional.empty()); - } - - @Test - void test_cannotAcquireLock() { - // Make lock acquisition fail. - acquireLock(); - enqueueMapreduceOnly(); - assertThat(fakeResponse.getPayload()).isEqualTo("Can't acquire lock; aborting."); - } - - @Test - void test_mapreduceHasWorkToDo_lockIsAcquired() { - ContactResource contact = persistContactPendingDelete("blah8221"); - persistResource(newDomainBase("example.tld", contact)); - DateTime timeEnqueued = clock.nowUtc(); - enqueuer.enqueueAsyncDelete( - contact, - timeEnqueued, - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - enqueueMapreduceOnly(); - assertThat(acquireLock()).isEmpty(); - } - - @Test - void test_noTasksToLease_releasesLockImmediately() { - enqueueMapreduceOnly(); - // If the Lock was correctly released, then we can acquire it now. - assertThat(acquireLock()).isPresent(); - } - - private void runSuccessfulContactDeletionTest(Optional clientTrid) throws Exception { - ContactResource contact = persistContactWithPii("jim919"); - DateTime timeEnqueued = clock.nowUtc(); - enqueuer.enqueueAsyncDelete( - contact, - timeEnqueued, - "TheRegistrar", - Trid.create(clientTrid.orElse(null), "fakeServerTrid"), - false); - runMapreduce(); - assertThat(loadByForeignKey(ContactResource.class, "jim919", clock.nowUtc())).isEmpty(); - ContactResource contactAfterDeletion = auditedOfy().load().entity(contact).now(); - assertAboutContacts() - .that(contactAfterDeletion) - .isNotActiveAt(clock.nowUtc()) - // Note that there will be another history entry of CONTACT_PENDING_DELETE, but this is - // added by the flow and not the mapreduce itself. - .and() - .hasOnlyOneHistoryEntryWhich() - .hasType(CONTACT_DELETE); - assertAboutContacts() - .that(contactAfterDeletion) - .hasNullLocalizedPostalInfo() - .and() - .hasNullInternationalizedPostalInfo() - .and() - .hasNullEmailAddress() - .and() - .hasNullVoiceNumber() - .and() - .hasNullFaxNumber(); - HistoryEntry historyEntry = getOnlyHistoryEntryOfType(contactAfterDeletion, CONTACT_DELETE); - assertPollMessageFor( - historyEntry, "TheRegistrar", "Deleted contact jim919.", true, contact, clientTrid); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - verify(action.asyncTaskMetrics).recordContactHostDeletionBatchSize(1L); - verify(action.asyncTaskMetrics) - .recordAsyncFlowResult(OperationType.CONTACT_DELETE, OperationResult.SUCCESS, timeEnqueued); - verifyNoMoreInteractions(action.asyncTaskMetrics); - - } - - @Test - void testSuccess_contactWithoutPendingTransfer_isDeletedAndHasNoTransferData() throws Exception { - ContactResource contact = persistContactPendingDelete("blah8221"); - enqueuer.enqueueAsyncDelete( - contact, - clock.nowUtc(), - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - runMapreduce(); - ContactResource contactAfterDeletion = auditedOfy().load().entity(contact).now(); - assertThat(contactAfterDeletion.getTransferData()).isEqualTo(ContactTransferData.EMPTY); - } - - @Test - void testSuccess_contactWithPendingTransfer_getsDeleted() throws Exception { - DateTime transferRequestTime = clock.nowUtc().minusDays(3); - ContactResource contact = - persistContactWithPendingTransfer( - newContactResource("sh8013").asBuilder().addStatusValue(PENDING_DELETE).build(), - transferRequestTime, - transferRequestTime.plus(Registry.DEFAULT_TRANSFER_GRACE_PERIOD), - clock.nowUtc()); - enqueuer.enqueueAsyncDelete( - contact, - clock.nowUtc(), - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - runMapreduce(); - // Check that the contact is deleted as of now. - assertThat(loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc())).isEmpty(); - // Check that it's still there (it wasn't deleted yesterday) and that it has history. - ContactResource softDeletedContact = - loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc().minusDays(1)).get(); - assertAboutContacts() - .that(softDeletedContact) - .hasOneHistoryEntryEachOfTypes(CONTACT_TRANSFER_REQUEST, CONTACT_DELETE); - // Check that the transfer data reflects the cancelled transfer as we expect. - TransferData oldTransferData = contact.getTransferData(); - assertThat(softDeletedContact.getTransferData()) - .isEqualTo( - oldTransferData.copyConstantFieldsToBuilder() - .setTransferStatus(TransferStatus.SERVER_CANCELLED) - .setPendingTransferExpirationTime(softDeletedContact.getDeletionTime()) - .build()); - assertNoBillingEvents(); - PollMessage deletePollMessage = - Iterables.getOnlyElement(getPollMessages("TheRegistrar", clock.nowUtc().plusMonths(1))); - assertThat(deletePollMessage.getMsg()).isEqualTo("Deleted contact sh8013."); - // The poll message in the future to the gaining registrar should be gone too, but there - // should be one at the current time to the gaining registrar. - PollMessage gainingPollMessage = - Iterables.getOnlyElement(getPollMessages("NewRegistrar", clock.nowUtc())); - assertThat(gainingPollMessage.getEventTime()).isLessThan(clock.nowUtc()); - assertThat( - gainingPollMessage - .getResponseData() - .stream() - .filter(TransferResponse.class::isInstance) - .map(TransferResponse.class::cast) - .collect(onlyElement()) - .getTransferStatus()) - .isEqualTo(TransferStatus.SERVER_CANCELLED); - PendingActionNotificationResponse panData = - gainingPollMessage - .getResponseData() - .stream() - .filter(PendingActionNotificationResponse.class::isInstance) - .map(PendingActionNotificationResponse.class::cast) - .collect(onlyElement()); - assertThat(panData.getTrid()) - .isEqualTo(Trid.create("transferClient-trid", "transferServer-trid")); - assertThat(panData.getActionResult()).isFalse(); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - } - - @Test - void testSuccess_contact_referencedByDeletedDomain_getsDeleted() throws Exception { - ContactResource contactUsed = persistContactPendingDelete("blah1234"); - persistResource( - newDomainBase("example.tld", contactUsed) - .asBuilder() - .setDeletionTime(clock.nowUtc().minusDays(3)) - .build()); - enqueuer.enqueueAsyncDelete( - contactUsed, - clock.nowUtc(), - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - runMapreduce(); - assertThat(loadByForeignKey(ContactResource.class, "blah1234", clock.nowUtc())).isEmpty(); - ContactResource contactBeforeDeletion = - loadByForeignKey(ContactResource.class, "blah1234", clock.nowUtc().minusDays(1)).get(); - assertAboutContacts() - .that(contactBeforeDeletion) - .isNotActiveAt(clock.nowUtc()) - .and() - .hasExactlyStatusValues(StatusValue.OK) - // Note that there will be another history entry of CONTACT_PENDING_DELETE, but this is - // added by the flow and not the mapreduce itself. - .and() - .hasOnlyOneHistoryEntryWhich() - .hasType(CONTACT_DELETE); - HistoryEntry historyEntry = getOnlyHistoryEntryOfType(contactBeforeDeletion, CONTACT_DELETE); - assertPollMessageFor( - historyEntry, - "TheRegistrar", - "Deleted contact blah1234.", - true, - contactUsed, - Optional.of("fakeClientTrid")); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - } - - @Test - void testSuccess_contact_notRequestedByOwner_doesNotGetDeleted() throws Exception { - ContactResource contact = persistContactPendingDelete("jane0991"); - enqueuer.enqueueAsyncDelete( - contact, - clock.nowUtc(), - "OtherRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - runMapreduce(); - ContactResource contactAfter = - loadByForeignKey(ContactResource.class, "jane0991", clock.nowUtc()).get(); - assertAboutContacts() - .that(contactAfter) - .doesNotHaveStatusValue(PENDING_DELETE) - .and() - .hasDeletionTime(END_OF_TIME); - HistoryEntry historyEntry = getOnlyHistoryEntryOfType(contactAfter, CONTACT_DELETE_FAILURE); - assertPollMessageFor( - historyEntry, - "OtherRegistrar", - "Can't delete contact jane0991 because it was transferred prior to deletion.", - false, - contact, - Optional.of("fakeClientTrid")); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - } - - @Test - void testSuccess_contact_notRequestedByOwner_isSuperuser_getsDeleted() throws Exception { - ContactResource contact = persistContactWithPii("nate007"); - enqueuer.enqueueAsyncDelete( - contact, - clock.nowUtc(), - "OtherRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - true); - runMapreduce(); - assertThat(loadByForeignKey(ContactResource.class, "nate007", clock.nowUtc())).isEmpty(); - ContactResource contactAfterDeletion = auditedOfy().load().entity(contact).now(); - assertAboutContacts() - .that(contactAfterDeletion) - .isNotActiveAt(clock.nowUtc()) - // Note that there will be another history entry of CONTACT_PENDING_DELETE, but this is - // added by the flow and not the mapreduce itself. - .and() - .hasOnlyOneHistoryEntryWhich() - .hasType(CONTACT_DELETE); - assertAboutContacts() - .that(contactAfterDeletion) - .hasNullLocalizedPostalInfo() - .and() - .hasNullInternationalizedPostalInfo() - .and() - .hasNullEmailAddress() - .and() - .hasNullVoiceNumber() - .and() - .hasNullFaxNumber(); - HistoryEntry historyEntry = getOnlyHistoryEntryOfType(contactAfterDeletion, CONTACT_DELETE); - assertPollMessageFor( - historyEntry, - "OtherRegistrar", - "Deleted contact nate007.", - true, - contact, - Optional.of("fakeClientTrid")); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - } - - @Test - void testSuccess_targetResourcesDontExist_areDelayedForADay() { - ContactResource contactNotSaved = newContactResource("somecontact"); - HostResource hostNotSaved = newHostResource("a11.blah.foo"); - DateTime timeBeforeRun = clock.nowUtc(); - enqueuer.enqueueAsyncDelete( - contactNotSaved, - timeBeforeRun, - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - enqueuer.enqueueAsyncDelete( - hostNotSaved, - timeBeforeRun, - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - enqueueMapreduceOnly(); - assertTasksEnqueued( - QUEUE_ASYNC_DELETE, - new TaskMatcher() - .etaDelta(standardHours(23), standardHours(25)) - .param(PARAM_RESOURCE_KEY, contactNotSaved.createVKey().stringify()) - .param("requestingClientId", "TheRegistrar") - .param("clientTransactionId", "fakeClientTrid") - .param("serverTransactionId", "fakeServerTrid") - .param("isSuperuser", "false") - .param("requestedTime", timeBeforeRun.toString()), - new TaskMatcher() - .etaDelta(standardHours(23), standardHours(25)) - .param(PARAM_RESOURCE_KEY, hostNotSaved.createVKey().stringify()) - .param("requestingClientId", "TheRegistrar") - .param("clientTransactionId", "fakeClientTrid") - .param("serverTransactionId", "fakeServerTrid") - .param("isSuperuser", "false") - .param("requestedTime", timeBeforeRun.toString())); - assertThat(acquireLock()).isPresent(); - } - - @Test - void testSuccess_unparseableTasks_areDelayedForADay() { - TaskOptions task = - TaskOptions.Builder.withMethod(Method.PULL).param("gobbledygook", "kljhadfgsd9f7gsdfh"); - getQueue(QUEUE_ASYNC_DELETE).add(task); - enqueueMapreduceOnly(); - assertTasksEnqueued( - QUEUE_ASYNC_DELETE, - new TaskMatcher() - .payload("gobbledygook=kljhadfgsd9f7gsdfh") - .etaDelta(standardHours(23), standardHours(25))); - verify(action.asyncTaskMetrics).recordContactHostDeletionBatchSize(1L); - verifyNoMoreInteractions(action.asyncTaskMetrics); - assertThat(acquireLock()).isPresent(); - } - - @Test - void testSuccess_resourcesNotInPendingDelete_areSkipped() { - ContactResource contact = persistActiveContact("blah2222"); - HostResource host = persistActiveHost("rustles.your.jimmies"); - DateTime timeEnqueued = clock.nowUtc(); - enqueuer.enqueueAsyncDelete( - contact, - timeEnqueued, - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - enqueuer.enqueueAsyncDelete( - host, - timeEnqueued, - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - enqueueMapreduceOnly(); - assertThat(loadByForeignKey(ContactResource.class, "blah2222", clock.nowUtc())) - .hasValue(contact); - assertThat(loadByForeignKey(HostResource.class, "rustles.your.jimmies", clock.nowUtc())) - .hasValue(host); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - verify(action.asyncTaskMetrics).recordContactHostDeletionBatchSize(2L); - verify(action.asyncTaskMetrics) - .recordAsyncFlowResult(OperationType.CONTACT_DELETE, STALE, timeEnqueued); - verify(action.asyncTaskMetrics) - .recordAsyncFlowResult(OperationType.HOST_DELETE, STALE, timeEnqueued); - verifyNoMoreInteractions(action.asyncTaskMetrics); - assertThat(acquireLock()).isPresent(); - } - - @Test - void testSuccess_alreadyDeletedResources_areSkipped() { - ContactResource contactDeleted = persistDeletedContact("blah1236", clock.nowUtc().minusDays(2)); - HostResource hostDeleted = persistDeletedHost("a.lim.lop", clock.nowUtc().minusDays(3)); - enqueuer.enqueueAsyncDelete( - contactDeleted, - clock.nowUtc(), - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - enqueuer.enqueueAsyncDelete( - hostDeleted, - clock.nowUtc(), - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - enqueueMapreduceOnly(); - assertThat(auditedOfy().load().entity(contactDeleted).now()).isEqualTo(contactDeleted); - assertThat(auditedOfy().load().entity(hostDeleted).now()).isEqualTo(hostDeleted); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - assertThat(acquireLock()).isPresent(); - } - - @Test - void testSuccess_host_referencedByActiveDomain_doesNotGetDeleted() throws Exception { - HostResource host = persistHostPendingDelete("ns1.example.tld"); - persistUsedDomain("example.tld", persistActiveContact("abc456"), host); - DateTime timeEnqueued = clock.nowUtc(); - enqueuer.enqueueAsyncDelete( - host, - timeEnqueued, - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - runMapreduce(); - HostResource hostAfter = - loadByForeignKey(HostResource.class, "ns1.example.tld", clock.nowUtc()).get(); - assertAboutHosts() - .that(hostAfter) - .doesNotHaveStatusValue(PENDING_DELETE) - .and() - .hasDeletionTime(END_OF_TIME); - DomainBase domain = - loadByForeignKey(DomainBase.class, "example.tld", clock.nowUtc()).get(); - assertThat(domain.getNameservers()).contains(hostAfter.createVKey()); - HistoryEntry historyEntry = getOnlyHistoryEntryOfType(hostAfter, HOST_DELETE_FAILURE); - assertPollMessageFor( - historyEntry, - "TheRegistrar", - "Can't delete host ns1.example.tld because it is referenced by a domain.", - false, - host, - Optional.of("fakeClientTrid")); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - verify(action.asyncTaskMetrics).recordContactHostDeletionBatchSize(1L); - verify(action.asyncTaskMetrics) - .recordAsyncFlowResult(OperationType.HOST_DELETE, OperationResult.FAILURE, timeEnqueued); - verifyNoMoreInteractions(action.asyncTaskMetrics); - } - - @Test - void testSuccess_host_notReferenced_getsDeleted() throws Exception { - runSuccessfulHostDeletionTest(Optional.of("fakeClientTrid")); - } - - @Test - void testSuccess_host_andNoClientTrid_deletesSuccessfully() throws Exception { - runSuccessfulHostDeletionTest(Optional.empty()); - } - - private void runSuccessfulHostDeletionTest(Optional clientTrid) throws Exception { - HostResource host = persistHostPendingDelete("ns2.example.tld"); - DateTime timeEnqueued = clock.nowUtc(); - enqueuer.enqueueAsyncDelete( - host, - timeEnqueued, - "TheRegistrar", - Trid.create(clientTrid.orElse(null), "fakeServerTrid"), - false); - runMapreduce(); - assertThat(loadByForeignKey(HostResource.class, "ns2.example.tld", clock.nowUtc())).isEmpty(); - HostResource hostBeforeDeletion = - loadByForeignKey(HostResource.class, "ns2.example.tld", clock.nowUtc().minusDays(1)).get(); - assertAboutHosts() - .that(hostBeforeDeletion) - .isNotActiveAt(clock.nowUtc()) - .and() - .hasExactlyStatusValues(StatusValue.OK) - // Note that there will be another history entry of HOST_PENDING_DELETE, but this is - // added by the flow and not the mapreduce itself. - .and() - .hasOnlyOneHistoryEntryWhich() - .hasType(HOST_DELETE); - HistoryEntry historyEntry = getOnlyHistoryEntryOfType(hostBeforeDeletion, HOST_DELETE); - assertPollMessageFor( - historyEntry, - "TheRegistrar", - "Deleted host ns2.example.tld.", - true, - host, - clientTrid); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - verify(action.asyncTaskMetrics).recordContactHostDeletionBatchSize(1L); - verify(action.asyncTaskMetrics) - .recordAsyncFlowResult(OperationType.HOST_DELETE, OperationResult.SUCCESS, timeEnqueued); - verifyNoMoreInteractions(action.asyncTaskMetrics); - } - - @Test - void testSuccess_host_referencedByDeletedDomain_getsDeleted() throws Exception { - HostResource host = persistHostPendingDelete("ns1.example.tld"); - persistResource( - newDomainBase("example.tld") - .asBuilder() - .setNameservers(ImmutableSet.of(host.createVKey())) - .setDeletionTime(clock.nowUtc().minusDays(5)) - .build()); - enqueuer.enqueueAsyncDelete( - host, - clock.nowUtc(), - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - runMapreduce(); - assertThat(loadByForeignKey(HostResource.class, "ns1.example.tld", clock.nowUtc())).isEmpty(); - HostResource hostBeforeDeletion = - loadByForeignKey(HostResource.class, "ns1.example.tld", clock.nowUtc().minusDays(1)).get(); - assertAboutHosts() - .that(hostBeforeDeletion) - .isNotActiveAt(clock.nowUtc()) - .and() - .hasExactlyStatusValues(StatusValue.OK) - // Note that there will be another history entry of HOST_PENDING_DELETE, but this is - // added by the flow and not the mapreduce itself. - .and() - .hasOnlyOneHistoryEntryWhich() - .hasType(HOST_DELETE); - HistoryEntry historyEntry = getOnlyHistoryEntryOfType(hostBeforeDeletion, HOST_DELETE); - assertPollMessageFor( - historyEntry, - "TheRegistrar", - "Deleted host ns1.example.tld.", - true, - host, - Optional.of("fakeClientTrid")); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - } - - @Test - void testSuccess_subordinateHost_getsDeleted() throws Exception { - DomainBase domain = - persistResource( - newDomainBase("example.tld") - .asBuilder() - .setSubordinateHosts(ImmutableSet.of("ns2.example.tld")) - .build()); - HostResource host = - persistResource( - persistHostPendingDelete("ns2.example.tld") - .asBuilder() - .setSuperordinateDomain(domain.createVKey()) - .build()); - enqueuer.enqueueAsyncDelete( - host, - clock.nowUtc(), - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - runMapreduce(); - // Check that the host is deleted as of now. - assertThat(loadByForeignKey(HostResource.class, "ns2.example.tld", clock.nowUtc())).isEmpty(); - assertNoBillingEvents(); - assertThat( - loadByForeignKey(DomainBase.class, "example.tld", clock.nowUtc()) - .get() - .getSubordinateHosts()) - .isEmpty(); - assertDnsTasksEnqueued("ns2.example.tld"); - HostResource hostBeforeDeletion = - loadByForeignKey(HostResource.class, "ns2.example.tld", clock.nowUtc().minusDays(1)).get(); - assertAboutHosts() - .that(hostBeforeDeletion) - .isNotActiveAt(clock.nowUtc()) - .and() - .hasExactlyStatusValues(StatusValue.OK) - .and() - .hasOnlyOneHistoryEntryWhich() - .hasType(HOST_DELETE); - HistoryEntry historyEntry = getOnlyHistoryEntryOfType(hostBeforeDeletion, HOST_DELETE); - assertPollMessageFor( - historyEntry, - "TheRegistrar", - "Deleted host ns2.example.tld.", - true, - host, - Optional.of("fakeClientTrid")); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - } - - @Test - void testSuccess_host_notRequestedByOwner_doesNotGetDeleted() throws Exception { - HostResource host = persistHostPendingDelete("ns2.example.tld"); - enqueuer.enqueueAsyncDelete( - host, - clock.nowUtc(), - "OtherRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - runMapreduce(); - HostResource hostAfter = - loadByForeignKey(HostResource.class, "ns2.example.tld", clock.nowUtc()).get(); - assertAboutHosts() - .that(hostAfter) - .doesNotHaveStatusValue(PENDING_DELETE) - .and() - .hasDeletionTime(END_OF_TIME); - HistoryEntry historyEntry = getOnlyHistoryEntryOfType(host, HOST_DELETE_FAILURE); - assertPollMessageFor( - historyEntry, - "OtherRegistrar", - "Can't delete host ns2.example.tld because it was transferred prior to deletion.", - false, - host, - Optional.of("fakeClientTrid")); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - } - - @Test - void testSuccess_host_notRequestedByOwner_isSuperuser_getsDeleted() throws Exception { - HostResource host = persistHostPendingDelete("ns66.example.tld"); - enqueuer.enqueueAsyncDelete( - host, - clock.nowUtc(), - "OtherRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - true); - runMapreduce(); - assertThat(loadByForeignKey(HostResource.class, "ns66.example.tld", clock.nowUtc())).isEmpty(); - HostResource hostBeforeDeletion = - loadByForeignKey(HostResource.class, "ns66.example.tld", clock.nowUtc().minusDays(1)).get(); - assertAboutHosts() - .that(hostBeforeDeletion) - .isNotActiveAt(clock.nowUtc()) - .and() - .hasExactlyStatusValues(StatusValue.OK) - // Note that there will be another history entry of HOST_PENDING_DELETE, but this is - // added by the flow and not the mapreduce itself. - .and() - .hasOnlyOneHistoryEntryWhich() - .hasType(HOST_DELETE); - HistoryEntry historyEntry = getOnlyHistoryEntryOfType(hostBeforeDeletion, HOST_DELETE); - assertPollMessageFor( - historyEntry, - "OtherRegistrar", - "Deleted host ns66.example.tld.", - true, - host, - Optional.of("fakeClientTrid")); - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - } - - @Test - void testSuccess_deleteABunchOfContactsAndHosts_butNotSome() throws Exception { - ContactResource c1 = persistContactPendingDelete("nsaid54"); - ContactResource c2 = persistContactPendingDelete("nsaid55"); - ContactResource c3 = persistContactPendingDelete("nsaid57"); - HostResource h1 = persistHostPendingDelete("nn5.example.tld"); - HostResource h2 = persistHostPendingDelete("no.foos.ball"); - HostResource h3 = persistHostPendingDelete("slime.wars.fun"); - ContactResource c4 = persistContactPendingDelete("iaminuse6"); - HostResource h4 = persistHostPendingDelete("used.host.com"); - persistUsedDomain("usescontactandhost.tld", c4, h4); - for (EppResource resource : ImmutableList.of(c1, c2, c3, c4, h1, h2, h3, h4)) { - enqueuer.enqueueAsyncDelete( - resource, - clock.nowUtc(), - "TheRegistrar", - Trid.create("fakeClientTrid", "fakeServerTrid"), - false); - } - runMapreduce(); - for (EppResource resource : ImmutableList.of(c1, c2, c3, h1, h2, h3)) { - EppResource loaded = auditedOfy().load().entity(resource).now(); - assertThat(loaded.getDeletionTime()).isLessThan(DateTime.now(UTC)); - assertThat(loaded.getStatusValues()).doesNotContain(PENDING_DELETE); - } - for (EppResource resource : ImmutableList.of(c4, h4)) { - EppResource loaded = auditedOfy().load().entity(resource).now(); - assertThat(loaded.getDeletionTime()).isEqualTo(END_OF_TIME); - assertThat(loaded.getStatusValues()).doesNotContain(PENDING_DELETE); - } - assertNoTasksEnqueued(QUEUE_ASYNC_DELETE); - } - - private static ContactResource persistContactWithPii(String contactId) { - return persistResource( - newContactResource(contactId) - .asBuilder() - .setLocalizedPostalInfo( - new PostalInfo.Builder() - .setType(PostalInfo.Type.LOCALIZED) - .setAddress( - new ContactAddress.Builder() - .setStreet(ImmutableList.of("123 Grand Ave")) - .build()) - .build()) - .setInternationalizedPostalInfo( - new PostalInfo.Builder() - .setType(PostalInfo.Type.INTERNATIONALIZED) - .setAddress( - new ContactAddress.Builder() - .setStreet(ImmutableList.of("123 Avenida Grande")) - .build()) - .build()) - .setEmailAddress("bob@bob.com") - .setVoiceNumber(new ContactPhoneNumber.Builder().setPhoneNumber("555-1212").build()) - .setFaxNumber(new ContactPhoneNumber.Builder().setPhoneNumber("555-1212").build()) - .addStatusValue(PENDING_DELETE) - .build()); - } - - /** - * Helper method to check that one poll message exists with a given history entry, resource, - * client id, and message. Also checks that the only resulting async response matches the resource - * type, and has the appropriate actionResult, nameOrId, and Trid. - */ - private static void assertPollMessageFor( - HistoryEntry historyEntry, - String registrarId, - String msg, - boolean expectedActionResult, - EppResource resource, - Optional clientTrid) { - PollMessage.OneTime pollMessage = (OneTime) getOnlyPollMessageForHistoryEntry(historyEntry); - assertThat(pollMessage.getMsg()).isEqualTo(msg); - assertThat(pollMessage.getRegistrarId()).isEqualTo(registrarId); - - ImmutableList pollResponses = pollMessage.getResponseData(); - assertThat(pollResponses).hasSize(1); - ResponseData responseData = pollMessage.getResponseData().get(0); - - String expectedResourceName; - if (resource instanceof HostResource) { - assertThat(responseData).isInstanceOf(HostPendingActionNotificationResponse.class); - expectedResourceName = ((HostResource) resource).getHostName(); - } else { - assertThat(responseData).isInstanceOf(ContactPendingActionNotificationResponse.class); - expectedResourceName = ((ContactResource) resource).getContactId(); - } - PendingActionNotificationResponse pendingResponse = - (PendingActionNotificationResponse) responseData; - assertThat(pendingResponse.getActionResult()).isEqualTo(expectedActionResult); - assertThat(pendingResponse.getNameAsString()).isEqualTo(expectedResourceName); - Trid trid = pendingResponse.getTrid(); - assertThat(trid.getClientTransactionId()).isEqualTo(clientTrid); - assertThat(trid.getServerTransactionId()).isEqualTo("fakeServerTrid"); - } - - private static ContactResource persistContactPendingDelete(String contactId) { - return persistResource( - newContactResource(contactId).asBuilder().addStatusValue(PENDING_DELETE).build()); - } - - private static HostResource persistHostPendingDelete(String hostName) { - return persistResource( - newHostResource(hostName).asBuilder().addStatusValue(PENDING_DELETE).build()); - } - - private static DomainBase persistUsedDomain( - String domainName, ContactResource contact, HostResource host) { - return persistResource( - newDomainBase(domainName, contact) - .asBuilder() - .setNameservers(ImmutableSet.of(host.createVKey())) - .build()); - } - - private Optional acquireLock() { - return Lock.acquire( - DeleteContactsAndHostsAction.class.getSimpleName(), - null, - standardDays(30), - requestStatusChecker, - false); - } -} diff --git a/core/src/test/java/google/registry/flows/EppLifecycleContactTest.java b/core/src/test/java/google/registry/flows/EppLifecycleContactTest.java index 93346e74e..ee2340c37 100644 --- a/core/src/test/java/google/registry/flows/EppLifecycleContactTest.java +++ b/core/src/test/java/google/registry/flows/EppLifecycleContactTest.java @@ -16,13 +16,10 @@ package google.registry.flows; import static google.registry.model.eppoutput.Result.Code.SUCCESS; import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACK_MESSAGE; -import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING; import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_NO_MESSAGES; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.testing.EppMetricSubject.assertThat; import com.google.common.collect.ImmutableMap; -import google.registry.model.eppoutput.Result; import google.registry.testing.AppEngineExtension; import google.registry.testing.DualDatabaseTest; import google.registry.testing.TestOfyAndSql; @@ -65,22 +62,14 @@ class EppLifecycleContactTest extends EppTestCase { .hasCommandName("ContactInfo") .and() .hasStatus(SUCCESS); - Result.Code deleteResultCode; - if (tm().isOfy()) { - assertThatCommand("contact_delete_sh8013.xml") - .hasResponse("contact_delete_response_sh8013_pending.xml"); - deleteResultCode = SUCCESS_WITH_ACTION_PENDING; - } else { - assertThatCommand("contact_delete_sh8013.xml") - .hasResponse("contact_delete_response_sh8013.xml"); - deleteResultCode = SUCCESS; - } + assertThatCommand("contact_delete_sh8013.xml") + .hasResponse("contact_delete_response_sh8013.xml"); assertThat(getRecordedEppMetric()) .hasClientId("NewRegistrar") .and() .hasCommandName("ContactDelete") .and() - .hasStatus(deleteResultCode); + .hasStatus(SUCCESS); assertThatLogoutSucceeds(); } diff --git a/core/src/test/java/google/registry/flows/EppLifecycleHostTest.java b/core/src/test/java/google/registry/flows/EppLifecycleHostTest.java index 42987cf9a..e03e33ae8 100644 --- a/core/src/test/java/google/registry/flows/EppLifecycleHostTest.java +++ b/core/src/test/java/google/registry/flows/EppLifecycleHostTest.java @@ -17,8 +17,6 @@ package google.registry.flows; import static com.google.common.truth.Truth.assertThat; import static google.registry.model.EppResourceUtils.loadByForeignKey; 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.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.createTlds; import static google.registry.testing.EppMetricSubject.assertThat; @@ -26,7 +24,6 @@ import static google.registry.testing.HostResourceSubject.assertAboutHosts; import com.google.common.collect.ImmutableMap; import google.registry.model.domain.DomainBase; -import google.registry.model.eppoutput.Result; import google.registry.model.host.HostResource; import google.registry.testing.AppEngineExtension; import google.registry.testing.DualDatabaseTest; @@ -82,24 +79,15 @@ class EppLifecycleHostTest extends EppTestCase { .hasCommandName("HostInfo") .and() .hasStatus(SUCCESS); - Result.Code deleteResultCode; - if (tm().isOfy()) { - assertThatCommand("host_delete.xml", ImmutableMap.of("HOSTNAME", "ns1.example.tld")) - .atTime("2000-06-02T00:03:00Z") - .hasResponse("generic_success_action_pending_response.xml"); - deleteResultCode = SUCCESS_WITH_ACTION_PENDING; - } else { - assertThatCommand("host_delete.xml", ImmutableMap.of("HOSTNAME", "ns1.example.tld")) - .atTime("2000-06-02T00:03:00Z") - .hasResponse("generic_success_response.xml"); - deleteResultCode = SUCCESS; - } + assertThatCommand("host_delete.xml", ImmutableMap.of("HOSTNAME", "ns1.example.tld")) + .atTime("2000-06-02T00:03:00Z") + .hasResponse("generic_success_response.xml"); assertThat(getRecordedEppMetric()) .hasClientId("NewRegistrar") .and() .hasCommandName("HostDelete") .and() - .hasStatus(deleteResultCode); + .hasStatus(SUCCESS); assertThatLogoutSucceeds(); } diff --git a/core/src/test/java/google/registry/flows/EppPointInTimeTest.java b/core/src/test/java/google/registry/flows/EppPointInTimeTest.java index bc536334c..21eca5321 100644 --- a/core/src/test/java/google/registry/flows/EppPointInTimeTest.java +++ b/core/src/test/java/google/registry/flows/EppPointInTimeTest.java @@ -148,19 +148,12 @@ class EppPointInTimeTest { .isEqualExceptFields(domainAfterCreate, "updateTimestamp"); tm().clearSessionCache(); - if (tm().isOfy()) { - // Both updates happened on the same day. Since the revisions field has day granularity in - // Datastore, the key to the first update should have been overwritten by the second, and its - // timestamp rolled forward. So we have to fall back to the last revision before midnight. - assertThat(loadAtPointInTime(latest, timeAtFirstUpdate)).isEqualTo(domainAfterCreate); - } else { - // In SQL, however, we are not limited by the day granularity, so when we request the object - // at timeAtFirstUpdate we should receive the object at that first update, even though the - // second update occurred one millisecond later. - assertAboutImmutableObjects() - .that(loadAtPointInTime(latest, timeAtFirstUpdate)) - .isEqualExceptFields(domainAfterFirstUpdate, "updateTimestamp"); - } + // In SQL, we are not limited by the day granularity, so when we request the object + // at timeAtFirstUpdate we should receive the object at that first update, even though the + // second update occurred one millisecond later. + assertAboutImmutableObjects() + .that(loadAtPointInTime(latest, timeAtFirstUpdate)) + .isEqualExceptFields(domainAfterFirstUpdate, "updateTimestamp"); tm().clearSessionCache(); assertAboutImmutableObjects() diff --git a/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java b/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java index 44d628641..6ba415659 100644 --- a/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java +++ b/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java @@ -14,12 +14,9 @@ package google.registry.flows; -import static com.google.common.collect.ImmutableList.toImmutableList; -import static com.google.common.truth.Truth.assertThat; import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY; import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects; -import static google.registry.model.ofy.ObjectifyService.auditedOfy; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm; import static google.registry.testing.LogsSubject.assertAboutLogs; @@ -28,9 +25,7 @@ import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; -import com.google.common.collect.Streams; import com.google.common.testing.TestLogHandler; -import com.googlecode.objectify.Key; import google.registry.model.EppResource; import google.registry.model.contact.ContactBase; import google.registry.model.contact.ContactHistory; @@ -41,8 +36,6 @@ import google.registry.model.eppinput.EppInput.ResourceCommandWrapper; import google.registry.model.eppinput.ResourceCommand; import google.registry.model.host.HostBase; import google.registry.model.host.HostHistory; -import google.registry.model.index.EppResourceIndex; -import google.registry.model.index.EppResourceIndexBucket; import google.registry.model.reporting.HistoryEntry; import google.registry.model.tmch.ClaimsList; import google.registry.model.tmch.ClaimsListDao; @@ -117,30 +110,6 @@ public abstract class ResourceFlowTestCase void assertEppResourceIndexEntityFor(final T resource) { - if (!tm().isOfy()) { - // Indices aren't explicitly stored as objects in SQL - return; - } - ImmutableList indices = - Streams.stream( - auditedOfy() - .load() - .type(EppResourceIndex.class) - .filter("kind", Key.getKind(resource.getClass()))) - .filter( - index -> - Key.create(resource).equals(index.getKey()) - && auditedOfy().load().key(index.getKey()).now().equals(resource)) - .collect(toImmutableList()); - assertThat(indices).hasSize(1); - assertThat(indices.get(0).getBucket()) - .isEqualTo(EppResourceIndexBucket.getBucketKey(Key.create(resource))); - } - /** Asserts the presence of a single enqueued async contact or host deletion */ protected void assertAsyncDeletionTaskEnqueued( T resource, String requestingClientId, Trid trid, boolean isSuperuser) { @@ -180,26 +149,24 @@ public abstract class ResourceFlowTestCase entity in SQL database(it is - // reconstructed by reading the information from Host table), we can not reconstruct the entity - // for the old host name because of the rename so we just skip the check for SQL database. - if (tm().isOfy()) { - // The old ForeignKeyIndex is invalidated at the time we did the rename. - ForeignKeyIndex oldFkiBeforeRename = - ForeignKeyIndex.load(HostResource.class, oldHostName(), clock.nowUtc().minusMillis(1)); - assertThat(oldFkiBeforeRename.getResourceKey()).isEqualTo(renamedHost.createVKey()); - assertThat(oldFkiBeforeRename.getDeletionTime()).isEqualTo(clock.nowUtc()); - } ForeignKeyIndex oldFkiAfterRename = ForeignKeyIndex.load(HostResource.class, oldHostName(), clock.nowUtc()); assertThat(oldFkiAfterRename).isNull(); diff --git a/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java b/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java index d6b402853..5f3b104be 100644 --- a/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java +++ b/core/src/test/java/google/registry/rdap/RdapDomainActionTest.java @@ -40,10 +40,11 @@ import google.registry.rdap.RdapMetrics.SearchType; import google.registry.rdap.RdapMetrics.WildcardType; import google.registry.rdap.RdapSearchResults.IncompletenessWarningType; import google.registry.request.Action; +import google.registry.testing.DualDatabaseTest; +import google.registry.testing.TestOfyAndSql; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; /** * Unit tests for {@link RdapDomainAction}. @@ -51,6 +52,7 @@ import org.junit.jupiter.api.Test; *

TODO(b/26872828): The next time we do any work on RDAP, consider adding the APNIC RDAP * conformance checker to the unit test suite. */ +@DualDatabaseTest class RdapDomainActionTest extends RdapActionBaseTestCase { RdapDomainActionTest() { @@ -247,7 +249,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase { assertThat(response.getStatus()).isEqualTo(200); } - @Test + @TestOfyAndSql void testInvalidDomain_returns400() { assertThat(generateActualJson("invalid/domain/name")) .isEqualTo( @@ -258,7 +260,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase { assertThat(response.getStatus()).isEqualTo(400); } - @Test + @TestOfyAndSql void testUnknownDomain_returns400() { assertThat(generateActualJson("missingdomain.com")) .isEqualTo( @@ -269,45 +271,45 @@ class RdapDomainActionTest extends RdapActionBaseTestCase { assertThat(response.getStatus()).isEqualTo(400); } - @Test + @TestOfyAndSql void testValidDomain_works() { login("evilregistrar"); assertProperResponseForCatLol("cat.lol", "rdap_domain.json"); } - @Test + @TestOfyAndSql void testValidDomain_asAdministrator_works() { loginAsAdmin(); assertProperResponseForCatLol("cat.lol", "rdap_domain.json"); } - @Test + @TestOfyAndSql void testValidDomain_notLoggedIn_noContacts() { assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts_with_remark.json"); } - @Test + @TestOfyAndSql void testValidDomain_loggedInAsOtherRegistrar_noContacts() { login("idnregistrar"); assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts_with_remark.json"); } - @Test + @TestOfyAndSql void testUpperCase_ignored() { assertProperResponseForCatLol("CaT.lOl", "rdap_domain_no_contacts_with_remark.json"); } - @Test + @TestOfyAndSql void testTrailingDot_ignored() { assertProperResponseForCatLol("cat.lol.", "rdap_domain_no_contacts_with_remark.json"); } - @Test + @TestOfyAndSql void testQueryParameter_ignored() { assertProperResponseForCatLol("cat.lol?key=value", "rdap_domain_no_contacts_with_remark.json"); } - @Test + @TestOfyAndSql void testIdnDomain_works() { login("idnregistrar"); assertThat(generateActualJson("cat.みんな")) @@ -325,7 +327,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase { assertThat(response.getStatus()).isEqualTo(200); } - @Test + @TestOfyAndSql void testIdnDomainWithPercentEncoding_works() { login("idnregistrar"); assertThat(generateActualJson("cat.%E3%81%BF%E3%82%93%E3%81%AA")) @@ -343,7 +345,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase { assertThat(response.getStatus()).isEqualTo(200); } - @Test + @TestOfyAndSql void testPunycodeDomain_works() { login("idnregistrar"); assertThat(generateActualJson("cat.xn--q9jyb4c")) @@ -361,7 +363,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase { assertThat(response.getStatus()).isEqualTo(200); } - @Test + @TestOfyAndSql void testMultilevelDomain_works() { login("1tldregistrar"); assertThat(generateActualJson("cat.1.tld")) @@ -381,35 +383,35 @@ class RdapDomainActionTest extends RdapActionBaseTestCase { // todo (b/27378695): reenable or delete this test @Disabled - @Test + @TestOfyAndSql void testDomainInTestTld_notFound() { persistResource(Registry.get("lol").asBuilder().setTldType(Registry.TldType.TEST).build()); generateActualJson("cat.lol"); assertThat(response.getStatus()).isEqualTo(404); } - @Test + @TestOfyAndSql void testDeletedDomain_notFound() { assertThat(generateActualJson("dodo.lol")) .isEqualTo(generateExpectedJsonError("dodo.lol not found", 404)); assertThat(response.getStatus()).isEqualTo(404); } - @Test + @TestOfyAndSql void testDeletedDomain_notFound_includeDeletedSetFalse() { action.includeDeletedParam = Optional.of(true); generateActualJson("dodo.lol"); assertThat(response.getStatus()).isEqualTo(404); } - @Test + @TestOfyAndSql void testDeletedDomain_notFound_notLoggedIn() { action.includeDeletedParam = Optional.of(true); generateActualJson("dodo.lol"); assertThat(response.getStatus()).isEqualTo(404); } - @Test + @TestOfyAndSql void testDeletedDomain_notFound_loggedInAsDifferentRegistrar() { login("1tldregistrar"); action.includeDeletedParam = Optional.of(true); @@ -417,7 +419,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase { assertThat(response.getStatus()).isEqualTo(404); } - @Test + @TestOfyAndSql void testDeletedDomain_works_loggedInAsCorrectRegistrar() { login("evilregistrar"); action.includeDeletedParam = Optional.of(true); @@ -436,7 +438,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase { assertThat(response.getStatus()).isEqualTo(200); } - @Test + @TestOfyAndSql void testDeletedDomain_works_loggedInAsAdmin() { loginAsAdmin(); action.includeDeletedParam = Optional.of(true); @@ -455,7 +457,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase { assertThat(response.getStatus()).isEqualTo(200); } - @Test + @TestOfyAndSql void testMetrics() { generateActualJson("cat.lol"); verify(rdapMetrics) diff --git a/core/src/test/java/google/registry/tools/EppLifecycleToolsTest.java b/core/src/test/java/google/registry/tools/EppLifecycleToolsTest.java index 142b4c44e..bfc79537f 100644 --- a/core/src/test/java/google/registry/tools/EppLifecycleToolsTest.java +++ b/core/src/test/java/google/registry/tools/EppLifecycleToolsTest.java @@ -29,27 +29,33 @@ import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainHistory; import google.registry.model.reporting.HistoryEntry.Type; import google.registry.testing.AppEngineExtension; +import google.registry.testing.DualDatabaseTest; +import google.registry.testing.TestOfyAndSql; import google.registry.util.Clock; import java.util.List; import org.joda.money.Money; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; /** Tests for tools that affect EPP lifecycle. */ +@DualDatabaseTest class EppLifecycleToolsTest extends EppTestCase { @RegisterExtension final AppEngineExtension appEngine = - AppEngineExtension.builder().withDatastoreAndCloudSql().withTaskQueue().build(); + AppEngineExtension.builder() + .withClock(clock) + .withDatastoreAndCloudSql() + .withTaskQueue() + .build(); @BeforeEach void beforeEach() { createTlds("example", "tld"); } - @Test + @TestOfyAndSql void test_renewDomainThenUnrenew() throws Exception { assertThatLoginSucceeds("NewRegistrar", "foo-BAR2"); createContacts(DateTime.parse("2000-06-01T00:00:00Z")); diff --git a/core/src/test/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesActionTest.java b/core/src/test/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesActionTest.java deleted file mode 100644 index 0c6833829..000000000 --- a/core/src/test/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesActionTest.java +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2021 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.tools.javascrap; - -import static com.google.common.truth.Truth.assertThat; -import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; -import static google.registry.testing.DatabaseHelper.createTld; -import static google.registry.testing.DatabaseHelper.loadByKey; -import static google.registry.testing.DatabaseHelper.loadRegistrar; -import static google.registry.testing.DatabaseHelper.persistActiveDomain; -import static google.registry.testing.DatabaseHelper.persistActiveHost; -import static google.registry.testing.DatabaseHelper.persistDomainAsDeleted; -import static google.registry.testing.DatabaseHelper.persistDomainWithDependentResources; -import static google.registry.testing.DatabaseHelper.persistResource; -import static google.registry.util.DateTimeUtils.END_OF_TIME; -import static google.registry.util.DateTimeUtils.START_OF_TIME; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.googlecode.objectify.Key; -import google.registry.model.EppResource; -import google.registry.model.contact.ContactResource; -import google.registry.model.domain.DomainBase; -import google.registry.model.domain.DomainHistory; -import google.registry.model.host.HostResource; -import google.registry.model.reporting.HistoryEntry; -import google.registry.model.reporting.HistoryEntryDao; -import google.registry.model.tld.Registry; -import google.registry.testing.FakeResponse; -import google.registry.testing.mapreduce.MapreduceTestCase; -import org.joda.time.DateTime; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** Tests for {@link CreateSyntheticHistoryEntriesAction}. */ -public class CreateSyntheticHistoryEntriesActionTest - extends MapreduceTestCase { - - private DomainBase domain; - private ContactResource contact; - - @BeforeEach - void beforeEach() { - action = - new CreateSyntheticHistoryEntriesAction( - makeDefaultRunner(), new FakeResponse(), "adminRegistrarId"); - - createTld("tld"); - domain = persistActiveDomain("example.tld"); - contact = loadByKey(domain.getAdminContact()); - } - - @Test - void testCreation_forAllTypes() throws Exception { - DomainBase domain2 = persistActiveDomain("exampletwo.tld"); - ContactResource contact2 = loadByKey(domain2.getAdminContact()); - HostResource host = persistActiveHost("ns1.foobar.tld"); - HostResource host2 = persistActiveHost("ns1.baz.tld"); - - assertThat(HistoryEntryDao.loadAllHistoryObjects(START_OF_TIME, END_OF_TIME)).isEmpty(); - runMapreduce(); - - for (EppResource resource : ImmutableList.of(contact, contact2, domain, domain2, host, host2)) { - HistoryEntry historyEntry = - Iterables.getOnlyElement( - HistoryEntryDao.loadHistoryObjectsForResource(resource.createVKey())); - assertThat(historyEntry.getParent()).isEqualTo(Key.create(resource)); - assertThat(historyEntry.getType()).isEqualTo(HistoryEntry.Type.SYNTHETIC); - } - assertThat(HistoryEntryDao.loadAllHistoryObjects(START_OF_TIME, END_OF_TIME)).hasSize(6); - } - - @Test - void testCreation_withPreviousHistoryEntry() throws Exception { - DateTime now = DateTime.parse("1999-04-03T22:00:00.0Z"); - DomainBase withHistoryEntry = - persistDomainWithDependentResources("foobar", "tld", contact, now, now, now.plusYears(1)); - assertThat( - Iterables.getOnlyElement( - HistoryEntryDao.loadHistoryObjectsForResource(withHistoryEntry.createVKey())) - .getType()) - .isEqualTo(HistoryEntry.Type.DOMAIN_CREATE); - - runMapreduce(); - - Iterable historyEntries = - HistoryEntryDao.loadHistoryObjectsForResource(withHistoryEntry.createVKey()); - assertThat(historyEntries).hasSize(2); - assertThat(Iterables.getLast(historyEntries).getType()).isEqualTo(HistoryEntry.Type.SYNTHETIC); - } - - @Test - void testDoesntSave_ifAlreadyReplayed() throws Exception { - DateTime now = DateTime.parse("1999-04-03T22:00:00.0Z"); - DomainHistory domainHistoryWithoutDomain = - persistResource( - new DomainHistory.Builder() - .setType(HistoryEntry.Type.DOMAIN_CREATE) - .setModificationTime(now) - .setDomain(domain) - .setRegistrarId(domain.getCreationRegistrarId()) - .build()); - DomainHistory domainHistoryWithDomain = - domainHistoryWithoutDomain.asBuilder().setDomain(domain).build(); - // Simulate having replayed the domain and history to SQL - jpaTm() - .transact( - () -> - jpaTm() - .putAll( - Registry.get("tld"), - loadRegistrar("TheRegistrar"), - contact, - domain, - domainHistoryWithDomain)); - runMapreduce(); - - // Since we already had a DomainHistory with the domain in SQL, we shouldn't create a synthetic - // history entry - assertThat(HistoryEntryDao.loadHistoryObjectsForResource(domain.createVKey())) - .containsExactly(domainHistoryWithoutDomain); - } - - @Test - void testCreation_forDeletedResource() throws Exception { - persistDomainAsDeleted(domain, domain.getCreationTime().plusMonths(6)); - runMapreduce(); - - assertThat( - Iterables.getOnlyElement( - HistoryEntryDao.loadHistoryObjectsForResource(domain.createVKey())) - .getType()) - .isEqualTo(HistoryEntry.Type.SYNTHETIC); - } - - private void runMapreduce() throws Exception { - action.run(); - executeTasksUntilEmpty("mapreduce"); - } -} diff --git a/core/src/test/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesPipelineTest.java b/core/src/test/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesPipelineTest.java deleted file mode 100644 index 432ca0ff0..000000000 --- a/core/src/test/java/google/registry/tools/javascrap/CreateSyntheticHistoryEntriesPipelineTest.java +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2021 The Nomulus Authors. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package google.registry.tools.javascrap; - -import static com.google.common.truth.Truth.assertThat; -import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects; -import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; -import static google.registry.persistence.transaction.TransactionManagerFactory.removeTmOverrideForTest; -import static google.registry.persistence.transaction.TransactionManagerFactory.setTmOverrideForTest; -import static google.registry.testing.DatabaseHelper.createTld; -import static google.registry.testing.DatabaseHelper.newDomainBase; -import static google.registry.testing.DatabaseHelper.persistActiveHost; -import static google.registry.testing.DatabaseHelper.persistNewRegistrar; -import static google.registry.testing.DatabaseHelper.persistSimpleResource; - -import com.google.common.collect.ImmutableList; -import google.registry.beam.TestPipelineExtension; -import google.registry.model.EppResource; -import google.registry.model.contact.ContactHistory; -import google.registry.model.contact.ContactResource; -import google.registry.model.domain.DomainBase; -import google.registry.model.domain.DomainHistory; -import google.registry.model.host.HostHistory; -import google.registry.model.host.HostResource; -import google.registry.model.reporting.HistoryEntry; -import google.registry.persistence.transaction.JpaTestExtensions; -import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension; -import google.registry.testing.DatastoreEntityExtension; -import google.registry.testing.FakeClock; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -/** Unit tests for {@link CreateSyntheticHistoryEntriesPipeline}. */ -public class CreateSyntheticHistoryEntriesPipelineTest { - - FakeClock clock = new FakeClock(); - - @RegisterExtension - JpaIntegrationTestExtension jpaEextension = - new JpaTestExtensions.Builder().withClock(clock).buildIntegrationTestExtension(); - - @RegisterExtension - DatastoreEntityExtension datastoreEntityExtension = - new DatastoreEntityExtension().allThreads(true); - - @RegisterExtension TestPipelineExtension pipeline = TestPipelineExtension.create(); - - DomainBase domain; - ContactResource contact; - HostResource host; - - @BeforeEach - void beforeEach() { - setTmOverrideForTest(jpaTm()); - persistNewRegistrar("TheRegistrar"); - persistNewRegistrar("NewRegistrar"); - createTld("tld"); - host = persistActiveHost("external.com"); - domain = - persistSimpleResource( - newDomainBase("example.tld").asBuilder().setNameservers(host.createVKey()).build()); - contact = jpaTm().transact(() -> jpaTm().loadByKey(domain.getRegistrant())); - clock.advanceOneMilli(); - } - - @AfterEach - void afterEach() { - removeTmOverrideForTest(); - } - - @Test - void testSuccess() { - assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(DomainHistory.class))).isEmpty(); - assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(ContactHistory.class))).isEmpty(); - assertThat(jpaTm().transact(() -> jpaTm().loadAllOf(HostHistory.class))).isEmpty(); - CreateSyntheticHistoryEntriesPipeline.setup(pipeline, "NewRegistrar"); - pipeline.run().waitUntilFinish(); - validateHistoryEntry(DomainHistory.class, domain); - validateHistoryEntry(ContactHistory.class, contact); - validateHistoryEntry(HostHistory.class, host); - } - - private static void validateHistoryEntry( - Class historyClazz, T resource) { - ImmutableList historyEntries = - jpaTm().transact(() -> jpaTm().loadAllOf(historyClazz)); - assertThat(historyEntries).hasSize(1); - HistoryEntry historyEntry = historyEntries.get(0); - assertThat(historyEntry.getType()).isEqualTo(HistoryEntry.Type.SYNTHETIC); - assertThat(historyEntry.getRegistrarId()).isEqualTo("NewRegistrar"); - EppResource embeddedResource; - if (historyEntry instanceof DomainHistory) { - embeddedResource = ((DomainHistory) historyEntry).getDomainContent().get(); - } else if (historyEntry instanceof ContactHistory) { - embeddedResource = ((ContactHistory) historyEntry).getContactBase().get(); - } else { - embeddedResource = ((HostHistory) historyEntry).getHostBase().get(); - } - assertAboutImmutableObjects().that(embeddedResource).hasFieldsEqualTo(resource); - } -} diff --git a/core/src/test/java/google/registry/tools/server/VerifyOteActionTest.java b/core/src/test/java/google/registry/tools/server/VerifyOteActionTest.java index c49995601..b194f5f52 100644 --- a/core/src/test/java/google/registry/tools/server/VerifyOteActionTest.java +++ b/core/src/test/java/google/registry/tools/server/VerifyOteActionTest.java @@ -20,12 +20,14 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import google.registry.model.OteStatsTestHelper; import google.registry.testing.AppEngineExtension; +import google.registry.testing.DualDatabaseTest; +import google.registry.testing.TestOfyAndSql; import java.util.Map; import java.util.regex.Pattern; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link VerifyOteAction}. */ +@DualDatabaseTest class VerifyOteActionTest { @RegisterExtension @@ -34,21 +36,21 @@ class VerifyOteActionTest { private final VerifyOteAction action = new VerifyOteAction(); - @Test + @TestOfyAndSql void testSuccess_summarize_allPass() throws Exception { OteStatsTestHelper.setupCompleteOte("blobio"); assertThat(getResponse(true)) .isEqualTo("# actions: 30 - Reqs: [----------------] 16/16 - Overall: PASS"); } - @Test + @TestOfyAndSql void testFailure_summarize_someFailures() throws Exception { OteStatsTestHelper.setupIncompleteOte("blobio"); assertThat(getResponse(true)) .isEqualTo("# actions: 34 - Reqs: [-.-----.------.-] 13/16 - Overall: FAIL"); } - @Test + @TestOfyAndSql void testSuccess_passNotSummarized() throws Exception { OteStatsTestHelper.setupCompleteOte("blobio"); String expectedOteStatus = @@ -78,7 +80,7 @@ class VerifyOteActionTest { assertThat(getResponse(false)).containsMatch(expectedOteStatusPattern); } - @Test + @TestOfyAndSql void testFailure_incomplete() throws Exception { OteStatsTestHelper.setupIncompleteOte("blobio"); String expectedOteStatus = diff --git a/core/src/test/java/google/registry/ui/server/registrar/OteStatusActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/OteStatusActionTest.java index ea512cfff..f8afdbe3b 100644 --- a/core/src/test/java/google/registry/ui/server/registrar/OteStatusActionTest.java +++ b/core/src/test/java/google/registry/ui/server/registrar/OteStatusActionTest.java @@ -30,14 +30,16 @@ import google.registry.model.registrar.Registrar.Type; import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role; import google.registry.testing.AppEngineExtension; +import google.registry.testing.DualDatabaseTest; +import google.registry.testing.TestOfyAndSql; import java.util.List; import java.util.Map; import java.util.function.Function; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link OteStatusAction} */ +@DualDatabaseTest public final class OteStatusActionTest { private static final String CLIENT_ID = "blobio-1"; @@ -57,7 +59,7 @@ public final class OteStatusActionTest { action.registrarAccessor = AuthenticatedRegistrarAccessor.createForTesting(authValues); } - @Test + @TestOfyAndSql @SuppressWarnings("unchecked") void testSuccess_finishedOte() throws Exception { OteStatsTestHelper.setupCompleteOte(BASE_CLIENT_ID); @@ -72,7 +74,7 @@ public final class OteStatusActionTest { assertThat(getFailingResultDetails(results)).isEmpty(); } - @Test + @TestOfyAndSql @SuppressWarnings("unchecked") void testSuccess_incomplete() throws Exception { OteStatsTestHelper.setupIncompleteOte(BASE_CLIENT_ID); @@ -103,7 +105,7 @@ public final class OteStatusActionTest { "completed", false)); } - @Test + @TestOfyAndSql void testFailure_malformedInput() { assertThat(action.handleJsonRequest(null)) .containsExactlyEntriesIn(errorResultWithMessage("Malformed JSON")); @@ -111,14 +113,14 @@ public final class OteStatusActionTest { .containsExactlyEntriesIn(errorResultWithMessage("Missing key for OT&E client: clientId")); } - @Test + @TestOfyAndSql void testFailure_registrarDoesntExist() { assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", "nonexistent-3"))) .containsExactlyEntriesIn( errorResultWithMessage("Registrar nonexistent-3 does not exist")); } - @Test + @TestOfyAndSql void testFailure_notAuthorized() { persistNewRegistrar(CLIENT_ID, "blobio-1", Type.REAL, 1L); action.registrarAccessor = @@ -128,7 +130,7 @@ public final class OteStatusActionTest { errorResultWithMessage("TestUserId doesn't have access to registrar blobio-1")); } - @Test + @TestOfyAndSql void testFailure_malformedRegistrarName() { assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", "badclient-id"))) .containsExactlyEntriesIn( @@ -136,7 +138,7 @@ public final class OteStatusActionTest { "ID badclient-id is not one of the OT&E registrar IDs for base badclient")); } - @Test + @TestOfyAndSql void testFailure_nonOteRegistrar() { persistNewRegistrar(CLIENT_ID, "SomeRegistrar", Type.REAL, 1L); assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", CLIENT_ID))) diff --git a/core/src/test/java/google/registry/ui/server/registrar/RegistryLockVerifyActionTest.java b/core/src/test/java/google/registry/ui/server/registrar/RegistryLockVerifyActionTest.java index cfb8b6a69..83d56338e 100644 --- a/core/src/test/java/google/registry/ui/server/registrar/RegistryLockVerifyActionTest.java +++ b/core/src/test/java/google/registry/ui/server/registrar/RegistryLockVerifyActionTest.java @@ -49,8 +49,10 @@ import google.registry.testing.AppEngineExtension; import google.registry.testing.CloudTasksHelper; import google.registry.testing.DatabaseHelper; import google.registry.testing.DeterministicStringGenerator; +import google.registry.testing.DualDatabaseTest; import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; +import google.registry.testing.TestOfyAndSql; import google.registry.testing.UserInfo; import google.registry.tools.DomainLockUtils; import google.registry.util.StringGenerator; @@ -58,10 +60,10 @@ import google.registry.util.StringGenerator.Alphabets; import javax.servlet.http.HttpServletRequest; import org.joda.time.Duration; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; /** Unit tests for {@link RegistryLockVerifyAction}. */ +@DualDatabaseTest final class RegistryLockVerifyActionTest { private final FakeClock fakeClock = new FakeClock(); @@ -96,7 +98,7 @@ final class RegistryLockVerifyActionTest { action = createAction(lockId, true); } - @Test + @TestOfyAndSql void testSuccess_lockDomain() { saveRegistryLock(createLock()); action.run(); @@ -112,7 +114,7 @@ final class RegistryLockVerifyActionTest { assertBillingEvent(historyEntry); } - @Test + @TestOfyAndSql void testSuccess_unlockDomain() { action = createAction(lockId, false); domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build()); @@ -130,7 +132,7 @@ final class RegistryLockVerifyActionTest { assertBillingEvent(historyEntry); } - @Test + @TestOfyAndSql void testSuccess_adminLock_createsOnlyHistoryEntry() { action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, true)); saveRegistryLock(createLock().asBuilder().isSuperuser(true).build()); @@ -142,7 +144,7 @@ final class RegistryLockVerifyActionTest { DatabaseHelper.assertNoBillingEvents(); } - @Test + @TestOfyAndSql void testFailure_badVerificationCode() { saveRegistryLock( createLock().asBuilder().setVerificationCode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").build()); @@ -151,7 +153,7 @@ final class RegistryLockVerifyActionTest { assertNoDomainChanges(); } - @Test + @TestOfyAndSql void testFailure_alreadyVerified() { saveRegistryLock(createLock().asBuilder().setLockCompletionTime(fakeClock.nowUtc()).build()); action.run(); @@ -159,7 +161,7 @@ final class RegistryLockVerifyActionTest { assertNoDomainChanges(); } - @Test + @TestOfyAndSql void testFailure_expired() { saveRegistryLock(createLock()); fakeClock.advanceBy(Duration.standardHours(2)); @@ -169,7 +171,7 @@ final class RegistryLockVerifyActionTest { assertNoDomainChanges(); } - @Test + @TestOfyAndSql void testFailure_nonAdmin_verifyingAdminLock() { saveRegistryLock(createLock().asBuilder().isSuperuser(true).build()); action.run(); @@ -177,7 +179,7 @@ final class RegistryLockVerifyActionTest { assertNoDomainChanges(); } - @Test + @TestOfyAndSql void testFailure_alreadyUnlocked() { action = createAction(lockId, false); saveRegistryLock( @@ -192,7 +194,7 @@ final class RegistryLockVerifyActionTest { assertNoDomainChanges(); } - @Test + @TestOfyAndSql void testFailure_alreadyLocked() { saveRegistryLock(createLock()); domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build()); @@ -201,7 +203,7 @@ final class RegistryLockVerifyActionTest { assertNoDomainChanges(); } - @Test + @TestOfyAndSql void testFailure_notLoggedIn() { action.authResult = AuthResult.NOT_AUTHENTICATED; action.run(); @@ -210,7 +212,7 @@ final class RegistryLockVerifyActionTest { assertNoDomainChanges(); } - @Test + @TestOfyAndSql void testFailure_doesNotChangeLockObject() { // A failure when performing Datastore actions means that no actions should be taken in the // Cloud SQL RegistryLock object @@ -229,7 +231,7 @@ final class RegistryLockVerifyActionTest { assertThat(afterAction).isEqualTo(lock); } - @Test + @TestOfyAndSql void testFailure_isLockTrue_shouldBeFalse() { domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build()); saveRegistryLock( @@ -242,7 +244,7 @@ final class RegistryLockVerifyActionTest { assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked"); } - @Test + @TestOfyAndSql void testFailure_isLockFalse_shouldBeTrue() { action = createAction(lockId, false); saveRegistryLock(createLock()); @@ -250,7 +252,7 @@ final class RegistryLockVerifyActionTest { assertThat(response.getPayload()).contains("Failed: Domain example.tld is already unlocked"); } - @Test + @TestOfyAndSql void testFailure_lock_unlock_lockAgain() { RegistryLock lock = saveRegistryLock(createLock()); action.run(); @@ -269,7 +271,7 @@ final class RegistryLockVerifyActionTest { assertThat(response.getPayload()).contains("Failed: Invalid verification code"); } - @Test + @TestOfyAndSql void testFailure_lock_lockAgain() { saveRegistryLock(createLock()); action.run(); @@ -279,7 +281,7 @@ final class RegistryLockVerifyActionTest { assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked"); } - @Test + @TestOfyAndSql void testFailure_unlock_unlockAgain() { action = createAction(lockId, false); domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build()); diff --git a/core/src/test/java/google/registry/webdriver/TestServerExtension.java b/core/src/test/java/google/registry/webdriver/TestServerExtension.java index 3851b2a22..80b51d0df 100644 --- a/core/src/test/java/google/registry/webdriver/TestServerExtension.java +++ b/core/src/test/java/google/registry/webdriver/TestServerExtension.java @@ -24,11 +24,14 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.net.HostAndPort; +import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.request.auth.AuthenticatedRegistrarAccessor; import google.registry.server.Fixture; import google.registry.server.Route; import google.registry.server.TestServer; import google.registry.testing.AppEngineExtension; +import google.registry.testing.DatabaseHelper; +import google.registry.testing.FakeClock; import google.registry.testing.UserInfo; import java.net.URL; import java.net.UnknownHostException; @@ -40,6 +43,8 @@ import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingDeque; import javax.servlet.Filter; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; @@ -202,7 +207,15 @@ public final class TestServerExtension implements BeforeEachCallback, AfterEachC } } - void runInner() throws InterruptedException { + void runInner() throws Exception { + // Clear the SQL database and set it as primary (we have to do this out of band because the + // AppEngineExtension can't natively do it for us yet due to remaining ofy dependencies) + new JpaTestExtensions.Builder().buildIntegrationTestExtension().beforeEach(null); + DatabaseHelper.setMigrationScheduleToSqlPrimary( + new FakeClock(DateTime.now(DateTimeZone.UTC))); + // sleep a few millis to make sure we get to SQL-primary mode + Thread.sleep(4); + AppEngineExtension.loadInitialData(); for (Fixture fixture : fixtures) { fixture.load(); } diff --git a/core/src/test/resources/google/registry/module/backend/backend_routing.txt b/core/src/test/resources/google/registry/module/backend/backend_routing.txt index 41b3ce86d..ef6f90fa9 100644 --- a/core/src/test/resources/google/registry/module/backend/backend_routing.txt +++ b/core/src/test/resources/google/registry/module/backend/backend_routing.txt @@ -9,8 +9,6 @@ PATH CLASS /_dr/task/brdaCopy BrdaCopyAction POST y INTERNAL,API APP ADMIN /_dr/task/checkDatastoreBackup CheckBackupAction POST,GET y INTERNAL,API APP ADMIN /_dr/task/copyDetailReports CopyDetailReportsAction POST n INTERNAL,API APP ADMIN -/_dr/task/createSyntheticHistoryEntries CreateSyntheticHistoryEntriesAction GET n INTERNAL,API APP ADMIN -/_dr/task/deleteContactsAndHosts DeleteContactsAndHostsAction GET n INTERNAL,API APP ADMIN /_dr/task/deleteExpiredDomains DeleteExpiredDomainsAction GET n INTERNAL,API APP ADMIN /_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST n INTERNAL,API APP ADMIN /_dr/task/deleteOldCommitLogs DeleteOldCommitLogsAction GET n INTERNAL,API APP ADMIN