mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 12:07:51 +02:00
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).
This commit is contained in:
parent
fad5d19110
commit
4c61df65b2
34 changed files with 200 additions and 2680 deletions
|
@ -37,7 +37,6 @@ def jsDir = "${project.projectDir}/src/main/javascript"
|
||||||
// TODO(weiminyu): identify cause and fix offending tests.
|
// TODO(weiminyu): identify cause and fix offending tests.
|
||||||
def outcastTestPatterns = [
|
def outcastTestPatterns = [
|
||||||
// Problem seems to lie with AppEngine TaskQueue for test.
|
// Problem seems to lie with AppEngine TaskQueue for test.
|
||||||
"google/registry/batch/DeleteContactsAndHostsActionTest.*",
|
|
||||||
"google/registry/batch/RefreshDnsOnHostRenameActionTest.*",
|
"google/registry/batch/RefreshDnsOnHostRenameActionTest.*",
|
||||||
"google/registry/flows/CheckApiActionTest.*",
|
"google/registry/flows/CheckApiActionTest.*",
|
||||||
"google/registry/flows/EppLifecycleHostTest.*",
|
"google/registry/flows/EppLifecycleHostTest.*",
|
||||||
|
@ -714,10 +713,6 @@ createToolTask(
|
||||||
createToolTask(
|
createToolTask(
|
||||||
'jpaDemoPipeline', 'google.registry.beam.common.JpaDemoPipeline')
|
'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
|
// Caller must provide projectId, GCP region, runner, and the kinds to delete
|
||||||
// (comma-separated kind names or '*' for all). E.g.:
|
// (comma-separated kind names or '*' for all). E.g.:
|
||||||
// nom_build :core:bulkDeleteDatastore --args="--project=domain-registry-crash \
|
// nom_build :core:bulkDeleteDatastore --args="--project=domain-registry-crash \
|
||||||
|
|
|
@ -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 =
|
|
||||||
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<TaskHandle> 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<String> kindCounts = HashMultiset.create(2);
|
|
||||||
ImmutableList.Builder<DeletionRequest> builder = new ImmutableList.Builder<>();
|
|
||||||
ImmutableList.Builder<Key<? extends EppResource>> resourceKeys = new ImmutableList.Builder<>();
|
|
||||||
final List<DeletionRequest> 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<DeletionRequest> 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<DeletionRequest> deletionRequests) {
|
|
||||||
if (deletionRequests.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final List<TaskHandle> 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<DeletionRequest> deletionRequests, Optional<Lock> 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<Void>(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> lock) {
|
|
||||||
logger.at(level).log(message);
|
|
||||||
response.setPayload(message);
|
|
||||||
lock.ifPresent(Lock::release);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mapper that iterates over all {@link DomainBase} entities.
|
|
||||||
*
|
|
||||||
* <p>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<DomainBase, DeletionRequest, Boolean> {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -253652818502690537L;
|
|
||||||
|
|
||||||
private final ImmutableList<DeletionRequest> deletionRequests;
|
|
||||||
|
|
||||||
DeleteContactsAndHostsMapper(ImmutableList<DeletionRequest> 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<? extends EppResource> resourceKey) {
|
|
||||||
if (resourceKey.getKind().equals(KIND_CONTACT)) {
|
|
||||||
return domain
|
|
||||||
.getReferencedContacts()
|
|
||||||
.contains(VKey.from((Key<ContactResource>) resourceKey));
|
|
||||||
} else if (resourceKey.getKind().equals(KIND_HOST)) {
|
|
||||||
return domain.getNameservers().contains(VKey.from((Key<HostResource>) 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<DeletionRequest, Boolean, Void> {
|
|
||||||
|
|
||||||
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<Boolean> 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()
|
|
||||||
.<ImmutableObject>entities(resourceToSave, historyEntry.asHistoryEntry(), pollMessage);
|
|
||||||
return DeletionResult.create(
|
|
||||||
deleteAllowed ? Type.DELETED : Type.NOT_DELETED, pollMessageText);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ImmutableList<? extends ResponseData> 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<? extends EppResource> 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<? extends EppResource> 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<String, String> params = ImmutableMap.copyOf(task.extractParams());
|
|
||||||
VKey<EppResource> 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<EppResource> 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -334,15 +334,6 @@
|
||||||
<url-pattern>/_dr/task/resaveEntity</url-pattern>
|
<url-pattern>/_dr/task/resaveEntity</url-pattern>
|
||||||
</servlet-mapping>
|
</servlet-mapping>
|
||||||
|
|
||||||
<!--
|
|
||||||
Deletes contacts and hosts enqueued for asynchronous deletion if they are
|
|
||||||
not referenced by any domain.
|
|
||||||
-->
|
|
||||||
<servlet-mapping>
|
|
||||||
<servlet-name>backend-servlet</servlet-name>
|
|
||||||
<url-pattern>/_dr/task/deleteContactsAndHosts</url-pattern>
|
|
||||||
</servlet-mapping>
|
|
||||||
|
|
||||||
<!-- Enqueues DNS update tasks following a host rename. -->
|
<!-- Enqueues DNS update tasks following a host rename. -->
|
||||||
<servlet-mapping>
|
<servlet-mapping>
|
||||||
<servlet-name>backend-servlet</servlet-name>
|
<servlet-name>backend-servlet</servlet-name>
|
||||||
|
@ -422,12 +413,6 @@ have been in the database for a certain period of time. -->
|
||||||
<url-pattern>/_dr/task/wipeOutDatastore</url-pattern>
|
<url-pattern>/_dr/task/wipeOutDatastore</url-pattern>
|
||||||
</servlet-mapping>
|
</servlet-mapping>
|
||||||
|
|
||||||
<!-- Action to create synthetic history entries during async replication to SQL -->
|
|
||||||
<servlet-mapping>
|
|
||||||
<servlet-name>backend-servlet</servlet-name>
|
|
||||||
<url-pattern>/_dr/task/createSyntheticHistoryEntries</url-pattern>
|
|
||||||
</servlet-mapping>
|
|
||||||
|
|
||||||
<!-- Action to sync Datastore to a snapshot of the primary SQL database. -->
|
<!-- Action to sync Datastore to a snapshot of the primary SQL database. -->
|
||||||
<servlet-mapping>
|
<servlet-mapping>
|
||||||
<servlet-name>backend-servlet</servlet-name>
|
<servlet-name>backend-servlet</servlet-name>
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
package google.registry.flows;
|
package google.registry.flows;
|
||||||
|
|
||||||
import static com.google.common.collect.Sets.intersection;
|
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.isLinked;
|
||||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||||
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
|
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
|
||||||
|
@ -54,8 +53,6 @@ import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
/** Static utility functions for resource flows. */
|
/** Static utility functions for resource flows. */
|
||||||
|
@ -63,12 +60,6 @@ public final class ResourceFlowUtils {
|
||||||
|
|
||||||
private 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. */
|
/** Check that the given registrarId corresponds to the owner of given resource. */
|
||||||
public static void verifyResourceOwnership(String myRegistrarId, EppResource resource)
|
public static void verifyResourceOwnership(String myRegistrarId, EppResource resource)
|
||||||
throws EppException {
|
throws EppException {
|
||||||
|
@ -85,39 +76,11 @@ public final class ResourceFlowUtils {
|
||||||
* consistent, so we only check a few domains to fail fast.
|
* consistent, so we only check a few domains to fail fast.
|
||||||
*/
|
*/
|
||||||
public static <R extends EppResource> void checkLinkedDomains(
|
public static <R extends EppResource> void checkLinkedDomains(
|
||||||
final String targetId,
|
final String targetId, final DateTime now, final Class<R> resourceClass) throws EppException {
|
||||||
final DateTime now,
|
|
||||||
final Class<R> resourceClass,
|
|
||||||
final Function<DomainBase, ImmutableSet<?>> getPotentialReferences)
|
|
||||||
throws EppException {
|
|
||||||
EppException failfastException =
|
EppException failfastException =
|
||||||
tm().isOfy()
|
tm().transact(
|
||||||
? tm().doTransactionless(
|
|
||||||
() -> {
|
() -> {
|
||||||
final ForeignKeyIndex<R> fki =
|
final ForeignKeyIndex<R> fki = ForeignKeyIndex.load(resourceClass, targetId, now);
|
||||||
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<VKey<DomainBase>> keys =
|
|
||||||
getLinkedDomainKeys(fki.getResourceKey(), now, FAILFAST_CHECK_COUNT);
|
|
||||||
|
|
||||||
VKey<R> resourceVKey = fki.getResourceKey();
|
|
||||||
Predicate<DomainBase> predicate =
|
|
||||||
domain -> getPotentialReferences.apply(domain).contains(resourceVKey);
|
|
||||||
return tm().loadByKeys(keys).values().stream().anyMatch(predicate)
|
|
||||||
? new ResourceToDeleteIsReferencedException()
|
|
||||||
: null;
|
|
||||||
})
|
|
||||||
: tm().transact(
|
|
||||||
() -> {
|
|
||||||
final ForeignKeyIndex<R> fki =
|
|
||||||
ForeignKeyIndex.load(resourceClass, targetId, now);
|
|
||||||
if (fki == null) {
|
if (fki == null) {
|
||||||
return new ResourceDoesNotExistException(resourceClass, targetId);
|
return new ResourceDoesNotExistException(resourceClass, targetId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||||
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
|
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
|
||||||
import static google.registry.model.ResourceTransferUtils.handlePendingTransferOnDelete;
|
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;
|
||||||
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.model.transfer.TransferStatus.SERVER_CANCELLED;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.assertAsyncActionsAreAllowed;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.assertAsyncActionsAreAllowed;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
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.flows.annotations.ReportingSpec;
|
||||||
import google.registry.model.contact.ContactHistory;
|
import google.registry.model.contact.ContactHistory;
|
||||||
import google.registry.model.contact.ContactResource;
|
import google.registry.model.contact.ContactResource;
|
||||||
import google.registry.model.domain.DomainBase;
|
|
||||||
import google.registry.model.domain.metadata.MetadataExtension;
|
import google.registry.model.domain.metadata.MetadataExtension;
|
||||||
import google.registry.model.eppcommon.AuthInfo;
|
import google.registry.model.eppcommon.AuthInfo;
|
||||||
import google.registry.model.eppcommon.StatusValue;
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
import google.registry.model.eppcommon.Trid;
|
import google.registry.model.eppcommon.Trid;
|
||||||
import google.registry.model.eppoutput.EppResponse;
|
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.HistoryEntry.Type;
|
||||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -97,41 +94,26 @@ public final class ContactDeleteFlow implements TransactionalFlow {
|
||||||
extensionManager.validate();
|
extensionManager.validate();
|
||||||
assertAsyncActionsAreAllowed();
|
assertAsyncActionsAreAllowed();
|
||||||
DateTime now = tm().getTransactionTime();
|
DateTime now = tm().getTransactionTime();
|
||||||
checkLinkedDomains(targetId, now, ContactResource.class, DomainBase::getReferencedContacts);
|
checkLinkedDomains(targetId, now, ContactResource.class);
|
||||||
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
|
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
|
||||||
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
|
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
|
||||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||||
if (!isSuperuser) {
|
if (!isSuperuser) {
|
||||||
verifyResourceOwnership(registrarId, existingContact);
|
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.
|
// Handle pending transfers on contact deletion.
|
||||||
newContact =
|
ContactResource newContact =
|
||||||
existingContact.getStatusValues().contains(StatusValue.PENDING_TRANSFER)
|
existingContact.getStatusValues().contains(StatusValue.PENDING_TRANSFER)
|
||||||
? denyPendingTransfer(existingContact, SERVER_CANCELLED, now, registrarId)
|
? denyPendingTransfer(existingContact, SERVER_CANCELLED, now, registrarId)
|
||||||
: existingContact;
|
: existingContact;
|
||||||
// Wipe out PII on contact deletion.
|
// Wipe out PII on contact deletion.
|
||||||
newContact =
|
newContact =
|
||||||
newContact.asBuilder().wipeOut().setStatusValues(null).setDeletionTime(now).build();
|
newContact.asBuilder().wipeOut().setStatusValues(null).setDeletionTime(now).build();
|
||||||
historyEntryType = Type.CONTACT_DELETE;
|
|
||||||
resultCode = SUCCESS;
|
|
||||||
}
|
|
||||||
ContactHistory contactHistory =
|
ContactHistory contactHistory =
|
||||||
historyBuilder.setType(historyEntryType).setContact(newContact).build();
|
historyBuilder.setType(Type.CONTACT_DELETE).setContact(newContact).build();
|
||||||
if (!tm().isOfy()) {
|
|
||||||
handlePendingTransferOnDelete(existingContact, newContact, now, contactHistory);
|
handlePendingTransferOnDelete(existingContact, newContact, now, contactHistory);
|
||||||
}
|
|
||||||
tm().insert(contactHistory);
|
tm().insert(contactHistory);
|
||||||
tm().update(newContact);
|
tm().update(newContact);
|
||||||
return responseBuilder.setResultFromCode(resultCode).build();
|
return responseBuilder.setResultFromCode(SUCCESS).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.intersection;
|
||||||
import static com.google.common.collect.Sets.union;
|
import static com.google.common.collect.Sets.union;
|
||||||
import static google.registry.model.domain.DomainBase.MAX_REGISTRATION_YEARS;
|
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.findTldForName;
|
||||||
import static google.registry.model.tld.Registries.getTlds;
|
import static google.registry.model.tld.Registries.getTlds;
|
||||||
import static google.registry.model.tld.Registry.TldState.GENERAL_AVAILABILITY;
|
import static google.registry.model.tld.Registry.TldState.GENERAL_AVAILABILITY;
|
||||||
|
@ -1159,15 +1158,6 @@ public class DomainFlowUtils {
|
||||||
|
|
||||||
private static List<? extends HistoryEntry> findRecentHistoryEntries(
|
private static List<? extends HistoryEntry> findRecentHistoryEntries(
|
||||||
DomainBase domainBase, DateTime now, Duration maxSearchPeriod) {
|
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()
|
return jpaTm()
|
||||||
.query(
|
.query(
|
||||||
"FROM DomainHistory WHERE modificationTime >= :beginning AND domainRepoId = "
|
"FROM DomainHistory WHERE modificationTime >= :beginning AND domainRepoId = "
|
||||||
|
@ -1177,7 +1167,6 @@ public class DomainFlowUtils {
|
||||||
.setParameter("repoId", domainBase.getRepoId())
|
.setParameter("repoId", domainBase.getRepoId())
|
||||||
.getResultList();
|
.getResultList();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Resource linked to this domain does not exist. */
|
/** Resource linked to this domain does not exist. */
|
||||||
static class LinkedResourcesDoNotExistException extends ObjectDoesNotExistException {
|
static class LinkedResourcesDoNotExistException extends ObjectDoesNotExistException {
|
||||||
|
|
|
@ -102,11 +102,6 @@ public final class DomainInfoFlow implements Flow {
|
||||||
verifyOptionalAuthInfo(authInfo, domain);
|
verifyOptionalAuthInfo(authInfo, domain);
|
||||||
flowCustomLogic.afterValidation(
|
flowCustomLogic.afterValidation(
|
||||||
AfterValidationParameters.newBuilder().setDomain(domain).build());
|
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.
|
// Registrars can only see a few fields on unauthorized domains.
|
||||||
// This is a policy decision that is left up to us by the rfcs.
|
// This is a policy decision that is left up to us by the rfcs.
|
||||||
DomainInfoData.Builder infoBuilder =
|
DomainInfoData.Builder infoBuilder =
|
||||||
|
|
|
@ -21,7 +21,6 @@ import static google.registry.flows.ResourceFlowUtils.verifyNoDisallowedStatuses
|
||||||
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
import static google.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||||
import static google.registry.flows.host.HostFlowUtils.validateHostName;
|
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;
|
||||||
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.assertAsyncActionsAreAllowed;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
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.TransactionalFlow;
|
||||||
import google.registry.flows.annotations.ReportingSpec;
|
import google.registry.flows.annotations.ReportingSpec;
|
||||||
import google.registry.model.EppResource;
|
import google.registry.model.EppResource;
|
||||||
import google.registry.model.domain.DomainBase;
|
|
||||||
import google.registry.model.domain.metadata.MetadataExtension;
|
import google.registry.model.domain.metadata.MetadataExtension;
|
||||||
import google.registry.model.eppcommon.StatusValue;
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
import google.registry.model.eppcommon.Trid;
|
import google.registry.model.eppcommon.Trid;
|
||||||
import google.registry.model.eppoutput.EppResponse;
|
import google.registry.model.eppoutput.EppResponse;
|
||||||
import google.registry.model.eppoutput.Result;
|
|
||||||
import google.registry.model.host.HostHistory;
|
import google.registry.model.host.HostHistory;
|
||||||
import google.registry.model.host.HostResource;
|
import google.registry.model.host.HostResource;
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
|
||||||
import google.registry.model.reporting.HistoryEntry.Type;
|
import google.registry.model.reporting.HistoryEntry.Type;
|
||||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
@ -100,7 +96,7 @@ public final class HostDeleteFlow implements TransactionalFlow {
|
||||||
assertAsyncActionsAreAllowed();
|
assertAsyncActionsAreAllowed();
|
||||||
DateTime now = tm().getTransactionTime();
|
DateTime now = tm().getTransactionTime();
|
||||||
validateHostName(targetId);
|
validateHostName(targetId);
|
||||||
checkLinkedDomains(targetId, now, HostResource.class, DomainBase::getNameservers);
|
checkLinkedDomains(targetId, now, HostResource.class);
|
||||||
HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now);
|
HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now);
|
||||||
verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES);
|
verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES);
|
||||||
if (!isSuperuser) {
|
if (!isSuperuser) {
|
||||||
|
@ -112,17 +108,8 @@ public final class HostDeleteFlow implements TransactionalFlow {
|
||||||
: existingHost;
|
: existingHost;
|
||||||
verifyResourceOwnership(registrarId, owningResource);
|
verifyResourceOwnership(registrarId, owningResource);
|
||||||
}
|
}
|
||||||
HistoryEntry.Type historyEntryType;
|
HostResource newHost =
|
||||||
Result.Code resultCode;
|
existingHost.asBuilder().setStatusValues(null).setDeletionTime(now).build();
|
||||||
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()) {
|
if (existingHost.isSubordinate()) {
|
||||||
dnsQueue.addHostRefreshTask(existingHost.getHostName());
|
dnsQueue.addHostRefreshTask(existingHost.getHostName());
|
||||||
tm().update(
|
tm().update(
|
||||||
|
@ -131,12 +118,9 @@ public final class HostDeleteFlow implements TransactionalFlow {
|
||||||
.removeSubordinateHost(existingHost.getHostName())
|
.removeSubordinateHost(existingHost.getHostName())
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
historyEntryType = Type.HOST_DELETE;
|
historyBuilder.setType(Type.HOST_DELETE).setHost(newHost);
|
||||||
resultCode = SUCCESS;
|
|
||||||
}
|
|
||||||
historyBuilder.setType(historyEntryType).setHost(newHost);
|
|
||||||
tm().insert(historyBuilder.build());
|
tm().insert(historyBuilder.build());
|
||||||
tm().update(newHost);
|
tm().update(newHost);
|
||||||
return responseBuilder.setResultFromCode(resultCode).build();
|
return responseBuilder.setResultFromCode(SUCCESS).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
// 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
|
// 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.
|
// 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
|
// 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
|
// acked, then we return a special status code indicating that. Note that the query will
|
||||||
// include the message being acked.
|
// include the message being acked.
|
||||||
|
|
||||||
int messageCount = tm().doTransactionless(() -> getPollMessageCount(registrarId, now));
|
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) {
|
if (messageCount <= 0) {
|
||||||
return responseBuilder.setResultFromCode(SUCCESS_WITH_NO_MESSAGES).build();
|
return responseBuilder.setResultFromCode(SUCCESS_WITH_NO_MESSAGES).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,13 +47,12 @@ public final class PollFlowUtils {
|
||||||
* <p>The only case where we do so is if an autorenew poll message is acked, but its next event is
|
* <p>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.
|
* already ready to be delivered.
|
||||||
*/
|
*/
|
||||||
public static boolean ackPollMessage(PollMessage pollMessage) {
|
public static void ackPollMessage(PollMessage pollMessage) {
|
||||||
checkArgument(
|
checkArgument(
|
||||||
isBeforeOrAt(pollMessage.getEventTime(), tm().getTransactionTime()),
|
isBeforeOrAt(pollMessage.getEventTime(), tm().getTransactionTime()),
|
||||||
"Cannot ACK poll message with ID %s because its event time is in the future: %s",
|
"Cannot ACK poll message with ID %s because its event time is in the future: %s",
|
||||||
pollMessage.getId(),
|
pollMessage.getId(),
|
||||||
pollMessage.getEventTime());
|
pollMessage.getEventTime());
|
||||||
boolean includeAckedMessageInCount = false;
|
|
||||||
if (pollMessage instanceof PollMessage.OneTime) {
|
if (pollMessage instanceof PollMessage.OneTime) {
|
||||||
// One-time poll messages are deleted once acked.
|
// One-time poll messages are deleted once acked.
|
||||||
tm().delete(pollMessage.createVKey());
|
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.
|
// autorenew poll message has no more events to deliver and should be deleted.
|
||||||
if (nextEventTime.isBefore(autorenewPollMessage.getAutorenewEndTime())) {
|
if (nextEventTime.isBefore(autorenewPollMessage.getAutorenewEndTime())) {
|
||||||
tm().put(autorenewPollMessage.asBuilder().setEventTime(nextEventTime).build());
|
tm().put(autorenewPollMessage.asBuilder().setEventTime(nextEventTime).build());
|
||||||
includeAckedMessageInCount = isBeforeOrAt(nextEventTime, tm().getTransactionTime());
|
|
||||||
} else {
|
} else {
|
||||||
tm().delete(autorenewPollMessage.createVKey());
|
tm().delete(autorenewPollMessage.createVKey());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Unknown poll message type: " + pollMessage.getClass());
|
throw new IllegalArgumentException("Unknown poll message type: " + pollMessage.getClass());
|
||||||
}
|
}
|
||||||
return includeAckedMessageInCount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,9 +16,7 @@ package google.registry.model.reporting;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
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.jpaTm;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
|
||||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||||
|
|
||||||
|
@ -72,17 +70,6 @@ public class HistoryEntryDao {
|
||||||
/** Loads all history objects in the times specified, including all types. */
|
/** Loads all history objects in the times specified, including all types. */
|
||||||
public static ImmutableList<HistoryEntry> loadAllHistoryObjects(
|
public static ImmutableList<HistoryEntry> loadAllHistoryObjects(
|
||||||
DateTime afterTime, DateTime beforeTime) {
|
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()
|
return jpaTm()
|
||||||
.transact(
|
.transact(
|
||||||
() ->
|
() ->
|
||||||
|
@ -91,11 +78,9 @@ public class HistoryEntryDao {
|
||||||
loadAllHistoryObjectsFromSql(ContactHistory.class, afterTime, beforeTime))
|
loadAllHistoryObjectsFromSql(ContactHistory.class, afterTime, beforeTime))
|
||||||
.addAll(
|
.addAll(
|
||||||
loadAllHistoryObjectsFromSql(DomainHistory.class, afterTime, beforeTime))
|
loadAllHistoryObjectsFromSql(DomainHistory.class, afterTime, beforeTime))
|
||||||
.addAll(
|
.addAll(loadAllHistoryObjectsFromSql(HostHistory.class, afterTime, beforeTime))
|
||||||
loadAllHistoryObjectsFromSql(HostHistory.class, afterTime, beforeTime))
|
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Loads all history objects corresponding to the given {@link EppResource}. */
|
/** Loads all history objects corresponding to the given {@link EppResource}. */
|
||||||
public static ImmutableList<HistoryEntry> loadHistoryObjectsForResource(
|
public static ImmutableList<HistoryEntry> loadHistoryObjectsForResource(
|
||||||
|
@ -115,22 +100,9 @@ public class HistoryEntryDao {
|
||||||
/** Loads all history objects in the time period specified for the given {@link EppResource}. */
|
/** Loads all history objects in the time period specified for the given {@link EppResource}. */
|
||||||
public static ImmutableList<HistoryEntry> loadHistoryObjectsForResource(
|
public static ImmutableList<HistoryEntry> loadHistoryObjectsForResource(
|
||||||
VKey<? extends EppResource> parentKey, DateTime afterTime, DateTime beforeTime) {
|
VKey<? extends EppResource> 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()
|
return jpaTm()
|
||||||
.transact(() -> loadHistoryObjectsForResourceFromSql(parentKey, afterTime, beforeTime));
|
.transact(() -> loadHistoryObjectsForResourceFromSql(parentKey, afterTime, beforeTime));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads all history objects in the time period specified for the given {@link EppResource} and
|
* Loads all history objects in the time period specified for the given {@link EppResource} and
|
||||||
|
@ -162,13 +134,6 @@ public class HistoryEntryDao {
|
||||||
/** Loads all history objects from all time from the given registrars. */
|
/** Loads all history objects from all time from the given registrars. */
|
||||||
public static Iterable<HistoryEntry> loadHistoryObjectsByRegistrars(
|
public static Iterable<HistoryEntry> loadHistoryObjectsByRegistrars(
|
||||||
ImmutableCollection<String> registrarIds) {
|
ImmutableCollection<String> registrarIds) {
|
||||||
if (tm().isOfy()) {
|
|
||||||
return auditedOfy()
|
|
||||||
.load()
|
|
||||||
.type(HistoryEntry.class)
|
|
||||||
.filter("clientId in", registrarIds)
|
|
||||||
.order("modificationTime");
|
|
||||||
} else {
|
|
||||||
return jpaTm()
|
return jpaTm()
|
||||||
.transact(
|
.transact(
|
||||||
() ->
|
() ->
|
||||||
|
@ -179,7 +144,6 @@ public class HistoryEntryDao {
|
||||||
.sorted(Comparator.comparing(HistoryEntry::getModificationTime))
|
.sorted(Comparator.comparing(HistoryEntry::getModificationTime))
|
||||||
.collect(toImmutableList()));
|
.collect(toImmutableList()));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static <T extends HistoryEntry> Stream<T> loadHistoryObjectFromSqlByRegistrars(
|
private static <T extends HistoryEntry> Stream<T> loadHistoryObjectFromSqlByRegistrars(
|
||||||
Class<T> historyClass, ImmutableCollection<String> registrarIds) {
|
Class<T> historyClass, ImmutableCollection<String> registrarIds) {
|
||||||
|
|
|
@ -23,7 +23,6 @@ import google.registry.backup.ExportCommitLogDiffAction;
|
||||||
import google.registry.backup.ReplayCommitLogsToSqlAction;
|
import google.registry.backup.ReplayCommitLogsToSqlAction;
|
||||||
import google.registry.backup.SyncDatastoreToSqlSnapshotAction;
|
import google.registry.backup.SyncDatastoreToSqlSnapshotAction;
|
||||||
import google.registry.batch.BatchModule;
|
import google.registry.batch.BatchModule;
|
||||||
import google.registry.batch.DeleteContactsAndHostsAction;
|
|
||||||
import google.registry.batch.DeleteExpiredDomainsAction;
|
import google.registry.batch.DeleteExpiredDomainsAction;
|
||||||
import google.registry.batch.DeleteLoadTestDataAction;
|
import google.registry.batch.DeleteLoadTestDataAction;
|
||||||
import google.registry.batch.DeleteProberDataAction;
|
import google.registry.batch.DeleteProberDataAction;
|
||||||
|
@ -93,7 +92,6 @@ import google.registry.tmch.TmchCrlAction;
|
||||||
import google.registry.tmch.TmchDnlAction;
|
import google.registry.tmch.TmchDnlAction;
|
||||||
import google.registry.tmch.TmchModule;
|
import google.registry.tmch.TmchModule;
|
||||||
import google.registry.tmch.TmchSmdrlAction;
|
import google.registry.tmch.TmchSmdrlAction;
|
||||||
import google.registry.tools.javascrap.CreateSyntheticHistoryEntriesAction;
|
|
||||||
|
|
||||||
/** Dagger component with per-request lifetime for "backend" App Engine module. */
|
/** Dagger component with per-request lifetime for "backend" App Engine module. */
|
||||||
@RequestScope
|
@RequestScope
|
||||||
|
@ -137,10 +135,6 @@ interface BackendRequestComponent {
|
||||||
|
|
||||||
CopyDetailReportsAction copyDetailReportAction();
|
CopyDetailReportsAction copyDetailReportAction();
|
||||||
|
|
||||||
CreateSyntheticHistoryEntriesAction createSyntheticHistoryEntriesAction();
|
|
||||||
|
|
||||||
DeleteContactsAndHostsAction deleteContactsAndHostsAction();
|
|
||||||
|
|
||||||
DeleteExpiredDomainsAction deleteExpiredDomainsAction();
|
DeleteExpiredDomainsAction deleteExpiredDomainsAction();
|
||||||
|
|
||||||
DeleteLoadTestDataAction deleteLoadTestDataAction();
|
DeleteLoadTestDataAction deleteLoadTestDataAction();
|
||||||
|
|
|
@ -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.
|
|
||||||
*
|
|
||||||
* <p>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.
|
|
||||||
*
|
|
||||||
* <p>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.
|
|
||||||
*
|
|
||||||
* <p>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.
|
|
||||||
*
|
|
||||||
* <p>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<? extends HistoryEntry> 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<? extends HistoryEntry> getHistoryClassFromParent(
|
|
||||||
Class<? extends EppResource> 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<? extends HistoryEntry> 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<Key<EppResource>, Void, Void> {
|
|
||||||
|
|
||||||
private final String registryAdminRegistrarId;
|
|
||||||
|
|
||||||
public CreateSyntheticHistoryEntriesMapper(String registryAdminRegistrarId) {
|
|
||||||
this.registryAdminRegistrarId = registryAdminRegistrarId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void map(final Key<EppResource> 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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.
|
|
||||||
*
|
|
||||||
* <p>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.
|
|
||||||
*
|
|
||||||
* <p>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.
|
|
||||||
*
|
|
||||||
* <p>To run the pipeline:
|
|
||||||
*
|
|
||||||
* <p><code>
|
|
||||||
* $ ./nom_build :core:cSHE --args="--region=us-central1
|
|
||||||
* --runner=DataflowRunner
|
|
||||||
* --registryEnvironment=CRASH
|
|
||||||
* --project={project-id}
|
|
||||||
* --workerMachineType=n2-standard-4"
|
|
||||||
* </code>
|
|
||||||
*
|
|
||||||
* @see google.registry.tools.javascrap.CreateSyntheticHistoryEntriesAction
|
|
||||||
*/
|
|
||||||
public class CreateSyntheticHistoryEntriesPipeline implements Serializable {
|
|
||||||
|
|
||||||
private static final ImmutableList<Class<? extends EppResource>> 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<? extends EppResource> 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<? extends EppResource> 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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<DeleteContactsAndHostsAction> {
|
|
||||||
|
|
||||||
@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<String> 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<String> 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.<EppResource>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.<EppResource>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.<EppResource>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<String> clientTrid) {
|
|
||||||
PollMessage.OneTime pollMessage = (OneTime) getOnlyPollMessageForHistoryEntry(historyEntry);
|
|
||||||
assertThat(pollMessage.getMsg()).isEqualTo(msg);
|
|
||||||
assertThat(pollMessage.getRegistrarId()).isEqualTo(registrarId);
|
|
||||||
|
|
||||||
ImmutableList<ResponseData> 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<Lock> acquireLock() {
|
|
||||||
return Lock.acquire(
|
|
||||||
DeleteContactsAndHostsAction.class.getSimpleName(),
|
|
||||||
null,
|
|
||||||
standardDays(30),
|
|
||||||
requestStatusChecker,
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACK_MESSAGE;
|
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.model.eppoutput.Result.Code.SUCCESS_WITH_NO_MESSAGES;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
|
||||||
import static google.registry.testing.EppMetricSubject.assertThat;
|
import static google.registry.testing.EppMetricSubject.assertThat;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import google.registry.model.eppoutput.Result;
|
|
||||||
import google.registry.testing.AppEngineExtension;
|
import google.registry.testing.AppEngineExtension;
|
||||||
import google.registry.testing.DualDatabaseTest;
|
import google.registry.testing.DualDatabaseTest;
|
||||||
import google.registry.testing.TestOfyAndSql;
|
import google.registry.testing.TestOfyAndSql;
|
||||||
|
@ -65,22 +62,14 @@ class EppLifecycleContactTest extends EppTestCase {
|
||||||
.hasCommandName("ContactInfo")
|
.hasCommandName("ContactInfo")
|
||||||
.and()
|
.and()
|
||||||
.hasStatus(SUCCESS);
|
.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")
|
assertThatCommand("contact_delete_sh8013.xml")
|
||||||
.hasResponse("contact_delete_response_sh8013.xml");
|
.hasResponse("contact_delete_response_sh8013.xml");
|
||||||
deleteResultCode = SUCCESS;
|
|
||||||
}
|
|
||||||
assertThat(getRecordedEppMetric())
|
assertThat(getRecordedEppMetric())
|
||||||
.hasClientId("NewRegistrar")
|
.hasClientId("NewRegistrar")
|
||||||
.and()
|
.and()
|
||||||
.hasCommandName("ContactDelete")
|
.hasCommandName("ContactDelete")
|
||||||
.and()
|
.and()
|
||||||
.hasStatus(deleteResultCode);
|
.hasStatus(SUCCESS);
|
||||||
assertThatLogoutSucceeds();
|
assertThatLogoutSucceeds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,6 @@ package google.registry.flows;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
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;
|
||||||
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.createTld;
|
||||||
import static google.registry.testing.DatabaseHelper.createTlds;
|
import static google.registry.testing.DatabaseHelper.createTlds;
|
||||||
import static google.registry.testing.EppMetricSubject.assertThat;
|
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 com.google.common.collect.ImmutableMap;
|
||||||
import google.registry.model.domain.DomainBase;
|
import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.model.eppoutput.Result;
|
|
||||||
import google.registry.model.host.HostResource;
|
import google.registry.model.host.HostResource;
|
||||||
import google.registry.testing.AppEngineExtension;
|
import google.registry.testing.AppEngineExtension;
|
||||||
import google.registry.testing.DualDatabaseTest;
|
import google.registry.testing.DualDatabaseTest;
|
||||||
|
@ -82,24 +79,15 @@ class EppLifecycleHostTest extends EppTestCase {
|
||||||
.hasCommandName("HostInfo")
|
.hasCommandName("HostInfo")
|
||||||
.and()
|
.and()
|
||||||
.hasStatus(SUCCESS);
|
.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"))
|
assertThatCommand("host_delete.xml", ImmutableMap.of("HOSTNAME", "ns1.example.tld"))
|
||||||
.atTime("2000-06-02T00:03:00Z")
|
.atTime("2000-06-02T00:03:00Z")
|
||||||
.hasResponse("generic_success_response.xml");
|
.hasResponse("generic_success_response.xml");
|
||||||
deleteResultCode = SUCCESS;
|
|
||||||
}
|
|
||||||
assertThat(getRecordedEppMetric())
|
assertThat(getRecordedEppMetric())
|
||||||
.hasClientId("NewRegistrar")
|
.hasClientId("NewRegistrar")
|
||||||
.and()
|
.and()
|
||||||
.hasCommandName("HostDelete")
|
.hasCommandName("HostDelete")
|
||||||
.and()
|
.and()
|
||||||
.hasStatus(deleteResultCode);
|
.hasStatus(SUCCESS);
|
||||||
assertThatLogoutSucceeds();
|
assertThatLogoutSucceeds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -148,19 +148,12 @@ class EppPointInTimeTest {
|
||||||
.isEqualExceptFields(domainAfterCreate, "updateTimestamp");
|
.isEqualExceptFields(domainAfterCreate, "updateTimestamp");
|
||||||
|
|
||||||
tm().clearSessionCache();
|
tm().clearSessionCache();
|
||||||
if (tm().isOfy()) {
|
// In SQL, we are not limited by the day granularity, so when we request the object
|
||||||
// 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
|
// at timeAtFirstUpdate we should receive the object at that first update, even though the
|
||||||
// second update occurred one millisecond later.
|
// second update occurred one millisecond later.
|
||||||
assertAboutImmutableObjects()
|
assertAboutImmutableObjects()
|
||||||
.that(loadAtPointInTime(latest, timeAtFirstUpdate))
|
.that(loadAtPointInTime(latest, timeAtFirstUpdate))
|
||||||
.isEqualExceptFields(domainAfterFirstUpdate, "updateTimestamp");
|
.isEqualExceptFields(domainAfterFirstUpdate, "updateTimestamp");
|
||||||
}
|
|
||||||
|
|
||||||
tm().clearSessionCache();
|
tm().clearSessionCache();
|
||||||
assertAboutImmutableObjects()
|
assertAboutImmutableObjects()
|
||||||
|
|
|
@ -14,12 +14,9 @@
|
||||||
|
|
||||||
package google.registry.flows;
|
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.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY;
|
||||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||||
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
|
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.TransactionManagerFactory.tm;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||||
import static google.registry.testing.LogsSubject.assertAboutLogs;
|
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.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Streams;
|
|
||||||
import com.google.common.testing.TestLogHandler;
|
import com.google.common.testing.TestLogHandler;
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import google.registry.model.EppResource;
|
import google.registry.model.EppResource;
|
||||||
import google.registry.model.contact.ContactBase;
|
import google.registry.model.contact.ContactBase;
|
||||||
import google.registry.model.contact.ContactHistory;
|
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.eppinput.ResourceCommand;
|
||||||
import google.registry.model.host.HostBase;
|
import google.registry.model.host.HostBase;
|
||||||
import google.registry.model.host.HostHistory;
|
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.reporting.HistoryEntry;
|
||||||
import google.registry.model.tmch.ClaimsList;
|
import google.registry.model.tmch.ClaimsList;
|
||||||
import google.registry.model.tmch.ClaimsListDao;
|
import google.registry.model.tmch.ClaimsListDao;
|
||||||
|
@ -117,30 +110,6 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
|
||||||
ClaimsListDao.save(ClaimsList.create(clock.nowUtc(), labelsToKeys));
|
ClaimsListDao.save(ClaimsList.create(clock.nowUtc(), labelsToKeys));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirms that an EppResourceIndex entity exists in Datastore for a given resource.
|
|
||||||
*/
|
|
||||||
protected static <T extends EppResource> void assertEppResourceIndexEntityFor(final T resource) {
|
|
||||||
if (!tm().isOfy()) {
|
|
||||||
// Indices aren't explicitly stored as objects in SQL
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ImmutableList<EppResourceIndex> 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 */
|
/** Asserts the presence of a single enqueued async contact or host deletion */
|
||||||
protected <T extends EppResource> void assertAsyncDeletionTaskEnqueued(
|
protected <T extends EppResource> void assertAsyncDeletionTaskEnqueued(
|
||||||
T resource, String requestingClientId, Trid trid, boolean isSuperuser) {
|
T resource, String requestingClientId, Trid trid, boolean isSuperuser) {
|
||||||
|
@ -180,7 +149,6 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertLastHistoryContainsResource(EppResource resource) {
|
protected void assertLastHistoryContainsResource(EppResource resource) {
|
||||||
if (!tm().isOfy()) {
|
|
||||||
HistoryEntry historyEntry = Iterables.getLast(DatabaseHelper.getHistoryEntries(resource));
|
HistoryEntry historyEntry = Iterables.getLast(DatabaseHelper.getHistoryEntries(resource));
|
||||||
if (resource instanceof ContactBase) {
|
if (resource instanceof ContactBase) {
|
||||||
ContactHistory contactHistory = (ContactHistory) historyEntry;
|
ContactHistory contactHistory = (ContactHistory) historyEntry;
|
||||||
|
@ -201,5 +169,4 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
|
||||||
.hasFieldsEqualTo(resource);
|
.hasFieldsEqualTo(resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
package google.registry.flows.contact;
|
package google.registry.flows.contact;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
|
||||||
import static google.registry.testing.ContactResourceSubject.assertAboutContacts;
|
import static google.registry.testing.ContactResourceSubject.assertAboutContacts;
|
||||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||||
import static google.registry.testing.DatabaseHelper.newContactResource;
|
import static google.registry.testing.DatabaseHelper.newContactResource;
|
||||||
|
@ -63,9 +62,6 @@ class ContactCreateFlowTest extends ResourceFlowTestCase<ContactCreateFlow, Cont
|
||||||
ContactResource contact = reloadResourceByForeignKey();
|
ContactResource contact = reloadResourceByForeignKey();
|
||||||
assertAboutContacts().that(contact).hasOnlyOneHistoryEntryWhich().hasNoXml();
|
assertAboutContacts().that(contact).hasOnlyOneHistoryEntryWhich().hasNoXml();
|
||||||
assertNoBillingEvents();
|
assertNoBillingEvents();
|
||||||
if (tm().isOfy()) {
|
|
||||||
assertEppResourceIndexEntityFor(contact);
|
|
||||||
}
|
|
||||||
assertLastHistoryContainsResource(contact);
|
assertLastHistoryContainsResource(contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@ package google.registry.flows.contact;
|
||||||
import static com.google.common.collect.MoreCollectors.onlyElement;
|
import static com.google.common.collect.MoreCollectors.onlyElement;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_DELETE;
|
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_DELETE;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
|
||||||
import static google.registry.testing.ContactResourceSubject.assertAboutContacts;
|
import static google.registry.testing.ContactResourceSubject.assertAboutContacts;
|
||||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||||
import static google.registry.testing.DatabaseHelper.createTld;
|
import static google.registry.testing.DatabaseHelper.createTld;
|
||||||
|
@ -87,26 +86,17 @@ class ContactDeleteFlowTest extends ResourceFlowTestCase<ContactDeleteFlow, Cont
|
||||||
@TestOfyAndSql
|
@TestOfyAndSql
|
||||||
void testDryRun() throws Exception {
|
void testDryRun() throws Exception {
|
||||||
persistActiveContact(getUniqueIdFromCommand());
|
persistActiveContact(getUniqueIdFromCommand());
|
||||||
if (tm().isOfy()) {
|
|
||||||
dryRunFlowAssertResponse(loadFile("contact_delete_response_pending.xml"));
|
|
||||||
} else {
|
|
||||||
dryRunFlowAssertResponse(loadFile("contact_delete_response.xml"));
|
dryRunFlowAssertResponse(loadFile("contact_delete_response.xml"));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@TestOfyAndSql
|
@TestOfyAndSql
|
||||||
void testSuccess() throws Exception {
|
void testSuccess() throws Exception {
|
||||||
persistActiveContact(getUniqueIdFromCommand());
|
persistActiveContact(getUniqueIdFromCommand());
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
assertTransactionalFlow(true);
|
assertTransactionalFlow(true);
|
||||||
if (tm().isOfy()) {
|
|
||||||
runFlowAssertResponse(loadFile("contact_delete_response_pending.xml"));
|
|
||||||
assertOfyDeleteSuccess();
|
|
||||||
} else {
|
|
||||||
runFlowAssertResponse(loadFile("contact_delete_response.xml"));
|
runFlowAssertResponse(loadFile("contact_delete_response.xml"));
|
||||||
assertSqlDeleteSuccess();
|
assertSqlDeleteSuccess();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@TestSqlOnly
|
@TestSqlOnly
|
||||||
void testSuccess_pendingTransfer_sql() throws Exception {
|
void testSuccess_pendingTransfer_sql() throws Exception {
|
||||||
|
@ -156,14 +146,9 @@ class ContactDeleteFlowTest extends ResourceFlowTestCase<ContactDeleteFlow, Cont
|
||||||
persistActiveContact(getUniqueIdFromCommand());
|
persistActiveContact(getUniqueIdFromCommand());
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
assertTransactionalFlow(true);
|
assertTransactionalFlow(true);
|
||||||
if (tm().isOfy()) {
|
|
||||||
runFlowAssertResponse(loadFile("contact_delete_response_no_cltrid_pending.xml"));
|
|
||||||
assertOfyDeleteSuccess("TheRegistrar", null, false);
|
|
||||||
} else {
|
|
||||||
runFlowAssertResponse(loadFile("contact_delete_response_no_cltrid.xml"));
|
runFlowAssertResponse(loadFile("contact_delete_response_no_cltrid.xml"));
|
||||||
assertSqlDeleteSuccess();
|
assertSqlDeleteSuccess();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@TestOfyAndSql
|
@TestOfyAndSql
|
||||||
void testFailure_neverExisted() throws Exception {
|
void testFailure_neverExisted() throws Exception {
|
||||||
|
@ -225,18 +210,10 @@ class ContactDeleteFlowTest extends ResourceFlowTestCase<ContactDeleteFlow, Cont
|
||||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||||
persistActiveContact(getUniqueIdFromCommand());
|
persistActiveContact(getUniqueIdFromCommand());
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
if (tm().isOfy()) {
|
|
||||||
runFlowAssertResponse(
|
|
||||||
CommitMode.LIVE,
|
|
||||||
UserPrivileges.SUPERUSER,
|
|
||||||
loadFile("contact_delete_response_pending.xml"));
|
|
||||||
assertOfyDeleteSuccess("NewRegistrar", "ABC-12345", true);
|
|
||||||
} else {
|
|
||||||
runFlowAssertResponse(
|
runFlowAssertResponse(
|
||||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("contact_delete_response.xml"));
|
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("contact_delete_response.xml"));
|
||||||
assertSqlDeleteSuccess();
|
assertSqlDeleteSuccess();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@TestOfyAndSql
|
@TestOfyAndSql
|
||||||
void testFailure_failfastWhenLinkedToDomain() throws Exception {
|
void testFailure_failfastWhenLinkedToDomain() throws Exception {
|
||||||
|
@ -272,25 +249,6 @@ class ContactDeleteFlowTest extends ResourceFlowTestCase<ContactDeleteFlow, Cont
|
||||||
DatabaseHelper.removeDatabaseMigrationSchedule();
|
DatabaseHelper.removeDatabaseMigrationSchedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertOfyDeleteSuccess(String registrarId, String clientTrid, boolean isSuperuser)
|
|
||||||
throws Exception {
|
|
||||||
ContactResource deletedContact = reloadResourceByForeignKey();
|
|
||||||
assertAsyncDeletionTaskEnqueued(
|
|
||||||
deletedContact, registrarId, Trid.create(clientTrid, "server-trid"), isSuperuser);
|
|
||||||
assertAboutContacts()
|
|
||||||
.that(deletedContact)
|
|
||||||
.hasStatusValue(StatusValue.PENDING_DELETE)
|
|
||||||
.and()
|
|
||||||
.hasOnlyOneHistoryEntryWhich()
|
|
||||||
.hasType(Type.CONTACT_PENDING_DELETE);
|
|
||||||
assertNoBillingEvents();
|
|
||||||
assertLastHistoryContainsResource(deletedContact);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertOfyDeleteSuccess() throws Exception {
|
|
||||||
assertOfyDeleteSuccess("TheRegistrar", "ABC-12345", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertSqlDeleteSuccess(HistoryEntry.Type... historyEntryTypes) throws Exception {
|
private void assertSqlDeleteSuccess(HistoryEntry.Type... historyEntryTypes) throws Exception {
|
||||||
assertThat(reloadResourceByForeignKey()).isNull();
|
assertThat(reloadResourceByForeignKey()).isNull();
|
||||||
assertAboutContacts()
|
assertAboutContacts()
|
||||||
|
|
|
@ -368,7 +368,6 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
|
||||||
GracePeriodStatus.ADD, domain.getRepoId(), billingTime, "TheRegistrar", null),
|
GracePeriodStatus.ADD, domain.getRepoId(), billingTime, "TheRegistrar", null),
|
||||||
createBillingEvent));
|
createBillingEvent));
|
||||||
assertDnsTasksEnqueued(getUniqueIdFromCommand());
|
assertDnsTasksEnqueued(getUniqueIdFromCommand());
|
||||||
assertEppResourceIndexEntityFor(domain);
|
|
||||||
|
|
||||||
replayExtension.expectUpdateFor(domain);
|
replayExtension.expectUpdateFor(domain);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ package google.registry.flows.host;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
|
||||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||||
import static google.registry.testing.DatabaseHelper.createTld;
|
import static google.registry.testing.DatabaseHelper.createTld;
|
||||||
import static google.registry.testing.DatabaseHelper.createTlds;
|
import static google.registry.testing.DatabaseHelper.createTlds;
|
||||||
|
@ -105,9 +104,6 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
|
||||||
.hasOnlyOneHistoryEntryWhich()
|
.hasOnlyOneHistoryEntryWhich()
|
||||||
.hasType(HistoryEntry.Type.HOST_CREATE);
|
.hasType(HistoryEntry.Type.HOST_CREATE);
|
||||||
assertNoBillingEvents();
|
assertNoBillingEvents();
|
||||||
if (tm().isOfy()) {
|
|
||||||
assertEppResourceIndexEntityFor(host);
|
|
||||||
}
|
|
||||||
assertLastHistoryContainsResource(host);
|
assertLastHistoryContainsResource(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ package google.registry.flows.host;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_DELETE;
|
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_DELETE;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
|
||||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||||
import static google.registry.testing.DatabaseHelper.createTld;
|
import static google.registry.testing.DatabaseHelper.createTld;
|
||||||
import static google.registry.testing.DatabaseHelper.loadByKey;
|
import static google.registry.testing.DatabaseHelper.loadByKey;
|
||||||
|
@ -47,7 +46,6 @@ import google.registry.flows.host.HostFlowUtils.HostNameNotNormalizedException;
|
||||||
import google.registry.flows.host.HostFlowUtils.HostNameNotPunyCodedException;
|
import google.registry.flows.host.HostFlowUtils.HostNameNotPunyCodedException;
|
||||||
import google.registry.model.domain.DomainBase;
|
import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.model.eppcommon.StatusValue;
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
import google.registry.model.eppcommon.Trid;
|
|
||||||
import google.registry.model.host.HostResource;
|
import google.registry.model.host.HostResource;
|
||||||
import google.registry.model.reporting.HistoryEntry.Type;
|
import google.registry.model.reporting.HistoryEntry.Type;
|
||||||
import google.registry.model.tld.Registry;
|
import google.registry.model.tld.Registry;
|
||||||
|
@ -86,26 +84,17 @@ class HostDeleteFlowTest extends ResourceFlowTestCase<HostDeleteFlow, HostResour
|
||||||
@TestOfyAndSql
|
@TestOfyAndSql
|
||||||
void testDryRun() throws Exception {
|
void testDryRun() throws Exception {
|
||||||
persistActiveHost("ns1.example.tld");
|
persistActiveHost("ns1.example.tld");
|
||||||
if (tm().isOfy()) {
|
|
||||||
dryRunFlowAssertResponse(loadFile("host_delete_response_pending.xml"));
|
|
||||||
} else {
|
|
||||||
dryRunFlowAssertResponse(loadFile("host_delete_response.xml"));
|
dryRunFlowAssertResponse(loadFile("host_delete_response.xml"));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@TestOfyAndSql
|
@TestOfyAndSql
|
||||||
void testSuccess() throws Exception {
|
void testSuccess() throws Exception {
|
||||||
persistActiveHost("ns1.example.tld");
|
persistActiveHost("ns1.example.tld");
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
assertTransactionalFlow(true);
|
assertTransactionalFlow(true);
|
||||||
if (tm().isOfy()) {
|
|
||||||
runFlowAssertResponse(loadFile("host_delete_response_pending.xml"));
|
|
||||||
assertOfyDeleteSuccess();
|
|
||||||
} else {
|
|
||||||
runFlowAssertResponse(loadFile("host_delete_response.xml"));
|
runFlowAssertResponse(loadFile("host_delete_response.xml"));
|
||||||
assertSqlDeleteSuccess();
|
assertSqlDeleteSuccess();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@TestOfyAndSql
|
@TestOfyAndSql
|
||||||
void testSuccess_clTridNotSpecified() throws Exception {
|
void testSuccess_clTridNotSpecified() throws Exception {
|
||||||
|
@ -113,14 +102,9 @@ class HostDeleteFlowTest extends ResourceFlowTestCase<HostDeleteFlow, HostResour
|
||||||
persistActiveHost("ns1.example.tld");
|
persistActiveHost("ns1.example.tld");
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
assertTransactionalFlow(true);
|
assertTransactionalFlow(true);
|
||||||
if (tm().isOfy()) {
|
|
||||||
runFlowAssertResponse(loadFile("host_delete_response_no_cltrid_pending.xml"));
|
|
||||||
assertOfyDeleteSuccess("TheRegistrar", null, false);
|
|
||||||
} else {
|
|
||||||
runFlowAssertResponse(loadFile("host_delete_response_no_cltrid.xml"));
|
runFlowAssertResponse(loadFile("host_delete_response_no_cltrid.xml"));
|
||||||
assertSqlDeleteSuccess();
|
assertSqlDeleteSuccess();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@TestOfyAndSql
|
@TestOfyAndSql
|
||||||
void testFailure_neverExisted() {
|
void testFailure_neverExisted() {
|
||||||
|
@ -178,16 +162,10 @@ class HostDeleteFlowTest extends ResourceFlowTestCase<HostDeleteFlow, HostResour
|
||||||
sessionMetadata.setRegistrarId("NewRegistrar");
|
sessionMetadata.setRegistrarId("NewRegistrar");
|
||||||
persistActiveHost("ns1.example.tld");
|
persistActiveHost("ns1.example.tld");
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
if (tm().isOfy()) {
|
|
||||||
runFlowAssertResponse(
|
|
||||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("host_delete_response_pending.xml"));
|
|
||||||
assertOfyDeleteSuccess("NewRegistrar", "ABC-12345", true);
|
|
||||||
} else {
|
|
||||||
runFlowAssertResponse(
|
runFlowAssertResponse(
|
||||||
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("host_delete_response.xml"));
|
CommitMode.LIVE, UserPrivileges.SUPERUSER, loadFile("host_delete_response.xml"));
|
||||||
assertSqlDeleteSuccess();
|
assertSqlDeleteSuccess();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@TestOfyAndSql
|
@TestOfyAndSql
|
||||||
void testSuccess_authorizedClientReadFromSuperordinate() throws Exception {
|
void testSuccess_authorizedClientReadFromSuperordinate() throws Exception {
|
||||||
|
@ -206,14 +184,9 @@ class HostDeleteFlowTest extends ResourceFlowTestCase<HostDeleteFlow, HostResour
|
||||||
.setSuperordinateDomain(domain.createVKey())
|
.setSuperordinateDomain(domain.createVKey())
|
||||||
.build());
|
.build());
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
if (tm().isOfy()) {
|
|
||||||
runFlowAssertResponse(loadFile("host_delete_response_pending.xml"));
|
|
||||||
assertOfyDeleteSuccess();
|
|
||||||
} else {
|
|
||||||
runFlowAssertResponse(loadFile("host_delete_response.xml"));
|
runFlowAssertResponse(loadFile("host_delete_response.xml"));
|
||||||
assertSqlDeleteSuccess(true);
|
assertSqlDeleteSuccess(true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@TestOfyAndSql
|
@TestOfyAndSql
|
||||||
void testFailure_unauthorizedClientReadFromSuperordinate() {
|
void testFailure_unauthorizedClientReadFromSuperordinate() {
|
||||||
|
@ -265,14 +238,9 @@ class HostDeleteFlowTest extends ResourceFlowTestCase<HostDeleteFlow, HostResour
|
||||||
.setSuperordinateDomain(domain.createVKey())
|
.setSuperordinateDomain(domain.createVKey())
|
||||||
.build());
|
.build());
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
if (tm().isOfy()) {
|
|
||||||
runFlowAssertResponse(loadFile("host_delete_response_pending.xml"));
|
|
||||||
assertOfyDeleteSuccess("NewRegistrar", "ABC-12345", false);
|
|
||||||
} else {
|
|
||||||
runFlowAssertResponse(loadFile("host_delete_response.xml"));
|
runFlowAssertResponse(loadFile("host_delete_response.xml"));
|
||||||
assertSqlDeleteSuccess(true);
|
assertSqlDeleteSuccess(true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@TestOfyAndSql
|
@TestOfyAndSql
|
||||||
void testFailure_unauthorizedClientReadFromTransferredSuperordinate() {
|
void testFailure_unauthorizedClientReadFromTransferredSuperordinate() {
|
||||||
|
@ -367,25 +335,6 @@ class HostDeleteFlowTest extends ResourceFlowTestCase<HostDeleteFlow, HostResour
|
||||||
DatabaseHelper.removeDatabaseMigrationSchedule();
|
DatabaseHelper.removeDatabaseMigrationSchedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertOfyDeleteSuccess(String registrarId, String clientTrid, boolean isSuperuser)
|
|
||||||
throws Exception {
|
|
||||||
HostResource deletedHost = reloadResourceByForeignKey();
|
|
||||||
assertAsyncDeletionTaskEnqueued(
|
|
||||||
deletedHost, registrarId, Trid.create(clientTrid, "server-trid"), isSuperuser);
|
|
||||||
assertAboutHosts()
|
|
||||||
.that(deletedHost)
|
|
||||||
.hasStatusValue(StatusValue.PENDING_DELETE)
|
|
||||||
.and()
|
|
||||||
.hasOnlyOneHistoryEntryWhich()
|
|
||||||
.hasType(Type.HOST_PENDING_DELETE);
|
|
||||||
assertNoBillingEvents();
|
|
||||||
assertNoDnsTasksEnqueued();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertOfyDeleteSuccess() throws Exception {
|
|
||||||
assertOfyDeleteSuccess("TheRegistrar", "ABC-12345", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertSqlDeleteSuccess(boolean isSubordinate) throws Exception {
|
private void assertSqlDeleteSuccess(boolean isSubordinate) throws Exception {
|
||||||
assertThat(reloadResourceByForeignKey()).isNull();
|
assertThat(reloadResourceByForeignKey()).isNull();
|
||||||
HostResource deletedHost = reloadResourceByForeignKey(clock.nowUtc().minusMillis(1));
|
HostResource deletedHost = reloadResourceByForeignKey(clock.nowUtc().minusMillis(1));
|
||||||
|
|
|
@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_HOST_KEY;
|
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_HOST_KEY;
|
||||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_HOST_RENAME;
|
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_HOST_RENAME;
|
||||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
|
||||||
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
|
||||||
import static google.registry.testing.DatabaseHelper.createTld;
|
import static google.registry.testing.DatabaseHelper.createTld;
|
||||||
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
|
import static google.registry.testing.DatabaseHelper.getOnlyHistoryEntryOfType;
|
||||||
|
@ -198,16 +197,6 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, HostResour
|
||||||
HostResource renamedHost = doSuccessfulTest();
|
HostResource renamedHost = doSuccessfulTest();
|
||||||
assertThat(renamedHost.isSubordinate()).isTrue();
|
assertThat(renamedHost.isSubordinate()).isTrue();
|
||||||
assertDnsTasksEnqueued("ns1.example.tld", "ns2.example.tld");
|
assertDnsTasksEnqueued("ns1.example.tld", "ns2.example.tld");
|
||||||
// As we don't actually store ForeignKeyIndex<HostResource> 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<HostResource> oldFkiBeforeRename =
|
|
||||||
ForeignKeyIndex.load(HostResource.class, oldHostName(), clock.nowUtc().minusMillis(1));
|
|
||||||
assertThat(oldFkiBeforeRename.getResourceKey()).isEqualTo(renamedHost.createVKey());
|
|
||||||
assertThat(oldFkiBeforeRename.getDeletionTime()).isEqualTo(clock.nowUtc());
|
|
||||||
}
|
|
||||||
ForeignKeyIndex<HostResource> oldFkiAfterRename =
|
ForeignKeyIndex<HostResource> oldFkiAfterRename =
|
||||||
ForeignKeyIndex.load(HostResource.class, oldHostName(), clock.nowUtc());
|
ForeignKeyIndex.load(HostResource.class, oldHostName(), clock.nowUtc());
|
||||||
assertThat(oldFkiAfterRename).isNull();
|
assertThat(oldFkiAfterRename).isNull();
|
||||||
|
|
|
@ -40,10 +40,11 @@ import google.registry.rdap.RdapMetrics.SearchType;
|
||||||
import google.registry.rdap.RdapMetrics.WildcardType;
|
import google.registry.rdap.RdapMetrics.WildcardType;
|
||||||
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
|
import google.registry.rdap.RdapSearchResults.IncompletenessWarningType;
|
||||||
import google.registry.request.Action;
|
import google.registry.request.Action;
|
||||||
|
import google.registry.testing.DualDatabaseTest;
|
||||||
|
import google.registry.testing.TestOfyAndSql;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link RdapDomainAction}.
|
* Unit tests for {@link RdapDomainAction}.
|
||||||
|
@ -51,6 +52,7 @@ import org.junit.jupiter.api.Test;
|
||||||
* <p>TODO(b/26872828): The next time we do any work on RDAP, consider adding the APNIC RDAP
|
* <p>TODO(b/26872828): The next time we do any work on RDAP, consider adding the APNIC RDAP
|
||||||
* conformance checker to the unit test suite.
|
* conformance checker to the unit test suite.
|
||||||
*/
|
*/
|
||||||
|
@DualDatabaseTest
|
||||||
class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||||
|
|
||||||
RdapDomainActionTest() {
|
RdapDomainActionTest() {
|
||||||
|
@ -247,7 +249,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||||
assertThat(response.getStatus()).isEqualTo(200);
|
assertThat(response.getStatus()).isEqualTo(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testInvalidDomain_returns400() {
|
void testInvalidDomain_returns400() {
|
||||||
assertThat(generateActualJson("invalid/domain/name"))
|
assertThat(generateActualJson("invalid/domain/name"))
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
|
@ -258,7 +260,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||||
assertThat(response.getStatus()).isEqualTo(400);
|
assertThat(response.getStatus()).isEqualTo(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUnknownDomain_returns400() {
|
void testUnknownDomain_returns400() {
|
||||||
assertThat(generateActualJson("missingdomain.com"))
|
assertThat(generateActualJson("missingdomain.com"))
|
||||||
.isEqualTo(
|
.isEqualTo(
|
||||||
|
@ -269,45 +271,45 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||||
assertThat(response.getStatus()).isEqualTo(400);
|
assertThat(response.getStatus()).isEqualTo(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testValidDomain_works() {
|
void testValidDomain_works() {
|
||||||
login("evilregistrar");
|
login("evilregistrar");
|
||||||
assertProperResponseForCatLol("cat.lol", "rdap_domain.json");
|
assertProperResponseForCatLol("cat.lol", "rdap_domain.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testValidDomain_asAdministrator_works() {
|
void testValidDomain_asAdministrator_works() {
|
||||||
loginAsAdmin();
|
loginAsAdmin();
|
||||||
assertProperResponseForCatLol("cat.lol", "rdap_domain.json");
|
assertProperResponseForCatLol("cat.lol", "rdap_domain.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testValidDomain_notLoggedIn_noContacts() {
|
void testValidDomain_notLoggedIn_noContacts() {
|
||||||
assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts_with_remark.json");
|
assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts_with_remark.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testValidDomain_loggedInAsOtherRegistrar_noContacts() {
|
void testValidDomain_loggedInAsOtherRegistrar_noContacts() {
|
||||||
login("idnregistrar");
|
login("idnregistrar");
|
||||||
assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts_with_remark.json");
|
assertProperResponseForCatLol("cat.lol", "rdap_domain_no_contacts_with_remark.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUpperCase_ignored() {
|
void testUpperCase_ignored() {
|
||||||
assertProperResponseForCatLol("CaT.lOl", "rdap_domain_no_contacts_with_remark.json");
|
assertProperResponseForCatLol("CaT.lOl", "rdap_domain_no_contacts_with_remark.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testTrailingDot_ignored() {
|
void testTrailingDot_ignored() {
|
||||||
assertProperResponseForCatLol("cat.lol.", "rdap_domain_no_contacts_with_remark.json");
|
assertProperResponseForCatLol("cat.lol.", "rdap_domain_no_contacts_with_remark.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testQueryParameter_ignored() {
|
void testQueryParameter_ignored() {
|
||||||
assertProperResponseForCatLol("cat.lol?key=value", "rdap_domain_no_contacts_with_remark.json");
|
assertProperResponseForCatLol("cat.lol?key=value", "rdap_domain_no_contacts_with_remark.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testIdnDomain_works() {
|
void testIdnDomain_works() {
|
||||||
login("idnregistrar");
|
login("idnregistrar");
|
||||||
assertThat(generateActualJson("cat.みんな"))
|
assertThat(generateActualJson("cat.みんな"))
|
||||||
|
@ -325,7 +327,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||||
assertThat(response.getStatus()).isEqualTo(200);
|
assertThat(response.getStatus()).isEqualTo(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testIdnDomainWithPercentEncoding_works() {
|
void testIdnDomainWithPercentEncoding_works() {
|
||||||
login("idnregistrar");
|
login("idnregistrar");
|
||||||
assertThat(generateActualJson("cat.%E3%81%BF%E3%82%93%E3%81%AA"))
|
assertThat(generateActualJson("cat.%E3%81%BF%E3%82%93%E3%81%AA"))
|
||||||
|
@ -343,7 +345,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||||
assertThat(response.getStatus()).isEqualTo(200);
|
assertThat(response.getStatus()).isEqualTo(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testPunycodeDomain_works() {
|
void testPunycodeDomain_works() {
|
||||||
login("idnregistrar");
|
login("idnregistrar");
|
||||||
assertThat(generateActualJson("cat.xn--q9jyb4c"))
|
assertThat(generateActualJson("cat.xn--q9jyb4c"))
|
||||||
|
@ -361,7 +363,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||||
assertThat(response.getStatus()).isEqualTo(200);
|
assertThat(response.getStatus()).isEqualTo(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testMultilevelDomain_works() {
|
void testMultilevelDomain_works() {
|
||||||
login("1tldregistrar");
|
login("1tldregistrar");
|
||||||
assertThat(generateActualJson("cat.1.tld"))
|
assertThat(generateActualJson("cat.1.tld"))
|
||||||
|
@ -381,35 +383,35 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||||
|
|
||||||
// todo (b/27378695): reenable or delete this test
|
// todo (b/27378695): reenable or delete this test
|
||||||
@Disabled
|
@Disabled
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testDomainInTestTld_notFound() {
|
void testDomainInTestTld_notFound() {
|
||||||
persistResource(Registry.get("lol").asBuilder().setTldType(Registry.TldType.TEST).build());
|
persistResource(Registry.get("lol").asBuilder().setTldType(Registry.TldType.TEST).build());
|
||||||
generateActualJson("cat.lol");
|
generateActualJson("cat.lol");
|
||||||
assertThat(response.getStatus()).isEqualTo(404);
|
assertThat(response.getStatus()).isEqualTo(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testDeletedDomain_notFound() {
|
void testDeletedDomain_notFound() {
|
||||||
assertThat(generateActualJson("dodo.lol"))
|
assertThat(generateActualJson("dodo.lol"))
|
||||||
.isEqualTo(generateExpectedJsonError("dodo.lol not found", 404));
|
.isEqualTo(generateExpectedJsonError("dodo.lol not found", 404));
|
||||||
assertThat(response.getStatus()).isEqualTo(404);
|
assertThat(response.getStatus()).isEqualTo(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testDeletedDomain_notFound_includeDeletedSetFalse() {
|
void testDeletedDomain_notFound_includeDeletedSetFalse() {
|
||||||
action.includeDeletedParam = Optional.of(true);
|
action.includeDeletedParam = Optional.of(true);
|
||||||
generateActualJson("dodo.lol");
|
generateActualJson("dodo.lol");
|
||||||
assertThat(response.getStatus()).isEqualTo(404);
|
assertThat(response.getStatus()).isEqualTo(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testDeletedDomain_notFound_notLoggedIn() {
|
void testDeletedDomain_notFound_notLoggedIn() {
|
||||||
action.includeDeletedParam = Optional.of(true);
|
action.includeDeletedParam = Optional.of(true);
|
||||||
generateActualJson("dodo.lol");
|
generateActualJson("dodo.lol");
|
||||||
assertThat(response.getStatus()).isEqualTo(404);
|
assertThat(response.getStatus()).isEqualTo(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testDeletedDomain_notFound_loggedInAsDifferentRegistrar() {
|
void testDeletedDomain_notFound_loggedInAsDifferentRegistrar() {
|
||||||
login("1tldregistrar");
|
login("1tldregistrar");
|
||||||
action.includeDeletedParam = Optional.of(true);
|
action.includeDeletedParam = Optional.of(true);
|
||||||
|
@ -417,7 +419,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||||
assertThat(response.getStatus()).isEqualTo(404);
|
assertThat(response.getStatus()).isEqualTo(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testDeletedDomain_works_loggedInAsCorrectRegistrar() {
|
void testDeletedDomain_works_loggedInAsCorrectRegistrar() {
|
||||||
login("evilregistrar");
|
login("evilregistrar");
|
||||||
action.includeDeletedParam = Optional.of(true);
|
action.includeDeletedParam = Optional.of(true);
|
||||||
|
@ -436,7 +438,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||||
assertThat(response.getStatus()).isEqualTo(200);
|
assertThat(response.getStatus()).isEqualTo(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testDeletedDomain_works_loggedInAsAdmin() {
|
void testDeletedDomain_works_loggedInAsAdmin() {
|
||||||
loginAsAdmin();
|
loginAsAdmin();
|
||||||
action.includeDeletedParam = Optional.of(true);
|
action.includeDeletedParam = Optional.of(true);
|
||||||
|
@ -455,7 +457,7 @@ class RdapDomainActionTest extends RdapActionBaseTestCase<RdapDomainAction> {
|
||||||
assertThat(response.getStatus()).isEqualTo(200);
|
assertThat(response.getStatus()).isEqualTo(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testMetrics() {
|
void testMetrics() {
|
||||||
generateActualJson("cat.lol");
|
generateActualJson("cat.lol");
|
||||||
verify(rdapMetrics)
|
verify(rdapMetrics)
|
||||||
|
|
|
@ -29,27 +29,33 @@ import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.model.domain.DomainHistory;
|
import google.registry.model.domain.DomainHistory;
|
||||||
import google.registry.model.reporting.HistoryEntry.Type;
|
import google.registry.model.reporting.HistoryEntry.Type;
|
||||||
import google.registry.testing.AppEngineExtension;
|
import google.registry.testing.AppEngineExtension;
|
||||||
|
import google.registry.testing.DualDatabaseTest;
|
||||||
|
import google.registry.testing.TestOfyAndSql;
|
||||||
import google.registry.util.Clock;
|
import google.registry.util.Clock;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.joda.money.Money;
|
import org.joda.money.Money;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
/** Tests for tools that affect EPP lifecycle. */
|
/** Tests for tools that affect EPP lifecycle. */
|
||||||
|
@DualDatabaseTest
|
||||||
class EppLifecycleToolsTest extends EppTestCase {
|
class EppLifecycleToolsTest extends EppTestCase {
|
||||||
|
|
||||||
@RegisterExtension
|
@RegisterExtension
|
||||||
final AppEngineExtension appEngine =
|
final AppEngineExtension appEngine =
|
||||||
AppEngineExtension.builder().withDatastoreAndCloudSql().withTaskQueue().build();
|
AppEngineExtension.builder()
|
||||||
|
.withClock(clock)
|
||||||
|
.withDatastoreAndCloudSql()
|
||||||
|
.withTaskQueue()
|
||||||
|
.build();
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void beforeEach() {
|
void beforeEach() {
|
||||||
createTlds("example", "tld");
|
createTlds("example", "tld");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void test_renewDomainThenUnrenew() throws Exception {
|
void test_renewDomainThenUnrenew() throws Exception {
|
||||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||||
createContacts(DateTime.parse("2000-06-01T00:00:00Z"));
|
createContacts(DateTime.parse("2000-06-01T00:00:00Z"));
|
||||||
|
|
|
@ -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<CreateSyntheticHistoryEntriesAction> {
|
|
||||||
|
|
||||||
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<? extends HistoryEntry> 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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <T extends EppResource> void validateHistoryEntry(
|
|
||||||
Class<? extends HistoryEntry> historyClazz, T resource) {
|
|
||||||
ImmutableList<? extends HistoryEntry> 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,12 +20,14 @@ import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import google.registry.model.OteStatsTestHelper;
|
import google.registry.model.OteStatsTestHelper;
|
||||||
import google.registry.testing.AppEngineExtension;
|
import google.registry.testing.AppEngineExtension;
|
||||||
|
import google.registry.testing.DualDatabaseTest;
|
||||||
|
import google.registry.testing.TestOfyAndSql;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
/** Unit tests for {@link VerifyOteAction}. */
|
/** Unit tests for {@link VerifyOteAction}. */
|
||||||
|
@DualDatabaseTest
|
||||||
class VerifyOteActionTest {
|
class VerifyOteActionTest {
|
||||||
|
|
||||||
@RegisterExtension
|
@RegisterExtension
|
||||||
|
@ -34,21 +36,21 @@ class VerifyOteActionTest {
|
||||||
|
|
||||||
private final VerifyOteAction action = new VerifyOteAction();
|
private final VerifyOteAction action = new VerifyOteAction();
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_summarize_allPass() throws Exception {
|
void testSuccess_summarize_allPass() throws Exception {
|
||||||
OteStatsTestHelper.setupCompleteOte("blobio");
|
OteStatsTestHelper.setupCompleteOte("blobio");
|
||||||
assertThat(getResponse(true))
|
assertThat(getResponse(true))
|
||||||
.isEqualTo("# actions: 30 - Reqs: [----------------] 16/16 - Overall: PASS");
|
.isEqualTo("# actions: 30 - Reqs: [----------------] 16/16 - Overall: PASS");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_summarize_someFailures() throws Exception {
|
void testFailure_summarize_someFailures() throws Exception {
|
||||||
OteStatsTestHelper.setupIncompleteOte("blobio");
|
OteStatsTestHelper.setupIncompleteOte("blobio");
|
||||||
assertThat(getResponse(true))
|
assertThat(getResponse(true))
|
||||||
.isEqualTo("# actions: 34 - Reqs: [-.-----.------.-] 13/16 - Overall: FAIL");
|
.isEqualTo("# actions: 34 - Reqs: [-.-----.------.-] 13/16 - Overall: FAIL");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_passNotSummarized() throws Exception {
|
void testSuccess_passNotSummarized() throws Exception {
|
||||||
OteStatsTestHelper.setupCompleteOte("blobio");
|
OteStatsTestHelper.setupCompleteOte("blobio");
|
||||||
String expectedOteStatus =
|
String expectedOteStatus =
|
||||||
|
@ -78,7 +80,7 @@ class VerifyOteActionTest {
|
||||||
assertThat(getResponse(false)).containsMatch(expectedOteStatusPattern);
|
assertThat(getResponse(false)).containsMatch(expectedOteStatusPattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_incomplete() throws Exception {
|
void testFailure_incomplete() throws Exception {
|
||||||
OteStatsTestHelper.setupIncompleteOte("blobio");
|
OteStatsTestHelper.setupIncompleteOte("blobio");
|
||||||
String expectedOteStatus =
|
String expectedOteStatus =
|
||||||
|
|
|
@ -30,14 +30,16 @@ import google.registry.model.registrar.Registrar.Type;
|
||||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
||||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
|
import google.registry.request.auth.AuthenticatedRegistrarAccessor.Role;
|
||||||
import google.registry.testing.AppEngineExtension;
|
import google.registry.testing.AppEngineExtension;
|
||||||
|
import google.registry.testing.DualDatabaseTest;
|
||||||
|
import google.registry.testing.TestOfyAndSql;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
/** Unit tests for {@link OteStatusAction} */
|
/** Unit tests for {@link OteStatusAction} */
|
||||||
|
@DualDatabaseTest
|
||||||
public final class OteStatusActionTest {
|
public final class OteStatusActionTest {
|
||||||
|
|
||||||
private static final String CLIENT_ID = "blobio-1";
|
private static final String CLIENT_ID = "blobio-1";
|
||||||
|
@ -57,7 +59,7 @@ public final class OteStatusActionTest {
|
||||||
action.registrarAccessor = AuthenticatedRegistrarAccessor.createForTesting(authValues);
|
action.registrarAccessor = AuthenticatedRegistrarAccessor.createForTesting(authValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
void testSuccess_finishedOte() throws Exception {
|
void testSuccess_finishedOte() throws Exception {
|
||||||
OteStatsTestHelper.setupCompleteOte(BASE_CLIENT_ID);
|
OteStatsTestHelper.setupCompleteOte(BASE_CLIENT_ID);
|
||||||
|
@ -72,7 +74,7 @@ public final class OteStatusActionTest {
|
||||||
assertThat(getFailingResultDetails(results)).isEmpty();
|
assertThat(getFailingResultDetails(results)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
void testSuccess_incomplete() throws Exception {
|
void testSuccess_incomplete() throws Exception {
|
||||||
OteStatsTestHelper.setupIncompleteOte(BASE_CLIENT_ID);
|
OteStatsTestHelper.setupIncompleteOte(BASE_CLIENT_ID);
|
||||||
|
@ -103,7 +105,7 @@ public final class OteStatusActionTest {
|
||||||
"completed", false));
|
"completed", false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_malformedInput() {
|
void testFailure_malformedInput() {
|
||||||
assertThat(action.handleJsonRequest(null))
|
assertThat(action.handleJsonRequest(null))
|
||||||
.containsExactlyEntriesIn(errorResultWithMessage("Malformed JSON"));
|
.containsExactlyEntriesIn(errorResultWithMessage("Malformed JSON"));
|
||||||
|
@ -111,14 +113,14 @@ public final class OteStatusActionTest {
|
||||||
.containsExactlyEntriesIn(errorResultWithMessage("Missing key for OT&E client: clientId"));
|
.containsExactlyEntriesIn(errorResultWithMessage("Missing key for OT&E client: clientId"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_registrarDoesntExist() {
|
void testFailure_registrarDoesntExist() {
|
||||||
assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", "nonexistent-3")))
|
assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", "nonexistent-3")))
|
||||||
.containsExactlyEntriesIn(
|
.containsExactlyEntriesIn(
|
||||||
errorResultWithMessage("Registrar nonexistent-3 does not exist"));
|
errorResultWithMessage("Registrar nonexistent-3 does not exist"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_notAuthorized() {
|
void testFailure_notAuthorized() {
|
||||||
persistNewRegistrar(CLIENT_ID, "blobio-1", Type.REAL, 1L);
|
persistNewRegistrar(CLIENT_ID, "blobio-1", Type.REAL, 1L);
|
||||||
action.registrarAccessor =
|
action.registrarAccessor =
|
||||||
|
@ -128,7 +130,7 @@ public final class OteStatusActionTest {
|
||||||
errorResultWithMessage("TestUserId doesn't have access to registrar blobio-1"));
|
errorResultWithMessage("TestUserId doesn't have access to registrar blobio-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_malformedRegistrarName() {
|
void testFailure_malformedRegistrarName() {
|
||||||
assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", "badclient-id")))
|
assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", "badclient-id")))
|
||||||
.containsExactlyEntriesIn(
|
.containsExactlyEntriesIn(
|
||||||
|
@ -136,7 +138,7 @@ public final class OteStatusActionTest {
|
||||||
"ID badclient-id is not one of the OT&E registrar IDs for base badclient"));
|
"ID badclient-id is not one of the OT&E registrar IDs for base badclient"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_nonOteRegistrar() {
|
void testFailure_nonOteRegistrar() {
|
||||||
persistNewRegistrar(CLIENT_ID, "SomeRegistrar", Type.REAL, 1L);
|
persistNewRegistrar(CLIENT_ID, "SomeRegistrar", Type.REAL, 1L);
|
||||||
assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", CLIENT_ID)))
|
assertThat(action.handleJsonRequest(ImmutableMap.of("clientId", CLIENT_ID)))
|
||||||
|
|
|
@ -49,8 +49,10 @@ import google.registry.testing.AppEngineExtension;
|
||||||
import google.registry.testing.CloudTasksHelper;
|
import google.registry.testing.CloudTasksHelper;
|
||||||
import google.registry.testing.DatabaseHelper;
|
import google.registry.testing.DatabaseHelper;
|
||||||
import google.registry.testing.DeterministicStringGenerator;
|
import google.registry.testing.DeterministicStringGenerator;
|
||||||
|
import google.registry.testing.DualDatabaseTest;
|
||||||
import google.registry.testing.FakeClock;
|
import google.registry.testing.FakeClock;
|
||||||
import google.registry.testing.FakeResponse;
|
import google.registry.testing.FakeResponse;
|
||||||
|
import google.registry.testing.TestOfyAndSql;
|
||||||
import google.registry.testing.UserInfo;
|
import google.registry.testing.UserInfo;
|
||||||
import google.registry.tools.DomainLockUtils;
|
import google.registry.tools.DomainLockUtils;
|
||||||
import google.registry.util.StringGenerator;
|
import google.registry.util.StringGenerator;
|
||||||
|
@ -58,10 +60,10 @@ import google.registry.util.StringGenerator.Alphabets;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import org.joda.time.Duration;
|
import org.joda.time.Duration;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
/** Unit tests for {@link RegistryLockVerifyAction}. */
|
/** Unit tests for {@link RegistryLockVerifyAction}. */
|
||||||
|
@DualDatabaseTest
|
||||||
final class RegistryLockVerifyActionTest {
|
final class RegistryLockVerifyActionTest {
|
||||||
|
|
||||||
private final FakeClock fakeClock = new FakeClock();
|
private final FakeClock fakeClock = new FakeClock();
|
||||||
|
@ -96,7 +98,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
action = createAction(lockId, true);
|
action = createAction(lockId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_lockDomain() {
|
void testSuccess_lockDomain() {
|
||||||
saveRegistryLock(createLock());
|
saveRegistryLock(createLock());
|
||||||
action.run();
|
action.run();
|
||||||
|
@ -112,7 +114,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertBillingEvent(historyEntry);
|
assertBillingEvent(historyEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_unlockDomain() {
|
void testSuccess_unlockDomain() {
|
||||||
action = createAction(lockId, false);
|
action = createAction(lockId, false);
|
||||||
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
|
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
|
||||||
|
@ -130,7 +132,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertBillingEvent(historyEntry);
|
assertBillingEvent(historyEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_adminLock_createsOnlyHistoryEntry() {
|
void testSuccess_adminLock_createsOnlyHistoryEntry() {
|
||||||
action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, true));
|
action.authResult = AuthResult.create(AuthLevel.USER, UserAuthInfo.create(user, true));
|
||||||
saveRegistryLock(createLock().asBuilder().isSuperuser(true).build());
|
saveRegistryLock(createLock().asBuilder().isSuperuser(true).build());
|
||||||
|
@ -142,7 +144,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
DatabaseHelper.assertNoBillingEvents();
|
DatabaseHelper.assertNoBillingEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_badVerificationCode() {
|
void testFailure_badVerificationCode() {
|
||||||
saveRegistryLock(
|
saveRegistryLock(
|
||||||
createLock().asBuilder().setVerificationCode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").build());
|
createLock().asBuilder().setVerificationCode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").build());
|
||||||
|
@ -151,7 +153,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertNoDomainChanges();
|
assertNoDomainChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_alreadyVerified() {
|
void testFailure_alreadyVerified() {
|
||||||
saveRegistryLock(createLock().asBuilder().setLockCompletionTime(fakeClock.nowUtc()).build());
|
saveRegistryLock(createLock().asBuilder().setLockCompletionTime(fakeClock.nowUtc()).build());
|
||||||
action.run();
|
action.run();
|
||||||
|
@ -159,7 +161,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertNoDomainChanges();
|
assertNoDomainChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_expired() {
|
void testFailure_expired() {
|
||||||
saveRegistryLock(createLock());
|
saveRegistryLock(createLock());
|
||||||
fakeClock.advanceBy(Duration.standardHours(2));
|
fakeClock.advanceBy(Duration.standardHours(2));
|
||||||
|
@ -169,7 +171,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertNoDomainChanges();
|
assertNoDomainChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_nonAdmin_verifyingAdminLock() {
|
void testFailure_nonAdmin_verifyingAdminLock() {
|
||||||
saveRegistryLock(createLock().asBuilder().isSuperuser(true).build());
|
saveRegistryLock(createLock().asBuilder().isSuperuser(true).build());
|
||||||
action.run();
|
action.run();
|
||||||
|
@ -177,7 +179,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertNoDomainChanges();
|
assertNoDomainChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_alreadyUnlocked() {
|
void testFailure_alreadyUnlocked() {
|
||||||
action = createAction(lockId, false);
|
action = createAction(lockId, false);
|
||||||
saveRegistryLock(
|
saveRegistryLock(
|
||||||
|
@ -192,7 +194,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertNoDomainChanges();
|
assertNoDomainChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_alreadyLocked() {
|
void testFailure_alreadyLocked() {
|
||||||
saveRegistryLock(createLock());
|
saveRegistryLock(createLock());
|
||||||
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
|
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
|
||||||
|
@ -201,7 +203,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertNoDomainChanges();
|
assertNoDomainChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_notLoggedIn() {
|
void testFailure_notLoggedIn() {
|
||||||
action.authResult = AuthResult.NOT_AUTHENTICATED;
|
action.authResult = AuthResult.NOT_AUTHENTICATED;
|
||||||
action.run();
|
action.run();
|
||||||
|
@ -210,7 +212,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertNoDomainChanges();
|
assertNoDomainChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_doesNotChangeLockObject() {
|
void testFailure_doesNotChangeLockObject() {
|
||||||
// A failure when performing Datastore actions means that no actions should be taken in the
|
// A failure when performing Datastore actions means that no actions should be taken in the
|
||||||
// Cloud SQL RegistryLock object
|
// Cloud SQL RegistryLock object
|
||||||
|
@ -229,7 +231,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertThat(afterAction).isEqualTo(lock);
|
assertThat(afterAction).isEqualTo(lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_isLockTrue_shouldBeFalse() {
|
void testFailure_isLockTrue_shouldBeFalse() {
|
||||||
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
|
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
|
||||||
saveRegistryLock(
|
saveRegistryLock(
|
||||||
|
@ -242,7 +244,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked");
|
assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_isLockFalse_shouldBeTrue() {
|
void testFailure_isLockFalse_shouldBeTrue() {
|
||||||
action = createAction(lockId, false);
|
action = createAction(lockId, false);
|
||||||
saveRegistryLock(createLock());
|
saveRegistryLock(createLock());
|
||||||
|
@ -250,7 +252,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertThat(response.getPayload()).contains("Failed: Domain example.tld is already unlocked");
|
assertThat(response.getPayload()).contains("Failed: Domain example.tld is already unlocked");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_lock_unlock_lockAgain() {
|
void testFailure_lock_unlock_lockAgain() {
|
||||||
RegistryLock lock = saveRegistryLock(createLock());
|
RegistryLock lock = saveRegistryLock(createLock());
|
||||||
action.run();
|
action.run();
|
||||||
|
@ -269,7 +271,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertThat(response.getPayload()).contains("Failed: Invalid verification code");
|
assertThat(response.getPayload()).contains("Failed: Invalid verification code");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_lock_lockAgain() {
|
void testFailure_lock_lockAgain() {
|
||||||
saveRegistryLock(createLock());
|
saveRegistryLock(createLock());
|
||||||
action.run();
|
action.run();
|
||||||
|
@ -279,7 +281,7 @@ final class RegistryLockVerifyActionTest {
|
||||||
assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked");
|
assertThat(response.getPayload()).contains("Failed: Domain example.tld is already locked");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_unlock_unlockAgain() {
|
void testFailure_unlock_unlockAgain() {
|
||||||
action = createAction(lockId, false);
|
action = createAction(lockId, false);
|
||||||
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
|
domain = persistResource(domain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build());
|
||||||
|
|
|
@ -24,11 +24,14 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.net.HostAndPort;
|
import com.google.common.net.HostAndPort;
|
||||||
|
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||||
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
import google.registry.request.auth.AuthenticatedRegistrarAccessor;
|
||||||
import google.registry.server.Fixture;
|
import google.registry.server.Fixture;
|
||||||
import google.registry.server.Route;
|
import google.registry.server.Route;
|
||||||
import google.registry.server.TestServer;
|
import google.registry.server.TestServer;
|
||||||
import google.registry.testing.AppEngineExtension;
|
import google.registry.testing.AppEngineExtension;
|
||||||
|
import google.registry.testing.DatabaseHelper;
|
||||||
|
import google.registry.testing.FakeClock;
|
||||||
import google.registry.testing.UserInfo;
|
import google.registry.testing.UserInfo;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
@ -40,6 +43,8 @@ import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.FutureTask;
|
import java.util.concurrent.FutureTask;
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
import javax.servlet.Filter;
|
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.AfterEachCallback;
|
||||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
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) {
|
for (Fixture fixture : fixtures) {
|
||||||
fixture.load();
|
fixture.load();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,6 @@ PATH CLASS
|
||||||
/_dr/task/brdaCopy BrdaCopyAction POST y INTERNAL,API APP ADMIN
|
/_dr/task/brdaCopy BrdaCopyAction POST y INTERNAL,API APP ADMIN
|
||||||
/_dr/task/checkDatastoreBackup CheckBackupAction POST,GET 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/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/deleteExpiredDomains DeleteExpiredDomainsAction GET n INTERNAL,API APP ADMIN
|
||||||
/_dr/task/deleteLoadTestData DeleteLoadTestDataAction POST 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
|
/_dr/task/deleteOldCommitLogs DeleteOldCommitLogsAction GET n INTERNAL,API APP ADMIN
|
||||||
|
|
Loading…
Add table
Reference in a new issue